aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-04 16:10:43 +0100
committereunhhu <[email protected]>2026-10-04 16:10:43 +0100
commit50d8263492d69ca8521d3b1d51f60b551c76c443 (patch)
tree722340f9d167ea985f9682442c9a923e6c3dbb28 /docs/research
parent90c8edece941f26cd280686c7063a7c9149a2212 (diff)
downloadpure-50d8263492d69ca8521d3b1d51f60b551c76c443.tar.gz
pure-50d8263492d69ca8521d3b1d51f60b551c76c443.zip
Say what pushing views costs and how fresh it keeps them (019, research 120)
Diffstat (limited to 'docs/research')
-rw-r--r--docs/research/120-pushing-views.md91
1 files changed, 91 insertions, 0 deletions
diff --git a/docs/research/120-pushing-views.md b/docs/research/120-pushing-views.md
new file mode 100644
index 0000000..53339b8
--- /dev/null
+++ b/docs/research/120-pushing-views.md
@@ -0,0 +1,91 @@
+# 120 — 보기를 미는 길
+
+2026-10-04. [019](../design/019-pushing-views.md)를 잰다. 기준은 구현하기 전에 적어 커밋했고(`f2675f0`), 구현하기 전에 한 번
+고쳐 커밋했다(`84483fe`: 신선도가 같은 둘을 견주게, 미는 간격을 두게, 모듈이 바뀌어도 스트림을 잇게).
+
+## 쉬운 말로
+
+- `pure-serve`가 보기의 값을 민다. 브라우저의 `EventSource('/views/total')`가 그대로 붙는다(`Accept: text/event-stream`). 처음에 지금
+ 값이 오고, 값이 바뀔 때마다 새 값이 온다. 같은 보기와 인자를 보는 구독자들은 계산 한 번을 나눈다.
+- **신선도.** 구독자 1,000이 보는 카운터를 쓰는 쪽이 초당 1번, 10번, 100번 바꿀 때, 쓰기의 답을 받은 때부터 구독자가 그 값을 보기까지
+ p99가 4–11 ms다(기준 20 ms). 절반 넘게는 쓰는 쪽이 답을 받기 전에 구독자가 먼저 본다.
+- **값.** 대시보드처럼 초당 1번 바뀌는 값을 1,000이 볼 때, 미는 호스트의 CPU는 100 ms마다 폴링할 때의 약 10분의 1이다(두 묶음에서
+ 0.093과 0.104, 여섯 라운드 모두 0.101). 기준(10분의 1 이하)의 경계에 있다. 같은 신선도라면(20 ms 폴링) 초당 10번 바뀔 때 13%, 초당
+ 100번 바뀔 때 같은 CPU로 더 신선하다(p99 11 ms 대 20 ms).
+- **크기.** 구독자 1만에서 호스트의 메모리는 구독자마다 0.29–0.42 KiB 늘었다(기준 1 KiB). 값 하나가 1만에게 다 닿기까지 p99는
+ 37–45 ms다(기준 100 ms).
+- **지금의 길.** TPC-C는 +0.3%, 은행 질의의 명령 수는 같다(±0.1%). 질의 하나(in_credit, 0.84 ms)의 시간이 처음 여섯 쌍에서 +4.2%,
+ 다음 여섯 쌍에서 +2.4%, 열두 쌍 모두 +3.0%였다. 명령 수가 같으니 일이 아니라 자리의 차이다.
+
+## 결과
+
+원시 결과와 명령은 [035-push](../../research/experiments/035-push/)에 있다(`runs.sh`, `push.py`, `counter.aup`, 도구는
+`benchmarks/http/watch.c`).
+
+**방법.** `pure-serve`(연결 스레드 셋, 저장소는 RAM, 코어 0–2)에 카운터 앱(`counter.aup`). `watch`(코어 3)가 클라이언트 N을 붙이고
+(미는 쪽은 붙자마자 구독), 쓰는 연결 하나가 초당 RATE번 카운터를 올린다(10초). 쓰기 k의 답이 온 때부터, 각 클라이언트가 k 이상의 값을
+본 때까지가 그 쓰기의 신선도다(먼저 봤으면 0). 폴링은 클라이언트마다 MS마다 `GET`(keep-alive, 첫 요청은 첫 MS 안의 아무 때). 호스트의
+CPU는 `/proc/PID/stat`의 utime+stime, 메모리는 클라이언트를 붙이기 전과 후의 VmRSS 차이다. 여섯 가지를 번갈아 세 라운드(`fresh`).
+
+| 구독자 1,000, 중앙값 | 신선도 p99 | 최대 | 호스트 CPU(10초) |
+|---|---:|---:|---:|
+| 초당 1번, 밀기 | 4.4 ms | 6.4 ms | 0.13 s |
+| 초당 1번, 100 ms 폴링 | 98 ms | 98 ms | 1.40 s |
+| 초당 10번, 밀기 | 6.1 ms | 7.7 ms | 0.66 s |
+| 초당 10번, 20 ms 폴링 | 19 ms | 29 ms | 5.17 s |
+| 초당 100번, 밀기 | 10.8 ms | 19 ms | 5.38 s |
+| 초당 100번, 20 ms 폴링 | 19 ms | 41 ms | 5.36 s |
+
+- 처음 세 라운드(`fresh-first-*`)는 구독자의 소켓을 쓸 수 있을 때마다 알리게 둔 채였다. 그러면 보낸 이벤트마다 상대의 확인이 스레드를
+ 깨운다. 보낼 것을 쥔 동안만 알리게 고친 뒤(`watch_subscriber`) 다시 쟀다(위). 초당 1번 밀기의 CPU는 둘 다 0.13 s로 같았다. 비율이
+ 0.104에서 0.093으로 바뀐 것은 폴링 쪽이 1.25 s에서 1.40 s로 흔들린 것이다. 그래서 이 기준은 경계에 있다고 적는다.
+- 미는 값의 대부분은 커널이다: 초당 1번 바뀔 때 쓰기 하나에 약 13 ms, 구독자 하나에 약 13 µs. 루프백의 TCP 보내기 한 번(받는 쪽의
+ 처리까지 보내는 스레드가 한다)의 값이다.
+- 초당 100번이면 밀기는 초당 10만 번 보내고, 20 ms 폴링은 초당 5만 번 요청을 받아 답한다. CPU는 같고 밀기가 더 신선하다. 미는
+ 간격(`--push-every-ms` 10 ms)이 쓰기의 빠르기와 상관없이 미는 일을 묶는다(초당 100번 쓰기에 미는 일 약 300번, 스레드 셋).
+
+**구독자 1만**(`many`, 세 라운드, 초당 1번).
+
+| 라운드 | 신선도 p50 | p99 | 최대 | 호스트 CPU(10초) | 구독자마다 메모리 |
+|---|---:|---:|---:|---:|---:|
+| 1 | 0 | 37 ms | 45 ms | 1.03 s | 0.42 KiB |
+| 2 | 0 | 41 ms | 45 ms | 1.10 s | 0.33 KiB |
+| 3 | 20 ms | 45 ms | 51 ms | 1.13 s | 0.29 KiB |
+
+구독자는 요청을 읽던 연결 기록(6,192 바이트)을 돌려주고 소켓과 구독만 남는다. 구독자 1,000의 런에서 구독자마다 1.5–4 KiB로 보인 것은
+붙는 동안 한꺼번에 생긴 연결 기록이 남은 것이다(돌려준 기록은 다음 연결이 다시 쓴다). 1만이면 그 몫이 묽어진다.
+
+**지금의 길**(전 `84483fe`, 후 `a7de450` 다음의 `watch_subscriber`를 넣은 빌드). TPC-C(RAM, W=1, 64터미널)와 은행 질의를 번갈아
+여섯 쌍, 은행 질의만 여섯 쌍 더.
+
+| | 전 | 후 | 바뀜 |
+|---|---:|---:|---:|
+| TPC-C 중앙값 | 42,542 | 42,679 | +0.3% |
+| per_branch(12쌍) | | | +0.3% |
+| in_credit(6쌍, 12쌍) | 0.835 ms, 0.84 ms | 0.87 ms, 0.865 ms | +4.2%, +3.0% |
+| large(12쌍) | | | +1.3% |
+| ranks(12쌍) | | | +1.2% |
+| 은행 질의의 명령 수 | | | −0.09–+0.02% |
+
+in_credit의 후는 열두 쌍 가운데 세 번 1.01–1.14 ms였고 나머지는 전과 같은 0.82–0.87 ms다.
+
+**검사**(`tests/check.py`의 `server_push`). 구독하면 지금 값이 먼저 온다. 50 ms마다 쓰면 값이 하나도 빠짐없이 차례로 오고, 값을 바꾸지
+않는 쓰기에는 오지 않는다. 몰아 쓴 200번에는 이벤트 6개가 왔고 마지막 값이 왔으며 `id`는 늘기만 했다. 인자를 받는 보기, 표의 쪽(100행),
+두 줄의 text(`data:` 두 줄), 계산하는 보기(018). 받기 버퍼를 작게 하고 읽지 않는 구독자에게 큰 표를 600번 바꿨을 때 호스트의 RSS는
+0 MiB 늘었고 기다리던 이벤트가 169–176번 바뀌었으며, 다시 읽자 마지막 값이 왔다. 여러 앱의 호스트에서: 토큰 없이 401, `read`로
+구독. 모듈을 바꾸면 같은 스트림에 새 모듈의 값(×10)이 왔다. 토큰 파일에서 그 토큰을 빼면 스트림이 끝났다. 보기가 없는 모듈로 바꾸면
+끝났다. 구독자가 모두 떠나면 호스트의 구독자 수가 0이다.
+
+## 판정
+
+- **019는 들어간다.** 신선도(20 ms), 크기(1 KiB), 퍼뜨리기(100 ms), 지금의 길(TPC-C, 명령 수)은 기준 안이다.
+- 대시보드의 값(폴링의 10분의 1 이하)은 경계에 있다(0.093–0.104). 미는 쪽의 값은 대부분 루프백의 TCP 보내기다. 그래도 폴링보다
+ 열 배 가깝게 적은 CPU로 스무 배 신선하다.
+- in_credit의 시간은 열두 쌍에서 기준의 경계(+3.0%)다. 명령 수가 같아 일의 차이는 아니다.
+
+## 다음
+
+- 하트비트(프록시가 쉬는 스트림을 끊지 않게), 보기가 읽는 상태가 바뀌지 않은 스냅숏은 계산하지 않기, 긴 보기를 연결 스레드 밖에서
+ 계산하기(119). 측정이 요구할 때.
+- 구독자가 아주 많으면 보내기의 값(구독자마다 약 13 µs)이 남는다. 같은 이벤트를 여러 소켓에 한 번에 보내는 길은 리눅스에 없다
+ (`io_uring`으로 묶는 것을 잴 수 있다).