aboutsummaryrefslogtreecommitdiffstats
path: root/docs/research/079-lent-views.md
blob: 668a832a4c1b9a7f2186a59f2f5af57c364feee5 (plain)
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
# 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`(지금은 Pure `tests/check.py`)(스레드 1, 연결 스레드 3 + 소유자):

- 한 연결에 1 MiB 보기 32개를 이어 보내면 커널 버퍼가 받지 못한 답이 빌린 채 기다린다. 그동안 메모를 다섯 번 바꿔도
  32개 답은 각각 한 버전 전체로 나온다(첫 답은 바꾸기 전 버전).
- 기다리는 동안 저장소 파일의 매핑은 2개다(빌려준 옛 매핑 + 현재). 1 MiB 쓰기가 이어지면 저장소가 압축해 파일을 바꾸는데,
  빌려준 옛 매핑은 `(deleted)`인 옛 파일의 페이지를 쥐고 있다. 답을 다 읽으면 1개로 돌아온다.
- 읽지 않고 연결을 닫아도(답의 티켓이 끝남) 1개로 돌아오고, 서버를 멈출 때 `retained_payload_bytes`는 0이다. 연결
  스레드 3개일 때는 소유자가 빌려준 매핑을 연결 스레드가 놓는다(참조 수가 원자적이다).

검사를 만들며 두 가지를 배웠다. 수신 버퍼를 4 KiB로 줄인 독자는 루프백에서도 지연 ACK와 맞물려 초당 40 KB쯤으로 기어간다.
그러면 서버의 쓰기가 10초 넘게 진전이 없어 연결 기한이 연결을 닫는다(의도된 동작). 그래서 답이 서버 쪽에 걸리게 하려면
수신 버퍼를 줄이는 대신, 커널이 흡수하는 양(수 MB)보다 훨씬 많이 이어 보내야 한다.