본문으로 건너뛰기

격리 벤치마크

이 benchmark는 실제 uvicorn ASGI process에서 aerospike-py와 공식 aerospike Python client를 비교합니다. 각 endpoint는 found, pred_sum, subset_sum 중 하나의 scalar만 반환해 response serialization 비용을 제외합니다. 따라서 측정 latency에는 batch_read, materialization, 선택적 inference가 포함되고 JSON encoding은 포함되지 않습니다.

Production Benchmark는 반대 범위를 측정합니다. HTTP parsing, 전체 record marshaling, DLRM, network output처럼 serving pod가 실제로 부담하는 비용을 모두 포함합니다. Client library 자체의 차이를 확인하려면 이 문서를, capacity planning에는 production benchmark를 사용하세요.

Source: 본 repo 의 benchmark/. podman/docker 로 Aerospike CE 8.1.2.1_3 띄워서 로컬 실행.

실행 방법

cd benchmark
make up # podman compose → aerospike:ce-8.1.2.1_3 on 127.0.0.1:18710
make seed # 결정론적 10k records, 5개 메타 bin + 64개 float feature f0..f63
make bench-all CONCURRENCY=10 DURATION=30s BATCH_SIZE=50
make report # → 시나리오별 markdown 비교 표

make bench는 client마다 uvicorn process를 시작하고 /healthz를 기다립니다. 지정한 시간 동안 oha를 실행한 뒤 process를 종료하고 다음 client로 넘어갑니다. 각 run은 oha.json 옆에 meta.json을 저장하므로 loadtest/report.py가 결과를 (scenario, batch_size, concurrency, python) 단위로 묶을 수 있습니다.

Aerospike CE container는 scripts/aerospike.template.conf를 mount하고 access-address127.0.0.1로 고정합니다. aerospike-py는 connect() 중에 전체 cluster를 찾습니다. 이 설정이 없으면 Podman 내부 IP로 다시 연결하다 실패합니다. 이 benchmark에서 공식 client는 seed host만 사용합니다.

실측은 follow-up

아래 수치는 warm cache에서 실행한 짧은 개발용 matrix를 사용합니다. 조건은 Python 3.11, concurrency 4, batch size 30, run당 3초입니다. 어느 시나리오에서 차이가 커지거나 작아지는지 비교할 때만 사용하고 절대 성능 수치로 인용하지 마세요. Production 수준의 matrix에는 seed record 10,000–100,000개, 60초 run, concurrency 10–50이 필요합니다.

시나리오

s1 — 순수 batch_read 라운드트립

가장 얇은 측정: {offset, batch_size} JSON body 를 받아 키를 만들고 batch_read 호출, found count 만 반환. 다운스트림 bin materialisation 없음, 추론 없음, full-record 직렬화 없음.

코드 경로 차이

client_materialise
aerospike-pyresult.found_count() — pure-Rust filter+count, PyDict build 없음
officialsum(1 for br in result.batch_records if br.result == 0) — BatchRecord 리스트 Python 순회

공식 client는 batch_read를 호출할 때마다 asyncio.to_thread 전환 비용을 부담합니다. s1은 이 비용을 가장 직접적으로 측정합니다.

Smoke (py3.11, c=4, batch=30, 3s)

clientRPSavg msp95
official2383.21.682.14
aerospike-py2466.81.622.39
speedup1.04×1.04×

5개 시나리오 중 가장 작은 격차 — request shape 이 HTTP framing + 단일 round trip 으로 dominate 되고 둘 다 공통 비용. concurrency 가 커지면 격차 벌어짐 (production-benchmark Env A 는 ASGI 레이어 없이 동일 DB shape 에서 4.8× 관찰).

3.14t 예상

두 client 모두 이 시나리오에서 절대 latency가 가장 크게 줄어들 것으로 예상합니다. 짧은 request에서 spawn_blocking_delayevent_loop_resume_delay가 큰 비중을 차지하기 때문입니다. Client 간 비율은 줄어들 수 있습니다. Production Benchmark의 같은 workload를 사용한 Python 3.14t run에서는 42%였던 차이가 약 2 ms로 줄었습니다.

의도적으로 측정 안 한 것

제외이유
Response body 직렬화각 endpoint는 record에서 계산한 scalar 하나, 즉 {found: N} 또는 pred_sum/subset_sum을 추가한 값만 반환합니다. Full-record JSON encoding은 두 client 모두 부담하지만 key와 digest shape가 달라 공식 client의 비용을 비대칭으로 키우고 client 간 차이를 가릴 수 있습니다.
OTel / Prometheus / nelo측정 결과에 변동을 더합니다. Client library는 이 기능을 제공하지만 benchmark suite에서는 비활성화했습니다.
Docker image / k8s manifests이 benchmark는 client library만 격리합니다. Deployment, registry, manifest 작업은 관련 없는 변동을 더합니다.
Bin projection (bins=[…] on the wire)Client 비교와 별개인 server-side 최적화입니다. s5는 같은 wire payload에서 client-side materialisation 비용을 측정합니다.

모든 production 비용을 포함한 결과는 Production Benchmark를 참고하세요.


Python 3.14t — 같은 스위트 위 런타임 swap

Python 3.14t는 GIL을 비활성화한 free-threaded CPython 빌드입니다. Production Benchmark의 3.14t 섹션에서 spawn_blocking_delay는 234 ms에서 0.12 ms로, event_loop_resume_delay는 39.7 ms에서 거의 0으로 줄었습니다. 여기서도 같은 현상이 나타날 가능성이 있습니다. 다만 isolated suite는 batch_read 이후의 GIL-bound stage를 이미 대부분 제외했으므로 절대 차이는 더 작을 것입니다.

benchmark/ 프로젝트는 인자 하나로 runtime을 바꿀 수 있습니다.

uv python install 3.14t        # 일회성, ~30 MB 다운로드 (uv 관리)
PYTHON=3.14t uv sync # 별도의 3.14t venv 생성
PYTHON=3.14t make bench-all CONCURRENCY=10 DURATION=30s BATCH_SIZE=50
make report # report.py 가 python 버전별 그룹화

시나리오별 예상 효과는 각 탭의 "3.14t 예상" 절에 정리했습니다.

3.14t isolated-benchmark 매트릭스는 TODO

각 시나리오의 "3.14t 예상"은 Production Benchmark의 stage breakdown을 바탕으로 추정한 값이며 측정값이 아닙니다. Harness는 runtime 변경을 지원합니다. 공개할 matrix 측정 작업은 Bottleneck Analysis에서 추적합니다.

관련 파일

benchmark/
├── pyproject.toml # uv project, editable parent aerospike-py
├── compose.yaml # aerospike:ce-8.1.2.1_3
├── scripts/aerospike.template.conf
├── Makefile # up · seed · serve-{py,official} · bench[-all] · report
├── src/app/
│ ├── main.py · lifespan.py · config.py · model.py · seed.py
│ ├── clients/{py_async,official_async}.py
│ └── routes/s{1..5}*.py
└── loadtest/
├── run_oha.sh · bench_matrix.sh
├── scenarios.py # id → 한 줄 description
└── report.py