From 0a57c3ad03df6da7eb0a72059bd85c4d716dbb8a Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 07:56:49 +0100 Subject: Say what TPC-C costs when SQLite commits in groups too, and with four warehouses (106) SQLite tuned first, by the criteria committed before (and, beyond them, checkpoints each 40 MB of WAL, in its favour). On the SD card Pure stays 4.2-10.8x ahead at equal terminals, SQLite writing 8-18x the bytes a transaction; in RAM 3.2-3.5x. All tables equal at W=1 and W=4, and every run consistent. In RAM the one owner is the limit (0.94 of a core at W=4, no gain from 16 to 64 terminals), a third of its busy time copying nodes the snapshots share; checkpoints and memory are within their criteria. --- docs/research/106-tpcc-conditions-first.md | 177 ++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/research/106-tpcc-conditions-first.md b/docs/research/106-tpcc-conditions-first.md index a0eae9d..919bcb4 100644 --- a/docs/research/106-tpcc-conditions-first.md +++ b/docs/research/106-tpcc-conditions-first.md @@ -2,8 +2,8 @@ 2026-09-30. [외부 리뷰 004](../outside-feedback/feedback-004.md)의 넷째 권고에서 시작한다. [092](092-tpcc.md)의 비교는 창고 하나(W=1)였고, SQLite는 트랜잭션마다 커밋했다. 리뷰는 SD 카드에서의 약 90배가 대부분 그룹 커밋과 로그 쓰기 구조에서 -온다고 읽었다. 그래서 SQLite도 묶어 커밋하게 하고, 창고를 넷으로 늘려 다시 잰다. 아래는 측정 행렬을 돌리기 전에 정했다 -(하니스를 확인하려고 5초짜리 시험을 몇 번 돌린 뒤다). +온다고 읽었다. 그래서 SQLite도 묶어 커밋하게 하고, 창고를 넷으로 늘려 다시 잰다. 아래 착수 기준은 조율과 측정 행렬을 +돌리기 전에 정해 커밋했다(`7f71c85`, 하니스를 확인하려고 5초짜리 시험을 몇 번 돌린 뒤다). 결과를 본 뒤에 고치지 않았다. 착수 기준: @@ -26,6 +26,26 @@ `mmap_size` 1 GiB, 연결마다 캐시 64 MiB, `temp_store=MEMORY`(SAVEPOINT의 하위 저널을 메모리에)를 견준다. SD 카드에서는 페이지 크기 4,096 B와 1,024 B를 견준다. 묶어 커밋하는 실행의 처리량이 높은 쪽을 쓴다. +## 쉬운 말로 + +- **SD 카드에서의 차이는 90배에서 4–11배로 줄었지만, 2배를 훨씬 넘게 남았다.** + - 092의 SQLite는 기본 설정으로 트랜잭션마다 커밋했다. 이번에는 SQLite도 Pure처럼 기다리는 거래를 묶어 한 번에 커밋하고, + 카드에 맞게 조율했다(페이지 1 KiB, 체크포인트는 WAL 40 MB마다). SQLite가 스스로 10배 넘게 빨라졌다. + - 그래도 Pure가 같은 터미널 수에서 4.2–10.8배다(W=1 64터미널: 4,980건 대 739건). + - 남은 차이는 거래당 장치에 쓰는 바이트에서 온다. SQLite는 행 하나를 바꿔도 그 행이 든 페이지를 통째로 쓴다(거래당 7.6–15 + KB). Pure는 바뀐 필드와 더한 행만 기록한다(0.7–1.3 KB). 8–18배다. +- **RAM에서는 Pure가 3.2–3.5배다.** 조율한 SQLite의 최고(10,357건)에 대해 Pure는 36,567건(W=1)이다. RAM에서는 아낄 동기화가 + 없어서, 묶어 커밋해도 SQLite가 빨라지지 않는다. Pure는 HTTP와 JSON까지 하면서도 거래당 CPU가 SQLite의 절반이다. +- **창고가 넷이어도 맞았다.** 같은 거래 2만 건 뒤 세 쪽(Pure, SQLite, 묶어 커밋하는 SQLite)의 모든 표가 같았다. 원격 재고와 + 원격 결제가 들어간다. 모든 실행 뒤 일관성 조건이 성립했고, 실패한 거래는 없었다. +- **RAM에서 Pure의 한계는 소유자 스레드 하나다.** W=4에서 터미널을 16에서 64로 늘려도 처리량이 늘지 않았고, 소유자가 코어의 + 94%를 썼다. 서버의 세 코어 가운데 1.74개만 썼다. 소유자의 바쁜 시간 가운데 약 3분의 1은 스냅숏이 공유한 노드를 복사하고 + 놓는 데 간다. SD 카드에서는 소유자가 코어의 7–15%만 쓴다(카드가 한계다). +- **메모리는 기준 안이다.** Pure는 모든 상태를 메모리에 든다. 상주가 상태 바이트의 1.6–2.2배였다(기준 2.5배). 같은 실행에서 + SQLite는 최고 상주가 190 MiB 아래였고, 데이터는 파일(최대 124 MiB)에 있다. 메모리를 더 쓰는 것이 Pure의 값이다. +- 아직 모르는 것: 레코드를 기록하는 다른 저장소(RocksDB 같은 LSM)와는 견주지 않았다. SD 카드에서의 이점은 레코드 로그가 + 페이지 WAL보다 적게 쓰는 데서 오므로, 그런 저장소와의 차이는 더 작을 것이다. + ## 방법 [`benchmarks/tpcc/run.py`](../../benchmarks/tpcc/run.py)와 [`sqlite-tpcc.c`](../../benchmarks/tpcc/sqlite-tpcc.c)에 SQLite의 @@ -42,3 +62,156 @@ - **행렬.** RAM(`/dev/shm`)과 SD 카드(ext4), 각각 W=1과 W=4. Pure 16·64터미널, SQLite 1·4스레드, 묶어 커밋하는 SQLite 16·64터미널. RAM은 20초, SD 카드는 30초씩이다. SD 카드는 상태에 따라 흔들리므로 세 번 돌려 범위를 적는다. - 서버와 SQLite는 코어 0–2, Pure의 부하 생성기(HTTP/1.1, JSON)는 코어 3이다. SQLite는 프로세스 안에서 돈다. +- 실행마다 앞에서 `sync`로 쌓인 쓰기를 비운다(적재와 복사가 남긴 것이 먼저 도는 쪽의 몫이 되지 않게). Pure는 제 실행이 + 끝나면 멈춘다. 그 체크포인트가 SQLite의 실행 동안 장치를 쓰지 않게 하려는 것이다. +- 같은 거래 2만 건의 확인은 W마다 RAM에서 한 번 했다. 장치와 상관없는 확인이고, SD 카드에서 한 터미널로 거래마다 동기화하며 + 돌리면 SQLite 쪽만 몇 분이 걸린다. SD 카드의 실행도 끝날 때마다 clause 3.3.2를 확인했다. + +## SQLite 설정 고르기 + +W=1에서 SQLite만 돌렸다([원시 결과](../../research/experiments/022-tpcc-grouped/), `tune-`로 시작하는 파일). + +**RAM, 20초, 초당 거래.** + +| SQLite | 1스레드 | 4스레드 | 묶음 16 | 묶음 64 | +|---|---:|---:|---:|---:| +| 기본값 | 9,390 | 9,083 | 7,872 | 8,555 | +| mmap 1 GiB, 캐시 64 MiB, `temp_store=MEMORY` | 9,886 | 9,470 | 9,680 | 10,230 | +| 여기에 체크포인트를 WAL 40 MB마다 | 10,361 | 10,272 | 9,890 | 10,211 | + +기본값의 묶음은 데이터베이스가 RAM에 있는데도 거래당 7.6 KB(16터미널)와 1.9 KB(64터미널)를 장치에 썼다. SAVEPOINT의 +하위 저널이 64 KiB를 넘으면 임시 파일로 넘치는데, 그 파일이 SD 카드의 `/var/tmp`에 생긴다. `temp_store=MEMORY`가 이것을 +막는다. + +**SD 카드, 30초, 초당 거래(거래당 장치에 쓴 바이트).** + +| SQLite | 1스레드 | 묶음 16 | 묶음 64 | +|---|---:|---:|---:| +| 페이지 4,096 B | 72 (60.7 KB) | 162 (41.8 KB) | 139 (29.3 KB) | +| 페이지 1,024 B | 132 (31.9 KB) | 203 (22.5 KB) | 263 (17.9 KB) | +| 1,024 B, 체크포인트를 WAL 40 MB마다 | 87 (40.9 KB) | 694 (10.7 KB) | 594 (7.7 KB) | + +- 기준대로 SD 카드에서는 페이지를 1,024 B로 한다. SQLite는 행 하나를 바꿔도 그 행이 든 페이지를 통째로 WAL에 쓰므로, + 페이지가 작을수록 덜 쓴다. +- **체크포인트 간격은 기준에 없던 조율이다.** SQLite에 유리한 쪽이라 더했다. 기본값은 1,000페이지마다다. 1 KiB 페이지라면 + WAL 1 MB마다 체크포인트가 데이터베이스 파일의 페이지를 되쓴다. 40 MB마다로 늘리면 한 페이지를 여러 번 바꿔도 한 번만 + 되쓴다. 묶음 16의 처리량이 3.4배가 됐다. RAM에서도 1스레드의 New-Order p99가 1.2 ms에서 0.2 ms가 됐다(체크포인트가 그 + 꼬리였다). 그래서 두 장치 모두 이 간격을 쓴다([`run.py`](../../benchmarks/tpcc/run.py)의 `tuned`). +- 1스레드는 이 간격에서 오히려 느려졌다(132 → 87건). 원인은 재지 않았다. WAL이 자라는 동안에는 커밋마다 파일 크기까지 + 동기화해야 하는데(ext4 저널), 그 몫일 수 있다. 묶음은 그 비용을 거래들이 나눠 진다. 판정은 묶음으로 한다. + +## 결과 + +원시 결과는 [022-tpcc-grouped](../../research/experiments/022-tpcc-grouped/)에 있다. 명령은 그 안의 `runs.sh`이고, 아래 표는 +`tables.py`가 만든다. + +**정합성.** W=1과 W=4 모두, 같은 거래 2만 건 뒤 Pure와 SQLite, SQLite와 묶어 커밋하는 SQLite의 아홉 표가 행마다 같았다 +(되돌린 New-Order도 W=1 90건, W=4 93건으로 같다). 실패한 거래는 없었다. 조율까지 시간 잰 실행 70번 모두, 끝난 뒤 clause +3.3.2.1–4가 성립했고 실패한 거래가 없었다. + +**RAM(tmpfs), 20초.** 칸마다 W=1 / W=4다. + +| | 초당 거래 | New-Order p99 (ms) | 거래당 CPU (µs) | 소유자의 코어 몫 | +|---|---:|---:|---:|---:| +| Pure, 16터미널 | 35,672 / 32,765 | 1.1 / 1.0 | 45.6 / 48.1 | 0.83 / 0.86 | +| Pure, 64터미널 | 36,567 / 32,255 | 4.4 / 3.6 | 48.9 / 53.9 | 0.90 / 0.94 | +| SQLite, 1스레드 | 10,357 / 10,272 | 0.2 / 0.2 | 96.5 / 97.1 | | +| SQLite, 4스레드 | 10,219 / 9,780 | 0.3 / 0.3 | 98.4 / 103.9 | | +| SQLite 묶음, 16터미널 | 9,843 / 8,828 | 4.4 / 8.2 | 134.0 / 147.2 | | +| SQLite 묶음, 64터미널 | 10,271 / 8,876 | 12.3 / 17.4 | 131.2 / 147.3 | | + +- Pure는 조율한 SQLite의 최고보다 3.5배(W=1), 3.2배(W=4)다. 같은 터미널 수의 묶음 SQLite보다는 3.6–3.7배이고, New-Order + p99는 2.8–8.2배 짧다. +- 거래당 CPU는 Pure가 45.6–53.9 µs(서버 프로세스 전체, HTTP와 JSON 포함), SQLite가 96.5–147.3 µs(프로세스 안, HTTP 없음)다. +- RAM에서는 묶어 커밋해도 SQLite가 빨라지지 않는다. 동기화가 거의 공짜라 아낄 것이 없고, 작성자에게 넘기고 SAVEPOINT를 두는 + 값(거래당 35–43 µs)만 든다. +- W=4에서 Pure는 W=1보다 8–12%, 묶음 SQLite는 10–14% 느리다. + +**SD 카드(ext4), 30초, 세 판의 중앙값(범위).** SQLite의 페이지는 1 KiB다. + +| | 초당 거래, W=1 | 초당 거래, W=4 | New-Order p99 (ms) | 거래당 장치 바이트 | +|---|---:|---:|---:|---:| +| Pure, 16터미널 | 2,138 (2,136–2,672) | 2,247 (1,893–2,314) | 59 / 59 | 1,324 / 1,245 | +| Pure, 64터미널 | 4,980 (4,864–6,083) | 4,590 (4,324–5,540) | 78 / 94 | 680 / 682 | +| SQLite, 1스레드 | 110 (96–124) | 84 (80–89) | 86 / 115 | 38,982 / 40,111 | +| SQLite, 4스레드 | 267 (152–293) | 161 (159–222) | 78 / 90 | 18,110 / 25,886 | +| SQLite 묶음, 16터미널 | 505 (485–520) | 335 (333–546) | 475 / 590 | 10,871 / 15,001 | +| SQLite 묶음, 64터미널 | 739 (578–853) | 426 (302–612) | 1,245 / 1,770 | 7,647 / 12,067 | + +(p99와 바이트는 W=1 / W=4다.) + +- 같은 터미널 수에서 Pure는 묶음 SQLite의 4.2배(W=1, 16터미널), 6.7배(W=1, 64), 6.7배(W=4, 16), 10.8배(W=4, 64)다. + New-Order p99는 8–19배 짧다. +- **거래당 장치에 쓴 바이트가 차이를 설명한다.** 묶음 SQLite는 7.6–15.0 KB, Pure는 0.68–1.32 KB를 썼다(8–18배). + - SQLite의 WAL은 바뀐 행이 든 페이지를 통째로 쓰고, 체크포인트가 그 페이지를 데이터베이스 파일에 한 번 더 쓴다. + - 창고가 넷이면 거래들이 더 흩어진 페이지를 건드린다. 한 커밋 안에서 겹치는 페이지가 줄어, SQLite는 거래당 W=1의 + 1.4–1.6배를 쓴다. + - Pure는 바뀐 필드(수 하나에 약 7 B)와 더한 행(주문 줄 약 24 B)을 기록하므로 W와 거의 상관없다. +- 장치가 받은 양으로 보면, 묶음 SQLite(64터미널)는 초당 5.1–5.7 MB를 썼다. 이 카드의 지속 쓰기(초당 3–8 MB, + [092](092-tpcc.md)) 안쪽이라, 바이트가 한계로 보인다. Pure는 초당 3.1–3.4 MB를 쓰면서 거래는 6.7–10.8배를 넣었다. +- Pure의 SD 처리량은 W=4가 W=1의 105%(16터미널), 92%(64터미널)다. 체크포인트는 W=1에서 두 실행을 합쳐 8–12번, W=4에서 + 3–5번 돌았다. 목록이 크면 그 이미지를 다시 쓰는 기준도 크다. + +**메모리.** Pure는 모든 상태를 메모리에 든다. 같은 행을 적재한 직후, Pure의 상주는 38 MiB(W=1)와 104 MiB(W=4)이고 SQLite의 +파일은 13.8 MiB와 52.8 MiB다. SQLite는 필요한 페이지만 읽어 든다. + +| | Pure 상주 | 상태 바이트 | 배수 | Pure 저장소 | SQLite 최고 상주 | SQLite 파일(직접 / 묶음) | +|---|---:|---:|---:|---:|---:|---:| +| RAM, W=1 | 835 MiB | 375 MiB | 2.23 | 434 MiB | 41–124 MiB | 84 / 82 MiB | +| RAM, W=4 | 846 MiB | 402 MiB | 2.10 | 411 MiB | 60–188 MiB | 124 / 118 MiB | +| SD, W=1 | 150–179 MiB | 73–85 MiB | 2.06–2.09 | 70–87 MiB | 17–89 MiB | 15–16 / 19–21 MiB | +| SD, W=4 | 209–239 MiB | 130–142 MiB | 1.61–1.68 | 104–128 MiB | 23–136 MiB | 54–55 / 56–58 MiB | + +Pure의 값은 Pure의 실행이 모두 끝난 뒤다. 상태는 거의 모두 실행이 더한 주문 줄이다(RAM W=1에서 144만 건의 거래로 22 MiB에서 +375 MiB). SQLite의 최고 상주는 실행마다의 범위다(SD는 세 판의 중앙값). + +**소유자가 하는 일(RAM, W=1, 64터미널).** Pure만 90초 돌리며 gdb로 소유자 스레드의 스택을 80번 떴다 +([`owner-samples.py`](../../research/experiments/022-tpcc-grouped/owner-samples.py)). 붙을 때마다 프로세스가 멈추므로 그동안의 +처리량은 33,376건이었다. 표본마다 스택에 든 함수로 나눴다. + +| 소유자가 하던 일 | 표본 | +|---|---:| +| 항목 찾기(목록의 B+트리를 내려간다, `pure_list_seek`) | 24 (30%) | +| 요청 기다리기 | 19 (24%) | +| 스냅숏이 공유한 노드를 고치기 전에 복사하기(`own`, `new_node`, `retire`) | 15 (19%) | +| 스냅숏 게시와 놓은 노드 해제(`publish`) | 5 (6%) | +| 해석기(`run`, `dispatch`) | 5 (6%) | +| 항목 바꾸기, 항목 읽기, 답 넘기기 | 각 3 (4%) | +| 저장(`pure_app_save`)과 그 밖 | 3 (4%) | + +- 바쁜 시간(표본 61개)의 39%가 찾기, 33%가 스냅숏에 딸린 복사와 해제다. 저장은 1%다. +- pure-serve는 저장마다 스냅숏을 게시한다. 읽기가 방금 답한 쓰기를 보게 하려는 것이다. 게시한 뒤 처음 고치는 노드는 그 + 스냅숏과 공유되어 있어 복사한다. RAM에서는 저장이 초당 약 1,000번이라, 무작위로 흩어진 항목(재고, 고객)을 고칠 때마다 + 거의 매번 그 잎을 복사한다. + +## 판정 + +1. **SD 카드에서의 주장: 2배 넘게 남았다.** 같은 터미널 수에서 4.2–10.8배다. 기준대로 차이가 어디서 오는지 적었다. 거래당 + 장치에 쓴 바이트가 8–18배 다르다. + - 092의 90배 가운데, 묶어 커밋하기와 조율이 SQLite를 10–15배 빠르게 했다(50건에서 505–739건). 남은 4–11배가 레코드를 + 기록하는 로그의 몫이다. + - Pure는 묶어 커밋하는 구조를 앱 코드 없이 준다. SQLite에서는 작성자 스레드와 SAVEPOINT를 손으로 짰다. + - README와 CHECKPOINT의 SD 비교를 이 비교로 바꿨다. 90배는 기본 설정으로 거래마다 커밋하는 SQLite에 대한 값이었다. +2. **소유자 하나: 병목이다.** W=4 RAM, 64터미널에서 소유자가 코어의 0.94를 썼고, 처리량은 16터미널보다 1.6% 적었다. 서버는 + 코어 셋 가운데 1.74개만 썼다. 기준대로 키로 나눈 소유자를 다음 설계로 올린다. [084](084-parallel-syncs.md)는 "앱의 적용이 + CPU 하나를 넘을 때 소유자를 나누되 동기화는 장치마다 한 줄로 모은다"고 했다. TPC-C는 RAM에서 그 조건에 닿았다. + - 설계는 표본이 보인 것과 함께 본다. 소유자 시간의 3분의 1이 스냅숏 때문에 드는 복사와 해제다. 이것을 줄이면 키로 나눌 수 + 없는 앱(한 키에 모이는 상태)까지 모두 빨라진다. + - 나누기는 창고를 넘는 거래를 두 소유자가 함께 해야 하는 문제를 새로 만든다. New-Order의 약 10%가 다른 창고의 재고를 + 쓰고, Payment의 15%가 다른 창고의 고객에게 간다. + - SD 카드에서는 소유자가 코어의 7–15%만 쓴다. 이 장치에서 쓰기를 늘리는 길은 여전히 거래당 바이트와 동기화다(084 그대로). +3. **체크포인트: 다음 일이 아니다.** SD 카드에서 W=4가 W=1의 92–105%다(기준은 절반 아래). +4. **메모리: 기준 안이다.** Pure의 상주가 상태 바이트의 1.61–2.23배다(기준 2.5배). + +## 한계와 다음 + +- W=4까지다. RAM에서 저장소와 데이터베이스까지 메모리에 들어야 해서다. SD 카드의 실행은 30초다. W=4에서 Pure의 체크포인트는 + 두 실행을 합쳐 3–5번이라, 더 긴 실행에서 체크포인트가 어떻게 겹치는지는 재지 않았다. +- SQLite는 프로세스 안에서 돌고 HTTP도 JSON도 없다. Pure의 수에는 그것이 들어 있다. CPU 비교는 SQLite에 유리하다. +- 규격의 생각 시간, 창고당 터미널 10개, 텍스트 필드는 없다(092와 같다). 공식 tpmC가 아니다. +- 1스레드 SQLite가 큰 WAL에서 느려진 까닭은 재지 않았다. +- 소유자 표본은 80개다. 한 칸이 ±5%포인트쯤 흔들린다. gdb가 붙을 때마다 프로세스가 멈춘다. +- 레코드를 기록하는 저장소(LSM 같은)와는 견주지 않았다. + +다음은 **소유자의 시간**이다. 설계 노트에서 두 길을 함께 다룬다. 스냅숏이 공유한 잎을 고칠 때 덜 복사하는 길(모든 앱에 +듣는다)과, 키로 나눈 소유자가 한 저장소의 그룹 커밋을 함께 쓰는 길(코어를 더 쓴다)이다. 덜 복사하는 쪽을 먼저 잰다. -- cgit v1.2.3