Fleet 大规模 Apple MDM 负载测试指标深度解读:76k 设备批量推送场景下的 apns-mock 压测报告分析 📅 发布时间:2026/9/21 7:41:19 👁 浏览次数: Fleet 大规模 Apple MDM 负载测试指标深度解读76k 设备批量推送场景下的 apns-mock 压测报告分析【免费下载链接】fleetOpen device management项目地址: https://gitcode.com/GitHub_Trending/fl/fleet本指南以 Fleet 仓库中一次真实的 Apple MDM 负载测试运行摘要mock-apns-2026-08-21-174828Z-20m.md为骨架逐项拆解 76k 已注册设备、向全部主机下发 3 个配置文件的压力场景下Fleet Server、apns-mock、RDS、Redis 与 ALB 的实测指标并结合 collect-metrics.sh 的采集逻辑与 apple-apns-mock 的底层实现说明每个数字的含义、采集方式、阈值依据与告警排查方法。读完本文你将掌握如何读懂 Fleet 负载测试报告、如何定位 MDM 推送链路的瓶颈以及如何在仓库中复现同样的指标采集与分析流程。一次负载测试的运行背景本次报告由 collect-metrics.sh 生成对应runs/mdm/mock-apns/目录下的原始数据文件 mock-apns-2026-08-21-174828Z-20m.json 与可读摘要 Markdown。报告头部字段给出了本次运行的基本信息Collected2026-08-21T17:48:28Z采集完成时间Interval20m回看窗口Window2026-08-21T17:28:28Z 至 2026-08-21T17:48:28Z采集覆盖的时间范围Note76k enrolled adding 3 profiles to all hosts——即 7.6 万设备已注册且全部主机都在接收 3 个配置文件的批量下发metadata.note字段是运行时可自由添加的注释用于说明该次压测正在练习什么负载。按 collect-metrics.sh 的校验规则note 必须是单行文本、长度不超过 500 字符且不含控制字符以保证它能被安全地嵌入 JSON 并在 dashboard.html 中展示。从压测规模看这是一次典型的 Apple MDM 推送风暴push storm测试7.6 万设备同时接收 3 个配置文件的 MDM 命令意味着 Fleet Server 需要通过 mock APNs 服务向海量设备推送InstallProfile/RemoveProfile类命令并在设备端产生密集的 SSE 长连接回流。复现这份报告指标采集工具链在深入解读数据之前先了解这些数字是如何被采集的。整个采集链路在 tools/loadtest/metrics/README.md 中有完整说明collect-metrics.sh — 根据 Terraform workspace 名自动发现负载测试环境的 AWS 资源ECS 集群、RDS 集群、ElastiCache、ALB、CloudWatch 日志组拉取回看窗口内的 CloudWatch 指标同时输出.json数据文件与.md可读摘要含阈值告警。compare-metrics.sh — 对两个或多个运行做并排 diff把增量标记为ok/WARN/ALERT用于跨版本捕捉回归。dashboard.html — 本地浏览器可视化把runs/文件夹拖入页面即可渲染无需服务器。复现一次同样格式的采集# 20m 回看窗口filed under mdm 类别附带运行注释 ./collect-metrics.sh --workspace mock-apns --interval 20m --category mdm \ --note 76k enrolled adding 3 profiles to all hosts关键参数--help可查看全部Flag含义-w, --workspaceTerraform workspace 名必填AWS 资源名由它推导-i, --interval回看窗口Nh、Nm或裸整数小时默认3h-c, --category归档类别baseline|migration|mdm-n, --note运行注释嵌入metadata.note-o, --output覆盖输出文件路径-r, --regionAWS 区域默认us-east-2需要注意--workspace不是自由文本collect-metrics.sh 会从它推导资源名fleet-ws-backend、fleetdm-ws-mysql、fleet-ws-redis、fleet-ws-apns-mock等且要求匹配^[A-Za-z0-9][A-Za-z0-9_-]*$防止路径穿越写出runs/目录。MDM 场景下报告会额外出现两个专属小节前提是部署时启用了 Apple MDM即var.enable_apple_mdmapns_mock与apns_mock_redis。两个小节在本报告中均有数据正是本次压测的核心观测对象。顶层资源负载Fleet Server 与压测机Fleet Server40 个容器Fleet Server: CPU82.27% Mem7.91% Containers40CPU 82.27%这是服务级平均由 collect_ecs_utilization 以Sum(CpuUtilized)/Sum(CpuReserved)计算得到与 ECS 性能面板公式一致。JSON 原始数据fleet_server.cpu_utilization显示 Average82.27、Minimum80.03、Maximum84.29整个 20 分钟窗口内 CPU 始终高于 80%处于持续饱和状态。Mem 7.91%内存利用率MemoryUtilized/MemoryReserved很低说明瓶颈在 CPU 而非内存。Containers 40task_counts.runningCount desiredCount 40全部按期望运行。这是本报告最值得关注的信号在 76k 设备 3 配置文件的场景下Fleet Server 的 CPU 已经顶着 80% 阈值运行。结合下面的Top SQL与错误日志可以进一步确认 CPU 花在了哪里。Loadtest 压测容器38 个Loadtest: CPU10.77% Mem11.15% Containers38压测容器模拟 osquery 设备端上报的负载CPU 只有 10.77%内存 11.15%余量充足说明压测流量本身没有成为瓶颈观测到的服务端压力全部来自真实负载。核心观测对象apns-mock 推送服务服务级平均 vs 单容器热点apns-mock: CPU4.86% Mem17.6% Containers2 (averaged across containers) PerTask CPU avg4.86% max9.37% Mem avg17.6% max20.75% RX17.26MB TX17.78MB AbnormalStops0 StartSpread21.17min ⚠ STAGGERED STARTSapns-mock 是 Fleet 对 Apple 推送服务APNs的本地模拟承担“把 Fleet 的推送请求转给模拟设备”的角色。本报告特别区分了两种视角服务级平均Sum(Utilized)/Sum(Reserved)跨全部任务的平均。这里的 CPU4.86% 看似健康但 collect-metrics.sh 明确指出服务平均会掩盖单个饱和容器——一个任务若被打满会把它持有的所有 SSE 流全部断开而平均值看起来仍然正常。因此必须同时看per_task。PerTaskavg_pct平均任务与max_pct最热任务。CPU avg4.86%、max9.37%两者都远低于告警阈值最热任务 CPU 95%说明两个 mock 实例负载均衡良好没有热点任务。spread_pctmax−avg 的差值只有 4.51%也印证 ALB 连接分发均匀。网络方面 RX17.26MB / TX17.78MB。与普通 HTTP 服务不同collect-metrics.sh 特别指出SSE 流都是持有的长连接流量体积比 ALB 的请求数更能反映推送扇出fan-out规模因此这里用 RX/TX 字节而非请求数衡量吞吐。容器健康是本报告的第一个红旗StartSpread21.17min两个任务的started_at分别为17:10:49与17:31:59见 JSON 的apns_mock.container_health.tasks启动时间相差约 21 分钟超过 10 分钟阈值被标记为start_spread_alert: true在摘要中显示为⚠ STAGGERED STARTS。这意味着有一个 apns-mock 实例在压测进行中重启过或较晚才完成扩容异常停止数AbnormalStops0则说明没有容器以非零退出码崩溃。值得留意的是重启期间它所持有的 SSE 流会断开并转移到其他实例这可能是 Fleet Server 5xx 与错误日志的来源之一。为什么 apns-mock 是轻量设计从源码看apns-mock 的 CPU 如此之低并非偶然。cmd/apple-apns-mock/README.md 说明了两个关键设计SSE 处理器劫持连接而非阻塞eventsSSEHandler劫持net/http的连接后立即返回每连接约 8 KB 开销75k 实时流实测约 598 MB 总量其中大部分是每个流约 4 KB 的 goroutine 栈。实例可互换通过 Redis 协调Fleet 可以推给任意一个 mock 实例由 Redis 负责把推送路由到持有对应设备流的实例。运行 apns-mock 的关键参数源码中给出了完整启动方式与参数表docker compose up -d redis go run ./cmd/apple-apns-mock --listen :8378 --redis-address 127.0.0.1:6379Flag默认值说明--listen:8378监听地址--default-ttl24h无apns-expiration头时离线设备推送的保留时长--keep-alive30sSSE keep-alive 注释间隔0关闭--write-timeout10s单次 SSE 写入截止时间停止读流的设备会被断开而非钉住 token--redis-address—共享 Redis 地址必填--redis-key-prefixapns:键与频道的命名空间并发压测可加不同前缀隔离--node-idhostname-pid集群统计中的实例标识--stats-interval5s实例发布计数器的间隔GET /stats可查看单实例node与全集群cluster的计数GET /memstats报告 Go runtime 真实内存占用压测时优先看它而不是 RSS。apns-mock 专属 Redis推送吞吐的真实记录仪apns Redis: CPU0.78% (max 1.9%) Mem0.19% Conns17.7 PendingItems3 Evictions0 StringCmds469180 PubSubCmds234348 NetOut142.72MBmock APNs 使用自己专属的 ElastiCachefleet-ws-apns-mock而非 Fleet 的 Redis——collect-metrics.sh 明确说明这样设计的目的是一次全量推送波不会扰动被测系统Fleet Server本身。各指标解读依据 collect-metrics.sh 与源码中 Redis 协调协议CPU0.78%max 1.9%采集的是EngineCPUUtilization。Redis 单线程执行命令这个指标会远早于宿主 CPU 饱和因此它是更敏感的推送吞吐信号。本次负载极轻。Conns17.7连接数。apns-mock每个任务只持有 1 条订阅连接加一个小的命令池所以负载与推送速率、实例数相关而不是与设备连接数相关。PendingItems3CurrItems表示等待认领的待推送项外加每个实例一个统计键。值为 3 说明几乎没有积压设备都能及时连上领取推送。一个持续抬升的底值意味着设备没有连上来领取推送是需要警惕的信号。Evictions0必须为零。任何一次逐出都意味着一条待推送在设备重连前被静默丢弃等价于真实 APNs 的投递丢失。StringCmds469180 / PubSubCmds234348StringBasedCmdsSET/GETDEL/INCR与PubSubBasedCmdsPUBLISH/SUBSCRIBE之和是 Redis 视角看到的推送吞吐。每次推送在接收实例上发生一次 SET 一次 PUBLISH在持有流的实例上发生一次 GETDEL且每条广播会扇出到所有订阅实例因此这两类命令数直接反映推送风暴的规模。NetOut142.72MBNetworkBytesOut随实例数增长——每条广播都会投递给每个已订阅的实例。底层推送路由协议cmd/apple-apns-mock/README.md 中给出了完整的 Redis 协调流程帮助理解为什么 Redis 指标是这样分布的push at any instance SET prefixpending:token ping GET PX ttl PUBLISH prefixpush {t:token} every instance holds the token? - GETDEL prefixpending:token value - write it to the stream nil - another instance got there first connect at any instance GETDEL prefixpending:token, then announce ownership涉及的键prefixpending:token、prefixstats:node-id、prefixseq以及prefixpush频道。核心语义包括每 token 一条待推送新推送覆盖旧的对齐 APNs 的 coalescing对已连接设备立即投递且不存储同一 token 最新连接生效旧连接被关闭设备停止读流时在--write-timeout后断开但保留待推送。Redis 不可达时推送返回503 ServiceUnavailable而不是静默丢弃——这对应了 ALB 的 5xx 计数。数据层RDS 写节点与读副本RDS Writer写节点RDS Writer: CPU24.85% Connections508.9 Deadlocks0 RDS Writer: FreeMem27.24GB CacheHit100% DiskN/A Threads4.01 IOPS13.76% SelectLat0.83ms InsertLat3.57msCPU24.85%、无死锁、BufferCacheHit100%、IOPS 利用率 13.76%实例类db.r6g.4xlargemax_iops 20000实际读写 IOPS 合计约 2752——数据库层整体健康远未饱和。Threads4.01vCPUs16来自 Performance Insights 的db.load.avg平均活跃会话 AAS。collect-metrics.sh 的判定标准是持续高于 vCPU 数16才表示查询并发导致 CPU 饱和4.01 远低于此。SelectLat0.83ms、InsertLat3.57ms写路径Fleet 的负载是插入密集型的——主机 checkin、软件清单延迟毫秒级健康。RDS 读副本RDS reader-1: CPU43.63% Connections365.45 RDS reader-2: CPU25.49% Connections281.25 RDS reader-1: FreeMem28.28GB CacheHit99.99% ReplicaLag7.9ms Threads3.13 IOPS0.29% SelectLat0.3ms RDS reader-2: FreeMem28.37GB CacheHit99.99% ReplicaLag7.1ms Threads1.84 IOPS0.22% SelectLat0.35msreader-1 CPU 43.63% 是三个数据库节点中最高的但仍低于 90% 的读副本阈值。两个副本的AuroraReplicaLag分别为 7.9ms 与 7.1ms——collect-metrics.sh 指出超过 20-50ms 才可能引起脏读当前延迟在正常范围。reader-1 承担了明显更多的查询负载CPU、连接数、AAS 均更高这与 Top SQL 中它排在最前的两类查询吻合见下节。Top SQLCPU 花在了哪里Top SQL (writer): #1 load2.92 COMMIT #2 load0.81 INSERT INTO host_seen_times ( host_id , seen_time ) VALUES (...) /* , ... #3 load0.14 SELECT c . command_uuid , c . request_type , c . command , c . su... #4 load0.08 UPDATE host_mdm_actions ... #5 load0.05 INSERT INTO host_seen_times ...这些由 flatten_top_sql 从 Performance Insights 的db.load.avg按db.sql_tokenized分组解析而来。写节点上COMMIT独占 load 2.92是第二大查询host_seen_times批量插入load 0.81的三倍多——事务提交本身成为写路径的头号开销其次是主机心跳时间戳的批量 upsert。这与 Fleet 高频主机 checkin 的工作负载特征完全一致。读副本上排名靠前的则是queries表调度查询配置、host_mdmMDM 注册信息、packs查询包与upcoming_activities待执行活动的查询反映了设备端获取配置与待办命令的读路径。边缘组件Redis、ALB 与网络Fleet 主 RedisRedis: CPU51.18% Mem3.59% Redis: Connections349.45 Evictions0 CacheHit84.96%三个节点中redis-1承担了主要负载CPU 51.18%、连接 349.45redis-2/redis-3几乎空闲CPU 6.78%/7.28%、连接约 6说明 Fleet 的 Redis 负载分布不均但总量远未到瓶颈阈值 CPU 80%、Mem 70%。CacheHit84.96% 正常Evictions0 意味着没有因内存压力丢数据。ALB 与网络ALB: Latency0s 5xx472 Requests42752257 Traffic82774.7MB Network: RX1361.43MB TX455.84MBRequests4275225720 分钟约 4275 万请求主要由模拟设备的 osquery 上报驱动。5xx472Fleet Server 容器返回了 472 个 HTTP 5xx数据覆盖率为 0.25仅 1 个数据点需谨慎解读。结合下面 1175 条错误日志这部分 5xx 大概率与mdm_macos_software_update_id查询摄取失败相关——它是本报告第二个需要排查的问题。Latency0sTargetResponseTime平均为 0 秒最大 1.79sAPI 层面延迟极低。Traffic82774.7MBProcessedBytes汇总用于发现载荷体积的意外变化。阈值检查三个告警的逐一拆解本报告共触发 3 个告警阈值定义见 collect-metrics.sh 的check_threshold逻辑 ALERT: Fleet Server CPU avg 82.27 (expected 80) ALERT: apns-mock Start Spread (min) 21.17 (expected 10) ALERT: Fleet Server Errors 1175.0 (expected 0)告警一Fleet Server CPU 82.27%阈值 80%前文已述整个窗口内 Fleet Server CPU 处于 80–84% 区间持续超限。结合Top SQL与错误日志可形成完整因果链大量设备的 osquery 上报4275 万请求驱动了高频写事务COMMITload 2.92与查询负载叠加 MDM 命令下发带来的额外请求使服务端 CPU 成为本次压测的首要瓶颈。这是规模与资源配比的问题而非故障——apns-mock 本身CPU 4.86%与数据库writer 24.85%都余量充足说明瓶颈集中在 Fleet Server 应用层。告警二apns-mock Start Spread 21.17min阈值 10min两个 mock 实例启动时间相差 21 分钟一个在窗口开始前 37 分钟启动另一个在窗口开始前 16 分钟才启动属于“错峰启动”STAGGERED STARTS。start_spread_alert在 collect_service_health 与整体容器健康检查中都会计算10 分钟阈值的依据是“正常扩容的所有容器应在几分钟内相继就绪”。对于持有 SSE 流的服务晚到的实例意味着部分设备流在压测中经历了一次重连迁移也可能贡献了 Fleet 端部分瞬时错误。告警三Fleet Server Errors 1175期望 0错误日志通过 CloudWatch Logs Insights 查询采集collect-metrics.sh查询会过滤掉两类已知噪音fleet_detail_query_software错误osquery-perf 默认对软件查询有 50% 失败率与context canceled容器部署期间的正常现象。从 JSON 中的sample_messages可见本次 1175 条错误全部来自同一类POST /api/osquery/distributed/write ingestion-err: ingesting query mdm_macos_software_update_id: directIngestMDMMacOSSoftwareUpdateID empty software update device ID err: error in query ingestion即 osquery 上报的mdm_macos_software_update_id查询在摄取时遇到“空的软件更新设备 ID”导致整条查询摄取失败并返回错误。这是压测模拟数据设备返回空 ID与摄取逻辑之间的数据完整性问题而非运行时崩溃——但 1175 次的规模使它达到告警线期望 0值得在真实产品代码中核查directIngestMDMMacOSSoftwareUpdateID对空设备 ID 的容错处理。未触发的阈值健康面同样值得记录的是所有通过的检查Fleet Server 内存、RDS 写/读节点 CPU、Redis 与 apns-mock Redis 的 CPU/内存、所有 Evictions、AbnormalStops、IOPS 利用率、读副本与写节点线程数对比 vCPU 等均处于阈值内。这为后续版本对比提供了健康基线。从运行到对比如何使用这份报告本次运行的 JSON 与 MD 已归档在 tools/loadtest/metrics/runs/mdm/mock-apns/同目录下还有同一天的 1h 与多次 20m 运行mock-apns-2026-08-21-171402Z-1h、172709Z-20m、184317Z-20m、193108Z-20m可横向对比同一场景下指标随时间的演变。跨版本回归对比则使用# 最近两个 MDM 运行并排对比 ./compare-metrics.sh runs/mdm/mock-apns/mock-apns-*.jsoncompare-metrics.sh会递归搜索runs/类别子目录自动包含--filter按 workspace 名匹配如--filter apns增量按ok/WARN/ALERT分级。结论与排查清单综合本报告可以给出以下结论与行动建议本次压测的瓶颈在 Fleet Server 应用层 CPU82.27%而非数据库、Redis 或 apns-mock——扩容方向应优先考虑 Fleet Server 任务数或降低单请求开销写事务COMMIT开销居首。apns-mock 推送链路健康mock 与专属 Redis 的 CPU、内存、Evictions、PendingItems 全部处于极低水平469k 字符串命令 234k PubSub 命令顺畅处理无积压、无丢失。两个运维级问题需跟进apns-mock 实例错峰启动 21 分钟检查部署/扩容策略以及mdm_macos_software_update_id空设备 ID 摄取错误 1175 次检查模拟数据与摄取容错。报告本身可复现通过 collect-metrics.sh 以--workspace mock-apns --interval 20m --category mdm即可重新采集源码级细节可继续阅读 cmd/apple-apns-mock/README.md推送协议与 Redis 协调、pkg/mdm/apnsmockGo 客户端与 docs/Contributing/mdm/apple/apple-apns-mock.md设计文档。如果你正在规划自己的 MDM 推送压测这份报告提供了一个可直接参照的观测模板服务级平均与单任务热点并行看、Redis Evictions 与 PendingItems 必须为零、以及 ALB 5xx 与 Fleet 错误日志要交叉印证——这三组信号足以快速定位推送链路中的绝大多数问题。【免费下载链接】fleetOpen device management项目地址: https://gitcode.com/GitHub_Trending/fl/fleet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考