Quickwit Ingest V2 深度解析:基于 Shard 的动态分布式写入架构、配置实战与 V1 迁移指南
2026/9/15 11:15:06 网站建设 项目流程

Quickwit Ingest V2 深度解析:基于 Shard 的动态分布式写入架构、配置实战与 V1 迁移指南

【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit

Ingest V2 是 Quickwit 自 0.9 起默认启用的新一代写入链路,它以“可动态分布的 WAL 单元(shard)”取代了 V1 中“节点本地队列 + 索引级 checkpoint”的模型,让索引写入可以跨越集群中任意节点进行负载均衡。本文以 docs/internals/ingest-v2.md 为主线,结合控制平面、ingester、router 与配置解析源码,完整拆解 Ingest V2 的架构、一次写入请求的端到端旅程、V1/V2 差异以及全部相关配置项,帮助你在大规模多索引集群中正确启用、调优并平滑迁移到 Ingest V2。

为什么需要 Ingest V2:V1 的局限与 V2 的设计目标

在 Ingest V1 中,接收写入请求的节点必须把文档持久化到自己本地的 WAL(write-ahead log)中,队列位置与索引进度被记录为索引元数据中的 checkpoint。这种“写哪存哪”的模型有两个明显约束:

  • 写入压力高度依赖接收节点的本地磁盘与内存容量,节点间负载天然不均;
  • 每个索引的 checkpoint 都要在 metastore 中维护,当集群承载上千个索引时,元数据交互与状态追踪的开销会显著放大。

Ingest V2 的设计目标正是解决这两点:面向数千个索引规模,让写入可以被动态分发到集群中的任意 indexer 节点,并由控制平面统一调度,从而把索引工作尽可能均衡地铺开到所有 indexer 上(见 ingest-v2.md 开篇)。

Ingest V2 架构剖析

仍然以 mrecordlog 为持久化基石

无论是 V1 还是 V2,待索引文档的持久化都依赖mrecordlog——一个高性能的多队列记录日志库。差异在于组织方式:

  • V1 使用queues/目录存放本地队列;
  • V2 使用wal/目录存放 WAL(ingest-v2.md 中的差异清单明确指出这一点)。

在源码层面,V2 的 ingester 通过MultiRecordLogAsync包装 mrecordlog 异步访问,并用WalCapacityTracker同时跟踪磁盘与内存两类容量预算(见 ingest_v2/state.rs)。

Shard:可动态分布的 WAL 单元

V2 的核心抽象是shard:一段被独立管理进度的 WAL 单元。一条写入请求中的文档会被划分成若干 shard,每个 shard 由控制平面动态指派给一个 ingester 节点。这个节点既可以是接收请求的本地节点,也可以是集群中的其他 indexer——这正是 V1 做不到的跨节点写入分发。

从 proto 定义可以看到 shard 的完整生命周期状态机(quickwit_proto::ingest::ShardState),ingester 在启动时会加载本地的 shard 状态、向控制平面注册本地 shard,并周期性广播自身的容量评分(见 ingest_v2/ingester.rs 中BroadcastLocalShardsTaskBroadcastIngesterCapacityScoreTask两个后台任务)。

控制平面:shard 分配与再平衡

控制平面(control plane)是 Ingest V2 的“调度中枢”。当 router 需要为某个 source 打开 shard 时,会向控制平面发起GetOrCreateOpenShardsRequest;控制平面则基于各 ingester 当前持有的 shard 数量与 WAL 容量评分决定分配目标。

分配策略在 ingest_controller.rs 中实现得非常直观:pick_least_loaded_ingester优先选择当前 shard 数最少的 ingester,并用随机化打破平局allocate_shards会先按“已持有 shard 数量”将 ingester 组织成有序结构,再逐 shard 挑选负载最低者(见该文件pick_least_loaded_ingesterallocate_shards两个函数)。此外控制平面还内置了ScalingArbiter与周期性的 shard 再平衡操作(源码中有REBALANCE_SHARDS指标与CLOSE_SHARDS_UPON_REBALANCE_DELAY延迟参数),保证运行期间 shard 分布随负载变化持续收敛到均衡状态。

进度跟踪:从索引级 checkpoint 到 metastoreshards

V1 把写入进度作为索引元数据 checkpoint 维护;V2 则不再这样做——每个 shard 的消费进度被记录在 metastore 中一张专用的shards里(ingest-v2.md 架构小节原文)。控制平面通过 metastore 客户端执行OpenShardsRequest/OpenShardsResponse等操作来登记和推进 shard 状态(见 ingest_controller.rs 中对quickwit_proto::metastore的引用),从而把“索引元数据”与“写入队列进度”两类状态彻底解耦。

副本写入与持久性模型(规划中)

