mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 10:57:03 +08:00
272 lines
10 KiB
Markdown
272 lines
10 KiB
Markdown
# 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 与目标模型请求体。
|