From ddcc1202daa7de84f13ff712fdb5ad12159facc1 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 1 Oct 2026 06:25:21 +0100 Subject: Measure one host of many apps against a process each (111) 017's first stage met its four criteria on the Pi (RAM stores, five rounds, the order of the ways turned): app B's 99th percentile while app A is flooded by 64 connections is 168 us, half of two processes' 336 us; the two apps busy together write 146.1k a second against 146.2k, with the same 1.4 ms tail; an app costs a host 120 KiB of its own memory (a process: 1,311 KiB PSS) and 100 apps still run on three threads; a host of one app keeps its speed (TPC-C +1.2% at the median of six pairs, two of them interfered and kept; the bank's queries within 2.6%) and its instructions (+0.94% at most). On the way: apps hopped between workers each turn (fixed in db4ee9c), connections of one app spread over all connection threads made the host a third more expensive a request and its tail a kernel tick (fixed by home threads, d24881d), and the work budget's counting cost every table row 26 instructions (fixed in be297ea). The earlier runs are kept apart (db4ee9c's isolation, d24881d's pairs and counts, the interrupted first try). --- docs/research/111-apps-on-one-host.md | 109 ++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 docs/research/111-apps-on-one-host.md (limited to 'docs') 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단계(앱의 수명). -- cgit v1.2.3