需要澄清的是,shard 副本写入目前仍处于规划阶段。按 ingest-v2.md 的说明,未来基于 shard 的写入将为每个 shard 维护一份副本,从而显著提升“等待索引文档”的持久性;而已索引文档的持久性本身由对象存储(如 S3)保证,与写入路径无关。配置项ingest_api.replication_factor在当前版本中尚未生效——详见下文配置章节。

一次写入请求的完整旅程

1. REST 入口的路由选择

POST /api/v1/{index_id}/ingest请求到达后,REST 处理器在 rest_handler.rs 的ingest函数中做版本裁决:当 V2 服务启用且请求未显式要求旧版写入时,走ingest_v2路径;否则回落到ingest_v1,此时若 V1 被禁用则直接返回错误(QW_DISABLE_INGEST_V1生效时)。这一分支逻辑同时作用于普通 ingest 端点与 bulk 批量写入端点(见 ingest-api.md 的说明)。

ingest_v2实现中,文档会先被构造成带DocUidDocBatchV2,随后包装成IngestSubrequest发送给IngestRouter。值得注意的是,V2 支持detailed_response选项以返回逐文档的解析结果,而 V1 对detailed_response会直接返回BadRequest(rest_handler.rs)。

2. IngestRouter:去抖、路由表与转发

IngestRouter 是 V2 写入路径上的转发层,其内部状态(见 ingest_v2/router.rs)包含:

  • RoutingTable:维护各节点、各 WAL 容量以及每个 source 当前打开的 shard 数,是“往哪转发”的依据;
  • 去抖器(debouncer):将高频的GetOrCreateOpenShardsRequest合并后批量发给控制平面,降低控制面压力;
  • 信号量:以字节为粒度限制在途 ingest 请求量,配合速率限制做写入背压。

3. 同步解析与校验:错误直接返回客户端

与 V1 不同,V2 会在写入路径上同步地解析并校验输入文档:JSON 格式错误与 schema(doc mapping)不匹配会在 ingest 响应中直接返回,而 V1 中这类错误只能从服务器日志中看到(ingest-v2.md 差异清单)。

底层实现在 ingest_v2/doc_mapper.rs:validate_doc_batch将 CPU 密集的校验任务放入run_cpu_intensive线程池执行,逐文档解析 JSON 并用 doc mapper 校验字段类型,失败项被记录为带ParseFailureReasonInvalidJsonInvalidSchema)的ParseFailure,随后连同合法的DocBatchV2一起随响应返回,实现“部分成功、逐条报告”。

4. Ingester:落盘、持久化与推进

目标节点上的Ingester收到转发来的批量后,将文档追加到对应 shard 的 WAL(mrecordlog),期间通过WalCapacityTracker检查磁盘/内存容量预算,超限时按速率限制策略拒绝请求(对应 router 侧的INGEST_RESULT_WAL_FULLINGEST_RESULT_RATE_LIMITED等指标,见 ingest_v2/router.rs 的指标枚举)。随后持久化过程(persist)会向控制平面与索引管道推进 shard 位置,索引管道消费 WAL 生成 split 并上传对象存储。

V1 与 V2 对比速查

维度Ingest V1Ingest V2
持久化目录queues/wal/
WAL 位置始终写入接收请求的本地节点可由控制平面转发到任意 indexer(本地或远端)
写入负载均衡无(取决于请求落在哪个节点)控制平面按 shard 数与容量评分动态分配、再平衡
进度追踪索引元数据 checkpointmetastore 专用shards
文档解析校验异步,错误仅出现在服务器日志同步解析校验,schema/JSON 错误随 ingest 响应返回
副本写入不支持规划中(replication_factor尚未生效)
公共配置max_queue_memory_usagemax_queue_disk_usage相同参数 +replication_factor(暂未生效)

以上差异均来自 ingest-v2.md 的差异清单小节。

配置指南

切换 Ingest 版本的环境变量

两个环境变量共同决定写入服务如何被启用(默认值来自 quickwit-config/src/lib.rs,与 ingest-api.md 文档一致):

变量说明默认值
QW_ENABLE_INGEST_V2启动 V2 ingest 服务并默认使用true
QW_DISABLE_INGEST_V1仅当 V2 被禁用时,API 才会使用 V1;保留 V1 是为了在不丢失存量未索引日志的前提下迁移到 V2false

也就是说:默认情况下 V2 与 V1 两个服务同时启动、API 走 V2;当你需要回退到 V1 时,同时设QW_ENABLE_INGEST_V2=false并保持QW_DISABLE_INGEST_V1=false即可。REST 处理器正是据此在 rest_handler.rs 中决定调用ingest_v2还是ingest_v1

ingest_api 配置项

ingest 容量相关配置位于节点配置的ingest_api段(解析与校验见 node_config/mod.rs):

