From c7ae77cdf4623c3eb07935081510478be9063024 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sat, 26 Sep 2026 05:48:40 +0100 Subject: Shard pure-serve across cores A burst now looks at the store once (pure_store_pin) instead of once per request; another shard's commit ends before its answer is sent, so a request made after that answer still reads it. With this alone one thread serves 125k small keep-alive reads per second, as many as axum on three cores, with a shorter p99. --threads N runs a shard per thread: its own SO_REUSEPORT listener, epoll set, runtime domain, app and store handle. Writer sessions of the tree store now take turns inside a process with a mutex (the file lock belongs to the process and does not keep threads apart), and revisions settle concurrent edits of a key. A module with states runs on one thread. Three shards serve 151k reads per second; they still commit one by one, so durable writes and 1 MiB reads are better on one thread for now, which stays the default. Note 075 records the numbers. --- docs/design/008-module-format.md | 5 ++++- docs/research/075-http-host-against-axum.md | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 2407980..01f2dc0 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -210,7 +210,10 @@ text·리스트가 남을 때만 위치를 유지한다. - **이벤트 한 바퀴가 묶음 하나다.** 준비된 요청을 모두 지연 저장 모드의 앱에 차례로 적용하고, 저장 한 번 **뒤에** 응답한다. 저장이 실패하면 묶음의 읽기까지 모두 실패한다. 같은 키가 다시 오거나 문서 수가 한 저장의 한도를 넘으려 하면 묶음을 먼저 끝낸다. 그래서 묶음 안의 모든 요청은 자기 묶음의 저장으로 저장되거나 그것과 함께 실패한다. -- **저장소 핸들.** 문서와 상태를 저장소 핸들(`pure_store_data`)로 열어 파일 매핑 하나를 공유한다. +- **저장소 핸들.** 문서와 상태를 저장소 핸들(`pure_store_data`)로 열어 파일 매핑 하나를 공유하고, 묶음마다 한 번만 + 저장소가 바뀌었는지 본다. +- **샤드.** `--threads N`은 스레드마다 같은 포트의 리스너(`SO_REUSEPORT`), epoll, 런타임 코어, 앱, 저장소 핸들을 둔다. + 키 없는 상태를 가진 모듈은 한 스레드로만 돈다(상태의 소유자는 하나다). - GET 응답은 예산에 계상되는 자원으로 복사한다. PUT 응답은 요청 본문에서 빌려야 하므로 `edit` 핸들러는 메시지 필드만 응답할 수 있다(`reply edit.insert`). 시작할 때 확인한다. diff --git a/docs/research/075-http-host-against-axum.md b/docs/research/075-http-host-against-axum.md index 308363e..8630fb3 100644 --- a/docs/research/075-http-host-against-axum.md +++ b/docs/research/075-http-host-against-axum.md @@ -75,3 +75,30 @@ PUT은 연결마다 자기 키 앞에 16 B를 넣고 받은 ETag로 다음 `If-M - 코어 하나로 axum(코어 3개)의 84%(작은 keep-alive GET), p99는 같다. 큰 읽기는 27% 빠르다. - 저장 보장 쓰기는 충돌 안전 axum의 3.0–5.3배, 안전하지 않은 axum(429/s)보다도 빠르다. - 남은 차이는 코어 수다. 다음은 2단계(코어마다 소유 스레드)다. + +## 2단계 결과: 묶음 스냅숏과 코어별 샤드 + +- **묶음 스냅숏.** 저장소가 바뀌었는지 요청마다 보지 않고 묶음 시작에 한 번 본다(`pure_store_pin`). 다른 샤드의 + 커밋은 그 응답이 나가기 전에 끝나므로, 응답을 받은 뒤 보낸 요청은 언제나 그 커밋 뒤의 묶음에서 읽힌다. +- **샤드.** `--threads N`이면 스레드마다 같은 포트의 리스너(`SO_REUSEPORT`), epoll, 런타임 코어, 앱, 저장소 핸들을 + 따로 둔다. 공유하는 것은 저장소뿐이다. 저장소의 쓰기 세션은 프로세스 안에서 뮤텍스로 줄을 선다(파일 잠금은 + 프로세스 단위라 스레드끼리 막지 못하고, 같은 파일의 아무 fd를 닫아도 풀린다). 같은 키의 동시 편집은 리비전이 + 가른다. 키 없는 상태를 가진 모듈은 한 스레드로만 돈다. + +| 작업 | pure-serve 1스레드 | pure-serve 3샤드 | axum 메모리(3코어) | axum 충돌 안전 | +|---|---:|---:|---:|---:| +| GET 64 B, keep-alive | **125,111** (p99 576) | **151,456** (p99 608) | 125,690 (p99 768) | 126,903 | +| GET 1 MiB, keep-alive | **1,848** | 1,046 | 1,380 | 1,446 | +| PUT 저장 보장, 연결 64 | **869** | 799 | – | 448 | +| PUT 저장 보장, 연결 8 | **282** | 134 | – | 68 | + +최대 RSS: 1스레드 6.2 MiB, 3샤드 9.8 MiB, axum 14.9–19.4 MiB. SD의 fsync 지연은 흔들림이 커서(p99 수백 ms) 쓰기 수치는 +실행마다 20–30% 달라진다. + +- 작은 읽기는 스레드 하나로 axum(코어 3개)과 같고, 샤드 셋이면 1.2배다. p99는 어느 쪽이든 axum보다 짧다. +- 샤드가 해가 되는 곳이 둘 있다. 저장 보장 쓰기는 샤드마다 따로 커밋해 fsync가 차례로 이어지고 묶음이 작아진다. + 1 MiB 읽기는 응답마다 1 MiB를 복사해 샤드들이 메모리 대역폭을 다툰다. 그래서 기본은 한 스레드다. +- 다음: 샤드들의 커밋을 fsync 한 번으로 합치는 저장소 수준의 그룹 커밋, 무복사 GET, 커밋당 fsync 한 번. + +ThreadSanitizer는 이 커널(47비트 가상 주소)을 지원하지 않아 쓰지 못했다. 스레드 동시 커밋은 ASan 빌드의 저장소 +검사(스레드 넷이 각자와 공유 키에 동시 커밋)와 aup의 3샤드 서버 검사로 확인한다. -- cgit v1.2.3