Hasura GraphQL 实时订阅架构剖析:如何支撑百万级并发 Live Queries
Hasura GraphQL 实时订阅架构剖析如何支撑百万级并发 Live Queries【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine本篇技术指南以 Hasura graphql-engine 仓库中的《借助 GraphQL 承载 100 万活动订阅实时查询》一文为骨架结合仓库源码server/src-lib下的订阅执行、轮询与多路复用实现展开纵深解析。你将理解 Hasura 将 GraphQL 订阅编译为单条 SQL、把授权声明式地嵌入查询并在单个 SQL 查询中批量复用多个客户端实时查询的三大核心思路同时掌握实时订阅相关的运行时配置参数与调优方向。说明本文部分架构图与基准测试结果图来自原文文档引用的在线图床原文档中为外部链接故不在此处重复引用图片但其中涉及的核心数据表格与配置信息已完整保留。结论先行一套“事件推送”的压测设定原文给出了一套极具代表性的压测设定与结果先交代清楚测试边界后续再解释背后的架构支撑。设置每个客户端Web 或移动应用用认证令牌登录并订阅一个实时查询结果数据存放于 Postgres 数据库。每秒更新 Postgres 中的 100 万行数据确保每个客户端都能收到一条新结果Hasura 是含授权的GraphQL API 提供者。测试目标Hasura 能并发处理多少个客户端的实时订阅是否可以纵向或横向伸缩单实例测试结果单例配置活动实时查询数量CPU 平均负载1xCPU, 2GB RAM500060%2xCPU, 4GB RAM1000073%4xCPU, 8GB RAM2000090%横向扩展结果当实时查询数量达到 100 万时Postgres 负载不超过 28%连接数峰值约 850。配置说明原文记录的测试环境未经任何微调AWS RDS Postgres、Fargate、ELB 均为默认配置RDS Postgres16xCPU、64GB RAM、Postgres 11Hasura 运行在 Fargate每实例 4xCPU、8GB RAM默认配置GraphQL 与订阅从 query 到 subscriptionGraphQL 让应用开发者轻松地从 API 中精确获取所需数据。以原文的外卖应用为例Postgres 中存在用户、订单、配送员等表应用界面显示当前用户的订单状态时一个 GraphQL 查询会获取订单最新状态和配送员定位。在底层查询被当作字符串发送给服务器经解析、授权后从数据库中获取数据返回的 JSON 数据结构与请求时相同。实时查询live queries的核心思想是订阅特定查询的最新结果——一旦底层数据改变服务器推送最新结果到客户端。这天然契合 GraphQL因为 GraphQL 客户端原生支持 subscription能自动处理繁琐的 websocket 连接对客户端而言把query换成subscription即可将普通查询升级为实时查询——前提是 GraphQL 服务器能实现它。实现 GraphQL 实时查询的难点实现实时查询是痛苦的当数据库查询包含授权规则时若要在变更事件发生时增量计算查询结果对 web 服务层而言实践难度极高对 Postgres 这类数据库来说这等同于“保持物化视图随底层表变更而更新”的难题。因此 Hasura 当前采取另一种方案为特定查询及其授权规则重新获取全部数据refetch。为什么“逐节点再获取”不可行一个典型 GraphQL 查询中授权 数据获取逻辑必须为每个“节点”运行一次。哪怕一个稍大的查询集合都可能轻易拖垮数据库——这正是 ORM 使用不当时的 N1 查询问题。Data loader 类模式可以缓解但底层仍会多次查询 Postgres从“响应中的条目数”降为“GraphQL 查询中的节点数”。对实时查询而言问题更严重每个客户端的查询都会转化为一次独立的再获取。即使查询“相同”由于授权规则产生不同的会话变量每个客户端仍需要独立的获取。Hasura 的方法声明式映射 单条 SQLHasura 的做法是从数据模型到 GraphQL schema 的声明式映射并用它创建单条 SQL 查询访问数据库——无论响应中条目有多少、GraphQL 查询节点有多少都避免对数据库的多次访问。这与“为每个节点运行 resolver”的典型实现形成鲜明对比。三大核心思路思路 #1把 GraphQL 查询“编译”成单条 SQL 查询Hasura 的一部分功能本质上是转译器transpiler利用“数据模型 → GraphQL schema”的映射元数据把 GraphQL 查询编译为 SQL 查询去数据库取数。编译链路为GraphQL 查询 → GraphQL AST → SQL AST → SQL这一步消除了 N1 查询问题且数据库能看到完整查询可以整体优化数据获取。但这还不够——resolver 通过只获取权限范围内的数据来强制授权因此必须把授权规则嵌入生成的 SQL 中。思路 #2让授权变得声明式访问数据时的授权本质上是一种约束它取决于所获取的数据行的值动态提供的、应用用户级的“会话变量”。例如最简单的行内含user_id表示数据所有权或存在关联表document_viewers表示用户可查看哪些文档其他场景中会话变量本身包含与行相关的所有权信息如账户管理员可访问的账户列表[1,2,3...]不存在于当前数据库而是来自其他数据系统提供的会话变量。为此 Hasura 在 API 层实现了类似Postgres RLS的授权层提供声明式框架配置访问控制。如果熟悉 RLS类比是SQL 查询中的“当前会话变量”换成了来自 cookie、JWT 或 HTTP 头的 HTTP 会话变量。值得一提的是原文提到 Hasura 工程在 Postgres RLS 特性进入 Postgres 之前就在应用层实现了该特性甚至遇到过与 Postgres RLS 在 insert returning 子句上修复的相同 bug。为什么在应用层做授权而非借助 RLS因为在 API 层持有所有应用用户级会话变量可以据此在单条 SQL 中嵌入授权规则表、视图、甚至返回 SETOF 的函数均可编译链路变为GraphQL 查询 → GraphQL AST → 含授权规则的内部 AST → SQL AST → SQL在仓库中这一“GraphQL 到 SQL”的编译逻辑位于 server/src-lib/Hasura/GraphQL/Execute/Subscription/Plan.hs而查询规划结果最终由 server/src-lib/Hasura/Backends/Postgres/Execute/Subscription.hs 中的mkMultiplexedQuery生成实际 SQL。思路 #3在单条 SQL 查询中批量复用多个实时查询仅有思路 #1、#2 时10 万个已连接客户端仍可能造成成比例的 10 万条 Postgres 查询假如 10 万次更新、每次对应一个客户端。但既然 API 层持有所有应用用户级会话变量可以创建单条 SQL 查询一次性为许多客户端再获取数据假设多个客户端在订阅“最新订单状态 配送员位置”在查询中创建一个“关系”relation把不同客户端的查询变量订单 id与会话变量用户 id作为不同行放入其中用 join 将实际数据查询与该关系关联在单次响应中为多个客户端取到最新数据响应中的每一行即对应用户的最终结果。这样即使各客户端的参数与会话变量完全动态、仅在查询时可知也能同时为多个用户取到最新结果。源码佐证在 server/src-lib/Hasura/Backends/Postgres/Execute/Subscription.hs#L344-L407 中mkMultiplexedQuery生成的 SQL 形如SELECT _subs.result_id, _fld_resp.root AS result FROM unnest($1::uuid[], $2::json[]) AS _subs (result_id, result_vars) LEFT OUTER JOIN LATERAL ( SELECT ... ) AS _fld_resp ON true即通过unnest将一批result_id与result_vars每个订阅的查询/会话变量展开为行再用LEFT OUTER JOIN LATERAL与每个订阅的查询结果关联——正是文档所述“在查询中创建包含所有变量的关系再 join”的落地实现。对应地server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/Common.hs 定义了Cohort同查询同变量的订阅者分组对应 SQL 中_subs表的单行、Poller每个唯一多路复用查询的轮询线程等核心数据结构。何时再获取refetchHasura 尝试过多种从 Postgres 底层捕获更新事件来触发再获取的方案Listen/Notify需要为所有表加触发器消费端web 服务器重启或网络中断时被消费事件可能丢失。WAL预写式日志流可靠但 replication slot 成本高、横向扩展困难托管数据库供应商通常不提供繁重写负载会污染 WAL需在应用层节流。因此当前回退为基于时间间隔的轮询再获取——不是事件驱动而是按时间间隔重新执行查询。两大原因把数据库事件映射到特定客户端的动态查询仅在权限与条件简单时可行如order_id 1 and user_id cookie.session_id对复杂条件如status ILIKE failed_%则不可行声明式权限有时还跨表。Hasura 在该方向含基础增量更新投入了大量调研。任何应用除非写吞吐量很小最终都要对事件做节流/防抖throttling/debouncing。该方法的代价是写负载很小时存在延迟不是立即再获取而是等几毫秒后的间隔。可通过适当调整再获取间隔与批量大小缓解。后续优化方向是用事件依赖event dependency减少每个间隔内被再获取的实时查询数量。源码级配置佐证轮询线程的休眠逻辑在 server/src-lib/Hasura/GraphQL/Execute/Subscription/State.hs#L219-L248每个新订阅对应的 Poller 以forever循环执行pollLiveQuery随后sleep (unRefetchInterval refetchInterval)。RefetchInterval与BatchSize类型定义于 server/src-lib/Hasura/GraphQL/Execute/Subscription/Options.hs默认值均为100batch size与1refetch interval秒。以下运行参数解析于 server/src-lib/Hasura/Server/Init/Arg/Command/Serve.hs命令行参数环境变量默认值说明--live-queries-multiplexed-refetch-intervalHASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL1000毫秒即 1 秒可多路复用的实时查询在此间隔内最多推送一次结果--live-queries-multiplexed-batch-sizeHASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_BATCH_SIZE100多路复用的实时查询按指定大小分批执行--streaming-queries-multiplexed-refetch-intervalHASURA_GRAPHQL_STREAMING_QUERIES_MULTIPLEXED_REFETCH_INTERVAL1000毫秒流式订阅streaming subscription对应的再获取间隔--streaming-queries-multiplexed-batch-sizeHASURA_GRAPHQL_STREAMING_QUERIES_MULTIPLEXED_BATCH_SIZE100流式订阅的多路复用批大小多路复用轮询与响应去重单次轮询周期内server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/LiveQuery.hs 的pollLiveQuery完成对当前所有 cohort 做快照并按 batch size 用chunksOf分批并发执行每批多路复用 SQLrunDBSubscription对每个 cohort计算本次响应的ResponseHashBLAKE2b-256与上一轮哈希比较——结果未变化则不推送变化了才推送见pushResultToCohort。哈希类型定义在 server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/Common.hs#L183-L194用加密哈希确保碰撞概率几乎为 0避免在内存中保留完整结果。结论正是“间隔轮询 批量多路复用 结果哈希去重”的组合使得 Hasura 能以极少的 Postgres 查询服务海量订阅。测试WebSocket 实时查询的规模化验证测试基于 WebSocket 的实时查询性能扩展性与可靠性极具挑战原文记录其测试套件与基础设施自动化工具构建耗时数周。设置如下一个 Node.js 脚本运行大量 GraphQL 实时查询客户端在内存中记录事件随后入库原文引用 github.com/hasura/subscription-benchmark 作为配套工具。一个在数据库上制造写负载的脚本使所有运行实时查询的客户端之间发生变更每秒更新 100 万行。测试套件运行完毕后验证脚本在数据库中提取日志/事件验证无错误且所有事件均被接收。测试仅在以下条件下视为有效收到的有效载荷错误数为 0从事件创建到客户端接收的平均延迟小于 1000 毫秒。仓库中与订阅状态、指标相关的实现如serverMetrics、Prometheus 的submActiveLiveQueries等可在 server/src-lib/Hasura/GraphQL/Execute/Subscription/State.hs 中看到可用于观测活动订阅数。本方法的优势Hasura 让实时查询变得触手可及查询的概念很容易扩展到实时查询使用 GraphQL 的开发者无需任何额外工作。这四点是最重要的优势功能特性丰富的实时查询全面支持 Postgres 的 operators / aggregations / views / functions 等性能可估查询被编译为单条 SQL行为可预期性能可纵向与横向伸缩扩展压测数据表明单实例 2 万订阅横向扩展可达 100 万订阅可运行于所有云、数据库供应商平台不依赖托管数据库特有的 WAL/复制槽等能力。未来展望进一步降低 Postgres 负载的两个方向映射事件到实时查询在适用场景用事件依赖替代全量间隔轮询减少每个间隔内被再获取的查询数量结果集的增量计算仅在结果变化的部分做增量更新而非整体重取。这两个方向也正是“事件驱动再获取”路线上的延续与原文“我们将在接下来的几个月里继续关注改进”的规划一致同时原文也说明内部已有基于事件的其他方法驱动可针对特定用例协作适配。【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

