Perf Baseline — Nougat DevPay
측정: node scripts/perf-baseline.mjs http://127.0.0.1:8080 50 300 환경: Windows 로컬, Rust Axum (debug build), SQLite WAL, dev auth, concurrency=50, n=300.
| 시나리오 | rps | p50 | p95 | p99 | 평균 페이로드 |
|---|---|---|---|---|---|
| GET /projects (json) | 1355 | 30.0ms | 45.4ms | 56.9ms | 12177B |
| GET /projects (msgpack) | 2585 | 18.1ms | 28.2ms | 30.1ms | 10113B |
| GET /projects (protobuf) | 3115 | 15.3ms | 23.0ms | 27.2ms | 7015B |
| POST /checkout (concurrent writes) | 4679 | 8.1ms | 20.8ms | 24.0ms | 50B |
| GET /orders (all) | 1879 | 25.2ms | 37.2ms | 41.8ms | 11815B |
| GET /metrics | 2844 | 20.9ms | 23.4ms | 24.4ms | 31270B |
| POST /projects (write+audit+outbox) | 254 | 185.9ms | 273.6ms | 295.9ms | 186B |
관찰
- 읽기는 코덱별로 유의미한 차이: protobuf가 json 대비 p50 49% 단축·페이로드 42% 절감.
- 쓰기 경로(
write+audit+outbox)는 p50 186ms로 읽기 대비 ~6~12배 — 단일Mutex<Connection>직렬화 + 커밋 fsync 비용. outbox 이벤트도 같은 tx에 들어간다. - SQLite 단일 writer 특성상 쓰기 rps ~254가 사실상 상한 — 병렬 write 확장이 필요하면 pool 전환(복수 읽기 conn + 단일 write) 또는 PG 어댑터가 답이다 (Phase 5 remainder 참조).
- 실패율 0 — dev auth·로컬 기준. 운영 인증·네트워크 지연은 별도 계산 필요.
재측정
bash
BIND_ADDR=127.0.0.1:8080 DATABASE_URL=/tmp/perf.db NOUGAT_AUTH_MODE=dev NOUGAT_RATE_LIMIT_RPM=0 \
./be/target/debug/nougat-devpay-be &
node scripts/perf-baseline.mjs http://127.0.0.1:8080 50 3001
2
3
2
3
동시성 측정 (2026-10-29 — Store::tx 이전 후 재측정)
환경: 로컬 MSVC 디버그 빌드, SQLite (fresh db), NOUGAT_RATE_LIMIT_RPM=0, 60샘플/구간.
| 경로 | 동시성 | p50 | p95 | p99 | 비고 |
|---|---|---|---|---|---|
| GET /projects | 8 | 12.6ms | 18.8ms | 20.9ms | 단일 커넥션 Mutex 내 안정 |
| POST /projects | 8 | 18.4ms | 25.0ms | 25.2ms | 기존 기준선 186ms 대비 ~10x 개선 |
| GET /projects | 16 | 27.3ms | 124.9ms | 125.9ms | Mutex 직렬화 — p95 꼬리 = SQLite 단일 커넥션 한계 |
| POST /projects | 16 | 36.1ms | 37.0ms | 37.2ms | 쓰기도 안정 — tx 경계가 짧아짐 |
관찰:
- Store::tx 이전으로 핸들러가 lock을 짧게 잡고 해제 → 쓰기 p50이 186ms→18ms로 대폭 개선.
- 읽기 동시성 16에서 p95~125ms — 단일 Mutex(읽기 포함) 직렬화의 한계. 개선 경로: 읽기 전용 커넥션 풀(SQLite는 WAL에서 다중 리더 가능) 또는 PgStore 전환.
- 다음 측정 후보: outbox 처리량, codec별 인코딩 비용, 동시 32+.
읽기 풀 도입 후 재측정 (2026-10-29)
SqliteStore.read_pool(NOUGAT_READ_POOL=4) — WAL 멀티리더로 읽기 경합 분산.
| 경로 | 동시성 | p50 | p95 | p99 | 변화 |
|---|---|---|---|---|---|
| GET /projects | 16 | 14.9ms | 25.9ms | 27.8ms | p95 124.9→25.9ms (~4.8x) |
결론: 읽기 직렬화 병목은 단일 Mutex가 아니라 단일 커넥션이었다 — 풀로 해소됨. 잔여: write는 여전히 단일 writer 설계(SQLite 물리 한계 — PgStore가 상향 경로).