我们关于 PostgREST 兼容性 的文章详细介绍了服务器在功能上涵盖的内容(过滤器、嵌入、RPC、RLS)以及它留给您的内容。这个来自其他地方。它以 Aurabase 代码和官方 PostgREST 文档作为来源,记录了 PostgREST 实际上在何处以及为何大规模停滞。我们不会在这里复制我们自己没有运行过的负载组。我们的 基准测试方法 解释了为什么没有发布协议的孤立数字对我们来说似乎不可靠。
要点
- PostgREST 本身是轻量级的:专用 Aurabase 实例上的每个副本有 50 到 250 毫核 CPU、64 到 128 MB RAM。原始 HTTP 吞吐量几乎从来都不是生产中的限制因素。
- 真正的上限是 Postgres 连接预算:
PGRST_DB_POOL× 副本数。在 Aurabase 代码中验证:每个项目在专用级别 (10×2) 上有 20 个连接,在共享级别 (2×2) 上有 4 个连接。这是为了在同一个max_connections上容纳更多租户而特意选择的。 Prefer: count=exact强制在大型表上进行昂贵的 MVCC 扫描。 PostgREST 记录了两种更便宜的替代方案:count=planned和count=estimated,总共花费大约。db-max-rows上限(Aurabase 默认为 1000 行)会截断响应,而不在Content-Range中报告它(在实际条件下测量,详细信息如下)。- DDL 迁移后,PostgREST 架构缓存会异步重新加载。 Aurabase 网关在放弃之前重试最多 8 次(最坏情况下累计约 3.5 秒),该行为直接记录在代码中。
PostgREST 基准衡量什么,不衡量什么
PostgREST 上的 HTTP 吞吐量测试主要测量 Postgres,很少测量 PostgREST。服务器是基础前面的一个薄薄的翻译层。在绝大多数现实世界的负载中,响应时间主要由执行的 SQL 查询决定,而不是生成它的进程。
PostgREST 项目在 GitHub 上维护了该主题的专用存储库 PostgREST/postgrest-benchmark,它跟踪版本之间的吞吐量变化,而不是发布孤立的营销数据。我们既没有在这里表演也没有重新发布它。其结果取决于硬件、原理图尺寸和测试场景,这正是我们自己的 基准协议 在引用数字之前需要记录的变量。
在 PostgREST 下面,pgbench 测量真正重要的层:并发负载下的 SQL 事务时间。这是官方 PostgreSQL 基准测试工具(postgresql.org/docs/current/pgbench.html,于 2026 年 8 月 24 日访问)。本文不是在这里重现该协议,而是记录了生产中 PostgREST 的四个具体架构限制,每个限制都在 Aurabase 源代码或官方项目文档中进行了验证。
Aurabase 上 PostgREST 实例的实际占用空间
每个 Aurabase Postgres 引擎项目都会收到两个与其集群位于同一位置的专用 PostgREST 副本。部署它们的 Kubernetes 清单设置了适度的资源。
这些副本真正消耗的不是 CPU:它们是与 Postgres 主数据库的连接。每个 PostgREST 实例都直接连接到主实例 (-rw),无需通过为租户部署的 PgBouncer 池化器。我们在关于 PostgREST 兼容性的文章中已经详细介绍了此选择:LISTEN/NOTIFY 模式重新加载机制需要持久连接,与事务模式下的池化器不兼容。本文添加的内容是:就连接而言,实际成本是多少,以及峰值在哪里。
每个副本的池大小 (PGRST_DB_POOL) 故意根据项目级别而有所不同,并在 k8s_tenant.rs中进行验证,该函数为每个项目构建 PostgREST 清单:
| 轴承 | PGRST_DB_POOL / 副本 | 复制品 | 连接/觉醒项目 |
|---|---|---|---|
| 专用(高级,A1) | 10(PostgREST 默认值) | 2 | 20 |
| 共享(舰队、免费/专业/团队) | 2(Aurabase 默认值,降低) | 2 | 4 |
在专用级别上,约束被放松:一个项目有自己的 CNPG 集群,因此有自己的 max_connections,没有多余的邻居。在共享级别上,来自同一组织的多个项目共享一个集群:正是这种背景使得连接预算具有决定性,这将在下一节中进行阐述。
连接预算决定同时运行的租户数量
在共享 Postgres 集群上,限制同时活动项目数量的不是 HTTP 吞吐量。这是与可用的 max_connections相比,它们的 PostgREST 实例在主数据库上保持打开的连接数。
Aurabase 直接从实际集群限制中得出此预算,在 fleet.rs: (max_connections − réserve superuser/CNPG − connexions réservées au pooler) ÷ connexions par projet éveillé中检查,下限为 1。固定保留为 10 个连接(超级用户、CNPG 实例管理器、指标导出器、配置者管理边际)。对于交付的缺陷(每个副本 2 个、2 个副本或每个唤醒项目 4 个连接的池),计算根据集群的大小级别给出三种不同的预算。
来源:源自 fleet.rs::derive_wake_budget 和 wake_budget_for_org_plan,Aurabase 代码,于 2026 年 8 月 24 日重读。
此预算不是所拥有项目的配额:一个 team 组织可以容纳 50 个项目,其中大部分处于休眠状态。这是并发上限:主节点上可以同时保持打开连接的项目数量。超出预算的唤醒不会失败,它会被推迟,直到同级项目重新进入睡眠状态并签入同一文件。调整 max_connections 本身大小的主题在我们关于 max_connections调整的文章中进行了扩展,以及 专用与共享基础中的专用/互用权衡作为一个整体。
为什么首选:count=exact 会减慢大表上的查询速度
要求精确的总数会迫使 Postgres 对每个查询的过滤结果的可见行进行计数,这是一个随表增长的成本,而不是免费操作。
PostgreSQL 不维护任何开箱即用的索引行计数器。在 MVCC 下,行的可见性取决于读取它的事务。因此,精确的 COUNT(*) 必须访问候选行而不是读取预先计算的值。这是 Postgres 生态系统中一个有据可查的结构限制,包括像 ClickHouse 这样的分析供应商,它们将自己的近似计数器与 Postgres 事务行为进行比较。
PostgREST 原生记录了这三种计数策略(postgrest.org,2026 年 8 月 24 日访问)。 exact 策略保证以扫描价格计算总计。 planned 从查询规划器返回几乎免费的估计。 estimated 根据阈值自动在两者之间切换。这个选择不是装饰性的:需要在包含数百万行的表上使用 count=exact 的分页需要为每页上的扫描付费,即使用户从不查阅最后一页。
如果没有 count=exact,db-max-rows 的截断是不可见的
行上限可以截断 PostgREST 响应,而无需在正文或标头中进行任何指示,除非明确请求确切的总数。我们在专用 Aurabase 实例上的真实条件下对其进行了测量,而不是假设。
在带有 PGRST_DB_MAX_ROWS=5的 10 行测试表上,PostgREST v12.2.3 在两种截然不同的情况下呈现完全相同的 Content-Range 标头:
| 查询 | 渲染线 | 内容范围 | 元(Aurabase) |
|---|---|---|---|
| ?limit=50(无计数) | 5/10 真实 | 0-4/* | {} |
| 限制=50&计数=精确 | 5/10 真实 | 0-4/10 | {总计:10} |
如果没有 count=exact,5 行的响应与实际上只包含 5 行的表无法区分: Content-Range: 0-4/* 描述了呈现的行,而不是应用的限制。在这种情况下,实际的上限不会出现在任何地方,直接在 Aurabase SDK 的 PostgREST 路径上测量。
如果您的 PostgREST 部署设置 db-max-rows(Aurabase 默认为 1000),则将 data.length 与请求的限制进行比较以检测整页的客户端可能会出错。一旦服务器上限低于此限制,就会出现该错误。唯一可靠的信号是将接收到的行数与 count=exact返回的 total 进行比较,这直接发挥了上一节中描述的成本权衡。
迁移后重新加载架构缓存
PostgREST 在启动时将 Postgres 架构保留在内存中。在执行 DDL(创建表、添加列)之后,必须在新路由响应之前重新加载此缓存,并且此重新加载是异步的。
到达此窗口的写入可能会收到瞬态 404(缓存尚未更新),即使该表确实存在于 Postgres 端。 Aurabase 网关通过有界重试循环吸收此问题,在 postgrest_proxy.rs中进行了验证:最多 8 次尝试,增加回退(250 毫秒加上每次尝试 100 毫秒),在最坏的情况下累计 3.5 秒。此机制仅影响写入,从不影响读取。
网关不会发出任何重新加载信号:它只是等待。唯一真正的触发器是 DDL 路径上的数据库服务发出的 pg_notify('pgrst', 'reload schema')。如果迁移路径忘记发出此信号,则 8 次尝试会耗尽永远不会更改的缓存,这是代码注释中记录的风险,而不是伪装的。
对于自托管 PostgREST 部署,本课程进行了概括。应用程序中的每个 DDL 路径都应通过 NOTIFY 或向进程发送 SIGUSR1 信号来触发重新加载。否则,迁移会在部署后立即产生伪装成间歇性错误的 p99 延迟峰值。
架构的切片,而不是原始吞吐量
这里记录的四个限制有一个共同点:在独立的 HTTP 吞吐量测试中看不到任何限制,但所有四个限制都决定了 PostgREST 部署是否可以扩展到生产环境。
- 连接预算:限制共享集群上同时活动的租户数量,无论每个租户的吞吐量如何。
- 精确 COUNT 的成本:随表增长,而不是随负载增长;被
planned/estimated绕过。 - 无提示截断:正确配置的行上限仍然可以破坏检测不良的分页。
- 架构重新加载:每次迁移后的延迟窗口,如果重新加载信号连接良好则有界,否则无限制。
无论您是在自托管 PostgREST、Hasura 风格的 GraphQL 层还是自定义 API 之间进行选择,这四个轴都是比孤立的 req/s 数字更好的比较点。请参阅我们的比较 PostgREST 与 Hasura 与自定义 API。数据库前面的池化器的选择同样重要:我们的比较 PgBouncer vs Supavisor vs PgCat 详细说明了为什么 PostgREST 无法在事务模式下通过池化器。
常见问题解答
要记住什么
PostgREST 在单独的 HTTP 负载下几乎永远不会崩溃:它的架构对此来说太简单了。生产中的中断在于它周围的东西:它的副本保持开放的连接数量,确切的总成本是多少。这还包括截断是否仍然可见,以及迁移后窗口持续多长时间。
这四个限制并非 Aurabase 特有的:它们适用于任何 PostgREST 部署(自托管或托管)。此代码显示的是多租户部署如何使它们变得明确,而不是让它们在生产中令人惊讶。