QMK Firmware 中的 Mullet 键盘:68 键 USB Type-C 与 RGB 底光键盘的固件构建指南

QMK Firmware 中的 Mullet 键盘:68 键 USB Type-C 与 RGB 底光键盘的固件构建指南

嵌入式固件驱动开发硬件开发 【免费下载链接】qmk_firmware Open-source keyboard firmware for Atmel AVR and Arm USB families 项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware 点击查看 免费下载 本指南以 keyboards/coseyfannitutti/mullet/r…

2026/9/20 23:32:48 阅读更多 →
使用 GNU Make 构建与安装 chezmoi:从二进制产物到完整开发工作流

使用 GNU Make 构建与安装 chezmoi:从二进制产物到完整开发工作流

使用 GNU Make 构建与安装 chezmoi:从二进制产物到完整开发工作流 【免费下载链接】chezmoi Manage your dotfiles across multiple diverse machines, securely. 项目地址: https://gitcode.com/gh_mirrors/ch/chezmoi 本篇技术指南以 chezmoi 开发者指南中…

2026/9/20 23:32:48 阅读更多 →
OpenClaw 接百炼 API Key 报错?把 openclaw.json 的 Base URL 改到 TaoToken 再重启

OpenClaw 接百炼 API Key 报错?把 openclaw.json 的 Base URL 改到 TaoToken 再重启

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 23:32:48 阅读更多 →

