跳转至

幽灵多收与消息查询性能验证

本文记录 2026-08-06 在 Hyper-V Ubuntu VM 中对幽灵多收路由和频道消息查询执行的可复现性能验证。数据用于比较实现方案和发现数量级问题,不是生产 SLA;真实网卡、生产数据分布和在线 DDL 仍需在目标环境复测。

测试环境

项目 配置
操作系统 Ubuntu 22.04 VM
CPU AMD Ryzen 9 7940HX,VM 分配 8 vCPU;Go 基准固定 GOMAXPROCS=4
内存 7.8 GiB
Go 1.25.5 linux/amd64
MySQL 8.4.10,Docker,同一 VM
消息数据 1,000,000 行,4 个群组,语音/文本各 50%

多收 fan-out

以下数据取 5 次 300ms Go benchmark 的中位数。locked_scan 模拟每帧遍历全部 Session 并复制目标的旧式热路径;cached_plan 是当前按通信域缓存不可变接收计划的热路径。

Session 数 方案 延迟 内存/操作 分配/操作
10,000 全表锁扫描 429.10 us 989 KiB 3,999
10,000 缓存命中 27.02 ns 0 B 0
20,000 全表锁扫描 840.05 us 1.92 MiB 7,999
20,000 缓存命中 27.57 ns 0 B 0

10,000 和 20,000 Session 下缓存命中分别约快 15,900 倍和 30,500 倍。20,000 Session 的完整缓存重建中位数为 11.25 ms、16.95 MiB 和 40,499 次分配;该成本只在路由或会话发生变化后支付,不进入每个媒体帧的稳定热路径。

现代幽灵专项基准使用 1,000 个 Session、32 个通信域、每 Session 订阅 4 个域。每个域恰有 125 个接收 Session:

操作 中位延迟 内存/操作 分配/操作 结果
全域缓存构建 1.91 ms 2.66 MiB 11,024 1,000 Session / 4 接收域
缓存命中 28.24 ns 0 B 0 125 目标
完整 UDP fan-out 978.49 us 632 B 5 排除来源后 124 次发送,错误为 0

该专项基准逐帧等待完成,没有出现队列拒绝或发送错误。持续过载时的完整帧淘汰率沿用同一 UDP writer 后端,其稳态与过载数据见UDP 转发压力测试;本次微基准不能替代真实网络 soak。

复现命令:

GOMAXPROCS=4 go test ./internal/interconnect -run '^$' \
  -bench '^(BenchmarkEdgeReceiverCollection|BenchmarkModernGhostMultiReceiveFanout)$' \
  -benchmem -benchtime=300ms -count=5

接收放大模型

默认限制为每账号最多 8 个幽灵 Session、每 Session 最多 16 个接收频道。路由索引最多保存 8 x 16 = 128 个“账号-Session-频道”成员关系,但单条频道消息会按 Session 去重,因此不会再乘一次订阅数:

  • 单账号在同一热门频道上的单消息最多产生 8 份下行。
  • H 个账号都达到 8 Session 时,单频道 fan-out 上界约为 8H,再排除精确来源 Session。
  • 16 个频道同时各产生一条消息时,单账号最坏接收 128 份;这是并发频道数造成的总量,不是单消息重复投递。
  • 缓存构建和内存规模与 Session 接收成员关系总数相关;单帧发送成本与该来源频道的去重 Session 数相关。

因此容量规划应分别使用“总成员关系”评估缓存重建和内存,用“最热门频道的去重 Session 数 x 实际包速”评估稳定 fan-out,不能直接把 Session、订阅数和频道人数无条件相乘。

百万行频道消息

频道历史现按 comm_record_delivery_groups 的发送时投递快照查询。快照同时保存不可变的 start_time 和 message_type。普通查询必须使用 idx_delivery_group_cursor(group_id, start_time, record_id),类型查询必须使用 idx_delivery_group_type_cursor(group_id, message_type, start_time, record_id),再通过主键关联通信记录。这样即使目标频道没有指定类型的消息,也不会扫描该频道的全部投递记录。

本文早期记录的 comm_records(group_id,status,...) 性能数字属于“按当前来源组和互联拓扑回算”的旧查询方案,不能作为投递快照实现的基线。新方案必须在目标 MySQL 版本重新记录以下结果:

  • 单频道和多频道各取 50 条的 EXPLAIN ANALYZE。
  • 文本、语音过滤在稀疏分布下的扫描行数和耗时。
  • 创建快照表、复合索引和旧记录来源组补录的持锁时间、磁盘增量与总耗时。
  • 相同记录同时命中多个投递群组时的去重和游标稳定性。

复现命令要求独立且可删除的 draarl_test_ 前缀数据库:

DRAARL_MESSAGE_PERFORMANCE_E2E=true \
DRAARL_TEST_MYSQL_DSN='<test-dsn>' \
go test ./internal/gormdb -run '^TestMillionRowMessageCursorPlanMySQL$' -v -count=1

十二万行发信记录分页

管理员发信记录列表先在 comm_records 上完成总数统计和当前页 ID 查询,再仅为当前页记录关联设备、用户和群组详情。全局首屏执行计划必须使用 idx_comm_records_status_start_id(status, start_time, id),不能先对全部记录执行详情联表或额外排序。

DRAARL_COMM_RECORDS_PERFORMANCE_E2E=true \
DRAARL_TEST_MYSQL_DSN='<test-dsn>' \
go test ./internal/handler -run '^TestCommRecordsGlobalPaginationPerformanceMySQL$' -v -count=1

结论

  • 多收路由应继续使用按通信域缓存、按 Session 去重的接收计划;每帧重新扫描 Session 不具备扩展性。
  • 频道历史必须按发送时投递快照查询,不能使用当前互联拓扑重建历史可见性。
  • idx_delivery_group_cursor 和 idx_delivery_group_type_cursor 是频道历史游标入口;发布前仍需在生产数据副本记录 DDL 时间、额外磁盘、补录耗时和回滚窗口。
  • 本轮只改变 fan-out 路由、历史查询和数据库结构,不改变 DraARLv1 的 90 字节包结构或标准 UDP 实体设备行为。