mcpField
N
neon-postgres-egress-optimizer

neon-postgres-egress-optimizer

github.com/neondatabase数据与数据库10730.5k 次安装1 天前更新
代码仓库

关于 neon-postgres-egress-optimizer

帮助开发者定位并减少 Postgres 应用中过量数据传输的技能。它结合查询统计、代码检查、针对性修复和改动后的验证。

主要功能

  1. 01用 pg_stat_statements 排序查询
  2. 02检查应用的查询模式
  3. 03通过缩小结果集减少传输
  4. 04处理分页、缓存和连接问题
  5. 05测试并衡量修复效果

适用场景

  1. 01找出导致高流量输出的查询
  2. 02数据库统计不可用时检查代码
  3. 03减少 API 返回的行数或列数
  4. 04比较查询改动前后的传输量

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/neondatabase/agent-skills --skill neon-postgres-egress-optimizer

安装后在 Claude Code 中输入/neon-postgres-egress-optimizer确认已加载。

详细介绍

首先:使用父级 neon 技能了解 Neon、开始使用 Neon、Neon 开发最佳实践等。

如果尚未安装 neon 技能,请从 https://neon.com/docs/ai/skills/neon/SKILL.md 获取,或通过以下命令安装:

neon skills -s neon -y

Postgres 出站流量优化器

引导用户诊断并修复导致 Postgres 数据库产生过多数据传输(出站流量)的应用端查询模式。大多数高额出站流量费用都源于应用获取了超出实际使用量的数据。

按顺序完成以下四个步骤:诊断传输数据量最大的查询,分析这些查询背后的代码库,修复反模式,然后验证没有引入问题且传输量确实下降。

第 1 步:诊断

找出传输数据量最大的查询。主要工具是 pg_stat_statements 扩展。

检查 pg_stat_statements 是否可用
SELECT 1 FROM pg_stat_statements LIMIT 1;

如果出错,则需要创建该扩展:

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

Neon 默认提供此扩展,但仍可能需要执行 CREATE EXTENSION 步骤。

处理空统计数据

Neon 计算资源缩减至零并重启时,统计数据会被清除。如果统计数据为空,或计算资源最近刚刚唤醒:

  1. 重置统计数据以开始新的测量时间段:SELECT pg_stat_statements_reset();
  2. 让应用在有代表性的流量下运行至少一小时。
  3. 返回并运行下面的诊断查询。

如果用户有生产数据库的统计数据,请使用这些数据。如果无法访问生产统计数据,则继续执行第 2 步,直接分析代码库——代码层面的模式通常足以找出影响最大的问题。

诊断查询

运行以下查询,找出出站流量的主要来源。重点关注返回行数多、行较宽(包含 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;

运行时间最长的查询(不能直接衡量出站流量,但有助于在流量激增期间找出问题查询):

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;
解读结果

按估算的出站流量影响对发现的问题排序:

  • 行数多 + 行较宽 = 出站流量最大。例如,查询返回 1,000 行,且每行包含一个 50KB 的 JSONB 列,则每次调用会传输约 50MB。
  • 调用频率极高,即使查询较小,累积起来也会产生大量流量。某个查询每天调用 50,000 次,每次返回 10 行,即每天返回 500,000 行。
  • 结合架构进行检查,找出哪些列较宽。留意 JSONB、TEXT、BYTEA 和较大的 VARCHAR 列。

第 2 步:分析代码库

对于第 1 步中找出的每个查询;如果没有统计数据,则检查代码库中的每个数据库查询,并确认:

  • 它是否只选择响应所需的列?
  • 它是否限制了返回的行数(LIMIT/分页)?
  • 它的调用频率是否足以从缓存中受益?
  • 它是否获取了原始数据,之后又在应用代码中进行聚合?
  • 它是否使用了会在子行中重复父数据的 JOIN?

第 3 步:修复

针对发现的每个问题应用适当的修复。以下是最常见的出站流量反模式及其修复方法。

未使用的列(SELECT *)

**问题:**查询获取了所有列,但应用只使用其中少数几列。较大的列(JSONB 数据块、TEXT 字段)会通过网络传输,随后被丢弃。

**修复:**只指定响应所需的列。

之前:

SELECT * FROM products;

之后:

SELECT id, name, price, image_urls FROM products;
缺少分页

问题: 列表端点返回所有行,没有使用 LIMIT。这会造成无界的出站流量风险——表中每增加一行,每次请求传输的数据就会增加。无论当前表的大小如何,都应标记此问题。

这个问题很容易被忽略,因为数据集较小时,应用可能运行正常。但在规模较大时,一个未分页的端点即使每列宽度适中,只要返回 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 的客户端是否已支持分页响应。如果不支持,请选择合理的默认值,并在 API 中记录分页参数。

静态数据的高频查询

问题: 某个查询每天调用数千次,但返回的数据很少变化。每次调用都会从数据库传输相同的行。这种模式只有通过 pg_stat_statements 才能发现——单看代码一切正常。

查找调用次数相对于其他查询异常高的查询。常见示例包括:配置表、类别列表、功能标志和用户角色定义。

修复: 在应用和数据库之间添加缓存层,避免每次请求都访问数据库。

应用端聚合

问题: 应用从表中获取所有行,然后在应用代码中计算聚合结果(平均值、计数、求和、分组)。尽管结果只是一个简短的摘要,整个数据集仍会通过网络传输。

修复: 将聚合操作下推到 SQL 中。

修改前: 应用获取整张表的数据,然后通过循环或 .reduce() 在代码中进行聚合。

修改后:

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。产品数据只获取一次,评论也只获取一次,不会有数据重复。

第 4 步:验证

应用修复后:

  1. 运行现有测试,确认没有问题。
  2. 检查响应——确保 API 返回的数据结构没有变化。列选择和分页方面的更改,可能会导致依赖特定字段或完整结果集的客户端出错。
  3. 衡量改进效果——如果有 pg_stat_statements 数据,请重置它(SELECT pg_stat_statements_reset();),让流量运行一段时间,然后重新运行诊断查询,对比前后结果。

Neon 基础设施即代码(neon.ts)

上述修复可减少出站流量(从 Postgres 传出的数据)。另一个重要的非生产环境成本杠杆是计算资源。你可以在 neon.ts 中将其持久化为代码——这是 Neon 的基础设施即代码文件(完整参考请参阅 neon skill)——让开发、预览和 CI 分支默认保持低成本,而不必依赖各分支单独设置标志:

npm i @neon/config
// neon.ts
import { defineConfig } from "@neon/config/v1";

export default defineConfig({
  branch: (branch) => {
    if (branch.exists || branch.isDefault) return {}; // don't touch prod
    return {
      ttl: "7d", // ephemeral branches auto-expire instead of accruing storage
      postgres: {
        computeSettings: {
          autoscalingLimitMinCu: 0.25, // scale to zero when idle
          autoscalingLimitMaxCu: 1, // cap autoscaling on throwaway branches
          suspendTimeout: "5m",
        },
      },
    };
  },
});
neon config apply   # apply to the current branch (neon deploy is an alias)

这两者相辅相成,不能互相替代:真正减少出站流量费用的是查询模式修复,而这些设置可以避免非生产环境的计算和存储成本悄然推高同一笔账单。由于 neon checkout 会在创建分支时应用策略,因此新的开发和预览分支会自动继承低成本配置。

延伸阅读

评论

支持 Markdown