version: 0.8 ingest_api: # 队列在内存中的最大占用,默认 2GiB。 max_queue_memory_usage: 2GiB # 队列(WAL)在磁盘上的最大占用,默认 4GiB。 max_queue_disk_usage: 4GiB # 单次写入请求体大小上限,默认 10MiB。 content_length_limit: 10MiB # 节点退役时等待在途写入完成/转移的超时,默认 300s。 decommission_timeout: 300s

注意事项(均为源码中可验证的硬性约束):

  • max_queue_disk_usage至少为 256 MiB,且必须不小于max_queue_memory_usage,否则配置校验直接失败并给出对应错误信息(node_config/mod.rs);
  • ingest_api.replication_factor当前会被解析器忽略并输出警告日志(源码将其反序列化为IgnoredAny并在warn_if_replication_factor_is_set中提示“尚未生效”),请勿依赖它——副本写入属于规划中的能力(node_config/mod.rs)。

启用协作式索引(enable_cooperative_indexing)

当集群中活跃写入的 indexer 数量很大(几十个量级)时,可以为 indexer 打开协作式索引选项,通过全局信号量协调各索引管道的提交节奏,从而限制 indexing workbench 的内存消耗。该选项默认关闭,在节点配置中开启:

version: 0.8 # [...] indexer: enable_cooperative_indexing: true

源码中,IndexerConfig.enable_cooperative_indexing默认值为false(node_config/mod.rs),开启后索引服务会创建一个共享的cooperative_indexing_permits信号量并传入各索引管道(见 indexing_service.rs 与 cooperative_indexing.rs 中基于CooperativeIndexingCycle的周期协同算法)。完整节点配置模板可参考 config/quickwit.yaml。

写入路径上的超时与批大小参数

V2 写入路径内部使用一组层级化超时参数,约束关系记录在 ingest_v2/ingest.md:

参数默认值含义
Itimeout(ingest 请求超时)35s一次 ingest 请求的总体超时
Ptimeout(persist 请求超时)6s一次持久化请求的超时
Rtimeout(replicate 请求超时)3s一次副本请求的超时(为未来副本能力预留)
k(persist 尝试次数)5持久化失败后的最大重试次数

由于 persist 请求内部会发起 replicate 请求、ingest 请求内部会发起 persist 请求,三者的取值必须满足约Ptimeout >= 2 * RtimeoutItimeout >= k * Ptimeout(当前 35s >= 5 × 6s = 30s,满足约束)。

此外还有两个可用环境变量调节的运行时参数:

  • QW_INGEST_BATCH_NUM_BYTES:单个 mrecordlog 批次的字节阈值,默认1 MiB(见 ingest_v2/ingester.rs);
  • QW_INGEST_REQUEST_TIMEOUT_MS:自定义 ingest 请求超时毫秒数,若设置的数值小于PERSIST_REQUEST_TIMEOUT × MAX_PERSIST_ATTEMPTS + 5s的下限,会被自动抬升到该下限(见 ingest_v2/router.rs)。

迁移与回退注意事项

  • 两版本并存是刻意的:默认配置下 V1 服务仍然运行,目的是让存量 V1 队列中尚未索引的文档可以继续被消费,避免切换瞬间丢数据。迁移完成后若确认无存量 V1 队列,再考虑设QW_DISABLE_INGEST_V1=true收紧(ingest-api.md)。
  • 错误可见性变化:迁移到 V2 后,文档级的 JSON/schema 错误会出现在 ingest 响应(配合detailed_response可拿到逐条解析结果),监控与告警策略应相应从“扫日志”调整为“解析响应”。
  • 容量语义max_queue_memory_usage/max_queue_disk_usage在 V1、V2 下语义一致;V2 下 WAL 分布在多个节点上,评估磁盘水位时应按节点聚合查看,而不是只盯接收节点。
  • 客户端重试:队列容量打满时服务端会返回429,Quickwit CLI 的./quickwit index ingest会自动重试;自定义客户端接入时同样应处理429(ingest-api.md 中已明确说明)。

参考阅读

  • 本文主文档:docs/internals/ingest-v2.md
  • Ingest API 使用教程与版本开关:docs/ingest-data/ingest-api.md
  • 节点配置总览(含 ingest_api 段):docs/configuration/node-config.md
  • 节点配置示例:config/quickwit.yaml
  • Ingest 路由与转发实现:quickwit-ingest/src/ingest_v2/router.rs
  • Ingester(WAL 落盘)实现:quickwit-ingest/src/ingest_v2/ingester.rs
  • 控制平面 shard 分配实现:quickwit-control-plane/src/ingest/ingest_controller.rs
  • REST 层版本路由:quickwit-serve/src/ingest_api/rest_handler.rs
  • 配置解析与校验:quickwit-config/src/node_config/mod.rs

【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询