原文链接,如有侵权,请联系删除。
GraphQL 生态系统正在快速发展,并且,针对复杂问题的解决方案也比以往任何时候更容易找到。
关于 GraphQL 最常见的问题之一是如何扩展或者丰富它。这可以通过添加自定义工作流,比如保护你的 /graphql
端点,通过缓存提升性能,编排分布式 GraphQL 的上游调用,或者仅仅是监控和跟踪功能。
无论是特定的临时解决方案,还是专为 GraphQL 设计的网关都可以解决如下所有问题。
在这篇文章中,我们将介绍 The Guild(以及其他公司)为期六个月的研究和探索,并产生一个 2023 年有关 GraphQL 网关的状况的调查报告。我们对各种开源解决方案进行了比较、测试和基准测试,我们想分享到目前为止我们的发现。
我们要感谢参与这段旅程的合作伙伴和客户!
但首先,什么是 GraphQL 网关
GraphQL 网关遵循一个工作流,该工作流可以允许添加功能,并充当消费者和提供 GraphQL 模型(Schemas)的实际 GraphQL 服务(Servers)之间的代理。此设置的架构图如下所示:
在上面的架构图中,GraphQL 网关充当代理服务器角色,负责向外部公开您的 GraphQL 服务并向其添加功能。另一方面,GraphQL 服务器实现实际的 GraphQL 模型并运行解析器、数据加载器,以及获取和连接数据或实体所需的任何其他自定义代码。
在你的架构图中引入 GraphQL 网关层可以帮助你减轻 GraphQL 服务器需要的一些常见的通用特性和功能。以下是 GraphQL 网关可以提供的能力:
- 缓存,GraphQL 中的缓存可以通过多种方式实现,但它通常不需要与你的模型实现紧密耦合。一个 GraphQL 网关可以处理 GraphQL 服务器的缓存,以减少实际服务器的网络流量。这种方式还可以保证一个更轻量级且更易于维护的 GraphQL 服务器实现。
- 身份验证和授权,可以由 GraphQL 网关管理。如果您使用 JWT 或任何其他标准,则可以在网关层面验证令牌、权限和范围。然后,您可以将身份验证元数据(例如用户 ID)传递到 GraphQL 服务器。这样,您的 GraphQL 服务器不需要处理身份验证,而可以只专注于实现应用程序的业务逻辑。
- 安全 ,当谈到 GraphQL 服务器时,安全性也是一个问题。通过在 GraphQL 服务器前面放置 GraphQL 网关,您可以轻松保护 GraphQL API 免遭恶意查询并防范常见攻击向量
- 加固,GraphQL 网关还可以帮助你加固 API ,防止敏感信息、错误细节甚至个人身份信息(PII)的泄漏。
- 策略验证,策略验证也是 GraphQL 中的一个常见功能。例如速率限制、深度限制或复杂性限制都可以在您的网关中实现,而不是在服务器上。
- 类似实时或订阅的现代化功能,GraphQL 网关还可以解决 GraphQL 服务器中缺乏实时功能或订阅等现代功能的问题。网关可以向消费者公开实时网络传输,例如 WebSocket 或 SSE,同时向上游 GraphQL 服务器发送常规 HTTP 请求。
- 模式过滤,也可以在网关层面实现,允许在每个端点上公开不同的功能、字段和类型集。
- 其他 ,它还可以做一些简单的事情,例如为您渲染最新版本的 GraphiQL ,并配置所有启用的传输(WebSocket/SSE/HTTP)。
GraphQL 网关的基准测试和比较
为了了解和比较 GraphQL 网关领域的情况,我们首先收集了有关现有解决方案的信息。这包括:
- 支持哪些分布式 GraphQL 规范。
- 如何使用产品/库(作为代码或作为产品)。
- 支持哪些功能。
对于我们当前的基准设置,我们做出了以下决定,以确保 GraphQL 网关之间的公平比较:
- 我们使用分布式 GraphQL 规范来检查复杂设置的性能,该设置需要网关承担执行流的重要部分。我们选择了 Federation (v1) 规范,因为它被广泛使用并受到许多网关的支持。
- 我们禁用响应结果缓存以确保执行完整的请求流。
- 我们比较响应结果以确保所有网关以相同的方式响应。
- 我们追踪了所有可用的关键指标和度量数据,如网络流量、CPU 和内存。
- 我们测试了不同的用例,模拟了不同流程的真实情景,如高峰时段或上游服务器延迟。
- 我们对每一次更改运行了所有的场景,并对每个网关的结果和统计数据进行了全面的概述。
- 我们正在运行基于 Rust 的服务器来实现子图(Subgraph),以确保它永远不会成为瓶颈。
对于基准测试,我们选择了以下网关:
- Apollo-Server:用于实现 GraphQL 服务器或网关的 JS/TS 库。
- Apollo-Router:用于运行 Apollo Federation 的基于 Rust 的产品。
- Wundergraph:基于 Go 的 GraphQL 网关和平台。
- GraphQL-Mesh:基于 GraphQL-Yoga 的 JS/TS GraphQL 网关,还支持将任何内容转化为 GraphQL,同时也支持将任何其他 API 协议(REST、OpenAPI、gRPC、SOAP 等)作为子图(Subgraphs)进行支持。
除此之外,我们还为 JavaScript/TypeScript 解决方案使用了各种运行时:NodeJS (18 and 20), and Bun 。
此外,所有场景都在 Docker 容器环境中运行,使用稳定的运行程序(专用的 GitHub Actions 运行程序,每次运行 1 个并发作业,以避免竞态条件或资源丢失),并且有内存和 CPU 的限制。
上述决策可能会发生变化,主要是因为我们希望引入更多的选项和更多的场景。
整个源代码是开源的,我们鼓励开发人员协助我们进行以下工作:
- 分享更多用例和现实场景;可以通过更改使用的规范、测试的网关、插件、执行流程或参数来实现。
- 改进实际网关的代码,并帮助改进 GraphQL 生态系统。
数据表现
constant-vus-over-time
您可以在这里找到最新的结果、报告和统计数据
译者注:constant-vus-over-time
是一个性能测试中常见的术语,它表示在一段时间内以恒定的虚拟用户 (VUs,Virtual Users)负载进行测试。在这种测试中,系统会模拟一定数量的虚拟用户,这些用户以固定的速率发送请求,以测试系统在持续负载下的性能表现。
这是最简单、最朴素的设置,没有额外的调整;网关运行 Federation 规范 v1,使用一个大型查询(2个顶级字段,以及4~7个嵌套级别,跨多个子图的不同实体,以及一些片段扩展)。
- VUs: 300
- Time: 10 minutes
- CPU limit: 2
- Memory limit: 4GB
Gateway | RPS ⬇️ | Requests | Duration | Notes |
---|---|---|---|---|
apollo-router | 175 | 105605 total, 0 failed | avg: 923ms, p95: 2598ms | ✅ |
wundergraph | 168 | 100951 total, 0 failed | avg: 900ms, p95: 2583ms | ✅ |
mesh-supergraph-bun | 120 | 72820 total, 0 failed | avg: 2400ms, p95: 4116ms | ✅ |
mesh-bun | 111 | 67229 total, 0 failed | avg: 2606ms, p95: 4439ms | ✅ |
mesh | 94 | 57124 total, 0 failed | avg: 3092ms, p95: 3723ms | ✅ |
mesh-supergraph | 94 | 56839 total, 0 failed | avg: 3109ms, p95: 3815ms | ✅ |
apollo-server | 69 | 41971 total, 980 failed | avg: 4282ms, p95: 3174ms | ❌ 980 failed requests, 980 non-200 responses, 980 unexpected GraphQL errors |
apollo-server-node16 | 67 | 40578 total, 0 failed | avg: 4432ms, p95: 6252ms | ✅ |
mercurius | 50 | 30432 total, 50 failed | avg: 5912ms, p95: 6121ms | ❌ 50 failed requests |
以下是一些值得一提的见解:
✅ 几乎测试的网关能够处理所有请求,没有任何失败(旁注:当 CPU 限制较低时,某些 JS 网关无法响应请求,并且请求会因超时或连接丢失而失败,如您所见NodeJS 18 上的 apollo-server
)。
🏆 Apollo-Router 和 Wundergraph 是最快的网关,在传入请求的压力下,平均响应时间为 900 毫秒,p95 为 2500 毫秒。
🚅 最快的基于 JS 的网关是 GraphQL-Mesh(几乎是其他基于 JS 的网关的两倍),并且在 Bun 运行时中运行时,速度甚至更快 (x1.5)。
📈 Apollo-Router 的峰值内存使用量约为 400MB,而 Wundergraph 需要更多资源(x3,1.3GB)来处理相同数量的请求。上述测试中 GraphQL-Mesh 的内存消耗在 Bun 上为 550MB,在 NodeJS 18 上为 1.4GB。
📊 mercurius
(基于 Fastify)无法处理约 50 个请求。
constant-vus-subgraphs-delay
与 constant-vus-over-time
相同,但所有上游 HTTP 调用具有随机延迟(20~150ms)。此场景迫使网关将更多正在进行的请求保留在内存中,并创建更真实的场景。
- VUs: 300
- Time: 10 minutes
- CPU limit: 2
- Memory limit: 4GB
Gateway | RPS ⬇️ | Requests | Duration | Notes |
---|---|---|---|---|
wundergraph | 192 | 115808 total, 0 failed | avg: 1311ms, p95: 2017ms | ✅ |
apollo-router | 186 | 112402 total, 0 failed | avg: 1123ms, p95: 2335ms | ✅ |
mesh-supergraph-bun | 109 | 65876 total, 0 failed | avg: 2664ms, p95: 4510ms | ✅ |
mesh-bun | 103 | 62060 total, 0 failed | avg: 2832ms, p95: 4791ms | ✅ |
mesh-supergraph | 101 | 61344 total, 0 failed | avg: 2875ms, p95: 3437ms | ✅ |
mesh | 93 | 56112 total, 0 failed | avg: 3154ms, p95: 3838ms | ✅ |
apollo-server | 64 | 38683 total, 92 failed | avg: 4652ms, p95: 6050ms | ❌ 92 failed requests, 92 non-200 responses, 92 unexpected GraphQL errors |
mercurius | 12 | 7838 total, 0 failed | avg: 23393ms, p95: 24457ms | ✅ |
以下是一些值得一提的见解:
🏆 与之前的测试类似,Apollo-Router 和 Wundergraph 是最快的网关,在传入请求的压力下,平均响应时间为 1100ms,p95 为 2300ms。
🚅 最快的基于 JS 的网关是 GraphQL-Mesh(几乎是其他基于 JS 的网关的两倍),并且在 Bun 运行时中运行时,速度甚至更快 (x1.5)。
📈 传输中请求时间的增加以及 Go 运行时的结合,导致 Wundergraph 使用 2.6GB RAM,而 Apollo-Router 能够使用 600MB RAM 处理相同数量的请求。
📈 在 Bun 上运行的 GraphQL-Mesh 也能够仅用 600MB RAM 处理大量请求。
constant-vus-subgraphs-delay-resources
与 constant-vus-subgraphs-delay
相同,但具有额外的资源(CPU 和 RAM)和更多并发 VU。
- VUs: 500
- Time: 10 minutes
- CPU limit: 4
- Memory limit: 8GB
Gateway | RPS ⬇️ | Requests | Duration | Notes |
---|---|---|---|---|
wundergraph | 192 | 115980 total, 0 failed | avg: 1808ms, p95: 3277ms | ✅ |
apollo-router | 185 | 111874 total, 0 failed | avg: 1674ms, p95: 3630ms | ✅ |
mesh-supergraph-bun | 109 | 65985 total, 0 failed | avg: 4451ms, p95: 7530ms | ✅ |
mesh-bun | 102 | 61716 total, 0 failed | avg: 4756ms, p95: 7949ms | ✅ |
mesh-supergraph | 102 | 61806 total, 0 failed | avg: 4746ms, p95: 5971ms | ✅ |
mesh | 95 | 57722 total, 0 failed | avg: 5109ms, p95: 5981ms | ✅ |
apollo-server | 67 | 40608 total, 2610 failed | avg: 7387ms, p95: 59998ms | ❌ 2610 failed requests, 2610 non-200 responses, 2610 unexpected GraphQL errors |
mercurius | 12 | 7941 total, 0 failed | avg: 38437ms, p95: 41293ms | ✅ |
以下是一些值得一提的见解:
📊 更多的 CPU 和更多的 RAM 能够推动测试的网关取得更好的结果。 Apollo-Router 似乎没有使用大部分提供的 RAM(只需要 700MB),而 Wundergraph 的内存消耗很高(几乎 3GB)。
📈 当有更多 CPU 可供使用时,GraphQL-Mesh 使用 NodeJS 的 cluster
功能的能力会得到回报。
📊 在压力下, apollo-server
(JS)无法完成一定比例的请求。
ramping-vus
与之前的设置相同,但此场景旨在通过逐渐增加 VU 来将网关推向极限。一些网关在这种规模上落后或中断(由于资源的限制),并且在压力下更容易发现它可以处理的请求的最大容量。
- VUs: 50 -> 2000 (ramping(逐渐增加))
- Time: 10 minutes
- CPU limit: 4
- Memory limit: 8GB
下图衡量了请求持续时间的 p95(越低越好)。
Gateway | duration(p95)⬇️ | RPS | Requests | Durations | Notes |
---|---|---|---|---|---|
wundergraph | 6798ms | 168 | 102752 total, 0 failed | avg: 2665ms, p95: 6799ms, max: 18164ms, med: 2218ms | ✅ |
apollo-router | 6826ms | 172 | 104975 total, 0 failed | avg: 2565ms, p95: 6827ms, max: 18279ms, med: 2098ms | ✅ |
mesh-supergraph-bun | 16168ms | 120 | 74289 total, 0 failed | avg: 8354ms, p95: 16169ms, max: 40017ms, med: 7648ms | ✅ |
mesh-supergraph | 18031ms | 105 | 64859 total, 0 failed | avg: 9598ms, p95: 18032ms, max: 25650ms, med: 9483ms | ✅ |
mesh-bun | 18066ms | 109 | 67768 total, 0 failed | avg: 9245ms, p95: 18067ms, max: 44697ms, med: 8680ms | ✅ |
mesh | 19777ms | 98 | 60981 total, 0 failed | avg: 10275ms, p95: 19777ms, max: 27667ms, med: 10122ms | ✅ |
apollo-server | 60000ms | 75 | 48009 total, 7489 failed | avg: 13363ms, p95: 60001ms, max: 60717ms, med: 4289ms | ❌ 7489 failed requests, 7489 non-200 responses, 7489 unexpected GraphQL errors |
以下是一些值得一提的见解:
🏆 Apollo-Router 和 Wundergraph 是最快的网关,能够处理所有请求,平均持续时间为 6500ms。
❌ Apollo-Server 和 mercurius (JS-based) 无法处理负载,并且都无法响应请求(超时或连接丢失)。从基于 JS 的解决方案来看,GraphQL-Mesh 是唯一能够处理所有请求的解决方案。
注意事项
以下是关于在此基准测试中测试的网关的一些重要事项:
- Federation(联合) 规范兼容性
- 在测试的网关列表中,只有
apollo-server
、apollo-router
和graphql-mesh
完全支持 Federation 规范 v1,无需任何调整。其他服务,如wundergraph
,不具备全面的支持(目前只有apollo-router
、apollo-server
和graphql-mesh
完全支持 Federation 规范 v2)。 - 在服务器运行期间,我们使用了 Federation Supgraph 规范,它是通过成功组合子图而生成的产物。然后,某些网关,如
wundergraph
和mercurius
,并不支持此规范,因此提供了一个基于 GraphQL 自省的实时组合的服务列表。 - 我们选择使用 Federation 规范 v1 而不是 v2,因为它被广泛采用,并且更多的网关支持它。
- 在测试的网关列表中,只有
- NodeJS 运行时
- 对于 NodeJS,我们使用了该引擎的 18(LTS) 版本。
- 在上述某些情景中,我们使用了 Bun(最近发布的 v1 版本)。
- 公平比较
- 所有网关都运行相同的 GraphQL 模型,并执行相同的 GraphQL 查询。
- 所有网关都以 Docker 容器的形式运行,使用最新的可用版本(并使用 Renovate 保持更新)
- 所有场景中所有网关的资源仅限于 1 个 CPU 和 1GB 内存。
- Subgraphs(子图) 指标
- 在提到的所有情景中,所有子图大致消耗了 3% 的 CPU 和 10MB 的内存。我们对此进行了测量,以确保子图不会成为瓶颈,并影响结果。
- 不仅仅是 Federation
- 我们对 GraphQL 网关的期望不仅仅是作为查询器,而是能够执行更多的任务 ------ 当前虽然 GraphQL-Mesh 和 Wundergraph 具有更广泛的范围并支持更多用例,但 Apollo 工具专为 Apollo 生态系统定制,用于特定目的而构建。
我们学到了什么?
- 兼容性是 GraphQL 生态系统中的一个大问题。GraphQL 网关缺乏标准规范,因此很难对不同的解决方案进行比较和基准测试。我们希望未来的规范能够得到社区更广泛的采用和支持。
- 性能是 GraphQL 网关的一个重要关注点。处理大量请求并轻松扩展的能力对于任何 GraphQL 网关都是必不可少的。资源消耗也是一个重要关注点,重要的是要保持较低的内存和 CPU 使用率,以避免瓶颈并保持较低的成本。
- 运行环境很重要:NodeJS 很好,但我们也在关注其他运行时,例如 Bun。我们也期待看到更多使用这些运行时构建的解决方案。当正确编写时,Rust 会保持较低的内存使用;而 Go 也是构建 GraphQL 网关的绝佳选择,但内存消耗可能会更高。
保持此报告的更新
我们欢迎社区的贡献。如果您注意到任何不正确的配置、设置或影响结果或比较的其他问题,请与我们联系并报告一个 GitHub 问题。这将帮助我们保持此基准测试的最新性,并确保比较的公平性。
我们还鼓励公司和开发人员改进其技术栈。当结果发生重大变化时,我们将很乐意更新此博文(代码仓库中的结果将始终是最新的!)。