docs(memory): 记录蜜罐表体积实测数据与 PostgreSQL 死元组/VACUUM 语义

This commit is contained in:
tamakyi
2026-10-21 13:17:06 +08:00
parent 63e851ddae
commit cbb0c51a14
2 changed files with 64 additions and 0 deletions
+50
View File
@@ -104,3 +104,53 @@
- 本轮修复需**重新部署**才在测试站生效;部署后 `curl` 一次 `/wp-login.php` 应 200、再一次 403。
- 需确认测试环境 `conf/app.ini` 的 `[security] enable_auto_ban`(零封禁直接原因)+ `enable_honeypot = true`。
- 无法 SSH(`100.79.185.45:19999` 全部账号 Permission denied),落库验证需用户执行 `TamaBox honeypot hits --since 24h --show-password`。
---
## 蜜罐表体积实测 + 自动清理(commit 63e851d,已 push)
用户问「这些记录会不会导致数据库很臃肿」。**不靠推断,本地 PostgreSQL 建同构表灌仿真数据实测。**
### 实测数字
- 典型行 **244 字节**(heap 201 + 索引 43),10.5 万行 = 24 MB;常规 UA 下实测 281 B/行。
- 字段均值:path 13B、user_agent 111B(单行最大头)、fields 3B、raw_body 3B。
- `raw_body` 被 `maxCredentialFieldLen = 512` 截断,故字段数量随之受限(200 字段场景 rawBody 仍 512B)。
- 最坏情况(手工构造 50 字段 × 500B)约 2375 B/行。
- **UA 截短收益**:截到 100 省 1129 kB / 截到 64 省 4824 kB(10 万行)→ 仅 5–20%,
远小于自动清理的确定性上界且损失取证信息 → **决定不做**。
### PostgreSQL 死元组语义(本轮最关键认知)
- `DELETE` 只标记死元组,**表文件不会变小**;空间需 VACUUM 后才可被后续 INSERT 复用。
- 对照(10 万行,每轮删 25% 插 25%,行数恒定):有 VACUUM 22→24 MB **收敛**;无 VACUUM 22→52 MB **膨胀**。
- ⚠️ **VACUUM 不缩小文件,只让空间可复用**。二次实测(5 万行、连续 4 轮删 12000 插 12000):
行数恒定 5 万、文件稳定 15 MB 不再增长 —— 这才是要的效果(止住增长,不是让文件变小)。
想让文件立刻变小只有 `VACUUM FULL`(重建整表 + 锁表),蜜罐表万级行没必要。
- ⚠️ `VACUUM` **不能在事务块内执行**(SQLSTATE 25001),也不能在 PL/pgSQL 函数体内执行(VACUUM 无法从函数执行)。
GORM `db.Exec()` 走 `callbacks.Raw()` 处理器、**不包事务** → `WithContext(ctx).Exec("VACUUM ...")` 可用(已实测)。
- PG18 已移除 `pg_stat_user_tables.n_tup_vacuum`,改用 `last_vacuum` 验证。
### 索引实测(EXPLAIN 驱动决策)
- `idx_honeypot_hits_provider`:10 万行 648 kB,**全代码库零查询使用**(TopSources 用 max(provider) 随 group by 顺带取)→ 已删。
- `idx_honeypot_hits_created_at`:EXPLAIN 显示 `Index Scan Backward`,被 `ListSince` 实际使用 → 保留。
- `idx_honeypot_hits_ip`:4.7 MB(最大),但 GROUP BY ip 走 Seq Scan + HashAggregate;**保留**(`ListSinceByIP` 需它)。
- ⚠️ **GORM 去掉 struct tag 的 `index` 不会删除既有库上的旧索引** → 需显式 `HasIndex` + `DropIndex`(已加 `dropUnusedHoneypotIndexes`,启动时跑,失败仅告警)。
### 落地改动
- `internal/db/honeypot_retention.go`(新):`HoneypotRetention` 后台清理器,每小时一轮,两道闸
(时间 `honeypot_retain_days` + 数量 `honeypot_max_rows`)→ 清理后调 `Compact()`。
保留期 <=0 视为不自动清理(只告警);**`runOnce` 开头必须再挡一次 `retainDays<=0`**,
否则 before=now 会把历史全删光(「保留 0 天」被误解释成「只留此刻」)。
- `internal/db/honeypot.go`(新方法):`Compact()`(PG `VACUUM (ANALYZE)`)、`DeleteOldestBeyond()`
(跨方言通用,子查询按 id 倒序取 keepRows 边界)、`ListSinceByIP()`、`Size()`。
- `internal/cmd/web.go`:接入清理器,`stopRetention` 用 defer close。
- `internal/cmd/honeypot.go`:新增 `honeypot size` 子命令(行数/磁盘占用/时间跨度 + 与两道闸对照)。
- 修 CLI `hits --ip` 真 bug:原先取回最近 N 条再在 Go 侧筛 → 指定 IP 的历史命中基本查不出来 → 改 SQL 层过滤。
- 新配置:`honeypot_auto_clean`(默认 true)、`honeypot_max_rows`(默认 200000 ≈ 53 MB)。
### 踩坑
- ⚠️ 测试预期别自己推算错:`DeleteBefore` 是**严格小于**(`created_at < before`),边界那条不删。
- ⚠️ Python 补丁脚本里写 Go struct tag 的反引号 → bash 命令替换报错,**必须写成 .py 文件执行**(不能 `python -c`)。
- ⚠️ `psql` 传中文 SQL 报编码错 → 用英文输出串或 `PGCLIENTENCODING=UTF8`。
- ⚠️ 库名带连字符要用双引号:`CREATE DATABASE "tamabox-cli-test"`。
- ⚠️ Edit 工具改 CRLF 文件会保持 CRLF,但**改写时我打的字会转码损坏**(出现过「实测指事/程定/不图」)→ 改完必须逐行读回复核。
- 仓库内统一存 LF,工作区 CRLF 由 `core.autocrlf=true` 自动转换 —— 提交时 warning 属正常,不是行尾被改。
+14
View File
@@ -45,3 +45,17 @@
- `RecordHoneypot` 的键必须与 `Record` 一致(同样 `shared.LimitKey` 归一化),否则 `IsBanned` 判不出来。
- 备份/配置/密钥泄露扫描(`/backup.zip`、`/db.sql`、`/id_rsa.pem`)文件名主体随机,**只有后缀稳定**
→ 用 `path.Base()` 后缀匹配表,不能只靠子串(`"/.zip"` 匹配不到 `/backup.zip`)。
### 蜜罐表体积与自动清理(2026-10-21)
- `honeypot_hits` 是**只追加**审计表,写入完全由外部扫描流量驱动(不可预期、不可节流)
→ 必须有自动保留期清理,否则表体积只增不减。
- 两道闸:时间(`honeypot_retain_days`)+ 数量(`honeypot_max_rows`)。数量闸不可省 ——
攻击者可用海量源 IP 在保留期内把表撑爆。
- ⚠️ PG 的 `DELETE` 只标记死元组,**表文件不会变小**;无 VACUUM 时表体积单调膨胀
(实测 22→52 MB)。清理后必须 `VACUUM (ANALYZE)`,但注意 **VACUUM 不缩小文件、
只让空间可复用**(止住增长即可,不必 `VACUUM FULL`)。
- ⚠️ `VACUUM` 不能在事务块/函数体内执行 → GORM 走 `Exec()`(Raw 处理器,不包事务)可行。
- ⚠️ 清理器的 `runOnce` 必须自己再挡一次 `retainDays<=0`,否则「保留 0 天」= 全删光。
- 索引:GORM 去掉 struct tag 的 `index` **不会**删既有库上的旧索引 → 需显式 DropIndex。
删索引前先用 EXPLAIN 确认零查询使用(`provider` 索引实测 10 万行 648 kB 且零使用)。
- CLI `honeypot size` 报告行数/磁盘占用/时间跨度 —— 把「有没有臃肿」交给运维自查。