aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 21:54:49 +0100
committereunhhu <[email protected]>2026-09-26 21:54:49 +0100
commitd693cc750b75b2d862614b6f79f81a311f6294ca (patch)
tree71588161adc22f5791ce38becf1df650b92501da /docs
parentcb23514a5a2b00f635444d0e98a429419e6a67b4 (diff)
downloadpure-d693cc750b75b2d862614b6f79f81a311f6294ca.tar.gz
pure-d693cc750b75b2d862614b6f79f81a311f6294ca.zip
Lend a text state's stored extents for its view (079)
A view that shows a text state as it is (pure_module_text_state) now goes out as the state's stored extents, as a document read does: the answer holds the store mapping, charged to the budget, until it is sent and its ticket released; a state with edits not yet stored is copied as before. Documents and views share one loan path, and the extent array grows as needed. A 1 MiB note read by 8 connections: 776-817 answers a second copied, 1,875-1,901 lent, p50 9.2 to 3.5 ms, peak RSS 10.3 to 5.7 MiB. The load generator learns GET /views/page.
Diffstat (limited to 'docs')
-rw-r--r--docs/design/008-module-format.md2
-rw-r--r--docs/research/079-lent-views.md52
2 files changed, 53 insertions, 1 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index db597e7..1c4d397 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -252,7 +252,7 @@ text·리스트가 남을 때만 위치를 유지한다.
| 요청 | 뜻 |
|---|---|
-| `GET /views/이름` | 보기의 값. 수는 가장 짧은 왕복 10진수, text는 UTF-8 그대로(`text/plain`) |
+| `GET /views/이름` | 보기의 값. 수는 가장 짧은 왕복 10진수, text는 UTF-8 그대로(`text/plain`). text 상태를 그대로 보이는 보기(`view page = note`)는 저장된 익스텐트를 빌려준다([079](../research/079-lent-views.md)) |
| `GET /views/표?from=N&count=M` | 표의 행: `{"rows":전체,"from":N,"items":[{열: 값}…]}`(`application/json`). 요청한 행의 칸만 계산한다. 값이 없는 칸은 `null` |
| `POST /inputs/이름` | 이벤트. text 입력은 본문이 text, 키 입력은 누름(down 뒤 up). 메시지는 고정 필드(u32, 수)를 쿼리(`?points=3`)로, 가변 필드 하나를 본문으로 받거나, `Content-Type: application/json` 본문의 객체로 필드를 받는다(문자열은 바이트·text, 수는 u32·수). 응답 본문은 핸들러의 `reply`(메시지 필드) |
| `GET`·`PUT /objects/키` | 모듈에 키별 메시지 `show`·`edit`가 있으면 기존 문서 서버(`server.c`)와 같은 계약. `GET`은 `show`, `PUT`(`If-Match`/`If-None-Match`)은 `edit` |
diff --git a/docs/research/079-lent-views.md b/docs/research/079-lent-views.md
new file mode 100644
index 0000000..1cb8453
--- /dev/null
+++ b/docs/research/079-lent-views.md
@@ -0,0 +1,52 @@
+# 079 — 보기도 빌려준다: text 상태를 저장된 익스텐트 그대로
+
+2026-09-26. [로드맵](../roadmap.md) 단기 1 "Vectored loan 완성".
+
+착수 기준:
+
+- 바꿀 결정: 앱의 보기를 HTTP로 내보낼 때 값을 버퍼에 복사해 보낼지, 저장소 매핑의 익스텐트를 그대로 `writev`에 넘길지.
+- 남은 질문: 문서 GET은 이미 빌려준다([075](075-http-host-against-axum.md)). 앱 보기(`GET /views/NAME`)는 text 상태를
+ 그대로 보여 줄 때도 매번 복사했다. 빌려주면 얼마나 달라지고, 빌려준 것은 언제나 돌아오는가(답이 끝날 때, 읽지 않고
+ 떠날 때, 다른 스레드가 답할 때, 그사이 저장소가 압축될 때)?
+- 최소 확인: 같은 1 MiB 보기를 복사하는 빌드와 빌려주는 빌드로 번갈아 재고, 빌려준 동안과 뒤의 저장소 매핑 수와 예산을
+ 확인한다.
+- 중단 기준: 한 번이라도 다른 버전이 섞여 나가거나, 답이 끝난 뒤 매핑이나 예산이 남으면 되돌린다.
+
+## 한 것
+
+- `pure_module_text_state`: 보기가 text 상태 그대로(`view page = note`, 명령 하나 `TEXT_LOAD`)인지 알려 준다.
+- `pure-serve`는 그런 보기의 text draft 현재 버전을 저장소 매핑이 모두 담고 있으면 그 익스텐트를 답의 본문으로 빌려준다.
+ 문서 GET과 같은 경로다. 답은 매핑 참조를 쥐고(예산에 계상), 소켓에 다 들어가고 티켓이 풀릴 때 놓는다. 방금 고쳐
+ 아직 저장소에 없는 조각이 있으면 전처럼 복사한다. 계산한 text(보간, 조건식)는 복사한다.
+- 익스텐트 배열은 필요한 만큼 늘린다(세는 패스가 없다). 대여 답도 보기의 `Content-Type`을 유지한다.
+
+## 장치가 말한 것
+
+Raspberry Pi 5, 서버는 CPU 0–2, 부하는 CPU 3. 1 MiB 메모를 `GET /views/page`로 8연결이 5초간 읽는다. 복사 빌드와 대여
+빌드를 번갈아 두 번씩 쟀다.
+
+| | 복사 | 대여 |
+|---|---:|---:|
+| 연결 스레드 1: 답/s | 776–817 | **1,875–1,901** |
+| p50 | 9.2 ms | **3.5 ms** |
+| 최대 RSS | 10.3 MiB | **5.7 MiB** |
+| 연결 스레드 3 + 소유자: 답/s | 590–617 | **1,167–1,194** |
+| 최대 RSS | 11.9–12.4 MiB | **6.2 MiB** |
+
+같은 크기의 문서 GET은 axum 1,372/s, drogon 708/s였다([075](075-http-host-against-axum.md)). 큰 답에서는 스레드 하나가 더
+빠르다. 답 하나에 1 MiB를 받는 부하 생성기(코어 하나)와 스레드 사이의 인계가 몫을 차지하는 것으로 보인다.
+
+## 회수 확인
+
+aup `make check`의 `server_lent_views`(스레드 1, 연결 스레드 3 + 소유자):
+
+- 한 연결에 1 MiB 보기 32개를 이어 보내면 커널 버퍼가 받지 못한 답이 빌린 채 기다린다. 그동안 메모를 다섯 번 바꿔도
+ 32개 답은 각각 한 버전 전체로 나온다(첫 답은 바꾸기 전 버전).
+- 기다리는 동안 저장소 파일의 매핑은 2개다(빌려준 옛 매핑 + 현재). 1 MiB 쓰기가 이어지면 저장소가 압축해 파일을 바꾸는데,
+ 빌려준 옛 매핑은 `(deleted)`인 옛 파일의 페이지를 쥐고 있다. 답을 다 읽으면 1개로 돌아온다.
+- 읽지 않고 연결을 닫아도(답의 티켓이 끝남) 1개로 돌아오고, 서버를 멈출 때 `retained_payload_bytes`는 0이다. 연결
+ 스레드 3개일 때는 소유자가 빌려준 매핑을 연결 스레드가 놓는다(참조 수가 원자적이다).
+
+검사를 만들며 두 가지를 배웠다. 수신 버퍼를 4 KiB로 줄인 독자는 루프백에서도 지연 ACK와 맞물려 초당 40 KB쯤으로 기어간다.
+그러면 서버의 쓰기가 10초 넘게 진전이 없어 연결 기한이 연결을 닫는다(의도된 동작). 그래서 답이 서버 쪽에 걸리게 하려면
+수신 버퍼를 줄이는 대신, 커널이 흡수하는 양(수 MB)보다 훨씬 많이 이어 보내야 한다.