Files
Aether/docs/gateway-db-pressure-testing.md
T

272 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Aether Gateway DB pressure testing
目标:验证 6k+ 并发长连接/流式请求时,gateway 不把请求并发线性放大成 DB 连接并发,并确认 DB pool、usage queue、后台维护任务不会成为瓶颈。
## 1. 预设环境
生产/压测环境建议显式设置:
```bash
export AETHER_GATEWAY_DATA_POSTGRES_MAX_CONNECTIONS=80
export AETHER_GATEWAY_DATA_POSTGRES_MIN_CONNECTIONS=12
export AETHER_GATEWAY_MAINTENANCE_POOL_IDLE_RESERVE=8
export AETHER_GATEWAY_USAGE_QUEUE_TERMINAL_EVENTS=true
export AETHER_GATEWAY_USAGE_QUEUE_LIFECYCLE_EVENTS=true
export AETHER_GATEWAY_USAGE_QUEUE_STREAM_MAXLEN=200000
export AETHER_GATEWAY_USAGE_QUEUE_BATCH_SIZE=500
export AETHER_GATEWAY_USAGE_QUEUE_RECLAIM_COUNT=500
# Pool score DB feedback is rate-limited per provider key to avoid one
# synchronous score UPDATE per successful request.
export AETHER_GATEWAY_POOL_SCORE_SUCCESS_FEEDBACK_MIN_INTERVAL_SECS=5
export AETHER_GATEWAY_POOL_SCORE_FAILURE_FEEDBACK_MIN_INTERVAL_SECS=1
# Invalid API keys are cached briefly so repeated bad credentials cannot
# linearly amplify into DB lookups. Set 0 to disable during auth debugging.
export AETHER_GATEWAY_AUTH_CONTEXT_NEGATIVE_CACHE_TTL_SECS=10
```
SQLite 不适合 6k 并发压测;请使用 Postgres/MySQL 和 Redis runtime backend。
## 2. Gateway HTTP 压测
准备请求体:
```bash
cat >/tmp/aether-pressure-request.json <<'JSON'
{"model":"gpt-5-mini","messages":[{"role":"user","content":"ping"}],"stream":true}
JSON
```
运行 6k 并发(高并发结论必须使用 release;debug 构建会把 CPU/调试开销误判成 planning timeout):
```bash
TARGET_URL=http://127.0.0.1:18080/v1/chat/completions \
METRICS_URL=http://127.0.0.1:18080/_gateway/metrics \
PRESSURE_METHOD=POST \
PRESSURE_REQUESTS=60000 \
PRESSURE_CONCURRENCY=6000 \
PRESSURE_TIMEOUT_MS=120000 \
PRESSURE_BODY_FILE=/tmp/aether-pressure-request.json \
AUTH_HEADER='Authorization: Bearer <api-key>' \
EXTRA_HEADERS='Content-Type: application/json' \
PRESSURE_RESPONSE_MODE=full \
PRESSURE_CARGO_PROFILE=release \
OUTPUT=/tmp/aether_gateway_pressure_6k.json \
tools/pressure/run_gateway_6k_pressure.sh
```
如果压测流式长连接,建议让 probe 读完整响应体,否则客户端拿到 headers 后会立刻断开:
```bash
PRESSURE_RESPONSE_MODE=full
```
## 3. 本地 mock upstream
没有可承载 6k 并发的真实上游 key 时,先用 testkit 启一个 OpenAI-compatible mock upstream:
```bash
cargo run --release -p aether-testkit --bin mock_openai_upstream -- \
--bind 127.0.0.1:18181 \
--chunks 8 \
--first-byte-delay-ms 0 \
--chunk-delay-ms 20 \
--payload-bytes 32
```
可直接压 mock 网络栈:
```bash
cat >/tmp/aether-mock-request.json <<'JSON'
{"model":"mock-model","messages":[{"role":"user","content":"ping"}],"stream":true}
JSON
cargo run --release -p aether-testkit --bin http_load_probe -- \
--url http://127.0.0.1:18181/v1/chat/completions \
--method POST \
--requests 60000 \
--concurrency 6000 \
--timeout-ms 120000 \
--header 'Content-Type: application/json' \
--body-file /tmp/aether-mock-request.json \
--response-mode full
```
要做 gateway 端到端压测,把一个本地压测 provider/endpoint 指到
`http://127.0.0.1:18181/v1`,provider key 用 dummy 值即可;gateway 侧仍需一个
本地 Aether API key,但不再消耗真实上游额度。
报告重点字段:
```json
{
"load": {
"throughput_rps": 0,
"failed_requests": 0,
"error_counts": {},
"p95_ms": 0,
"p99_ms": 0
},
"metrics": {
"db_pool_max_checked_out": 0,
"db_pool_min_idle": 0,
"db_pool_max_usage_basis_points": 0,
"db_pool_pressure_samples": 0,
"gateway_requests_max_rejected_total": 0
}
}
```
### 本地全链路 release 基线(mock upstream)
本地全链路压测固定为:release gateway + release mock upstream + 本地 Postgres/Redis + 临时 Aether API key/provider/model。
不要把 6k 长连接理解为 6k DB 连接;目标是 6k 前端连接下 DB pool 维持在几十级。
最近可复现基线:
| 场景 | 结果 | throughput | p50 | p95 | p99 | DB pool | mock in-flight |
| --- | --- | ---: | ---: | ---: | ---: | --- | ---: |
| 1000 req / 100 conc | 1000x 200 / 0 fail | 211 rps | 209ms | 729ms | 1171ms | max checked out 33/48 | - |
| 6000 req / 6000 conc, sync terminal candidate | 6000x 200 / 0 fail, `error_counts={}` | 262 rps | 20198ms | 22496ms | 22748ms | max checked out 48/48, pressure samples 7 | 396 |
| 6000 req / 6000 conc, async candidate queue | 6000x 200 / 0 fail, `error_counts={}` | 367 rps | 13678ms | 16060ms | 16245ms | max checked out 48/48, pressure samples 3 | - |
| 6000 req / 6000 conc, async queue + slot compaction | 6000x 200 / 0 fail, `error_counts={}` | 344 rps | 14613ms | 17143ms | 17315ms | max checked out 48/48, pressure samples 3 | - |
6000/6000 下实际打开约 6k FD,说明客户端长连接链路有效。terminal 模式下同一请求可能产生 2 条 candidate 状态写入;
async queue 会把这部分从前台请求路径移出,slot compaction 会把同 slot/同状态的重复记录合并后再落库。
如需测纯转发极限,可临时把 `AETHER_GATEWAY_REQUEST_CANDIDATE_PERSISTENCE=none` 与 terminal 模式对照。
常用本地命令(不要打印 API key):
```bash
source /tmp/aether_local_env.sh
KEY=$(cat /tmp/aether_fullchain_api_key)
CARGO_TARGET_DIR=/tmp/aether-release-pressure \
TARGET_URL=http://127.0.0.1:8088/v1/chat/completions \
METRICS_URL=http://127.0.0.1:8088/_gateway/metrics \
PRESSURE_METHOD=POST \
PRESSURE_REQUESTS=6000 \
PRESSURE_CONCURRENCY=6000 \
PRESSURE_TIMEOUT_MS=120000 \
PRESSURE_SAMPLE_INTERVAL_MS=250 \
PRESSURE_BODY_FILE=/tmp/aether-mock-request.json \
PRESSURE_RESPONSE_MODE=full \
PRESSURE_CARGO_PROFILE=release \
AUTH_HEADER="Authorization: Bearer ${KEY}" \
EXTRA_HEADERS='Content-Type: application/json' \
OUTPUT=/tmp/aether_gateway_release_6000_6000_full.json \
tools/pressure/run_gateway_6k_pressure.sh
```
```bash
docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" postgres psql -U postgres -d aether -P pager=off -c "
SELECT calls, round(total_exec_time::numeric,1) total_ms,
round(mean_exec_time::numeric,3) mean_ms, rows,
left(regexp_replace(query, '\s+', ' ', 'g'), 280) query
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 60;"
```
### Request candidate async persistence
`request_candidates` 是当前 6k 全链路下最明显的同步 DB 写入点。生产/压测可以先保留
`AETHER_GATEWAY_REQUEST_CANDIDATE_PERSISTENCE=terminal`,再把 terminal 写入从前台 await 改为异步队列:
```bash
export AETHER_GATEWAY_REQUEST_CANDIDATE_PERSISTENCE=terminal
export AETHER_GATEWAY_REQUEST_CANDIDATE_WRITE_MODE=async
export AETHER_GATEWAY_REQUEST_CANDIDATE_QUEUE_CAPACITY=65536
export AETHER_GATEWAY_REQUEST_CANDIDATE_QUEUE_BATCH_SIZE=512
export AETHER_GATEWAY_REQUEST_CANDIDATE_QUEUE_FLUSH_INTERVAL_MS=50
export AETHER_GATEWAY_REQUEST_CANDIDATE_QUEUE_WORKERS=2
# 队列满时默认 drop trace,保护前台请求;需要强一致审计时可设 sync。
export AETHER_GATEWAY_REQUEST_CANDIDATE_QUEUE_FULL=drop
```
新增 metrics:
- `request_candidate_queue_depth`
- `request_candidate_queue_pending_depth`
- `request_candidate_queue_capacity`
- `request_candidate_queue_enqueued_total`
- `request_candidate_queue_dropped_total`
- `request_candidate_queue_flushed_total`
- `request_candidate_queue_flush_failed_total`
- `request_candidate_queue_flush_batches_total`
- `request_candidate_queue_flush_sql_ops_total`
- `request_candidate_queue_compacted_total`
- `request_candidate_queue_sync_fallback_total`
判定:6k/6k 下 `dropped_total=0`、`flush_failed_total=0`,压测结束后
`queue_depth` 和 `pending_depth` 应回到 0。
`flush_sql_ops_total` 应低于 `flushed_total`;差值体现在 `compacted_total`,
用于确认 terminal candidate 的重复状态写入已在队列侧合并。
当前本地 6000/6000 合并版观测:
- `enqueued_total=12000`
- `flushed_total=12000`
- `flush_sql_ops_total=6406`
- `compacted_total=5594`
- `dropped_total=0`
- `flush_failed_total=0`
- `request_candidates` 最终 `6000 rows / 6000 request_id`
## 4. 判定标准
优先看这些信号:
- `failed_requests == 0` 或仅包含预期的上游错误。
- `db_pool_max_checked_out` 不应接近 `AETHER_GATEWAY_DATA_POSTGRES_MAX_CONNECTIONS`。
- `db_pool_min_idle` 在大部分采样中应高于 idle reserve。
- `db_pool_max_usage_basis_points < 8000` 比较健康;持续 `>9000` 表示 DB pool 或 SQL 写入已接近瓶颈。
- `db_pool_pressure_samples` 可以短暂出现,但不应贯穿压测全程。
- `gateway_requests_max_rejected_total` 不应增长,除非有意测试 admission limit。
## 5. DB 热点写入专项压测
这些 testkit 场景会启动临时 Postgres,适合回归验证 counter/settlement 热点锁竞争:
```bash
cargo run -p aether-testkit --bin usage_counter_hotspot_baseline -- \
--requests 20000 --concurrency 1000 \
--flush-interval-ms 50 --monitor-interval-ms 20 \
--output /tmp/aether_usage_counter_20000_1000.json
cargo run -p aether-testkit --bin usage_settlement_hotspot_baseline -- \
--requests 20000 --concurrency 1000 \
--flush-interval-ms 50 --monitor-interval-ms 20 \
--output /tmp/aether_usage_settlement_20000_1000.json
cargo run -p aether-testkit --bin usage_aux_counter_hotspot_baseline -- \
--requests 20000 --concurrency 1000 \
--flush-interval-ms 50 --monitor-interval-ms 20 \
--output /tmp/aether_usage_aux_counter_20000_1000.json
```
关注:
- `failed_requests`
- `throughput_rps`
- `p95_ms`
- `lock_monitor.max_*_update_waiters`
- `lock_monitor.max_oldest_lock_wait_ms`
定向 update waiter 持续大于 0,说明某类 counter/settlement 仍有热点行锁竞争,需要继续分桶或延迟聚合。
## 6. 本地回归基线
最近一次本地临时 Postgres 回归(`requests=60000, concurrency=6000, max_connections=64`):
| suite | throughput | failed | p95 | 定向 update waiters |
| --- | ---: | ---: | ---: | ---: |
| `usage_settlement_hotspot_baseline` | 5452 rps | 0 | 740ms | usage 10 / wallet 0 / provider 0 |
| `usage_counter_hotspot_baseline` | 1358 rps | 0 | 1865ms | api_key 0 / provider_key 0 / model 0 / provider 0 |
| `usage_aux_counter_hotspot_baseline` | 1899 rps | 0 | 785ms | proxy 0 / management_token 0 / api_key 0 |
这些是 DB 热点写入专项结果,不等价于完整 gateway 端到端 6k 流式压测;完整压测仍需使用第 2 节的 gateway URL、真实 API key、Redis runtime backend 与目标模型请求体。