最新新闻

V8 字符串表示体系详解:从 SeqString 到 ConsString 的内部表示、internalization 与 String Table

V8 字符串表示体系详解:从 SeqString 到 ConsString 的内部表示、internalization 与 String Table

语言运行时编译器JIT编译解释器内存管理 【免费下载链接】v8 The official mirror of the V8 Git repository 项目地址: https://gitcode.com/gh_mirrors/v81/v8 点击查看 免费下载 导读 JavaScript 中的字符串是最基础的数据类型,V8 并没有使用单一的…

2026/9/21 3:26:55 阅读更多 →
rust-analyzer 排障 FAQ 实战指南:sysroot 损坏与 Cargo 构建锁竞争

rust-analyzer 排障 FAQ 实战指南:sysroot 损坏与 Cargo 构建锁竞争

rust-analyzer 排障 FAQ 实战指南:sysroot 损坏与 Cargo 构建锁竞争 【免费下载链接】rust-analyzer A Rust compiler front-end for IDEs 项目地址: https://gitcode.com/gh_mirrors/ru/rust-analyzer 本篇指南聚焦 rust-analyzer 官方 Troubleshooting FAQ…

2026/9/21 3:26:55 阅读更多 →
RxJS 4 `partition` 操作符深度解析:按谓词将一条 Observable 流一分为二

