Benchmarks
本文件给出 Realtime 和 PG 服务在单实例部署下的可扩展性基准测试结果,覆盖 Broadcast(公共与私有频道)、Payload 大小、连接与授权速率、Presence 与 Postgres Changes 五个方面,并给出生产环境推荐容量与配置参数。
测试对象为单实例 Realtime Server,建议业务使用中按自身场景复测。
Methodology
测试环境
| 项目 | 配置 |
|---|---|
| Realtime Server | 2 vCPU / 4 GB × 1 实例 |
| PostgreSQL | 1 vCPU / 2 GB |
租户配额(测试期间)
| 配额项 | 取值 | 备注 |
|---|---|---|
max_concurrent_users | 30,000 | 单个租户可同时保持的活跃 WebSocket 连接总数上限,超出后新连接被拒绝(TooManyConnections) |
max_joins_per_second | 3,000 | 单个租户每秒可处理的频道加入(join)次数上限,超出后加入请求被拒绝(TooManyJoins) |
max_events_per_second | 30,000 | 按 60 秒均值判定 |
max_presence_events_per_second | 10,000 | 按投递数计 |
max_bytes_per_second | 10 MB/s | 仅计入站流量,对高扇出无效 |
db_pool(授权连接池) | 10 | 决定授权吞吐上限 |
subs_pool_size(订阅连接池) | 10 | 决定 CDC 熔断阈值 |
测试方法
| 项目 | 说明 |
|---|---|
| 协议 | 裸 WebSocket + Phoenix 协议,每连接独立 join |
| 建连方式 | 以 50/s 的速率阶梯爬坡,稳态持续发送 |
| 统计口径 | 速率类指标按 60 秒滑动均值;延迟以「发送至首个客户端收到」计 |
| 观测指标 | 连接成功率、投递率、每秒投递条数、p50/p95/p99 延迟、服务端 CPU 与内存 |
指标定义
| 指标 | 定义 |
|---|---|
| 每秒发送条数 | 向频道发布一次消息计 1 条,与订阅人数无关 |
| 每秒投递条数 | 服务端推送至客户端的消息条数 = 发送条数 × 订阅人数 |
| 频道加入速率 | 客户端完成一次建连并加入频道计 1 次 |
| 投递率 | 实际收到消息数 ÷ 应收到消息数 |
1. Broadcast over WebSocket(公共广播)
公共频道不做授权校验,所有连接订阅同一topic,由单一发送端按速率广播,用于测定纯扇发路径的容量。
1.1 连接数维度
| 连接数 | 服务端内存 | 投递率 | 结果 |
|---|---|---|---|
| 20,000 | 82% | 100% | 稳定 |
| 24,000 | 91.6% | 100% | 接近上限 |
| 25,000 | 拐点 | 100% | 不可用 |
内存随连接数线性增长,拟合式为 内存 ≈ 35% + 4 个百分点 × (连接数 ÷ 1000)。单实例内存是连接数的第一道限制,20,000 为推荐上限,25,000 时内存耗尽。
1.2 投递量维度
| 每秒投递条数 | 服务端 CPU | 延迟 | 结果 |
|---|---|---|---|
| 5,000 | 18% | 低 | 稳定 |
| 19,591 | 52% | 低 | 稳定 |
| 27,000 | 70% | 正常 | 安全线 |
| 33,988 | 88% | 开始抬升 | 接近上限 |
| 40,000 | 约 95% | 秒级 | 劣化 |
| 45,800 | 94.8% 平台期 | p50 21 s / p95 52 s | 容量上限,零丢弃 |
CPU 与投递量线性相关,与连接数无关:CPU ≈ 6.5% + 2.35% × (每秒投递条数 ÷ 1000)。该维度达到 45,800/s 时功能仍完整、无丢弃,但延迟已不可接受,属于「先坏体验、后坏功能」的失效形态。
2. Broadcast over Private Channels(私有广播)
私有频道每次发布需经 RLS 授权校验并写入消息表,且同一频道的扇发为单链串行。全部测试在 4,000 连接下进行,连接均成功加入,无断开。
2.1 发送与投递组合维度
| 场景 | 拓扑 | 每秒发送条数 | 每秒投递条数 | 首报 p50 | 投递率 | 结果 |
|---|---|---|---|---|---|---|
| 高频稳态 | 500 房 × 8 人,5 条/s | 2.5k | 17.2k | 77 ms | 100% | 稳定 |
| 低频稳态 | 2,000 房 × 2 人,2.5 条/s | 5.0k | 4.9k | 64 ms | 100% | 稳定 |
| 高频过载 | 500 房 × 8 人,10 条/s | 4.6k | 24.7k | 1,586 ms | 78% | 延迟劣化 |
| 中频过载 | 2,000 房 × 2 人,5 条/s | 10.0k | 8.1k | 93 ms(p95 6.4 s) | 82% | 延迟劣化 |
表中「稳态」与「过载」用于对照同一负载模型下的容量边界;各档连接数均为 4,000,全部加入成功且无断开。
私有广播的容量由两个方向共同约束:每秒发送条数反映授权与分发开销,每秒投递条数反映扇出开销。上表中,发送 2.5k/s、投递 17.2k/s 与发送 5.0k/s、投递 4.9k/s 两档均在 100% 投递下保持数十毫秒延迟;投递升至 24.7k/s(发送 4.6k/s)时首报延迟升至 1.59 s;发送升至 10.0k/s 时 p95 延迟达 6.4 s、投递率降至 82%。据此,单实例私有广播建议同时满足:每秒发送条数 ≤ 6,000、每秒投递条数 ≤ 17,000。
2.2 连接数维度
| 连接数 | 服务端内存 | 投递率 | 结果 |
|---|---|---|---|
| 8,000 | — | 100% | 稳定 |
| 16,000 | 72.8% | 100% | 稳定 |
| 20,000 | 88.3% | 100% | 接近上限 |
| 24,000 | 100% | 丢失 2.13% | 不可用 |
私有广播常驻内存高于公共广播(每个频道需保留授权与订阅状态),因此连接数上限低于公共广播。
3. Payload Size(消息大小)
公共广播场景,8,000 连接,固定发送速率,仅改变单条消息体积。
| payload | 投递率 | 扇出跨度 p50 | 出口带宽 | 结果 |
|---|---|---|---|---|
| 64 B | 100% | 387 ms | — | 稳定 |
| 1 KB | 100% | 387 ms | — | 稳定 |
| 10 KB | 100% | — | — | 稳定 |
| 100 KB | 仅发出 3/5 条 | 5,222 ms | 114 MiB/s ≈ 1 Gbps | 不可用 |
单实例出口带宽约 150 MB/s(约 1.2 Gbps),拐点位于 10 KB 至 100 KB 之间。带宽瓶颈的表现为发送端发不出去,而非接收端丢包。
4. Connection and Authorization Rate(连接与授权速率)
私有频道加入需执行一次 RLS 授权查询,该查询占用授权连接池,因此建连速率存在硬上限。
| 建连速率 | 加入成功率 | 结果 |
|---|---|---|
| 50/s | 100% | 稳定 |
| 100/s | 15,998 / 16,000 | 稳定 |
| 120/s | 100% | 上限附近 |
| 130/s | 投递 0 | 熔断 |
| 200/s | 58.3% 被拒(9,330 次失败) | 熔断 |
机制为 授权连接池 ÷ 单次授权耗时 ≈ 10 ÷ 80 ms ≈ 125/s。超限时返回 IncreaseConnectionPool 错误,30 秒窗口后自愈。该限制为全拒而非降级,因此客户端必须配置重连退避与抖动,避免集体重连造成二次熔断。
5. Presence(在线状态)
Presence 的每次状态变更需广播给频道内全部成员,成本随成员数呈平方增长。
| 单 topic 成员数 | 场景 | 结果 |
|---|---|---|
| 8 | 16,000 连接,10% 更新 | 12,670/s,p50 85 ms,零错误 |
| 8 | 25% 更新 | 23,497/s,p50 796 ms,零错误 |
| 8 | 70% 更新 | 约 78,000/s,p50 3,444 ms,内存 97.5% |
| 10,000 | 单热 topic | CPU 99.6% / 内存 100%,快照 174 KB/人,4,228 连接被断开 |
配额需求按下式核算:
所需配额 = 成员数² × 更新频率
单 topic 更新频率上限 = 配额 ÷ 成员数
在配额 1,000/s 下:
| 成员数 | 允许更新频率 |
|---|---|
| 8 | 125 次/s |
| 200 | 5 次/s |
| 1,000 | 1 次/s |
| 10,000 | 0.1 次/s(不可用) |
结论:单 topic 成员数应控制在 200 以内。大厅类场景不建议使用 Presence。
6. Postgres Changes(数据库变更订阅)
每条变更需按订阅数逐个投递,且 Postgres Changes 由单线程轮询器处理以保证顺序。
| 订阅数 | 订阅对账 | 投递(4 轮) | 轮询峰值 | 结果 |
|---|---|---|---|---|
| 16,000 | 100% | 4 × 16,000,缺失 0 | 390.8 ms | 稳定 |
| 17,000 | 100% | 4 × 17,000,缺失 0 | 612.2 ms | 稳定 |
| 18,000 | 17,743(差 257) | 劣化 | 840.7 ms | 熔断 |
| 18,000(复现) | 17,774(差 226) | 劣化 | 430.8 ms | 熔断 |
16,000 档的端到端延迟为 p50 1,317 ms、p95 ≤ 1,694 ms、p99 ≤ 1,710 ms。持续变更速率实测为 3,000 ~ 5,000/s;在与广播、Presence 并发的混合负载下,CDC 延迟劣化约 5.8 倍(由 2.6 s 升至 15 s)。
熔断根因为轮询器失败导致订阅被清除,客户端重新注册时撞上订阅池上限。调大订阅池只能推后熔断,不能解决根因。
7. Capacity Summary(推荐容量)
| 序号 | 维度 | 推荐值(生产) |
|---|---|---|
| 1 | 公共广播 — 并发连接数 | ≤ 20,000 |
| 2 | 私有广播 — 并发连接数 | ≤ 18,000 |
| 3 | 公共广播 — 每秒投递条数 | ≤ 27,000/s(CPU 70%) |
| 4 | 私有广播 — 每秒发送条数 | ≤ 6,000/s |
| 5 | 私有广播 — 每秒投递条数 | ≤ 17,000/s |
| 6 | 纯写入(无接收者) | 无限制(≥ 28,600/s 未触及瓶颈) |
| 7 | 建连 / 频道加入速率 | ≤ 60/s |
| 8 | 单条消息大小 | ≤ 4 KB |
| 9 | CDC 订阅数 / 租户 | ≤ 14,000 ~ 15,000 |
| 10 | CDC — 每秒变更条数 | ≤ 3,000/s |
| 11 | Presence 单 topic 成员数 | ≤ 200 |
| 12 | Presence — 每秒投递条数 | ≤ 23,000/s |
上表数值为单实例安全线。超出后先表现为延迟升高,持续加压才出现投递率下降或连接被断开,不会静默丢数据。业务规模超出推荐值时,应增加实例并实施房间或租户分片;仅提升内存无效,此时瓶颈位于 CPU。
8. Configuration Recommendations(配置建议)
以下参数均为标准设置项,可在平台侧调整,无需修改代码或重启服务。
| 设置项(配置键) | 建议值 | 依据 | 收益 |
|---|---|---|---|
Database connection pool size(db_pool) | 10 | 私有频道加入与 REST 发布各需一次 RLS 校验;吞吐约为「池大小 ÷ 单次 80 ms」 | 加入吞吐由 125/s 提升至约 375/s,红线由 60/s 提升至 180/s |
Postgres Changes connection pool size(subs_pool_size) | 20 | 该池仅用于创建订阅;CDC 熔断阈值等于池大小 | 延后 CDC 熔断,属缓解措施 |
Max events per second(max_events_per_second) | 25,000 | 按投递口径计数;实测安全线 27,000/s(CPU 70%) | 与实际容量对齐,避免误限流 |
Max presence events per second(max_presence_events_per_second) | 10,000 | 每次状态变更广播给频道内全部客户端,并计入 Max events per second | 支撑 200 人 topic 及秒级更新 |