diff options
| author | eunhhu <[email protected]> | 2026-10-04 11:07:55 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-04 11:07:55 +0100 |
| commit | 52e51e4231f2e119c97826ae6d6e2f2af0da0c58 (patch) | |
| tree | 0f17ced79a1881bd2b67fbaa9e7ffb823f54b984 /docs | |
| parent | 420b749742badd26daf2474e9f157af17be2907f (diff) | |
| download | pure-52e51e4231f2e119c97826ae6d6e2f2af0da0c58.tar.gz pure-52e51e4231f2e119c97826ae6d6e2f2af0da0c58.zip | |
Say what apps that change on a running host showed (017 stage 3, research 115)
Changing an app in place keeps a million items' app answering but for
1.12 ms (a host restart: 150 ms; stopping and starting it again from its
store, as first built, 150.4 ms), 171.5 ms when its records change shape
(332 ms). Another app's p99 stays 0.97 times as it was while one changes
every 0.2 s; a host of one app is as it was (TPC-C +0.7%, instructions
at most +0.02%); each module change leaves 19 KB. 030 holds the windows
of both builds, the isolation, the pairs, the counts and the memory.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/design/017-apps-on-one-host.md | 30 | ||||
| -rw-r--r-- | docs/research/115-apps-that-change.md | 115 |
2 files changed, 139 insertions, 6 deletions
diff --git a/docs/design/017-apps-on-one-host.md b/docs/design/017-apps-on-one-host.md index f9ff765..3f2ce2b 100644 --- a/docs/design/017-apps-on-one-host.md +++ b/docs/design/017-apps-on-one-host.md @@ -1,7 +1,8 @@ # 017 — 한 호스트의 여러 앱: 차례, 예산, 실패 경계 작성일: 2026-10-01. 상태: 1·2단계와 2b 구현, 검사로 고정하고 쟀다([111](../research/111-apps-on-one-host.md), -[112](../research/112-sending-between-apps.md), [113](../research/113-one-sending-loop.md)). 3단계는 착수 기준을 적었다(2026-10-04). +[112](../research/112-sending-between-apps.md), [113](../research/113-one-sending-loop.md)). 3단계(수명) 구현, 검사로 고정하고 +쟀다([115](../research/115-apps-that-change.md)). 3b(경로의 인증)는 설계 전. [외부 리뷰 004](../outside-feedback/feedback-004.md)의 "구현 방향에 대한 평가" 셋째에서 시작한다. 작은 OS의 운영 모델( [007](007-execution-domains.md))을 Pure가 실제로 갖게 하는 단계다. @@ -238,17 +239,20 @@ counter apps/count.pmod stores/counter - 새 줄의 앱은 시작한다. 1단계의 시작과 같다(모듈 검증, 저장소, 배선, 실패하면 그 앱만 멈추고 까닭을 남긴다). - 없어진 줄의 앱은 멈춘다(아래의 순서). 그 뒤로 그 경로는 404 `no_app`이다. -- 바뀐 줄의 앱은 멈추고 새 줄로 다시 시작한다. 모듈 파일의 내용, 저장소, 예산, 배선 가운데 하나라도 바뀐 줄이다. 상태는 - 저장소에서 이름으로 이어받는다(지금 다른 모듈로 다시 띄울 때와 같다, `server_upgrade`). +- 바뀐 줄의 앱은 그 자리에서 바뀐다. 모듈 파일의 내용, 예산, 배선 가운데 하나라도 바뀐 줄이다. 받은 일을 다 적용하고 저장한 + 뒤, 메모리의 상태를 그대로 두고 모듈을 바꾼다(`pure_app_replace`: 상태와 outbox의 보냄을 이름으로 옮긴다). 저장소는 열린 + 채이고, 출구만 새 모듈로 다시 만든다. 새 모듈에 상태가 들어가지 않으면 그 앱은 멈추고 까닭을 남긴다. +- 저장소가 바뀐 줄의 앱, 저장이 불확실해진 앱은 멈추고 저장소에서 다시 연다(상태는 이름으로 이어받는다, `server_upgrade`). - 그대로인 줄의 앱은 건드리지 않는다. 멈춰 있거나(시작하지 못했거나) 불확실해진 앱만 다시 시작한다. 저장소에서 다시 연다. -- 다 맞추면 시작할 때와 같은 꼴의 줄 하나를 stdout에 낸다. 앱마다 상태와 까닭, 그리고 이번에 한 일(시작, 멈춤, 다시 시작)이다. +- 다 맞추면 시작할 때와 같은 꼴의 줄 하나를 stdout에 낸다(`{"reading":N,"apps":[…]}`). 앱마다 상태와 까닭, 그리고 이번에 한 + 일이다(kept, changed, restarted, started, removed). - 파일을 읽지 못하거나 틀린 줄이 있으면 아무것도 바꾸지 않고 까닭을 낸다. HTTP로 앱을 더하는 길을 두지 않는 까닭은 인증이다. 그 길은 경로의 인증(3b)을 먼저 요구한다. 신호는 프로세스의 주인만 보낼 수 있고, 파일은 호스트의 구성을 그대로 남긴다(다시 띄워도 같은 모습이다). 다시 읽기는 호스트의 스레드 하나(`pure-apps`)가 한다. 연결 스레드와 실행 스레드는 기다리지 않는다. -**멈추는 순서.** 앱 하나를 멈추는 것은 이렇다. +**멈추는 순서.** 앱 하나를 멈추는 것은 이렇다(그 자리에서 바꿀 때는 1–3까지 하고, 모듈을 바꾼 뒤 이어 간다). 1. 새 일을 받지 않는다. 그 앱의 경로는 503 `stopped`다. 링크와 안쪽 출구의 기록은 받지 않는다(보내는 쪽이 쥐고 다시 보낸다). 2. 소유자가 이미 받은 일을 다 적용하고, 저장하고, 답한다. @@ -279,11 +283,25 @@ HTTP로 앱을 더하는 길을 두지 않는 까닭은 인증이다. 그 길은 **하지 않는 것.** -- 끊김 없는 교체(새 모듈을 옆에서 띄우고 넘기기). 멈추고 다시 여는 동안 그 앱은 503을 답한다. 그 시간을 잰다. +- 끊김 없는 교체(새 모듈을 옆에서 띄우고 넘기기). 바꾸는 동안 그 앱은 503을 답한다. 그 자리에서 바꾸면 1 ms 남짓이다(기록의 + 모양이 바뀌는 큰 목록은 목록을 다시 쓰는 동안). - HTTP로 앱을 더하고 바꾸는 길. 경로의 인증(3b) 뒤에 따로 정한다. **3b: 경로의 인증.** 앱의 경로를 누가 부를 수 있는지다(CHECKPOINT의 남은 것). 3단계의 수명과 따로 설계한다. +### 3단계에서 알게 된 것 + +- **멈추고 다시 열면 다시 읽기가 전부였다.** 처음 구현(`12878cf`)은 바뀐 앱을 멈추고 저장소에서 다시 열었다. 항목 100만 개의 + 앱이 150.6 ms를 답하지 못해 호스트를 다시 띄우는 것(149.7 ms)과 같았다. 그 가운데 142 ms가 저장소를 다시 읽는 일이었다. + 저장소가 그대로인 앱을 그 자리에서 바꾸자(`7daf406`) 1.12 ms가 됐다(1,024개 1.06 ms). 기록의 모양이 바뀌는 모듈은 목록을 새 + 모양으로 다시 쓰느라 171.5 ms다(다시 띄우면 332 ms). +- **결과.** 한 앱을 거듭 바꾸는 동안 다른 앱의 p99는 0.97배, 앱 하나의 호스트는 TPC-C +0.7%와 명령 수 +0.02% 이하, 모듈을 바꿀 + 때마다 19 KB가 남는다. 파일을 읽는 스레드가 하나 늘었다(앱 100개에 스레드 넷). 커밋: Pure `14c75d0`(해석한 모듈과 앱의 자리), + `1d892e2`(링크 스트림의 입력을 다시 찾기), `12878cf`(SIGHUP과 멈추고 다시 열기), `7daf406`(그 자리에서 바꾸기), `f5e9db5`와 + 그 뒤(측정). +- **검사가 찾은 것.** 보내는 앱이 멈추는 동안 받는 앱의 차례가 놓인 출구를 읽던 자리, 시작하기 전의 앱의 락을 다른 앱의 출구가 + 쓰던 것. 둘 다 바꾸기 한가운데의 보내는 앱과 받는 앱을 함께 바꿔 보는 검사로 드러났다. + ## 3단계의 착수 기준 - **바꿀 결정:** 달리는 호스트의 앱을 SIGHUP과 구성 파일로 더하고, 멈추고, 바꿀지다. 아래를 채우지 못하면 앱을 바꿀 때 호스트를 diff --git a/docs/research/115-apps-that-change.md b/docs/research/115-apps-that-change.md new file mode 100644 index 0000000..a76f9dd --- /dev/null +++ b/docs/research/115-apps-that-change.md @@ -0,0 +1,115 @@ +# 115 — 달리는 호스트의 앱을 더하고, 멈추고, 바꾸기 + +2026-10-04. [017](../design/017-apps-on-one-host.md)의 3단계를 잰다. 기준은 구현하기 전에 017에 적어 커밋했다(`e4c7f7e`). + +- 바꿀 결정: 달리는 호스트의 앱을 SIGHUP과 구성 파일로 더하고, 멈추고, 바꿀지. 채우지 못하면 앱을 바꿀 때 호스트를 다시 띄운다. +- 통과 조건(RAM의 Pi 5): + - 바꾸는 앱이 답하지 못하는 시간이 호스트를 다시 띄울 때보다 짧다(항목 1,000개와 100만 개). + - 한 앱을 거듭 바꾸는 동안 다른 앱(한 연결)의 p99가 바꾸지 않을 때의 1.5배를 넘지 않는다. + - 앱 하나의 호스트는 그대로다(TPC-C와 은행 질의의 중앙값이 3% 넘게 느려지지 않고, 명령 수가 1% 넘게 늘지 않는다). + - 바꿀 때마다 남는 메모리를 밝힌다. + +## 쉬운 말로 + +- **통과했다.** 호스트가 SIGHUP에 `--apps` 파일을 다시 읽고, 줄이 없어진 앱은 멈추고, 새 줄의 앱은 시작하고, 바뀐 줄의 앱은 그 + 자리에서 바꾼다. 항목 100만 개의 앱이 모듈을 바꾸는 동안 답하지 못한 시간은 1.1 ms다(호스트를 다시 띄우면 150 ms). 그동안 + 다른 앱의 p99는 그대로다(0.97배). 앱 하나의 호스트도 그대로다. +- **처음 구현은 큰 앱에서 기준을 넘지 못했다.** 앱을 멈추고 저장소에서 다시 여는 것으로 바꿨다. 100만 항목의 앱은 150.6 ms를 + 답하지 못했다. 호스트를 다시 띄우는 것(149.7 ms)과 같았다. 시간을 나눠 보니 거의 전부(142 ms)가 저장소를 다시 읽는 + 데(`pure_app_start`) 들었다. 호스트를 다시 띄울 때도 같은 일을 하니 짧아질 수 없었다. +- **고친 것은 다시 읽지 않는 것이다.** 저장소가 그대로인 앱은 받은 일을 다 적용하고 저장한 뒤, 메모리의 상태를 그대로 두고 + 모듈만 바꾼다(코어의 `pure_app_replace`: 상태와 outbox의 보냄을 이름으로 옮긴다). 출구만 새 모듈로 다시 만든다. 그러자 크기와 + 상관없이 1 ms 남짓이 됐다. 기록의 모양이 바뀌는 모듈(필드가 하나 늘어난 기록)은 목록을 새 모양으로 다시 만들고 이미지를 다시 + 써야 해서 172 ms가 들었다. 호스트를 다시 띄우면 다시 읽기에 그 일까지 더해 332 ms다. +- **모듈을 바꿀 때마다 19 KB가 남는다.** 바뀐 모듈을 놓지 않는다. 그 모듈로 해석된 요청과 스냅숏이 아직 그것을 가리킬 수 있기 + 때문이다. 예산이나 배선만 바뀌면 남는 것이 없다. + +## 결과 + +원시 결과와 명령은 [030-app-lifecycle](../../research/experiments/030-app-lifecycle/)에 있다(`runs.sh`의 단계 `build`, `window`, +`isolation`, `pairs`, `counts`, `memory`; `window.py`, `isolation.py`, `memory.py`, `pairs.py`; 앱 `items.aup`과 그 다음 판 +`items-next.aup`(보기 하나 더), `items-wide.aup`(기록에 필드 하나 더)). + +**답하지 못하는 시간**(`window.py`). 호스트(`--apps`, 저장소는 RAM, 코어 0–2)에 앱 하나가 항목을 든다(사건 하나가 목록을 두 배로 +늘려 1,024개나 1,048,576개). 클라이언트(코어 3)가 한 연결로 쓰기를 쉬지 않고 보내고, 답을 받은 두 쓰기 사이의 가장 긴 틈을 잰다. +바꾸기는 줄이 다른 모듈 파일을 가리키게 하고 SIGHUP을 보내는 것이다(두 모듈을 번갈아). 다시 띄우기는 같은 변경을 전처럼 하는 +것이다(줄을 바꾸고 SIGTERM, 다시 시작). 1,024개는 바꾸기 20번과 다시 띄우기 10번, 1,048,576개는 10번과 5번의 중앙값이다. + +| 앱, 바뀌는 것 | 멈추고 다시 열기(`12878cf`) | 그 자리에서(`7daf406`) | 호스트를 다시 띄우기 | +|---|---:|---:|---:| +| 1,024개, 보기 하나 더 | 1.52 ms | **1.06 ms** | 2.42 ms | +| 1,024개, 기록에 필드 하나 더 | 1.58 ms | **1.31 ms** | 2.51–2.64 ms | +| 1,048,576개, 보기 하나 더 | 150.42 ms | **1.12 ms** | 149.63–149.94 ms | +| 1,048,576개, 기록에 필드 하나 더 | 337.33 ms | **171.54 ms** | 329.85–332.42 ms | + +다시 띄우기의 범위는 두 빌드에서 잰 값이다. 처음 잰 것(`f5e9db5`)은 다시 띄울 때 모듈을 바꾸지 않았다(1,024개 1.59 대 3.05 ms, +100만 개 150.64 대 149.73 ms). 비교가 같은 변경이 되도록 고쳐 다시 쟀다. + +멈추고 다시 열 때 100만 항목의 시간을 나누면, 멈추기 7–8 ms(상태를 놓기), 시작 142 ms(저장소를 다시 읽기)다. 호스트를 다시 +띄울 때의 시작은 170 ms였다. 그 자리에서 바꾸면 이 둘이 없다. 기록의 모양이 바뀌면 목록 100만 항목을 새 모양으로 다시 만들고 +이미지를 쓰는 데 약 170 ms가 든다. 다시 띄울 때도 같은 일을 다시 읽기에 더해서 한다. + +**다른 앱의 꼬리**(`isolation.py`). 같은 호스트(연결 스레드 둘, 실행 스레드 둘)에 100만 항목의 앱 A와 점수판 B. B에 한 연결의 +점수를 10초 보내는 동안 A를 그대로 두거나 바꾼다. 바꾸는 쪽은 보기 하나 더인 모듈로 0.2초마다(판마다 50번), 기록에 필드 하나 +더인 모듈로 0.4초마다(판마다 18번, 한 번에 약 170 ms의 일)다. 다섯 라운드, 순서를 번갈아. + +| B의 p99 중앙값 | 그대로 | 바꾸는 동안 | 비 | +|---|---:|---:|---:| +| 보기 하나 더, 0.2초마다 | 120 µs | 116 µs | 0.97 | +| 기록에 필드 하나 더, 0.4초마다 | 120 µs | 116 µs | 0.97 | + +B는 5xx를 하나도 받지 않았다. 바꾸는 일은 파일을 읽는 스레드(`pure-apps`)에서 하고, 실행 스레드는 그 앱의 차례만 쉰다. + +**앱 하나의 호스트**(`runs.sh pairs`, 3단계 전 `e4c7f7e`와 후 `7daf406`, TPC-C W=1 64터미널 20초와 은행 질의, 여섯 쌍 번갈아, +부하 평균 0.9–1.5). + +| | 전 | 후 | +|---|---:|---:| +| TPC-C 초당 거래 중앙값 | 42,900 | 43,220 (+0.7%) | +| 거래당 CPU | 44.4 µs | 44.4 µs | +| `per_branch`, `in_credit`, `large`, `ranks` | 5.31, 0.85, 0.38, 6.81 ms | +0.1%, −2.4%, 0.0%, −0.7% | + +쌍마다 TPC-C는 −3.8%에서 +6.2%로 흔들렸다(소유자의 코어 몫 0.87–0.91). 명령 수(025의 `count.py`, 전과 후를 두 번씩): + +| 한 번 물을 때 | 전 | 후 | +|---|---:|---:| +| `per_branch` | 26.980M | 26.827M (−0.57%) | +| `in_credit` | 3.471M | 3.437M (−0.99%) | +| `large` | 1.445M | 1.445M (+0.01%) | +| `ranks` | 24.170M | 24.087M (−0.35%) | +| `none`, `field`, `found` | 0.607M, 8.776M, 15.691M | +0.00%, +0.00%, +0.02% | + +요청이 해석한 모듈을 들고 다니고 소유자와 보기가 그것을 견주는 일(원자적 읽기와 포인터 비교)은 보이지 않는다. + +**남는 메모리**(`memory.py`). 항목 1,000개의 앱을 100번 바꿔 데운 뒤, 1,000번 더 바꾼 앞뒤의 자기만의 더티 메모리. + +| 바꾸는 것 | 앞 → 뒤 | 한 번에 | +|---|---:|---:| +| 모듈(두 파일을 번갈아, 바꿀 때마다 하나를 읽는다) | 3,120 → 21,952 KiB | 19,284 B | +| 큐 예산만(모듈은 그대로) | 1,200 → 1,200 KiB | 0 B | + +한 번에 남는 것은 바뀐 모듈 하나(구조체 18,760 B와 모듈의 바이트)다. + +**검사**(`tests/check.py`의 `server_apps_lifecycle`). 한 호스트에서 앱을 더하고(바로 답한다), 지우고(404 `no_app`, 다른 프로세스가 그 +저장소를 연다), 바꾼다. 받는 앱 둘(같은 호스트의 앱이 안쪽 출구로, 다른 노드가 링크로 보내는)과 보내는 앱 둘을 보내기 한가운데에서 +바꾼다(바꿀 때 보낸 수가 0과 600 사이임을 단언한다). 받는 앱의 새 모듈은 입력의 번호가 다르고 보냄을 이름으로 받는다. 그동안 +목록 보기를 쉬지 않고 읽는다. 600건씩이 모두 한 번씩 순서대로 도착하고, 200을 받은 쓰기는 한 번씩 적용되고 503 `stopped`를 받은 +쓰기는 적용되지 않으며, 보기는 200이나 503 `stopped`만 답하고, 그대로인 앱은 하나도 실패하지 않는다. 저장소를 옮긴 앱은 새 저장소로 +다시 시작하고, 저장소를 만들 수 없던 앱은 고친 뒤의 SIGHUP에 시작하고, 틀린 줄이 있는 파일은 아무것도 바꾸지 않고, `kill -9` 뒤에는 +파일 그대로 시작한다. 혼자 10번, CPU 부하 아래 5번, ASan으로 빌드한 `pure-serve`로 6번 지났다. + +이 검사가 처음 구현에서 찾은 것: 보내는 앱이 멈추는 동안 받는 앱의 차례가 그 출구를 놓은 뒤에 읽는 자리(`local_done`), 앱이 +시작하기 전에 다른 앱의 출구가 그 소유자의 락을 쓰는 것(락을 자리와 함께 만든다). + +## 판정 + +- **017의 3단계(수명)는 들어간다.** 네 조건을 모두 채웠다. 달리는 호스트의 앱은 SIGHUP과 파일로 더하고, 멈추고, 바꾼다. +- 바꾸기는 그 자리에서 한다. 저장소가 바뀐 앱, 저장이 불확실해진 앱, 달리지 않던 앱만 저장소에서 다시 연다. +- 파일을 읽는 스레드(`pure-apps`)가 여러 앱의 호스트마다 하나 늘었다. 앱 100개의 호스트는 스레드 넷이다(111에서 셋). + +## 다음 + +- **바뀐 모듈을 놓기.** 지금은 바꿀 때마다 19 KB가 남는다(하루 한 번 바꾸면 한 해 7 MB). 놓으려면 그 모듈로 해석된 요청이 + 끝났는지(본문을 받는 중인 요청은 `convert`에서 모듈을 다시 읽는다)와 연결 스레드의 리더가 다른 모듈로 옮겼는지를 알아야 한다. +- **3b: 경로의 인증.** 앱의 경로를 누가 부를 수 있는지. 그 뒤에 HTTP로 앱을 더하고 바꾸는 길. +- 기록의 모양이 바뀌는 큰 목록의 교체(100만 항목 172 ms)는 목록을 새 모양으로 다시 쓰는 값이다. 그동안 그 앱만 쉰다. |
