topfans/backend/scripts/loadgen/seed/README.md
2026-06-16 23:00:07 +08:00

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

详见 ../scripts/README.md


一句话总结

./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

这个脚本会自动:

  1. /opt/topfans/docker/.env.prod 拿 DB_PASSWORD + JWT_SECRET
  2. 跑 seed (插入 23k 行测试数据,直接写入 docker 里的 topfans-postgres)
  3. 自动重置 PG 序列 (CLAUDE.md 规范)
  4. 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: 同时改两个地方:

  1. tokens.gou.Mobile, "Test@123" → 你的密码
  2. 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_id
  • TestJoinInt64: CSV 序列化辅助函数

测试状态: 5/5 PASS