8.7 KiB
seed - 压测数据准备工具
给 prod 凌晨压测灌 1000 个测试用户 + 资产 + JWT,数据用
star_id=999900物理隔离。
⭐ 推荐入口:prod_loadtest.sh
如果你目标是打生产压测,优先用
../scripts/prod_loadtest.sh一站式脚本。 它会把下面的所有步骤(SSH 隧道/seed/preflight/loadgen/report/cleanup)封装成子命令, 凭据从docker/.env.prod自动读,避免明文泄露。
# 一键完整流程
./../scripts/prod_loadtest.sh pipeline --scenarios=S1,S2,S4 \
--stage=step --step-schedule='5,10,20' --duration=60s
一句话总结
跑 ./seed,数据库里多出 1000 个用户 + 5000 个 assets + 2000 个 exhibitions,本地多出 users.csv (含 JWT)。
编译
cd backend
go build -o bin/seed ./scripts/loadgen/seed/
# 或
make loadgen-build
在 prod 上跑 (凌晨 T0 = 02:00,推荐)
ssh root@101.132.250.62
cd /opt/topfans/loadtest
bash scripts/prod_seed.sh
这个脚本会自动:
- 读
/opt/topfans/docker/.env.prod拿 DB_PASSWORD + JWT_SECRET - 跑 seed (插入 23k 行测试数据,直接写入 docker 里的
topfans-postgres) - 自动重置 PG 序列 (CLAUDE.md 规范)
- 写
users.csv(含 1000 个 JWT)
预计耗时:30-60 秒
⚠️ 为什么必须在生产机上跑:
seed默认值是localhost:5432/topfans, 这正好等于生产机上 docker 暴露的5432:5432+POSTGRES_DB=topfans。 从本地 Mac 跑默认值会连到你本机的 Postgres(可能根本不是 topfans 库), 后续users.csv拿去打生产网关会全部 401。详见下一节。
从本地 Mac 连到生产 DB (不 ssh 进生产机)
只适合本地紧急补 seed、或者压测脚本调试。首选仍是上一节 ssh 进生产机。
方式 A:SSH 端口转发 (推荐,改动最小)
# 1. 建立转发:本地 25432 → 生产机 docker 5432
ssh -L 25432:127.0.0.1:5432 -N -f root@101.132.250.62
# 2. 跑 seed (从 docker/.env.prod 拿密码)
cd /Users/liulujian/Documents/code/TopFansByGithub
DB_PASSWORD=$(grep '^DB_PASSWORD=' docker/.env.prod | cut -d= -f2) \
JWT_SECRET=$(grep '^JWT_SECRET=' docker/.env.prod | cut -d= -f2) \
go run ./backend/scripts/loadgen/seed \
--db-host=127.0.0.1 \
--db-port=25432 \
--db-name=topfans \
--db-user=postgres
# 3. 用完记得关转发
pkill -f 'ssh -L 25432'
方式 B:从本地直连 docker 容器 (生产机已开 5432 端口时)
如果生产机 topfans-postgres 已经通过 ports: - "5432:5432" 暴露到外网,
也可以直接走公网,但强烈不建议 (生产 PG 暴露公网本身就有风险):
DB_PASSWORD=$(grep '^DB_PASSWORD=' docker/.env.prod | cut -d= -f2) \
JWT_SECRET=$(grep '^JWT_SECRET=' docker/.env.prod | cut -d= -f2) \
go run ./backend/scripts/loadgen/seed \
--db-host=101.132.250.62 \
--db-port=5432 \
--db-name=topfans \
--db-user=postgres
在本地 docker 跑 (开发联调)
cd backend/scripts/loadgen/seed
# 1. 生成 bcrypt 哈希 (与 tokens.go 硬编码的 "Test@123" 匹配)
python3 -c "import bcrypt; print(bcrypt.hashpw(b'Test@123', bcrypt.gensalt(rounds=10)).decode())" \
> loadtest_bcrypt.txt
# 2. 跑 seed (假设本地 docker postgres 在 15432)
cd /Users/liulujian/Documents/code/TopFansByGithub/backend
DB_PASSWORD=123456 \
JWT_SECRET=topfans-secret-key-local-dev-only \
./bin/seed \
--db-name=top-fans \
--db-host=localhost \
--db-port=15432 \
--db-user=postgres
注意: loadtest_bcrypt.txt 必须在 seed 二进制运行的当前目录(代码用相对路径读)。
命令行参数
./bin/seed --help
Usage of ./bin/seed:
-cleanup # 跑清理 (默认保留 1000 users)
-cleanup-star-id int # 要清的 star_id (默认 999900, 防止误删)
-full # 配合 -cleanup: 也删用户和 stars
-reset # 删旧数据再 seed (隐含 --cleanup 行为)
-reset-tokens # 只重签 JWT (数据保留)
-jwt-secret string # JWT 密钥 (默认 $JWT_SECRET)
-db-host string # PG host (默认 localhost)
-db-port int # PG port (默认 5432)
-db-name string # PG 数据库 (默认 topfans,即 prod 默认;
# 本地 docker dev 用 top-fans,需显式 --db-name=top-fans)
-db-user string # PG user (默认 postgres)
-db-password string # PG 密码 (默认 $DB_PASSWORD)
三种部署对应的 DB 参数速查表
| 部署 | host | port | dbname | user | password |
|---|---|---|---|---|---|
| 生产机直跑 (ssh 进去) | localhost |
5432 |
topfans |
postgres |
$DB_PASSWORD (从 docker/.env.prod 读) |
| 本地连生产 (SSH 端口转发) | 127.0.0.1 |
25432 |
topfans |
postgres |
$DB_PASSWORD (从 docker/.env.prod 读) |
| 本地 docker 联调 (宿主机 postgresql-database-1) | localhost |
15432 |
top-fans |
postgres |
123456 (从 docker/.env.local 读) |
默认值设计意图:
localhost:5432/topfans是给"在生产机上直接跑"设计的, 因为生产 docker 把 5432 暴露到宿主机 5432。从本地 Mac 跑时必须显式覆盖--db-host/--db-port/--db-name,否则会连到本机 Postgres。
三种"清理"模式对比
| 命令 | 删 stars | 删 users | 删 assets/exhibits | 用途 |
|---|---|---|---|---|
./seed --cleanup |
❌ | ❌ | ✅ | 压完一轮,清理资产但保留账号 |
./seed --cleanup --full |
✅ | ✅ | ✅ | 全部清,下次重新 seed |
./seed --reset |
❌ | ❌ | ✅ | 等同 --cleanup(保留用户) |
./seed --reset-tokens |
❌ | ❌ | ❌ | 只重新签 JWT,数据不动 |
典型流程:
# 第 1 轮压测 (02:00-03:00)
./seed # 灌数据
./loadgen --cmd=run --scenarios=S1,S2,S4 # 压测
./seed --cleanup # 压完清理资产
# 第 2 轮压测 (下周,JWT 过期了)
./seed --reset-tokens --jwt-secret=$JWT_SECRET # 只重签 JWT
./loadgen --cmd=run --scenarios=S1,S2,S4 # 复测
# 完全重来 (例如改了用户模型)
./seed --cleanup --full # 全删
./seed # 重新灌
数据规模
| 表 | 行数 | 备注 |
|---|---|---|
stars |
+1 | star_id=999900 |
users |
+1000 | mobile 19900000001 ~ 19900001000 |
fan_profiles |
+1000 | 每个 user 一个 |
crystal_transaction_records |
+1000+ | 初始水晶 |
assets |
+5000 | 每个 user ~5 个 |
booth_slots |
+3000 | |
exhibitions |
+2000 | |
friendships |
+10000 | |
| TOTAL | ~23k 行 |
关键设计
1. star_id 隔离
所有测试数据用 star_id = 999900,不影响真实业务 (87, 88, 91, 93, 94, 95)。
2. PG max_connections = 50
prod 已将 POSTGRES_MAX_CONNECTIONS 从 100 调到 50,避免被测试数据耗尽连接池。
3. CLAUDE.md 序列重置
seed 末尾自动 setval() 所有相关表的 sequence,避免后续 GORM 插入报 duplicate key。
4. JWT 7 天过期
跨周第二轮压测前需 --reset-tokens 重签。
5. bcrypt 哈希与密码硬编码
tokens.go硬编码密码为"Test@123"(写到 users.csv 的 password 列)loadtest_bcrypt.txt是这个密码的 bcrypt(cost=10) 哈希- 二者必须匹配,否则 login 会报 500
常见问题
Q: 跑完 seed 但 login 报"密码错误"?
A: loadtest_bcrypt.txt 没匹配上 Test@123。
python3 -c "import bcrypt; print(bcrypt.hashpw(b'Test@123', bcrypt.gensalt(rounds=10)).decode())" \
> loadtest_bcrypt.txt
./seed --cleanup --full && ./seed
Q: 想换密码怎么办?
A: 同时改两个地方:
tokens.go的u.Mobile, "Test@123"→ 你的密码loadtest_bcrypt.txt重新生成
Q: "loadtest_bcrypt.txt: no such file or directory"?
A: seed 用相对路径读这个文件,必须在 seed 目录跑(或者把文件 cp 到当前目录)。
Q: --reset 没生效,users 还是旧的?
A: 因为 --reset 等同 --cleanup(保留用户)。要删用户用 --cleanup --full。
单元测试
cd backend
go test ./scripts/loadgen/seed/ -v
5 个测试:
TestMobileNumbering: mobile 编号正确性TestSequenceMapping: loadtestSeqs 映射TestPKColumnMapping: pkColumns 映射(关键 stars/star_id, booth_slots/slot_id)TestCleanupRejectsInvalidStarID: cleanup 拒绝非 loadtest star_idTestJoinInt64: CSV 序列化辅助函数
测试状态: 5/5 PASS