RxJS 4 `partition` 操作符深度解析:按谓词将一条 Observable 流一分为二

后端 【免费下载链接】RxJS The Reactive Extensions for JavaScript 项目地址: https://gitcode.com/gh_mirrors/rxj/RxJS 点击查看 免费下载 本文基于 RxJS v4(The Reactive Extensions for JavaScript)官方 API 文档与仓库源码&#xff0…

2026/9/21 3:26:55 阅读更多 →
sentence-transformers CrossEncoder 模型卡模板全解析:为 Reranker 自动生成专业 README 的完整机制

sentence-transformers CrossEncoder 模型卡模板全解析:为 Reranker 自动生成专业 README 的完整机制

sentence-transformers CrossEncoder 模型卡模板全解析:为 Reranker 自动生成专业 README 的完整机制 【免费下载链接】sentence-transformers State-of-the-Art Embeddings, Retrieval, and Reranking 项目地址: https://gitcode.com/gh_mirrors/se/sentence-tra…

2026/9/21 3:26:55 阅读更多 →
BrowserSkill错误码速查手册:cdp_failed、timeout、cancelled常见错误一次看懂

BrowserSkill错误码速查手册:cdp_failed、timeout、cancelled常见错误一次看懂

BrowserSkill错误码速查手册:cdp_failed、timeout、cancelled常见错误一次看懂 【免费下载链接】BrowserSkill Let AI agents use your real, logged-in browser without interrupting your work. CLI extension for browser automation across any shell-capable …

2026/9/21 3:26:55 阅读更多 →
claude-seo 实战:用 FLOW 框架 Audience Avatar 提示词构建可执行的「Find」阶段受众画像交付物

claude-seo 实战:用 FLOW 框架 Audience Avatar 提示词构建可执行的「Find」阶段受众画像交付物

claude-seo 实战:用 FLOW 框架 Audience Avatar 提示词构建可执行的「Find」阶段受众画像交付物 【免费下载链接】claude-seo Universal SEO skill for Claude Code. 25 sub-skills 18 sub-agents covering technical SEO, E-E-A-T, schema, GEO/AEO, backlinks, l…

2026/9/21 3:25:55 阅读更多 →

日新闻

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and …

2026/9/21 0:00:01 阅读更多 →
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,…

2026/9/21 0:00:01 阅读更多 →
Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

桌面应用AI 应用插件系统 【免费下载链接】Wox A cross-platform launcher that simply works 项目地址: https://gitcode.com/gh_mirrors/wo/Wox 点击查看 免费下载 全功能插件(Full-featured Plugin)是 Wox 三类插件实现方式中能力最完整的…

2026/9/21 0:00:01 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/19 23:01:36 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →