Uber 如何用原生 gRPC 加速 OpenSearch:从 REST/JSON 瓶颈到搜索与写入性能优化

发布时间:2026/7/7 4:49:17
Uber 如何用原生 gRPC 加速 OpenSearch:从 REST/JSON 瓶颈到搜索与写入性能优化 本文是对 Accelerating Search and Inestion with High-Performance gRPC™ in OpenSearch™ 的整理与翻译。内容结构概览本文会围绕 Uber 如何在 OpenSearch 中引入原生 gRPC 传输层展开主要包括为什么搜索系统在 Uber 几乎无处不在REST/JSON 在高吞吐、低延迟、大 payload 场景下为什么成为瓶颈Uber 内部基础设施为什么更偏向 gRPC ProtobufOpenSearch 原本只有 REST/JSON API 带来了什么集成成本为什么 Uber 选择在 OpenSearch 内部实现原生 gRPC而不是长期维护适配层gRPC transport 如何作为 OpenSearch module 与 REST transport 并存REST 和 gRPC 如何共享 OpenSearch 内部逻辑为什么优先覆盖 Search 和 Bulk APIJSON/OpenAPI specification 如何自动转换为 ProtobufREST API 和 Protobuf API 在语义与演进方式上的差异三阶段转换流水线preprocessing、core conversion、postprocessingSearch Gateway 如何从 Proto ↔ JSON 适配层迁移到 gRPC pass-throughBulk 写入场景下的性能收益M3 p99 写入延迟下降约 60%M3 Indexer 最大索引延迟为什么下降 20% 到 35%Spark 批量索引任务为什么也能缩短 20% 到 35%向量搜索为什么特别适合 gRPC ProtobufUber Eats 推荐场景中向量搜索延迟如何下降高维向量为什么在 JSON 下特别浪费空间文档二进制格式CBOR、SMILE 与 gRPC 的组合gRPC SMILE 为什么比 REST JSON、gRPC JSON、REST SMILE 都更快这次改造的核心结论API 表示形式不是表层选择而是系统演进能力的一部分对后端工程师的启发不要把协议层当成无关紧要的外壳一、搜索在 Uber 不是一个边缘系统很多人一听到 OpenSearch会先想到日志检索、站内搜索、全文搜索。但在 Uber 这种公司里“搜索”远不只是用户在搜索框里输入关键词。原文开头就强调搜索支撑了 Uber 内部大量实时体验包括乘客和司机匹配欺诈检测Uber Eats 推荐检索和排序近实时索引向量搜索大规模搜索平台能力也就是说搜索系统已经成为 Uber 实时决策系统的一部分。如果搜索链路延迟变高影响的可能不是一个后台管理页面而是用户匹配、推荐结果、风控判断、商家/商品召回、指标索引延迟这些核心体验。在这种背景下搜索平台的协议层就不再是一个“随便用 REST 就行”的问题。当系统规模、请求体大小、吞吐量和延迟敏感度都上来之后REST/JSON 的成本会变得越来越明显。二、REST/JSON 一开始很方便后来变成性能和演进瓶颈OpenSearch 历史上主要暴露 REST/JSON API。这对生态兼容很友好。REST 好理解JSON 可读性强调试方便curl 一下就能验证客户端也容易写。对通用搜索系统来说这些优势非常重要。但 Uber 内部大量基础设施已经使用 gRPC 和 Protobuf 通信。这套系统依赖的是强类型接口契约Protobuf 的二进制序列化HTTP/2 之上的传输能力更适合 streaming 的通信模型更低的编码/解码开销更稳定的跨语言接口演进方式于是问题出现了。Uber 内部调用链上客户端可能已经在用 Protobuf 构造请求但 OpenSearch 只接受 JSON。这就必须在中间做转换Protobuf request ↓ 转换成 JSON ↓ 发送到 OpenSearch REST endpoint ↓ OpenSearch 返回 JSON ↓ 转换回 Protobuf response这个适配层会带来三个问题。第一增加延迟。每一次请求都要做 Protobuf 到 JSON再从 JSON 到 Protobuf 的转换。第二增加 CPU 和内存开销。尤其是大 payload、向量、批量写入场景JSON 序列化和反序列化并不便宜。第三增加系统复杂度和运维风险。适配层本身也要维护、升级、监控、处理兼容性问题。一旦 OpenSearch REST API 变化适配层也可能需要调整。所以原文的核心判断是REST/JSON 不只是“慢一点”它开始限制 Uber 搜索平台未来如何演进。三、为什么不继续维护适配层面对 REST/JSON 和 gRPC/Protobuf 的不匹配最自然的办法是继续做 gateway 或 adaptor。也就是外部仍然暴露 gRPC内部转换成 OpenSearch REST。这条路短期最简单因为不用改 OpenSearch。但 Uber 最终没有把它当成长期方案。原因很现实适配层一旦成为永久架构性能开销和复杂度也会永久存在。如果只是少数低频请求适配层没什么问题。但如果是高吞吐、低延迟、海量搜索和写入请求转换层就会成为每个请求都要经过的税。而且 Uber 不希望长期维护自己的 OpenSearch fork也不想在 OpenSearch 外面堆一个越来越复杂、越来越难删的翻译系统。因此他们选择了更根本的方向在 OpenSearch 内部实现原生 gRPC endpoints让 gRPC 成为和 REST 并存的一等 transport。这也是原文标题里的重点不是“用 gRPC 包一层 OpenSearch”而是让 OpenSearch 自己支持高性能 gRPC。四、整体架构REST 和 gRPC 并存Uber 的设计不是用 gRPC 取代 REST而是让二者并存。这点非常关键。OpenSearch 的 REST API 有大量现有用户、工具和生态。如果直接替换成 gRPC会破坏兼容性也会让迁移风险非常大。所以他们选择REST transport 继续存在 gRPC transport 作为 module 引入 二者运行在不同端口 内部 OpenSearch node-to-node 逻辑尽量共享也就是说差异主要发生在 client-server transport 层。从外部看REST/JSON client - REST transport - OpenSearch internal logic gRPC/Proto client - gRPC transport - OpenSearch internal logic内部执行逻辑尽量不复制。这是一种很稳妥的架构。它保留了 REST 的兼容性和运维便利同时给性能敏感、高吞吐、大 payload 的业务提供 gRPC 路径。迁移也可以逐步完成。不是所有服务一夜之间切到 gRPC而是先让最适合的 Search 和 Bulk 工作负载使用 gRPC其他场景仍然保留 REST。五、gRPC Server作为 OpenSearch module 提供原生 gRPC transport 的核心组件是 OpenSearch 内部的 gRPC server implementation。原文提到为了提供灵活性gRPC transport 以 module 的形式提供。这意味着可以和 REST transport 同时启用可以运行在不同端口可以让不同客户端选择不同协议可以避免把 gRPC 逻辑硬编码成唯一入口可以让未来更多插件扩展 gRPC 能力在图 1 中REST 和 gRPC 在 client-server 层不同但进入 OpenSearch 内部后很多核心逻辑是共享的。这一点对长期维护非常重要。如果 REST 和 gRPC 后面各走一套完全不同的内部逻辑就会出现两个问题第一行为可能不一致。同一个 search request经 REST 和 gRPC 可能出现不同结果或不同错误行为。第二维护成本翻倍。每次 OpenSearch 功能演进都要分别维护 REST 路径和 gRPC 路径。所以正确做法是transport 层可以不同但 query execution、core logic、handler 这些内部逻辑要尽量复用。六、插件扩展为什么需要 SPIOpenSearch 的生态里有大量插件。比如 k-NN plugin 就是向量搜索的重要组成部分。如果 gRPC transport 只能支持 OpenSearch core 里的少数 API而插件没法扩展那它很快会变成一个封闭能力无法覆盖真实生产场景。所以 Uber 的设计里gRPC transport 发布了一个 SPI也就是 Service Provider Interface。这个 SPI 允许外部插件扩展 gRPC transport。原文提到像 k-NN plugin 这样的插件可以扩展这个接口提供自己的 Proto-to-POJO conversion utility methods。这里的 POJO 可以理解成 OpenSearch Java 内部使用的普通对象模型。也就是说插件可以告诉 gRPC transport这个 Protobuf request 应该如何转换成我插件内部需要的 Java 对象此外插件还可以实现并暴露自己的 gRPC services并接入主 transport-gRPC module。这解决了一个长期演进问题gRPC 不能只服务 core API它必须能随 OpenSearch 插件生态一起扩展。否则很多高级搜索能力比如向量检索、专用查询类型、插件自定义 API都无法自然迁移到 gRPC。七、优先覆盖哪些 APISearch 和 BulkOpenSearch API 很多但 Uber 没有一开始就全量支持所有 gRPC API。他们优先做了两个最重要的方向Search Bulk原因也很直接。对 Uber 和许多 OpenSearch 用户来说最延迟敏感、最吞吐敏感的 API往往就是搜索和写入。Search 关系到用户请求的在线延迟。Bulk 关系到索引写入吞吐、数据新鲜度和后台作业效率。这两个 API 最能体现 gRPC 的优势大 payload高频请求强类型 schema二进制编码更低序列化成本更少适配层转换更适合后续 streaming 扩展所以 Uber 没有先追求“覆盖面最大”而是先追求“收益最大”。这是一个很典型的工程优先级选择不要平均用力改造所有接口先改最热、最痛、最能证明价值的路径。八、Protobuf 数据模型gRPC transport 的根基gRPC 不只是换了一个 HTTP 协议入口。它的核心在于数据模型。REST/JSON API 很多时候依赖 OpenAPI specification、JSON schema、path、query parameters、status code 等约定。而 Protobuf 要求更明确的消息结构。比如 REST 里可能是POST /index/_search?qxxx body: JSON到了 gRPC/Protobuf 里就要变成更明确的 RPC 方法和 messageservice SearchService { rpc Search(SearchRequest) returns (SearchResponse); } message SearchRequest { string index 1; Query query 2; ... }这只是示意不是原文的完整 schema。重点是REST API 里很多隐式语义需要在 Protobuf 里变成显式字段和类型。如果人工维护这一切非常容易出错。OpenSearch REST API 在演进Protobuf API 也要跟着演进。如果两边手写很快就会 drift也就是漂移。一边加了字段另一边忘了一边修改了语义另一边没同步一边兼容旧客户端另一边破坏了 wire compatibility。所以 Uber 需要一条自动化流水线。九、从 JSON/OpenAPI spec 自动生成 Protobuf原文的图 2 展示了一个简化示例如何把 Search 的 JSON specification 转成 Search RPC endpoint 和 Protobuf schema。但这件事比“机械转换字段”复杂得多。REST 和 Protobuf 的设计哲学并不一样。REST 里很多信息来自HTTP methodURL pathquery parameterrequest bodyresponse bodystatus codeheaderendpoint conventionProtobuf 里很多信息必须变成servicerpc methodrequest messageresponse messagefield typefield numberrepeated fieldenumnested messagebackward-compatible schema evolution这意味着转换过程不仅是语法转换而是语义转换。Uber 建立了一条端到端自动化 pipeline分成三个阶段preprocessing core conversion postprocessing十、第一阶段Preprocessing先处理 REST 和 Protobuf 的语义差异预处理阶段要解决的问题是REST specification 里的很多隐式语义不能直接丢给 Protobuf 生成器。比如 REST API 常见这些模式GET /index/_search POST /index/_search query parameter path parameter status-code-driven behavior JSON request body JSON response body这些东西在 REST 世界里很自然。但在 Protobuf 世界里你需要显式定义 request 和 response message。所以 preprocessing 阶段会做几类处理过滤不需要或暂时不支持的 APIsanitize specification修改 schema处理 vendor extensions统一命名规则把 REST-specific 行为转换成更明确的 Protobuf-friendly 结构让隐式 request/response 语义显式化可以把这一步理解为把 REST 世界里“大家心照不宣”的东西整理成 Protobuf 能安全表达的东西。如果没有这一步自动生成出来的 Protobuf 要么很丑要么不稳定要么很难长期兼容。十一、第二阶段Core Conversion真正生成 Protobuf预处理之后就进入核心转换阶段。原文提到OpenAPI Generator 对很多 JSON-based clients 和 servers 有成熟支持但没有等价的、健壮的 JSON-to-Protobuf conversion 能力。所以 Uber 做了一组转换规则并贡献给 OpenAPI Generator。这点很重要。它说明这不是一个 Uber 内部临时脚本而是试图把这套能力推回更广泛的工具生态。转换阶段要把 OpenAPI specification 变成 Protobuf artifacts。包括.protoschemaservice definitionsrequest/response messages字段类型映射repeated / map / enum 处理nullable / optional / required 的表达JSON schema 到 Protobuf schema 的结构映射这一步决定了 gRPC API 和 REST API 的结构是否能长期保持一致。如果生成规则不稳定后续 OpenSearch API 每演进一次都可能引入大量不必要 diff。所以自动生成不仅要“能生成”还要“可重复、可维护、可演进”。十二、第三阶段Postprocessing守住 Protobuf wire compatibilityProtobuf 和 JSON 最大的不同之一是字段编号。在 Protobuf 中字段号是 wire format 的一部分。一旦某个字段号被发布出去后续就不能随便改。比如message SearchRequest { string index 1; Query query 2; }如果以后把index的字段号从 1 改成 3就可能破坏旧客户端。客户端发送的二进制数据仍然带着 field number 1服务端如果不再按原语义解析就会出问题。JSON 没有这种字段编号问题。JSON 里字段名才是主要标识。所以 Protobuf API 演进要更谨慎。Postprocessing 阶段负责做长期 wire compatibility 检查检查字段号有没有被重新编号检查旧字段有没有被错误复用检查新生成 schema 是否破坏旧客户端对比之前生成的 Protobuf应用兼容性保护规则这一步非常关键。因为 Uber 的目标不是做一个“一次性 gRPC demo”而是让 gRPC 成为 OpenSearch 的长期一等 API surface。长期 API 最怕的不是第一次生成麻烦而是后续演进过程中慢慢破坏兼容性。十三、为什么这条生成流水线很重要这条 pipeline 解决的是一个更深的问题如何让 REST 和 gRPC API 长期并存而不让它们变成两套互相漂移的接口如果手工维护会很快失控。因为 OpenSearch API 会变化。插件 API 会变化。查询类型会增加。字段会增加。旧字段会 deprecated。客户端会升级不一致。如果 REST 是一套规范gRPC 是另一套手写规范它们迟早会不一致。自动化转换和兼容性检查让 Uber 能做到REST API specification 继续作为基础 ↓ 规范化和预处理 ↓ 生成 Protobuf ↓ 兼容性检查 ↓ 生成代码 ↓ 通过 PR 合并到 protos/generated这让 gRPC API 能跟随 OpenSearch REST API 演进而不是变成一个很快过时的旁路接口。十四、Uber 集成案例Search Gateway原文给出的 Uber 内部集成例子是 OpenSearch Gateway。这个 Gateway 的职责是代理 Uber 内部所有 OpenSearch customer 的 search 和 ingest 请求然后再转发到 OpenSearch cluster。它提供了一些平台能力安全控制可观测性rate limitingauditing统一入口集群访问控制请求代理在原生 gRPC 之前Search Gateway 面向内部客户可能已经使用 gRPC/Protobuf但底层 OpenSearch 只支持 REST/JSON。所以 Gateway 内部必须维护一个 adaptor。这个 adaptor 做两件事ProtoToJsonMapper JsonToProtoMapper请求路径大概是内部客户端 ↓ gRPC / Protobuf Search Gateway ↓ Proto 转 JSON Adaptor ↓ REST / JSON OpenSearch ↓ JSON response Adaptor ↓ JSON 转 Proto Search Gateway ↓ Protobuf response 内部客户端这层 adaptor 是典型技术债。它存在的原因不是业务需要而是因为两边协议不一致。引入 OpenSearch 原生 gRPC endpoint 之后Search Gateway 可以把客户的 Protobuf request 直接 pass-through 到 OpenSearch gRPC endpoint。新的路径变成内部客户端 ↓ gRPC / Protobuf Search Gateway ↓ pass-through OpenSearch gRPC endpoint这样就去掉了中间的 Proto/JSON 双向转换。收益包括延迟下降CPU 开销下降Gateway 逻辑变简单减少自研 adaptor 维护成本减少转换错误和兼容性风险让内部 gRPC 生态和 OpenSearch 更自然对齐这也是整篇文章最核心的架构收益之一。十五、性能收益之一Bulk 写入引入 gRPC 的重要驱动力之一是性能。原文首先讲 Bulk也就是批量写入。Bulk 写入在搜索系统里非常关键。无论是日志、指标、文档、离线任务输出还是业务数据索引批量写入都是 OpenSearch 的核心路径。Uber 的 M3 是内部指标系统。M3 通过 OpenSearch 相关能力做索引写入。迁移到 gRPC 后M3 在生产中看到p99 write latency 从 34.1ms 降到 13.6ms 大约下降 60%p50 也有明显改善p50 从 15.8ms 降到 10.5ms 大约下降 34%这说明收益不是只体现在尾部延迟。中位数和 p99 都有下降只是 p99 下降更明显。为什么 Bulk 写入适合 gRPC因为 Bulk 请求往往 payload 大字段多写入频率高。JSON 解析、字符串编码、字段名重复、数字文本化都会造成开销。Protobuf 的二进制格式可以减少这些成本。同时去掉 Gateway 中的 Proto/JSON 转换层也会直接减少每次写入的 CPU 和延迟。十六、M3 Indexer最大索引延迟下降 20% 到 35%M3 Indexer 负责把数百万数据点每秒写入 M3。这里一个关键指标是 maximum indexing delay。可以理解为一个指标数据点从产生到被索引进 M3最长要等多久。这个指标非常重要尤其在 failover 时。如果索引延迟太高用户查询到的数据就不够新告警、监控和分析都会受影响。原文提到在 M3 short index 上使用 gRPC 后maximum indexing delay 降低了 20% 到 35%。在更高 RPS 下gRPC 的优势更明显。图 7 中REST 和 gRPC 在较低 RPS 时差距没那么大但当 RPS 上升到 600、800 时REST 的 delay 增长更快而 gRPC 保持得更好。这说明 gRPC 的价值不只是单请求更快还包括在更高吞吐压力下系统退化更慢。这对平台系统非常重要。低负载下谁都能跑得不错。真正关键的是高峰流量、failover、回放、批量补数据这些压力场景。十七、Spark 批量索引任务作业时间下降 20% 到 35%除了在线系统Uber 还有大量 Apache Spark 批处理任务会把数据批量索引到 OpenSearch cluster。这些任务通常不只是单次请求延迟问题而是整个 job runtime 问题。如果底层 Bulk API 更快Spark 作业整体完成时间也会缩短。原文提到将这些 batch ingestion jobs 底层迁移到 gRPC Bulk API 后作业运行时间减少了 20% 到 35%。这很有意义。因为批处理作业时间缩短会带来多方面收益数据更快可查计算资源占用时间更短作业排队压力降低集群吞吐提升failover 或补数恢复更快成本下降这也说明协议层优化不仅影响在线 latency也影响离线数据管道吞吐。十八、性能收益之二Search尤其是向量搜索除了 Bulk另一个收益明显的领域是 Search。原文特别强调最大收益出现在 vector search queries。原因很容易理解。向量搜索请求通常包含一个大向量比如[0.123, -0.456, 0.789, ...]如果用 JSON 表示每个 float 都要变成文本[0.123,-0.456,0.789]这里会有很多额外开销数字转字符串字符串解析回 float逗号、括号、空格等结构符号小数文本表示比二进制 float 更长JSON parser 需要处理通用语法大向量请求体非常膨胀而 Protobuf 对 repeated float 可以使用 packed encoding。也就是说多个 float 可以紧凑地按二进制格式连续编码。这对向量搜索非常适合。所以向量维度越高gRPC/Protobuf 相比 REST/JSON 的收益通常越大。十九、Uber Eats 推荐场景搜索延迟明显下降原文给出的具体案例是 Delivery shopping lists。这个能力用于 Uber Eats 上 grocery stores 的推荐。迁移到 gRPC 后搜索延迟变化如下p50: 83ms - 38ms下降约 53% p95: 114ms - 64ms下降约 43% p99: 205ms - 176ms下降约 14%这里有一个值得注意的现象p50 和 p95 降得非常明显但 p99 降幅较小。原文解释p99 受到 long-tail large queries 影响。这说明 gRPC 解决了很多常规请求的序列化和传输开销但最尾部的大查询可能还有其他瓶颈比如查询本身计算复杂向量维度更高返回结果更多后端执行耗时shard 分布不均cache misscluster 负载波动下游资源竞争这也是性能优化里常见的情况协议优化能显著降低整体延迟但不能自动消灭所有尾延迟。二十、高维向量下请求体大小差距非常大原文图 9 对比了不同向量维度下 REST/JSON 和 gRPC/Protobuf 的 request size。三个维度分别是1572 512 256对应数据大致是向量维度REST/JSON 请求大小gRPC/Protobuf 请求大小节省比例157240523 bytes4590 bytes88.7%51214531 bytes2500 bytes82.8%2567954 bytes1500 bytes81.1%这个表非常能说明问题。1572 维向量里REST/JSON 请求体约 40KB而 gRPC/Protobuf 只有约 4.6KB。大小差距接近一个数量级。为什么差这么多因为 JSON 对浮点数组非常不友好。假设一个 float32 在二进制里只需要 4 字节。但在 JSON 里一个浮点数可能写成0.12345678这就已经 10 个字符左右。再加逗号、括号、字段名、引号等整体很容易膨胀。而 Protobuf packed repeated float 接近原始二进制表示。所以向量越大JSON 的文本膨胀越严重gRPC 的优势越明显。二十一、文档格式不只是 JSON vs Protobuf还有 CBOR / SMILEOpenSearch REST 用户主要使用 JSON 表示文档。但在 gRPC 场景下使用二进制文档格式会更自然比如CBORSMILESMILE 可以理解成一种 JSON 的二进制表示格式。它保留了类似 JSON 的数据模型但用二进制编码减少体积和解析成本。原文特别比较了几种组合REST JSON gRPC JSON REST SMILE gRPC SMILE结果显示gRPC SMILE search 最快。具体来说gRPC SMILE search比 REST JSON 快 30% 比 gRPC JSON 快 45% 比 REST SMILE 快 47%这个结果很有意思因为它说明性能不只取决于 transport也取决于 payload representation。也就是说REST vs gRPC JSON vs SMILE这两个维度都重要。如果你只把 JSON 放进 gRPC 里仍然要承担 JSON 编解码成本。如果你只用 REST 传 SMILE也仍然保留 REST 路径上的一些限制。最好的组合是高效 transport 高效 payload format也就是 gRPC SMILE。二十二、性能总结gRPC 适合哪些场景原文对性能收益做了总结。gRPC 更适合这些工作负载大请求体 大 RPS 下的高吞吐 二进制文档格式展开说就是第一大 request size请求越大JSON 文本格式的劣势越明显。例如向量搜索、Bulk 写入、大文档查询、大批量索引都可能受益。第二高吞吐和高 RPS低 RPS 下协议差异可能被其他因素淹没。但高 RPS 下序列化、反序列化、适配层转换、网络传输大小都会变成累计成本。每个请求省一点乘以海量请求就变成明显资源收益。第三二进制文档格式如果 payload 本身可以用 CBOR、SMILE 或其他二进制格式表达再配合 gRPC transport效果会更好。第四强类型内部系统如果你的公司内部已经广泛使用 ProtobufOpenSearch 这类系统继续只暴露 JSON会导致大量转换层。原生 gRPC 可以减少架构摩擦。二十三、这次改造最大的意义不是“gRPC 更快”这么简单很多人读这类文章容易得出一个简单结论gRPC 比 REST 快。这个说法太粗糙。原文真正想强调的是API representation 不是表面选择。到了足够大的规模它会影响系统演进、性能上限和开发效率。REST/JSON 当然有它的价值。兼容、可读、调试方便、生态成熟这些都是优势。但在 Uber 的搜索平台里问题是内部生态已经是 Protobuf/gRPCOpenSearch 只有 REST/JSONSearch Gateway 需要维护转换层向量搜索请求越来越大Bulk 写入吞吐越来越高ML-driven queries 越来越多streaming 和强类型接口越来越重要在这个上下文里REST/JSON 不再只是一个“入口格式”而开始塑造系统上限。它影响请求体大小编解码成本Gateway 架构插件扩展方式API 兼容性模型客户端开发体验未来 streaming 能力搜索平台和内部基础设施的对齐程度所以这次改造本质上是让 OpenSearch 和 Uber 内部服务通信方式对齐。二十四、为什么保留 REST 仍然重要虽然 gRPC 带来明显收益但 Uber 没有删除 REST。这是非常正确的工程取舍。REST 仍然有不可替代的价值生态兼容运维调试方便curl 可测试现有客户端无需迁移小流量、低性能敏感场景足够好外部用户熟悉API 文档和工具链成熟如果强行只保留 gRPC就会制造巨大迁移成本。所以更合理的架构是REST 作为兼容和通用入口继续存在 gRPC 作为高性能、一等 transport 提供给性能敏感场景这也是很多大型系统演进的正确方式。不是“新技术替换旧技术”而是“新路径和旧路径并存让业务按收益逐步迁移”。二十五、这件事对 OpenSearch 社区有什么意义原文强调这项工作不仅服务 Uber 内部也回馈给 OpenSearch 开源生态。因为 Uber 的目标不是长期维护自己的 fork而是把原生 gRPC transport 推进 OpenSearch。这对社区有几个意义。第一OpenSearch 获得了新的 first-class transport。用户不再只能通过 REST/JSON 使用性能敏感 API。第二插件可以通过 SPI 扩展 gRPC 服务。这让向量搜索、k-NN 等插件生态可以自然接入。第三OpenAPI 到 Protobuf 的转换规则和工具可以帮助长期维护 API parity。这对 OpenSearch 这种 API surface 很大的项目非常重要。第四更多使用 Protobuf/gRPC 的公司可以减少集成适配层。尤其是那些内部服务已经标准化 gRPC 的企业。第五为未来 streaming、高吞吐、大 payload、ML 查询场景打基础。换句话说这不是 Uber 内部做了一个“快一点的客户端”而是在推动 OpenSearch 的 API 形态演进。二十六、下一步扩大覆盖、加强 streaming、安全和可观测性原文最后提到几个下一步方向expand gRPC API coverage deepen streaming support continue improving security and observability逐个理解。1. 扩大 gRPC API 覆盖当前优先覆盖的是 Search 和 Bulk。未来可以覆盖更多 OpenSearch API。但覆盖面扩大要非常谨慎因为每个 API 都涉及 schema、兼容性、插件扩展、错误语义和客户端生成。2. 加强 streaming supportgRPC 的一个优势是更适合 streaming。对于大规模搜索、批量写入、长连接数据传输、持续导入导出streaming 可能会带来更明显的架构收益。3. 改进 security一旦 gRPC 成为一等 transport安全能力也要对齐 REST。包括认证、授权、审计、加密、访问控制、插件安全边界等。4. 改进 observability生产系统里新的 transport 不能只是能跑。还要有metricstracingloggingerror classificationlatency breakdownrequest size distributionmethod-level visibilityretry / timeout / backpressure 观测否则迁移到 gRPC 之后排障能力反而下降。Uber 原本的 Gateway 就承担了 observability、rate limiting、auditing 等责任所以 gRPC transport 也必须纳入这些平台能力。二十七、原文致谢与背景原文由 Uber 工程团队多位成员撰写包括 Karen Xu、Xi Lu、Sam Akrah、Shuyi Zhang、Michael Froh 等。文章最后感谢了 Uber 领导层的指导也感谢 AWS 工程师参与构建 transport、query converters 和 Protobuf tooling。同时还感谢 Uber 内部 SIA、LucenePlus 和客户团队参与迁移与测试。这也说明这不是单个工程师的局部优化而是一个跨团队合作项目。它涉及Uber Search PlatformOpenSearchAWS 工程师Protobuf toolingtransport implementationquery converters客户团队迁移性能测试生产验证这种项目的难度并不只在“写 gRPC server”而在于让它进入一个真实的大规模搜索平台并且能长期维护、兼容、观测和推广。二十八、从后端工程师角度看这篇文章真正讲了什么如果只看标题这篇文章像是在讲gRPC 比 REST 快。但真正值得学习的是更深的几个点。第一协议不是外壳而是架构的一部分很多系统早期会认为协议层只是入口反正进来之后都转成内部对象REST 也行gRPC 也行。小规模时确实如此。但当请求量、payload 大小、延迟敏感度、内部生态标准化程度上来后协议层会影响整个系统。Uber 的 Search Gateway 例子就是这样。因为 OpenSearch 只有 RESTGateway 必须维护 adaptor。这不是业务逻辑而是协议不匹配导致的基础设施复杂度。第二性能优化要找最热路径Uber 没有全量改造所有 OpenSearch API而是先做 Search 和 Bulk。因为它们最热、最敏感、最能体现收益。这比“为了架构完整性一次做完所有 API”更实际。第三兼容性比性能更难新增 gRPC endpoint 不难。难的是让 REST 和 gRPC 长期保持一致让 Protobuf wire compatibility 不被破坏让插件也能扩展让客户可以逐步迁移。这就是为什么文章花了很多篇幅讲 JSON-to-Protobuf conversion pipeline。第四性能数据要按 workload 拆开看Bulk 有 Bulk 的收益。Vector search 有 vector search 的收益。SMILE binary document 有 SMILE 的收益。Spark batch ingestion 有作业时间收益。M3 indexer 有最大索引延迟收益。不能只给一个“整体提升百分比”。真正的大规模优化都要按 workload 拆开验证。第五开源项目演进要避免私有 forkUber 没有满足于内部 patch而是将能力贡献回 OpenSearch 生态。这对长期维护非常重要。私有 fork 短期快长期贵。原生支持短期难长期收益更大。二十九、对普通团队有什么启发你可能会问我们公司没有 Uber 这么大也没有 OpenSearch maintainer要不要学这个可以学但不要机械照搬。1. 先看瓶颈是不是协议层如果你的系统慢在数据库查询、业务计算、锁竞争、GC、缓存 miss那么换 gRPC 不会神奇解决。先 profile再决定。2. 大 payload 更值得关注如果你的请求体很小比如几个字段的 CRUDREST/JSON 足够好。但如果你传的是大数组高维向量大批量写入大文档二进制内容高频实时请求那 Protobuf/gRPC 的收益可能更明显。3. 内部系统统一契约很重要如果公司内部已经标准化 Protobuf/gRPC但某个核心系统只支持 REST/JSON那么中间适配层迟早会变成技术债。这时可以考虑让核心系统支持内部标准协议。4. 不要只关注“传输协议”gRPC JSON 不一定最优。REST SMILE 也不一定最优。真正要看组合transport serialization format data model schema evolution client generation observability security migration path5. 迁移要保留兼容路径不要一上来砍掉 REST。更稳妥的是让新旧协议并存先迁移高收益 workload。6. 自动化 schema 生成和兼容性检查很重要如果你同时维护 REST 和 gRPC API手工同步迟早出错。要尽早建立自动化转换和 compatibility check。三十、我的理解这是一篇关于“表示形式”的系统工程文章我觉得这篇文章最核心的一句话是API representation is not a surface-level choice.翻译一下就是API 的表示形式不是表层选择。这句话很值得后端工程师记住。很多时候我们把 JSON、Protobuf、REST、gRPC 看成“接口格式偏好”。喜欢可读性就 JSON。喜欢性能就 Protobuf。喜欢通用就 REST。喜欢强类型就 gRPC。但到了 Uber 这种规模API 表示形式会直接影响系统延迟请求体大小CPU 消耗Gateway 复杂度适配层技术债客户端生成API 演进插件扩展流式能力观测和安全接入平台未来上限所以这不是“换个协议”的小改动而是搜索平台的一次接口层重构。尤其是向量搜索和 ML-driven queries 越来越多之后JSON 的低效会被进一步放大。过去很多搜索请求可能只是关键词、过滤条件、排序字段。现在搜索请求可能包含几百维、上千维向量。这时再用 JSON 文本表达 float 数组成本就非常明显。所以 Uber 的这次改造也代表一个趋势随着搜索系统从文本搜索走向向量搜索和机器学习驱动的检索底层 API 也需要从“人类可读优先”逐步支持“机器高效传输优先”。REST 仍然重要。但高性能路径需要更合适的表示形式。三十一、总结Uber 这篇文章讲的是他们如何在 OpenSearch 中引入原生 gRPC endpoints从而加速搜索和写入工作负载。问题背景是Uber 内部大量基础设施使用 gRPC 和 Protobuf而 OpenSearch 历史上主要暴露 REST/JSON API。为了连接这两个世界Uber 过去需要在 Search Gateway 中维护 Proto ↔ JSON 的适配层。这层转换增加了延迟、CPU 开销和系统复杂度。Uber 最终选择在 OpenSearch 内部实现原生 gRPC transport并让它和 REST transport 并存。gRPC transport 作为 module 运行在不同端口REST 和 gRPC 在 client-server 层不同但尽量复用 OpenSearch 内部执行逻辑。为了支持插件生态他们还提供 SPI让像 k-NN 这样的插件可以扩展 Proto-to-POJO 转换和自定义 gRPC services。API 覆盖上他们优先做 Search 和 Bulk因为这是最延迟敏感、吞吐敏感、收益最明显的路径。为了让 REST API 和 gRPC API 长期保持一致Uber 建立了 JSON/OpenAPI specification 到 Protobuf 的自动转换流水线包括 preprocessing、core conversion、postprocessing 三个阶段并通过兼容性检查保护 Protobuf wire compatibility。生产效果非常明显M3 Bulk 写入 p99 延迟从 34.1ms 降到 13.6ms下降约 60% M3 Bulk 写入 p50 从 15.8ms 降到 10.5ms下降约 34% M3 Indexer 最大索引延迟降低 20% 到 35% Spark 批量索引任务运行时间降低 20% 到 35% Uber Eats 推荐场景的向量搜索 p50 从 83ms 降到 38ms下降约 53% p95 从 114ms 降到 64ms下降约 43% p99 从 205ms 降到 176ms下降约 14%向量搜索收益尤其明显因为高维 float 向量在 JSON 中非常低效而 Protobuf packed repeated float 可以大幅减少请求体大小。1572 维向量场景中REST/JSON 请求约 40523 bytes而 gRPC/Protobuf 约 4590 bytes节省约 88.7%。此外gRPC 和二进制文档格式结合也有显著收益。gRPC SMILE search 比 REST JSON 快 30%比 gRPC JSON 快 45%比 REST SMILE 快 47%。这篇文章真正的启发是在大规模系统里API 表示形式不是无关紧要的外壳。它会影响性能上限、系统复杂度、开发效率和长期演进能力。REST/JSON 仍然适合兼容、调试和通用访问。但在高吞吐、大 payload、向量搜索、批量写入、内部强类型服务生态中gRPC Protobuf 可以显著降低系统成本。Uber 的做法不是简单“用 gRPC 替换 REST”而是让 REST 和 gRPC 并存让高收益 workload 逐步迁移同时把能力贡献回 OpenSearch 社区。这才是这篇文章最值得学习的地方协议层优化不是单点性能技巧而是平台架构演进的一部分。参考资料Accelerating Search and Ingestion with High-Performance gRPC™ in OpenSearch™