1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
# 106 — 조건을 먼저 정한 TPC-C: 묶어 커밋하는 SQLite, 창고 넷
2026-09-30. [외부 리뷰 004](../outside-feedback/feedback-004.md)의 넷째 권고에서 시작한다. [092](092-tpcc.md)의 비교는
창고 하나(W=1)였고, SQLite는 트랜잭션마다 커밋했다. 리뷰는 SD 카드에서의 약 90배가 대부분 그룹 커밋과 로그 쓰기 구조에서
온다고 읽었다. 그래서 SQLite도 묶어 커밋하게 하고, 창고를 넷으로 늘려 다시 잰다. 아래 착수 기준은 조율과 측정 행렬을
돌리기 전에 정해 커밋했다(`7f71c85`, 하니스를 확인하려고 5초짜리 시험을 몇 번 돌린 뒤다). 결과를 본 뒤에 고치지 않았다.
착수 기준:
- **바꿀 결정.**
1. **SD 카드에서의 주장.** SQLite도 묶어 커밋할 때 Pure와의 처리량 차이가 2배 안으로 줄면, 092의 SD 비교를 "묶어 커밋하는
구조를 앱 코드 없이 얻는다"로 고쳐 쓴다. 2배 넘게 남으면, 거래당 장치에 쓴 바이트로 차이가 어디서 오는지 적는다.
2. **소유자 하나.** W=4에서 Pure의 소유자 스레드가 코어 하나의 90% 이상을 쓰는데도, 64터미널의 처리량이 16터미널보다 10%
넘게 늘지 않으면 소유자가 병목이다. 그러면 키(창고)로 나눈 소유자 여럿을 다음 설계로 올린다. 아니면 지금은 하지 않는다.
3. **체크포인트.** SD 카드에서 W=4의 Pure 처리량이 W=1의 절반 아래면, 목록의 이미지를 통째로 다시 쓰는 체크포인트가
다음 일이다(092의 "덧붙는 목록의 이미지").
- **정합성 통과 조건.** W마다 시간 잰 실행 전에, 한 씨앗에서 뽑은 같은 거래 2만 건을 Pure, SQLite, 묶어 커밋하는 SQLite에
돌린다. 모든 표의 모든 행이 같아야 하고(시각 제외), 실패한 거래가 없어야 한다(되돌린 New-Order는 규격대로 성공이다).
시간 잰 실행마다 그 뒤에 clause 3.3.2.1–4가 성립해야 한다. 하나라도 어긋난 실행은 결과로 싣지 않는다.
- **허용 메모리.** 실행이 모두 끝난 뒤 Pure의 상주 메모리가 상태 바이트의 2.5배 이하여야 한다([104](104-where-memory-goes.md)에서
최고가 약 2배였다). 넘으면 그 차이가 어디로 가는지가 다음 일이다. W는 RAM(tmpfs)에서 저장소와 데이터베이스까지 메모리에
들고도 1 GB가 남게 고른다. 이 Pi(8 GB, 쓸 수 있는 것 약 4.8 GB)에서는 W=4다.
- **저장 완료와 지연.** 두 쪽 모두 커밋이 장치에 닿은 뒤 답한다. Pure는 저장마다 로그에 쓰고 `fdatasync`한다. SQLite는 WAL,
`synchronous=FULL`이다. 지연은 같은 수의 터미널이 기다릴 때(16, 64) 비교한다.
- **SQLite 설정.** 신중한 엔지니어가 고를 설정으로 겨룬다. 행렬 전에 조율 실행으로 고른다. RAM에서는 기본값과
`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의
묶음 모드를 더했다.
- **묶어 커밋하는 SQLite.** 터미널들이 쓰는 거래(New-Order, Payment, Delivery)를 작성자 스레드 하나에 넘긴다. 작성자는
기다리는 것을 모두 한 트랜잭션(`BEGIN IMMEDIATE`)으로 돌리고, 거래마다 `SAVEPOINT`를 둔다. 실패하거나 되돌릴 거래는 제
SAVEPOINT까지만 되돌린다. 한 번의 `COMMIT`(동기화 한 번) 뒤에 각 터미널을 깨운다.
- **읽기 거래.** Order-Status와 Stock-Level은 각 터미널이 제 연결에서 마지막 커밋을 읽는다. pure-serve가 보기를 소유자에게
넘기지 않고 연결 스레드에서 마지막 저장의 스냅숏으로 답하는 것과 같다.
- **잰 것.** 실행마다 초당 거래, 종류별 p50/p99, 거래당 CPU(Pure는 서버 프로세스, SQLite는 제 프로세스), 소유자 스레드가
쓴 코어의 몫(pure-serve의 소유자 스레드 이름을 `pure-owner`로 붙였다), 메모리(Pure는 서버의 상주, SQLite는 최고 상주),
거래당 쓴 바이트(프로세스의 `write_bytes`, 그리고 장치의 쓴 섹터)다. 끝에 Pure의 상태 바이트와 두 쪽의 파일 크기를 적는다.
- **행렬.** 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 같은)와는 견주지 않았다.
다음은 **소유자의 시간**이다. 설계 노트에서 두 길을 함께 다룬다. 스냅숏이 공유한 잎을 고칠 때 덜 복사하는 길(모든 앱에
듣는다)과, 키로 나눈 소유자가 한 저장소의 그룹 커밋을 함께 쓰는 길(코어를 더 쓴다)이다. 덜 복사하는 쪽을 먼저 잰다.
그 첫걸음은 [107](107-snapshot-nodes-reused.md)이다. 복사의 몫 대부분이 할당이었고, 스냅숏이 놓은 노드를 되써 없앴다.
|