数据库
当数据库账单因网络传输(egress)过高时…
适用于数据库出站流量费用异常升高、需要定位并优化应用侧查询以减少数据传输量的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
当数据库账单因网络传输(egress)过特定
解决思路
- 先使用父级 `neon` 技能了解 Neon 概览、入门与最佳实践;若未安装,从 https://neon.com/docs/ai/skills/neon/SKILL.md 获取或用 `neon skills -s neon -y` 安装。
- 按四步顺序执行:诊断(diagnose)哪些查询传输数据最多、分析(analyze)其背后的代码库、修复(fix)反模式、验证(verify)没有破坏功能且传输量确实下降。
- 检查 `pg_stat_statements` 是否可用:`SELECT 1 FROM pg_stat_statements LIMIT 1;`;若报错则执行 `CREATE EXTENSION IF NOT EXISTS pg_stat_statements;`。Neon 上该扩展默认可用,但仍可能需要此 CREATE EXTENSION 步骤。
- 处理空统计:Neon 计算节点缩容到零并重启时统计会被清空。若统计为空或计算节点刚唤醒:先执行 `SELECT pg_stat_statements_reset();` 重置统计以开始干净的测量窗口;让应用在代表性流量下运行至少一小时;再回来运行诊断查询。
- 若有生产数据库的统计就用生产统计;若无法访问生产统计,直接进入第 2 步分析代码库——代码层面的模式通常足以识别最严重的元凶。
- 运行诊断查询找出主要 egress 贡献者,重点关注返回大量行、返回宽行(JSONB、TEXT、BYTEA 列)或调用极频繁的查询。
- 查询返回总行数最多的语句:`SELECT query, calls, rows AS total_rows, rows / calls AS avg_rows_per_call FROM pg_stat_statements WHERE calls > 0 ORDER BY rows DESC LIMIT 10;`
- 查询每次执行返回行数最多的语句(范围不当的 SELECT、缺少分页):`SELECT query, calls, rows AS total_rows, rows / calls AS avg_rows_per_call FROM pg_stat_statements WHERE calls > 0 ORDER BY avg_rows_per_call DESC LIMIT 10;`
- 查询调用最频繁的语句(缓存候选):`SELECT query, calls, rows AS total_rows, rows / calls AS avg_rows_per_call FROM pg_stat_statements WHERE calls > 0 ORDER BY calls DESC LIMIT 10;`
- 查询运行时间最长的语句(不是直接的 egress 指标,但有助于在流量高峰期间识别问题查询):`SELECT query, calls, rows AS total_rows, round(total_exec_time::numeric, 2) AS total_exec_time_ms FROM pg_stat_statements WHERE calls > 0 ORDER BY total_exec_time DESC LIMIT 10;`
- 解读结果并按预估 egress 影响排序:高行数 + 宽行 = 最大 egress(例如返回 1,000 行、每行含 50KB JSONB 列的查询,每次调用传输约 50MB);即使小查询,极高的调用频率也会累积(每天调用 50,000 次、每次返回 10 行 = 每天 500,000 行);结合 schema 识别哪些列是宽列,查找 JSONB、TEXT、BYTEA 和大型 VARCHAR 列。
- 对第 1 步识别出的每个查询(若无统计则对代码库中每个数据库查询)检查:是否只选择了响应所需的列?是否返回有界行数(LIMIT/分页)?调用是否频繁到足以受益于缓存?是否获取原始数据后在应用代码中聚合?是否使用了会在子行间重复父数据的 JOIN?
- 修复未使用的列(SELECT *):问题是查询获取所有列但应用只用少数几列,大列(JSONB blob、TEXT 字段)被传输到网络上又被丢弃;修复方法是只列出响应需要的列。例如将 `SELECT * FROM products;` 改为 `SELECT id, name, price, image_urls FROM products;`。
- 修复缺少分页:问题是列表端点返回所有行且无 LIMIT,这是无界 egress 风险——表中每新增一行都会增加每次请求的数据传输量,无论当前表大小都应标记。这容易被忽略,因为小数据集下应用可能运行正常,但规模化后一个返回 10,000 行、列宽中等的未分页端点每天可传输数百 MB。修复方法是用 `ORDER BY` 加 `LIMIT`/`OFFSET` 限定结果集。例如将 `SELECT id, name, price FROM products;` 改为 `SELECT id, name, price FROM products ORDER BY id LIMIT 50 OFFSET 0;`。添加分页时检查消费客户端是否已支持分页响应;若不支持,选择合理默认值并在 API 中记录分页参数。
- 修复对静态数据的高频查询:问题是查询每天被调用数千次但返回的数据很少变化,每次调用都从数据库传输相同的行。这种模式只能从 `pg_stat_statements` 看出——代码本身看起来正常。查找调用次数相对其他查询极高的查询,常见例子:配置表、分类列表、功能开关、用户角色定义。修复方法是在应用与数据库之间加缓存层,避免每次请求都打到数据库。
- 修复应用侧聚合:问题是应用从表中获取所有行后在应用代码中计算聚合(平均值、计数、求和、分组),完整数据集通过网络传输,尽管结果只是一个小摘要。修复方法是把聚合下推到 SQL。例如改为 `SELECT p.category_id, AVG(r.rating) AS avg_rating, COUNT(r.id) AS review_count FROM reviews r INNER JOIN products p ON r.product_id = p.id GROUP BY p.category_id;`。
- 修复 JOIN 重复:问题是宽父表与子表之间的 JOIN 会在每个子行上重复所有父列。若一个产品有 200 条评论且产品行含 50KB JSONB 列,该 join 单次请求就发送 50KB × 200 = 约 10MB。这与 SELECT * 问题不同:即使只选择所需列,JOIN 仍会为每个子行重复父数据。修复是结构性的:完全避免该 join。将 join 拆成两个查询,每表一个。例如将 `SELECT * FROM products LEFT JOIN reviews ON reviews.product_id = products.id WHERE products.id = 1;` 改为两个独立查询:`SELECT id, name, price, description, image_urls FROM products WHERE id = 1;` 和 `SELECT id, user_name, rating, body FROM reviews WHERE product_id = 1;`。用两个查询代替一个 JOIN:产品数据获取一次,评论获取一次,没有重复。
- 验证修复:运行现有测试确认没有破坏;检查响应确保 API 仍返回相同的数据形状(列选择和分页变更可能破坏依赖特定字段或完整结果集的客户端);测量改进——若有 pg_stat_statements 数据,重置它(`SELECT pg_stat_statements_reset();`),让流量运行,再重新运行诊断查询对比前后。
- 用 Neon 基础设施即代码(`neon.ts`)控制非生产计算成本:上述修复削减 egress(传出 Postgres 的数据),另一个大的非生产成本杠杆是计算,可在 `neon.ts`(Neon 的 IaC 文件,完整参考见 `neon` 技能)中持久化编码,使 dev、preview 和 CI 分支默认保持低成本,而不依赖逐分支标志。安装:`npm i @neon/config`。配置示例:`import { defineConfig } from "@neon/config/v1"; export default defineConfig({ branch: (branch) => { if (branch.exists || branch.isDefault) return {}; return { ttl: "7d", postgres: { computeSettings: { autoscalingLimitMinCu: 0.25, autoscalingLimitMaxCu: 1, suspendTimeout: "5m" } } }; } });`。应用:`neon config apply`(`neon deploy` 是别名)。这是补充而非替代:查询模式修复才是真正减少 egress 费用的手段,而这些设置防止非生产计算和存储悄悄推高同一账单。因为 `neon checkout` 在创建分支时会应用该策略,新的 dev/preview 分支会自动继承低成本配置。
- 延伸阅读:https://neon.com/docs/introduction/network-transfer.md 和 https://neon.com/docs/introduction/cost-optimization.md。
适用边界 · 注意事项
- 不适用于与数据库网络传输成本无关的性能问题(如查询延迟、CPU 或内存瓶颈),本技能专注于减少传出 Postgres 的数据量。
- 若无法访问 `pg_stat_statements` 且代码库中查询模式清晰可辨,仍可进行代码分析,但缺少统计时诊断精度会下降。
- Neon 基础设施即代码部分(`neon.ts`)特定于 Neon 平台,不适用于其他 Postgres 服务商。