diff options
Diffstat (limited to 'docs/research/111-apps-on-one-host.md')
| -rw-r--r-- | docs/research/111-apps-on-one-host.md | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/docs/research/111-apps-on-one-host.md b/docs/research/111-apps-on-one-host.md new file mode 100644 index 0000000..3086a23 --- /dev/null +++ b/docs/research/111-apps-on-one-host.md @@ -0,0 +1,109 @@ +# 111 — 한 호스트의 여러 앱: 앱마다 프로세스와 견주어 + +2026-10-01. [017](../design/017-apps-on-one-host.md)의 1단계를 잰다. 기준은 구현하기 전에 017에 적어 커밋했다(`0b5fa1d`). +비교 상대는 앱마다 프로세스 하나다. + +- 격리: 앱 A를 64연결의 쓰기로 포화시키는 동안 앱 B(한 연결)의 p99가 두 프로세스일 때의 1.5배를 넘지 않는다(실행 스레드 둘). +- 비용: 작은 앱 하나를 더하는 자기만의 메모리가 유휴 프로세스(864 KiB)의 4분의 1 미만이고, 앱 100개까지 스레드가 그대로다. +- 처리량: A와 B의 처리량 합이 두 프로세스보다 10% 넘게 낮지 않다. +- 앱 하나: TPC-C(RAM, W=1, 64터미널)와 은행 질의를 전(`0b5fa1d`)과 후 번갈아 여섯 쌍 재서 중앙값이 3% 넘게 느려지지 않고, + 은행 질의의 명령 수([110](110-counting-instead-of-timing.md)의 `count.py`)가 1% 넘게 늘지 않는다. + +## 쉬운 말로 + +- **통과했다.** A가 몰릴 때 B의 p99는 두 프로세스의 0.5배다. 앱 하나에 120 KiB가 든다(프로세스 하나의 PSS 1,311 KiB의 9%). 둘 + 다 바쁠 때 처리량과 p99는 두 프로세스와 같다. 앱 하나의 호스트는 전과 같다. +- **처음 구현은 처리량을 넘지 못했다.** 짧게 쟀을 때 두 앱이 84k/s, 두 프로세스가 117k/s였다. 실행 스레드가 차례마다 앱을 줄 + 끝에 다시 세우며 다른 실행 스레드를 깨웠다. 바쁜 앱이 차례마다 코어를 옮겨 다닌 것이다. 다른 앱이 기다리지 않으면 같은 + 실행 스레드가 잇게 하자 같아졌다(`d95740e`). +- **그다음은 꼬리였다.** 두 앱이 다 바쁘면 p99가 늘 4.1 ms였다(두 프로세스 1.5 ms). 한 앱의 연결이 연결 스레드들에 흩어져서, + 넘기고 돌려받는 일이 모두-대-모두였다. 요청당 CPU가 3분의 1 더 들고 자발적 문맥 전환이 다섯 배였다. 코어가 붐비니 깨어난 + 스레드가 커널의 한 틱(HZ=250, 4 ms)을 기다렸다. 앱마다 집 연결 스레드를 두고 연결을 첫 요청에서 그리로 옮기자 1.4 ms가 됐다 + (`41502b9`). 프로세스의 1:1 모양을 한 프로세스 안에서 되찾은 셈이다. +- **실행량 예산이 표의 줄마다 일을 더하고 있었다.** 앱 하나의 명령 수 기준(1%)을 넘었다(1만 줄 표 +3.0%). 표본은 호스트가 + 칸마다 실패의 까닭을 묻는 데를 가리켰다. 예산이 없으면 세지 않고, 까닭은 실패했을 때만 쓰게 하자 +0.94%가 됐다(`aef14c0`). +- **시간은 여기서도 흔들렸다.** 은행 질의의 시간이 후에 +3–6%로 보인 네 쌍이 있었지만 명령 수는 오히려 줄어 있었다. 110에서 본 + 배치의 흔들림이다. 판정은 깨끗한 여섯 쌍과 명령 수로 했다. + +## 결과 + +원시 결과와 명령은 [026-apps-on-one-host](../../research/experiments/026-apps-on-one-host/)에 있다(`runs.sh` 단계별, `isolation.py`, +`memory.py`; 부하 도구 `benchmarks/http/load`는 경로 앞에 앱의 이름을 붙이는 인자를 받는다). + +**격리와 처리량**(`isolation.py`, 점수판 앱 둘, RAM, 서버는 코어 0–2, 부하는 코어 3, 15초씩 다섯 라운드, 방식의 순서를 +번갈았다. 라운드 사이의 차이는 1–2%였다). 프로세스 둘은 스레드 넷(연결 하나와 소유자 하나씩), 호스트는 스레드 넷(연결 둘, +실행 둘), host1은 셋(연결 하나, 실행 둘)이다. + +| 중앙값 | 프로세스 둘 | 호스트 | host1 | +|---|---:|---:|---:| +| B 혼자, p99 | 108 µs | 112 µs | 112 µs | +| A가 몰릴 때 B의 p99 | 336 µs | **168 µs (0.50배)** | 544 µs (1.62배) | +| A가 몰릴 때 A의 처리량 | 111.1k/s | 104.5k/s | 136.1k/s | +| 둘 다 바쁠 때 처리량 합 | 146.2k/s | **146.1k/s (−0.04%)** | 139.5k/s (−4.6%) | +| 둘 다 바쁠 때 p99 | 1,408 µs | 1,408 µs | 1,088 µs | + +host1은 연결 스레드가 하나라 둘 다 바쁠 때 꼬리가 가장 짧지만, A가 몰리면 B가 같은 스레드를 기다린다. 기준은 스레드 수가 같은 +호스트로 판정했다. + +집 스레드를 두기 전(`d95740e`, `2026-10-01-isolation-d95740e-…`)에는 같은 표가 이랬다. A가 몰릴 때 B의 p99는 232 µs였고, +둘 다 바쁠 때 처리량 합은 145.6k/s, p99는 4,096 µs였다. 그때 두 앱이 둘 다 바쁜 6초 동안 스레드마다 잰 것(한 번 잰 것으로, +파일로 남기지 않았다): + +| 6초 동안 | 프로세스 둘 | 호스트(집 스레드 전) | +|---|---:|---:| +| 연결 스레드의 CPU, 자발적 전환 | 3.0 s, 3.3만 (각각) | 4.0 s, 18만 (각각) | +| 소유자·실행 스레드의 CPU, 자발적 전환 | 0.82 s, 5.8만 | 1.37 s, 10.5만 | + +모으기(저장 뒤에 잠깐 기다리기)를 끈 빌드도 p99가 4,096 µs 그대로여서 원인에서 뺐다. + +**비용**(`memory.py`, 점수판 앱 N개를 한 호스트에, 앱마다 한 번 쓰고 한 번 읽은 뒤의 `smaps_rollup`, 연결 스레드 하나, 실행 +스레드 둘). + +| | 자기만의 더티 | RSS | PSS | 스레드 | +|---|---:|---:|---:|---:| +| 프로세스 하나(`--module`) | 1,296 KiB | 2,752 KiB | 1,311 KiB | 2 | +| 호스트, 앱 1 | 1,328 KiB | 2,784 KiB | 1,343 KiB | 3 | +| 호스트, 앱 10 | 2,384 KiB | 3,840 KiB | 2,399 KiB | 3 | +| 호스트, 앱 100 | 13,232 KiB | 14,688 KiB | 13,247 KiB | 3 | + +앱 하나에 (13,232 − 1,328) / 99 = 120 KiB다. 기준(216 KiB)의 절반 남짓이다. 프로세스 하나의 자기만의 더티가 087의 864 KiB보다 +큰 것은 재부팅 뒤 공유 라이브러리를 함께 매핑한 프로세스가 적어서다(PSS는 같다). + +**앱 하나**(`runs.sh pairs`, 전은 `0b5fa1d`, 후는 `aef14c0`). + +| 쌍 | TPC-C 초당 거래(전 → 후) | 거래당 CPU(µs) | 소유자의 코어 몫 | +|---|---:|---:|---:| +| 1 | 46,682 → 45,754 (−2.0%) | 41.0 → 41.8 | 0.92 → 0.91 | +| 2 | 45,195 → 45,762 (+1.3%) | 42.3 → 42.0 | 0.90 → 0.91 | +| 3 | 44,967 → 44,905 (−0.1%) | 42.5 → 42.5 | 0.90 → 0.90 | +| 4 | 44,086 → 45,181 (+2.5%) | 42.7 → 42.4 | 0.89 → 0.90 | +| 5 | 42,992 → 40,751 (−5.2%) | 42.9 → 44.1 | 0.88 → 0.85 (후가 방해받음) | +| 6 | 43,066 → 37,280 (−13.4%) | 43.0 → 45.3 | 0.87 → 0.81 (후가 방해받음) | + +중앙값 44,527 → 45,043(+1.2%), 거래당 CPU 42.6 → 42.45 µs. 은행 질의의 중앙값은 지점별 합계 +1.7%, 흑자 계좌 수 −1.8%, 조인 +−2.6%, 순위 −0.4%다. 017의 커밋마다 짧게 잰 TPC-C(10초, 두 라운드, `2026-10-01-tpcc-by-commit-…`)도 모두 ±3% 안이었다. + +| 명령 수(한 번 물을 때) | 전 | 후 | +|---|---:|---:| +| `per_branch` | 27.07M | 26.77M (−1.1%) | +| `in_credit` | 3.49M | 3.42M (−1.9%) | +| `large` | 1.438M | 1.445M (+0.44%) | +| `ranks` | 24.21M | 24.06M (−0.6%) | +| `field`(1만 줄, 열 하나) | 8.695M | 8.776M (+0.94%) | +| `found`(1만 줄, 키로 찾기) | 15.64M | 15.69M (+0.35%) | + +`aef14c0` 전(`41502b9`)에는 `field` +3.0%, `large` +1.5%, `found` +1.4%였다(`41502b9/`). 그때의 쌍 넷과, 시간 한도로 끊긴 첫 +시도(`interrupted/`)도 남겼다. + +## 판정 + +- **017의 1단계는 들어간다.** 네 기준을 모두 채웠다. 앱 여러 개를 한 프로세스에서 Pure의 차례로 돌린다. +- 같은 프로세스 안의 격리는 검증기와 예산과 집 스레드가 만든다. 리눅스 프로세스만큼의 보호(호스트 자신의 결함)는 아니다. + +## 다음 + +- **앱당 120 KiB의 내역.** 모듈(18.7 KB), 소유자의 앱과 연결 스레드마다의 리더(앱 하나에 6 KB씩), 저장소의 매핑, 스냅숏이 + 있다. 잠든 앱의 리더를 처음 쓸 때 만들면 더 줄 것이다. +- **연결 스레드의 수.** 앱이 연결 스레드보다 많으면 한 스레드에 앱 여럿이 산다. 한 앱이 몰리면 같은 집의 앱이 기다린다 + (host1의 1.6배가 그 모습이다). 집을 정하는 규칙(지금은 순서)을 부하에 맞출 수 있다. +- 017의 2단계(앱 사이와 노드 사이)와 3단계(앱의 수명). |
