From 93785a2e38699b4e65020153f434de9db5e8e098 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 4 Oct 2026 21:52:34 +0100 Subject: Measure views off the connection threads: writes' tail as with the CPU shared, two askers 1.95 times one (122) --- CHECKPOINT.md | 6 +- .../122-views-off-the-connection-threads.md | 84 ++++++++++++++++++++++ .../037-view-workers/2026-10-04-control-1.jsonl | 10 +++ .../037-view-workers/2026-10-04-control-2.jsonl | 10 +++ .../037-view-workers/2026-10-04-plain.jsonl | 13 ++++ .../037-view-workers/2026-10-04-scaling-1.jsonl | 7 ++ .../037-view-workers/2026-10-04-scaling-2.jsonl | 7 ++ .../037-view-workers/2026-10-04-tail-1.jsonl | 10 +++ .../037-view-workers/2026-10-04-tail-2.jsonl | 10 +++ research/experiments/037-view-workers/plain.py | 5 +- research/experiments/037-view-workers/runs.sh | 2 +- 11 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 docs/research/122-views-off-the-connection-threads.md create mode 100644 research/experiments/037-view-workers/2026-10-04-control-1.jsonl create mode 100644 research/experiments/037-view-workers/2026-10-04-control-2.jsonl create mode 100644 research/experiments/037-view-workers/2026-10-04-plain.jsonl create mode 100644 research/experiments/037-view-workers/2026-10-04-scaling-1.jsonl create mode 100644 research/experiments/037-view-workers/2026-10-04-scaling-2.jsonl create mode 100644 research/experiments/037-view-workers/2026-10-04-tail-1.jsonl create mode 100644 research/experiments/037-view-workers/2026-10-04-tail-2.jsonl diff --git a/CHECKPOINT.md b/CHECKPOINT.md index 4b79218..a676af1 100644 --- a/CHECKPOINT.md +++ b/CHECKPOINT.md @@ -13,8 +13,8 @@ | 영역 | 확인한 것 | 근거 | |---|---|---| | 앱 표현 | 상태(수, `bool`, 정확한 64비트 정수 `i64`, text와 커서·마크, 레코드의 리스트와 키 있는 리스트, 인덱스), 입력(키, text, 필드 있는 메시지, 타이머), 원자적 이벤트 핸들러, 순수 보기(조건식, 표와 걸러진 표, count·sum·max·min·first), `fn`·`use` 라이브러리, text 비교, 시간(`now`, `every`, `floor`, `%`). 로드할 때 타입·스택·합류·커서와 인덱스의 소속을 모두 검증하고 이벤트의 실행량에 상한이 있다 | 008, [016](docs/design/016-bool-and-i64.md), aup `docs/design/002-dsl-direction.md`, aup `make check`(파이썬 참조 모델 대조, 진단 223건) | -| 수명과 계산 | 값의 수명은 선언이 말한다: `state`(저장), `transient state`(앱이 도는 동안: 저장소에 쓰지 않고 다시 시작하면 초기값, SD 카드에서 쓰기 17배), `let`(이벤트 동안). 핸들러와 보기가 계산한다: 이벤트의 리스트(`let seen: list of Seen by node`), 세는 `for i in a .. b`, `break`, 8겹까지의 중첩, 그리고 `view 이름(인자) { … return … }`(스냅숏에서 돌아 소유자를 막지 않는다). 모든 반복은 시작할 때 횟수의 상한을 갖고, 모든 이벤트와 보기는 실행량 예산(정하지 않으면 2^28 명령)과 메모리 몫(2^28 바이트) 안에서 끝난다. 노드 10만·간선 100만의 BFS 한 이벤트 396 ms(CPython 441, C 9), TPC-C −1.7%. 같은 계산이 Pi·WSL2·M3 Mac에서 같다. 같은 탐색을 보기로 하면 쓰기는 CPU를 나누는 만큼만 줄고(이벤트로 하면 0.2%로 멈춘다), 보기를 계산하는 연결 스레드가 자기의 다른 연결을 기다리게 하는 꼬리가 남았다 | [018](docs/design/018-lifetimes-and-computation.md), [117](docs/research/117-transient-states.md), [118](docs/research/118-computing-in-one-event.md), [119](docs/research/119-views-that-compute.md) | -| 보기 밀기 | 보기의 `GET`에 `Accept: text/event-stream`이면 그 값을 민다(SSE, 브라우저의 `EventSource` 그대로). 같은 보기와 인자를 보는 구독자는 계산 한 번을 나누고, 값이 바뀔 때만, 많아야 10 ms마다 한 번 보낸다. 느린 구독자에게는 가장 새 값 하나만 기다리게 한다. 모듈이 바뀌어도 같은 이름의 보기로 이어 가고, 토큰을 빼면 끊는다. 구독자 1,000에 쓰기 초당 100번까지 신선도 p99 11 ms 이하, 초당 1번이면 100 ms 폴링의 약 10분의 1 CPU, 구독자 1만에 구독자마다 0.3–0.4 KiB, 1만에게 퍼지기까지 p99 45 ms | [019](docs/design/019-pushing-views.md), [120](docs/research/120-pushing-views.md) | +| 수명과 계산 | 값의 수명은 선언이 말한다: `state`(저장), `transient state`(앱이 도는 동안: 저장소에 쓰지 않고 다시 시작하면 초기값, SD 카드에서 쓰기 17배), `let`(이벤트 동안). 핸들러와 보기가 계산한다: 이벤트의 리스트(`let seen: list of Seen by node`), 세는 `for i in a .. b`, `break`, 8겹까지의 중첩, 그리고 `view 이름(인자) { … return … }`(스냅숏에서 돌아 소유자를 막지 않는다). 모든 반복은 시작할 때 횟수의 상한을 갖고, 모든 이벤트와 보기는 실행량 예산(정하지 않으면 2^28 명령)과 메모리 몫(2^28 바이트) 안에서 끝난다. 노드 10만·간선 100만의 BFS 한 이벤트 191 ms(처음 397; CPython 449, C 9): 이벤트의 키 있는 리스트가 키를 비트나 표로도 갖고, 키로 넣기를 한 번 내려가서 하며, 리스트의 코드는 `-O3`다. TPC-C는 그 덕에 +4.1%, 은행 질의의 명령 수는 1% 안. 같은 계산이 Pi·WSL2·M3 Mac에서 같다. 같은 탐색을 보기로 하면 쓰기는 CPU를 나누는 만큼만 줄고(이벤트로 하면 0.2%로 멈춘다), 본문이 있는 보기는 보기의 스레드(`--view-workers`)가 계산해 연결 스레드를 막지 않는다(쓰기 최대 지연이 CPU만 나눌 때의 1.0배, 연결 스레드 하나에서 묻는 연결 둘이 1.95배) | [018](docs/design/018-lifetimes-and-computation.md), [020](docs/design/020-computing-faster.md), [117](docs/research/117-transient-states.md), [118](docs/research/118-computing-in-one-event.md), [119](docs/research/119-views-that-compute.md), [121](docs/research/121-computing-faster.md), [122](docs/research/122-views-off-the-connection-threads.md) | +| 보기 밀기 | 보기의 `GET`에 `Accept: text/event-stream`이면 그 값을 민다(SSE, 브라우저의 `EventSource` 그대로). 같은 보기와 인자를 보는 구독자는 계산 한 번을 나누고, 값이 바뀔 때만, 많아야 10 ms마다 한 번 보낸다. 느린 구독자에게는 가장 새 값 하나만 기다리게 한다. 모듈이 바뀌어도 같은 이름의 보기로 이어 가고, 토큰을 빼면 끊는다. 구독자 1,000에 쓰기 초당 100번까지 신선도 p99 11 ms 이하, 초당 1번이면 100 ms 폴링의 약 10분의 1 CPU(이벤트 하나와 요청 하나의 값이 같아 메시지 수의 비다), 구독자 1만에 구독자마다 0.3–0.4 KiB, 1만에게 퍼지기까지 p99 45 ms | [019](docs/design/019-pushing-views.md), [120](docs/research/120-pushing-views.md) | | 영속 | 선언한 상태가 곧 영속 상태다. 목록은 이미지와 로그(저장마다 바뀐 기록 한 항목, 재사용하는 할당 구간, 앞 항목의 다이제스트로 이어짐)에, 나머지는 트리 저장소에(커밋당 한 연속 구간과 동기화 한 번). 이벤트 묶음마다 저장 한 번, 이미지는 소유자 밖에서 체크포인트. `kill -9`와 커밋 각 단계의 죽음 뒤 확인 응답한 이벤트가 모두 남는다. 레코드·키가 바뀐 모듈로 바꿔도 데이터가 이름으로 옮겨진다 | [076](docs/research/076-one-sync-commit.md), [081](docs/research/081-tail-records.md), [092](docs/research/092-tpcc.md), 008 상태 영속·교체 | | 인터페이스 | 한 명세가 TUI(셀 단위 갱신), CLI, 문장(NUI), 웹 GUI가 된다. 저장하면 실행 중인 앱이 상태를 이어 새 모듈로 바뀐다(`aup watch`, `vega … --watch`) | Vega `docs/design/001-first-component.md`, Vega `make check`(가상 터미널, 헤드리스 Chromium 포함) | | HTTP 서비스 | 보기가 GET, 입력이 POST다(JSON 본문과 그 안의 그룹 객체를 제자리에서 읽는다, 라우터 코드 없음). 앱의 `send`는 이벤트와 함께 저장되고, `--send 출력=URL`로 다른 앱이나 HTTP 끝점에 순서대로 묶어 전해지며, 받는 앱은 번호마다 한 번 적용한다(서버를 죽였다 살려도 정확히 한 번). `--seal 출력=공개키`면 받는 노드의 키로 봉인해(HPKE, 전달선마다 세션 하나, 보내는 노드의 키로 인증) 사이의 중계가 읽지도 바꾸지도 못하고, 받는 노드는 `--sender 이름=키`로 보낸 이를 가린다(봉인 전달 462k/s, 봉인 없이 468k/s). 답은 저장 뒤에 나간다. 상태를 가진 앱은 소유자 스레드 하나가 입력을 적용하고, 연결 스레드는 마지막 저장의 스냅숏으로 보기를 답한다(쓰기가 동기화하는 동안에도 읽기는 기다리지 않는다) | 008 HTTP 호스트, [077](docs/research/077-apps-as-http-services.md), [080](docs/research/080-snapshot-reads.md), [082](docs/research/082-json-in-place.md), [083](docs/research/083-delivery-over-http.md), [086](docs/research/086-sealed-sends.md), [088](docs/research/088-sealed-sessions.md) | @@ -35,7 +35,7 @@ 블록을 받지 않는다(저장소 커밋 +21%, 저널 커밋 −59%, [085](docs/research/085-cycle-sized-files.md)). 남은 것: macOS의 호스트(`pure-serve`는 리눅스의 것), 바뀐 모듈을 놓기(바꿀 때마다 19 KB, 115), 압축을 없애는 제자리 재사용 저장소(085 다음), 응답을 기다리는 출력의 묶음 전달(083), 앱 -라우트의 TLS(앞단이 한다; 문서 피어의 TLS는 있다), HTTP로 앱을 더하고 바꾸는 길, 긴 보기를 연결 스레드 밖에서 계산하기(119), aup·Vega의 배포 묶음, 캐시보다 큰 목록을 찾는 걸음(109: 미리 가져오기로는 줄지 +라우트의 TLS(앞단이 한다; 문서 피어의 TLS는 있다), HTTP로 앱을 더하고 바꾸는 길, aup·Vega의 배포 묶음, 캐시보다 큰 목록을 찾는 걸음(109: 미리 가져오기로는 줄지 않았다. 한 사건의 조회를 겹치거나 구조를 바꿔야 한다). ## 공개할 작업물과 완료 증거 diff --git a/docs/research/122-views-off-the-connection-threads.md b/docs/research/122-views-off-the-connection-threads.md new file mode 100644 index 0000000..5af9c09 --- /dev/null +++ b/docs/research/122-views-off-the-connection-threads.md @@ -0,0 +1,84 @@ +# 122 — 긴 보기를 연결 스레드 밖에서 + +2026-10-04. [020](../design/020-computing-faster.md)의 2단계(본문이 있는 보기를 보기의 스레드에서)를 잰다. 기준은 구현하기 +전에 적어 커밋했다(`1d8c1f1`). 만든 것은 `2fb1da2`이고, 만들면서 정한 것은 020의 "2단계에서 정한 것"에 적었다. + +## 쉬운 말로 + +- **계산하는 보기가 이제 다른 연결을 기다리게 하지 않는다.** 본문이 있는 보기(BFS 같은)는 보기의 스레드가 계산하고, 연결 스레드는 + 그동안 다른 연결을 읽고 쓴다. 연결 둘이 BFS 보기를 쉬지 않고 묻는 동안 쓰기의 최대 지연이 11 ms다. 같은 코어를 다른 프로세스와 + 나눌 때와 같다(1.0배, 기준 2배 이하). 전처럼 연결 스레드가 계산하면 76 ms(8.2배)다. +- **보기가 연결의 자리와 상관없이 나란히 돈다.** 연결 스레드 하나에서 묻는 연결 둘이 하나의 1.95배를 받는다(기준 1.8배, 전에는 + 1.0배). 전에는 두 연결이 같은 연결 스레드에 떨어지면(커널이 정한다) 차례로 돌아 초당 36번, 다른 스레드면 70번이었다. 이제 늘 + 56–60번이다. +- **짧은 보기는 그대로다.** 식 하나의 보기(`total`)는 연결 스레드가 계산하고, 그 읽기는 전과 같다(−0.2%, 기준 3% 안). +- 보기를 계산하는 데 드는 CPU는 그대로다. 쓰기가 줄어드는 몫(42%만 남는다)은 다른 프로세스와 코어를 나눌 때와 같다(39.5%). + +## 결과 + +원시 결과와 명령은 [037-view-workers](../../research/experiments/037-view-workers/)에 있다(`runs.sh`, `views.py`, `plain.py`). +`views.py`는 034의 것에 쓰기 최대 지연의 중앙값과 호스트에 줄 인자(`VIEWS_SERVE_ARGS`)를 더한 것이다. 보기의 길이는 1단계 덕에 +119 때(약 43 ms)보다 짧다. 그래서 견주는 쪽(`--view-workers 0`, 전처럼 연결 스레드가 계산한다)을 같은 빌드로 함께 쟀다. + +**방법.** `pure-serve`(연결 스레드 셋, 저장소는 RAM, 코어 0–2)에 034의 `graph.aup`(노드 2만, 간선 10만, 시드 119, `distance` +보기의 본문이 BFS). 부하 도구(코어 3)가 16연결로 점수를 10초 동안 보낸다. 셋을 번갈아: 쓰기만(writes), 연결 둘이 같은 호스트의 +`distance`를 쉬지 않고 물으며(both), 그 둘이 같은 코어의 다른 호스트에 물으며(apart: CPU만 나눈다). 세 라운드씩 두 번(`runs.sh +tail 1`, `tail 2`), 같은 것을 `--view-workers 0`으로 두 번(`control`). 묻는 쪽은 미리 고른 출발 64곳에서 묻고 모든 답을 모델과 +견준다. 보기의 스레드는 기본(CPU 수: `taskset` 0–2이니 셋)이다. + +| 여섯 라운드의 중앙값 | 쓰기(초당) | 쓰기 p99 | 쓰기 최대 | 보기(초당) | +|---|---:|---:|---:|---:| +| writes | 120,598 | 0.28 ms | 6.0 ms | — | +| both | 50,637 (42.0%) | 3.3 ms | **11.3 ms** (9.7–20.5) | 58.2 (56.4–59.6) | +| apart | 47,688 (39.5%) | 3.5 ms | 11.3 ms (10.2–16.4) | 57.8 | +| both, `--view-workers 0` | 47,431 (38.7%) | 14.6 ms | 75.8 ms (59–131) | 53.2 (35.7–70.5) | +| apart, `--view-workers 0` | 51,301 (41.9%) | 2.0 ms | 9.2 ms | 64.0 | + +- 꼬리의 기준: both의 최대 지연 중앙값이 apart의 1.0배다(기준 2배 이하). `--view-workers 0`이면 8.2배다(119에서 98–188 ms 대 + 6–9 ms였다: 그때의 보기는 1단계 전이라 길었다). +- `--view-workers 0`의 both는 라운드마다 둘 중 하나다. 두 묻는 연결이 다른 연결 스레드에 떨어지면 보기가 초당 70번 나란히 돌고 쓰기가 + 준다. 같은 스레드에 떨어지면 36번 차례로 돌고, 그 스레드에 붙은 쓰기 연결이 보기 하나만큼 기다린다(최대 65–131 ms). 보기의 + 스레드가 있으면 이 갈림이 없다(56–60번). +- 쓰기가 줄어드는 몫은 둘이 같다(42.0% 대 39.5%). 보기의 계산이 코어를 쓰는 만큼이다. 쓰기의 p99가 쓰기만일 때보다 큰 것(3.3 ms)도 + apart와 같다. + +**나란히**(`runs.sh scaling 1`, `scaling 2`): 연결 스레드 하나(`--threads 1`)에 묻는 연결 하나, 둘. 세 라운드씩 두 번. + +| | 하나 | 둘 | 배 | +|---|---:|---:|---:| +| 1 | 37.8 | 73.7 | 1.95 | +| 2 | 37.8 | 73.3 | 1.94 | + +여섯 라운드 모두 하나는 37.8–37.9, 둘은 72.8–74.1이다. 119에서 같은 조건은 1.0배(23.6 대 23.6)였다. + +**짧은 보기**(`runs.sh plain`): 점수판(`benchmarks/http/scores.aup`)에 8연결이 1초 동안 점수를 넣고, 64연결이 `GET /views/total`을 10초 동안. +전(`dde73e4`)과 후(`2fb1da2`)를 번갈아 여섯 쌍, 매번 새 호스트. + +| 중앙값 | 전 | 후 | 바뀜 | +|---|---:|---:|---:| +| 읽기(초당) | 148,867 | 148,530 | −0.2% | + +여섯 쌍 모두 p50 416 µs, p99 640 µs로 같다. + +### 검사 + +- Pure `tests/check.py`의 `server_view_workers`(새 검사): 앱 하나의 호스트(`--apps`, 연결 스레드 하나, 보기의 스레드 둘)에서 + 300만 번 도는 보기를 8연결이 쉬지 않고 묻는 동안, 앱의 모듈을 그 보기의 자리에 다른 보기(`other = 7`)가 있고 값이 열 배인 + 모듈로 바꾸고(SIGHUP), 이어서 앱을 뺀다. 모든 답이 옛 모듈의 값, 새 모듈의 값, 503 `stopped`, 404 가운데 하나다(섞인 답은 + `7`로 보였을 것이다). 그 보기의 스트림이 같은 스트림에서 새 모듈의 값을 받고, 앱이 빠지면 끝난다. 값이 오기 전에 닫은 스트림 + 열 개가 아무것도 남기지 않는다(멈출 때 구독자 0). `--view-workers 0`이면 보기의 스레드가 없고 같은 답이다. +- `server_view_bodies`(64연결이 동시에 묻는 `distance` 1,280개가 모두 모델과 같다)와 `server_push`(계산하는 보기를 미는 것 포함)가 + 이제 보기의 스레드를 거쳐 그대로 초록이다. +- Pure의 `make check`, aup와 Vega의 모든 검사가 초록이다. + +## 판정 + +- **020의 2단계는 들어간다.** 세 통과 조건(꼬리 2배 이하: 1.0배, 나란히 1.8배 이상: 1.95배, 짧은 보기 3% 안: −0.2%)을 모두 채웠다. +- 119가 남긴 두 가지(연결 스레드가 막는 꼬리, 연결의 자리에 따라 갈리는 보기의 처리량)가 없어졌다. + +## 다음 + +- 019의 미는 보기도 보기의 스레드를 거친다(검사로만 봤다: 계산하는 보기의 스트림, 값이 오기 전에 닫힌 스트림). 구독자가 많은 + 계산하는 보기의 신선도는 재지 않았다. +- 보기의 스레드가 다 바쁠 때 넘긴 일은 줄을 선다. 그 줄의 한도는 앱의 `queue=N`이고, 한 앱의 호스트에서는 연결 수다. 줄을 서는 + 동안의 지연을 다른 보기와 나누는 것(앱마다 공평하게)은 앱이 여럿일 때 잰다. diff --git a/research/experiments/037-view-workers/2026-10-04-control-1.jsonl b/research/experiments/037-view-workers/2026-10-04-control-1.jsonl new file mode 100644 index 0000000..e0145f7 --- /dev/null +++ b/research/experiments/037-view-workers/2026-10-04-control-1.jsonl @@ -0,0 +1,10 @@ +{"round": 1, "way": "writes", "writes_per_second": 113527, "write_p50_us": 120, "write_p99_us": 384, "write_max_us": 9216, "views_per_second": 0.0} +{"round": 1, "way": "both", "writes_per_second": 14652, "write_p50_us": 108, "write_p99_us": 28672, "write_max_us": 131072, "views_per_second": 70.5} +{"round": 1, "way": "apart", "writes_per_second": 52102, "write_p50_us": 232, "write_p99_us": 2304, "write_max_us": 9216, "views_per_second": 63.1} +{"round": 2, "way": "apart", "writes_per_second": 50586, "write_p50_us": 240, "write_p99_us": 2176, "write_max_us": 9216, "views_per_second": 64.8} +{"round": 2, "way": "both", "writes_per_second": 28232, "write_p50_us": 168, "write_p99_us": 28672, "write_max_us": 59392, "views_per_second": 70.0} +{"round": 2, "way": "writes", "writes_per_second": 122103, "write_p50_us": 120, "write_p99_us": 240, "write_max_us": 5888, "views_per_second": 0.0} +{"round": 3, "way": "writes", "writes_per_second": 122828, "write_p50_us": 120, "write_p99_us": 240, "write_max_us": 5888, "views_per_second": 0.0} +{"round": 3, "way": "both", "writes_per_second": 70782, "write_p50_us": 104, "write_p99_us": 448, "write_max_us": 118784, "views_per_second": 35.7} +{"round": 3, "way": "apart", "writes_per_second": 50748, "write_p50_us": 256, "write_p99_us": 1792, "write_max_us": 7936, "views_per_second": 66.9} +{"nodes": 20000, "edges": 100000, "threads": 3, "askers": 2, "medians": {"writes": 122103, "both": 28232, "apart": 50748}, "write_p99_medians_us": {"writes": 240, "both": 28672, "apart": 2176}, "write_max_medians_us": {"writes": 5888, "both": 118784, "apart": 9216}, "serve_args": ["--view-workers", "0"], "writes_kept": {"both": 23.1, "apart": 41.6}, "p99_both_over_apart": 13.18, "max_both_over_apart": 12.89} diff --git a/research/experiments/037-view-workers/2026-10-04-control-2.jsonl b/research/experiments/037-view-workers/2026-10-04-control-2.jsonl new file mode 100644 index 0000000..ab057cc --- /dev/null +++ b/research/experiments/037-view-workers/2026-10-04-control-2.jsonl @@ -0,0 +1,10 @@ +{"round": 1, "way": "writes", "writes_per_second": 124372, "write_p50_us": 116, "write_p99_us": 232, "write_max_us": 6144, "views_per_second": 0.0} +{"round": 1, "way": "both", "writes_per_second": 73789, "write_p50_us": 108, "write_p99_us": 496, "write_max_us": 65536, "views_per_second": 36.1} +{"round": 1, "way": "apart", "writes_per_second": 50945, "write_p50_us": 224, "write_p99_us": 2560, "write_max_us": 12288, "views_per_second": 61.7} +{"round": 2, "way": "apart", "writes_per_second": 51657, "write_p50_us": 256, "write_p99_us": 1792, "write_max_us": 15872, "views_per_second": 67.1} +{"round": 2, "way": "both", "writes_per_second": 66630, "write_p50_us": 104, "write_p99_us": 416, "write_max_us": 86016, "views_per_second": 36.4} +{"round": 2, "way": "writes", "writes_per_second": 121285, "write_p50_us": 120, "write_p99_us": 272, "write_max_us": 9216, "views_per_second": 0.0} +{"round": 3, "way": "writes", "writes_per_second": 130697, "write_p50_us": 116, "write_p99_us": 200, "write_max_us": 5120, "views_per_second": 0.0} +{"round": 3, "way": "both", "writes_per_second": 28187, "write_p50_us": 168, "write_p99_us": 28672, "write_max_us": 65536, "views_per_second": 70.0} +{"round": 3, "way": "apart", "writes_per_second": 86641, "write_p50_us": 152, "write_p99_us": 768, "write_max_us": 7936, "views_per_second": 35.1} +{"nodes": 20000, "edges": 100000, "threads": 3, "askers": 2, "medians": {"writes": 124372, "both": 66630, "apart": 51657}, "write_p99_medians_us": {"writes": 232, "both": 496, "apart": 1792}, "write_max_medians_us": {"writes": 6144, "both": 65536, "apart": 12288}, "serve_args": ["--view-workers", "0"], "writes_kept": {"both": 53.6, "apart": 41.5}, "p99_both_over_apart": 0.28, "max_both_over_apart": 5.33} diff --git a/research/experiments/037-view-workers/2026-10-04-plain.jsonl b/research/experiments/037-view-workers/2026-10-04-plain.jsonl new file mode 100644 index 0000000..e7cc9bc --- /dev/null +++ b/research/experiments/037-view-workers/2026-10-04-plain.jsonl @@ -0,0 +1,13 @@ +{"round": 1, "version": "before", "reads_per_second": 148378, "p50_us": 416, "p99_us": 640} +{"round": 1, "version": "after", "reads_per_second": 148600, "p50_us": 416, "p99_us": 640} +{"round": 2, "version": "after", "reads_per_second": 148824, "p50_us": 416, "p99_us": 640} +{"round": 2, "version": "before", "reads_per_second": 148867, "p50_us": 416, "p99_us": 640} +{"round": 3, "version": "before", "reads_per_second": 149299, "p50_us": 416, "p99_us": 640} +{"round": 3, "version": "after", "reads_per_second": 148556, "p50_us": 416, "p99_us": 640} +{"round": 4, "version": "after", "reads_per_second": 148503, "p50_us": 416, "p99_us": 640} +{"round": 4, "version": "before", "reads_per_second": 148976, "p50_us": 416, "p99_us": 640} +{"round": 5, "version": "before", "reads_per_second": 147896, "p50_us": 416, "p99_us": 640} +{"round": 5, "version": "after", "reads_per_second": 148133, "p50_us": 416, "p99_us": 640} +{"round": 6, "version": "after", "reads_per_second": 148499, "p50_us": 416, "p99_us": 640} +{"round": 6, "version": "before", "reads_per_second": 148866, "p50_us": 416, "p99_us": 640} +{"before_median": 148866.5, "after_median": 148529.5, "change": -0.2} diff --git a/research/experiments/037-view-workers/2026-10-04-scaling-1.jsonl b/research/experiments/037-view-workers/2026-10-04-scaling-1.jsonl new file mode 100644 index 0000000..64f7382 --- /dev/null +++ b/research/experiments/037-view-workers/2026-10-04-scaling-1.jsonl @@ -0,0 +1,7 @@ +{"round": 1, "way": "one", "views_per_second": 37.8} +{"round": 1, "way": "many", "views_per_second": 73.7} +{"round": 2, "way": "many", "views_per_second": 72.8} +{"round": 2, "way": "one", "views_per_second": 37.8} +{"round": 3, "way": "one", "views_per_second": 37.9} +{"round": 3, "way": "many", "views_per_second": 74.1} +{"nodes": 20000, "edges": 100000, "threads": 1, "askers": 2, "medians": {"one": 37.8, "many": 73.7}, "write_p99_medians_us": {}, "write_max_medians_us": {}, "serve_args": [], "scaling": 1.95} diff --git a/research/experiments/037-view-workers/2026-10-04-scaling-2.jsonl b/research/experiments/037-view-workers/2026-10-04-scaling-2.jsonl new file mode 100644 index 0000000..c869d71 --- /dev/null +++ b/research/experiments/037-view-workers/2026-10-04-scaling-2.jsonl @@ -0,0 +1,7 @@ +{"round": 1, "way": "one", "views_per_second": 37.8} +{"round": 1, "way": "many", "views_per_second": 73.5} +{"round": 2, "way": "many", "views_per_second": 73.3} +{"round": 2, "way": "one", "views_per_second": 37.8} +{"round": 3, "way": "one", "views_per_second": 37.8} +{"round": 3, "way": "many", "views_per_second": 73.3} +{"nodes": 20000, "edges": 100000, "threads": 1, "askers": 2, "medians": {"one": 37.8, "many": 73.3}, "write_p99_medians_us": {}, "write_max_medians_us": {}, "serve_args": [], "scaling": 1.94} diff --git a/research/experiments/037-view-workers/2026-10-04-tail-1.jsonl b/research/experiments/037-view-workers/2026-10-04-tail-1.jsonl new file mode 100644 index 0000000..3ac4326 --- /dev/null +++ b/research/experiments/037-view-workers/2026-10-04-tail-1.jsonl @@ -0,0 +1,10 @@ +{"round": 1, "way": "writes", "writes_per_second": 118131, "write_p50_us": 120, "write_p99_us": 336, "write_max_us": 5888, "views_per_second": 0.0} +{"round": 1, "way": "both", "writes_per_second": 47411, "write_p50_us": 216, "write_p99_us": 3456, "write_max_us": 11776, "views_per_second": 58.1} +{"round": 1, "way": "apart", "writes_per_second": 44605, "write_p50_us": 240, "write_p99_us": 3584, "write_max_us": 13824, "views_per_second": 58.8} +{"round": 2, "way": "apart", "writes_per_second": 46915, "write_p50_us": 216, "write_p99_us": 3456, "write_max_us": 16384, "views_per_second": 56.9} +{"round": 2, "way": "both", "writes_per_second": 43621, "write_p50_us": 224, "write_p99_us": 3840, "write_max_us": 11776, "views_per_second": 57.1} +{"round": 2, "way": "writes", "writes_per_second": 114928, "write_p50_us": 120, "write_p99_us": 368, "write_max_us": 8192, "views_per_second": 0.0} +{"round": 3, "way": "writes", "writes_per_second": 121761, "write_p50_us": 120, "write_p99_us": 272, "write_max_us": 6144, "views_per_second": 0.0} +{"round": 3, "way": "both", "writes_per_second": 49962, "write_p50_us": 208, "write_p99_us": 3328, "write_max_us": 20480, "views_per_second": 58.3} +{"round": 3, "way": "apart", "writes_per_second": 48011, "write_p50_us": 224, "write_p99_us": 3456, "write_max_us": 10752, "views_per_second": 58.4} +{"nodes": 20000, "edges": 100000, "threads": 3, "askers": 2, "medians": {"writes": 118131, "both": 47411, "apart": 46915}, "write_p99_medians_us": {"writes": 336, "both": 3456, "apart": 3456}, "write_max_medians_us": {"writes": 6144, "both": 11776, "apart": 13824}, "serve_args": [], "writes_kept": {"both": 40.1, "apart": 39.7}, "p99_both_over_apart": 1.0, "max_both_over_apart": 0.85} diff --git a/research/experiments/037-view-workers/2026-10-04-tail-2.jsonl b/research/experiments/037-view-workers/2026-10-04-tail-2.jsonl new file mode 100644 index 0000000..676bbc2 --- /dev/null +++ b/research/experiments/037-view-workers/2026-10-04-tail-2.jsonl @@ -0,0 +1,10 @@ +{"round": 1, "way": "writes", "writes_per_second": 119435, "write_p50_us": 120, "write_p99_us": 288, "write_max_us": 12288, "views_per_second": 0.0} +{"round": 1, "way": "both", "writes_per_second": 51312, "write_p50_us": 208, "write_p99_us": 3200, "write_max_us": 9728, "views_per_second": 59.1} +{"round": 1, "way": "apart", "writes_per_second": 47365, "write_p50_us": 208, "write_p99_us": 3712, "write_max_us": 11776, "views_per_second": 57.3} +{"round": 2, "way": "apart", "writes_per_second": 48569, "write_p50_us": 216, "write_p99_us": 3328, "write_max_us": 10240, "views_per_second": 59.3} +{"round": 2, "way": "both", "writes_per_second": 52952, "write_p50_us": 216, "write_p99_us": 2688, "write_max_us": 10240, "views_per_second": 59.6} +{"round": 2, "way": "writes", "writes_per_second": 121843, "write_p50_us": 120, "write_p99_us": 256, "write_max_us": 5376, "views_per_second": 0.0} +{"round": 3, "way": "writes", "writes_per_second": 125827, "write_p50_us": 116, "write_p99_us": 224, "write_max_us": 5888, "views_per_second": 0.0} +{"round": 3, "way": "both", "writes_per_second": 52896, "write_p50_us": 192, "write_p99_us": 3200, "write_max_us": 10752, "views_per_second": 56.4} +{"round": 3, "way": "apart", "writes_per_second": 48953, "write_p50_us": 200, "write_p99_us": 3584, "write_max_us": 10752, "views_per_second": 56.4} +{"nodes": 20000, "edges": 100000, "threads": 3, "askers": 2, "medians": {"writes": 121843, "both": 52896, "apart": 48569}, "write_p99_medians_us": {"writes": 256, "both": 3200, "apart": 3584}, "write_max_medians_us": {"writes": 5888, "both": 10240, "apart": 10752}, "serve_args": [], "writes_kept": {"both": 43.4, "apart": 39.9}, "p99_both_over_apart": 0.89, "max_both_over_apart": 0.95} diff --git a/research/experiments/037-view-workers/plain.py b/research/experiments/037-view-workers/plain.py index 96c937f..a313cfb 100644 --- a/research/experiments/037-view-workers/plain.py +++ b/research/experiments/037-view-workers/plain.py @@ -2,8 +2,9 @@ """A view of one expression read, before and after (docs/design/020's stage 2, research 122): the scoreboard (benchmarks/http/scores.aup) on pure-serve BEFORE and AFTER in turns (connection threads 3, store in RAM, cores 0-2, -a fresh host each run), 1,000 points posted first, then benchmarks/http/load -(core 3) reads GET /views/total on 64 connections for SECONDS. ROUNDS +a fresh host each run), points posted on 8 connections for a second first, +then benchmarks/http/load (core 3) reads GET /views/total on 64 connections +for SECONDS. ROUNDS pairs, the first in each even round the after one. plain.py BEFORE AFTER AUP SECONDS ROUNDS a JSON line per run, then medians diff --git a/research/experiments/037-view-workers/runs.sh b/research/experiments/037-view-workers/runs.sh index bb91997..4814e19 100644 --- a/research/experiments/037-view-workers/runs.sh +++ b/research/experiments/037-view-workers/runs.sh @@ -11,7 +11,7 @@ # connection threads, as before) # runs.sh scaling ROUND views.py, connection threads 1: one asker, two # runs.sh plain plain.py: GET /views/total (64 connections, -# 10 s), before (9484700) and after, 6 pairs +# 10 s), before (dde73e4, stage 1) and after, 6 pairs set -e here=research/experiments/037-view-workers builds=$HOME/.cache/pure-037 -- cgit v1.2.3