资讯动态

Quickwit 压缩调度的前沿窗口优先级缺口(GAP-009):无 Leading Edge 优先策略的成因、影响与演进方案

发布时间:2026/9/15 15:15:41 来源:尧图企业网站定制
Quickwit 压缩调度的前沿窗口优先级缺口GAP-009无 Leading Edge 优先策略的成因、影响与演进方案【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit本篇技术分析基于 Quickwit 仓库中的设计文档 GAP-009docs/internals/adr/gaps/009-no-leading-edge-prioritization.md并结合其关联的 ADR-003 时间窗口化排序压缩设计、Phase 1 Sorted Splits 设计文档 以及合并策略、合并调度器、独立压缩规划器的真实源码实现展开。导读在可观测性Observability场景下Quickwit 的查询几乎总是命中最近的数据——仪表盘、告警、故障排查全部聚焦于前沿窗口Leading Edge即最新时间窗口中小文件split堆积最快、查询频率最高的区域。然而 GAP-009 指出Quickwit 当前的压缩compaction机制并不对时间窗口做优先级区分前沿窗口与冷窗口平等竞争压缩资源导致高写入速率下最近数据查询性能下降、新数据可见性延迟。本文将从设计文档出发用仓库源码逐层证实无优先级的现状对比 Husky、Prometheus/Mimir 等行业方案并深入剖析三种候选解决思路及其落地路径帮助你理解如何为 Quickwit 的压缩调度引入窗口级优先级机制。一、背景时间窗口化压缩与前沿窗口概念的由来要理解 GAP-009首先要理解 Quickwit 压缩架构中时间窗口Time Window这一核心抽象。1.1 时间窗口化压缩ADR-003Quickwit 在 ADR-003: Time-Windowed Sorted Compaction 中提出了面向 Parquet 指标管线的压缩设计所有数据被划分为固定时长、与 Unix 纪元对齐、互不重叠的时间窗口压缩compaction只合并同一窗口内的 split绝不跨窗口合并数据。窗口计算方式为window_start t - (t % window_duration_seconds) window_end window_start window_duration_seconds窗口时长window_duration默认 15 分钟必须能整除一小时合法值1m、2m、3m、4m、5m、6m、10m、12m、15m、20m、30m、60m。这一设计带来四个直接收益限定压缩范围每个窗口是独立的压缩单元单次合并的数据量被窗口时长与写入速率共同约束对齐查询模式可观测性查询总是携带时间范围谓词查询引擎可以直接丢弃窗口范围之外的 split支撑高效保留策略过期窗口内全部 split 可以成批删除限制写放大旧窗口完成压缩后不再被新数据扰动。1.2 什么是前沿窗口Leading Edge前沿窗口指最新的一批时间窗口——也就是当前时刻正在接收写入、小 split 以最快速度累积的那些窗口。GAP-009 明确指出其量级在高写入速率下每个 15 分钟窗口内会累积数十万个hundreds of thousands of小 split详见 ADR-003该文档给出的极端推算是 10 GiB/s 写入速率下约 92 万个 split/窗口。如果压缩跟不上这种累积速度查询最近数据时就必须 fan-out 到所有这些小 split 上性能急剧恶化——而这恰恰是可观测性负载中最常见、最敏感的查询路径。二、缺口定义Quickwit 压缩为何对前沿窗口一视同仁GAP-009 的核心论断可以归纳为三句话Quickwit 的压缩不优先处理最新时间窗口老旧窗口与前沿窗口在压缩资源上是公平竞争关系合并规划器Merge Planner把所有符合条件的窗口同等对待按发现合并候选merge candidate的顺序处理而不是按窗口新旧程度排序系统缺乏三种关键机制优先压缩新窗口而非旧窗口的机制在前沿窗口存在积压backlog时退避旧窗口压缩的机制发出某窗口需要紧急压缩信号例如小文件过多已影响查询的机制。文档特别强调了两类后果查询性能劣化前沿窗口小 split 过多 → 每次查询必须 fan-out 到全部小 split → 最近数据查询延迟上升。由于可观测性查询压倒性地针对近期数据仪表盘、告警、故障排查这是最可见、影响最大的劣化。新数据可见性受损系统不优先让新写入数据尽快可查询。极端情况下旧窗口的压缩应该让出资源以确保新数据在 ingest-to-query 延迟 SLO如 30 秒 p99.9内可见。2.1 与既有时间窗口设计的关系值得说明的是GAP-009 并不是说 Quickwit 完全没有时间维度处理——恰恰相反StableLogMergePolicy 在构建合并层级时确实会按时间结束倒序cmp_splits_by_reverse_time_end见stable_log_merge_policy.rs第 170-178 行排序 split。但这只是单次策略运行内部的处理顺序优化用于让时间剪枝更高效它不构成跨窗口的资源分配优先级没有前沿窗口分到更多压缩并发没有旧窗口压缩退避也没有按窗口紧急程度排序执行。这正是 GAP-009 所指的没有窗口优先级概念。三、源码证据从实现层逐级证实无优先级现状GAP-009 的Evidence一节断言StableLogMergePolicy没有窗口优先级概念仅依据**成熟度maturity与文档数document count**评估合并候选。这一论断可以在当前仓库源码中得到完整印证。我们从策略选哪些 split 合并到这些合并按什么顺序执行逐层分析。3.1 策略层StableLogMergePolicy 只认成熟度与文档数MergePolicytrait 定义在 quickwit/quickwit-indexing/src/merge_policy/mod.rspub trait MergePolicy: Send Sync fmt::Debug { /// Returns the list of merge operations that should be performed. fn operations(self, splits: mut VecSplitMetadata) - VecMergeOperation; ... fn split_maturity(self, split_num_docs: usize, split_num_merge_ops: usize) - SplitMaturity; }其核心方法split_maturity的逻辑stable_log_merge_policy.rs 第 116-123 行fn split_maturity(self, split_num_docs: usize, _split_num_merge_ops: usize) - SplitMaturity { if split_num_docs self.split_num_docs_target { return SplitMaturity::Mature; } SplitMaturity::Immature { maturation_period: self.config.maturation_period, } }也就是说一个 split 是否进入合并候选取决于文档数是否达到目标split_num_docs_target与成熟期maturation_period测试中常见 48 小时——完全没有时间窗口新旧的概念。而merge_candidate_size第 274-297 行判断合并候选大小时依据的也只是max_merge_factor、merge_factor与split_num_docs_target。GAP-009 文档本身的表述是策略基于成熟度和文档数评估合并候选而非基于时间窗口的年龄或压缩对查询性能的紧迫性。源码与之完全一致——没有任何分支依据window_start或窗口内 split 数量为某个窗口提高优先级。3.2 调度层合并调度器按分裂收益/字节数打分而非窗口新旧合并操作从规划器产出后进入MergeSchedulerServicequickwit/quickwit-indexing/src/actors/merge_scheduler_service.rs。调度器内部用一个BinaryHeapScheduledMerge维护待执行合并排序依据是order_key()第 100-102 行fn order_key(self) - (u64, Reverseu64) { (self.score, std::cmp::Reverse(self.id)) }而这个score来自compute_merge_scorequickwit/quickwit-indexing/src/merge_policy/mod.rs 第 159-170 行pub fn compute_merge_score(num_splits: usize, total_num_bytes: u64) - u64 { if total_num_bytes 0 { return u64::MAX; } let delta_num_splits num_splits.saturating_sub(1) as u64; (delta_num_splits 48) .checked_div(total_num_bytes) .unwrap_or(1u64) }代码注释明确指出优先级含义一个良好的合并操作大幅减少 split 数量、且轻量字节少。即评分 减少的 split 数 / 总字节数——与窗口新旧、窗口内 split 堆积量、查询热度完全无关。同文件第 155-158 行的注释 The higher, the sooner we will execute the merge operation 说明分数越高执行越早但分数的两个自变量都不含时间窗口维度。此外该调度器还通过Semaphore以merge_concurrency限制并发合并数默认 3但同一信号量下等待队列的排序也只认score。3.3 独立压缩器按成熟时间而非窗口新旧扫描在独立的压缩器compactor管线中CompactionPlannerquickwit/quickwit-compaction/src/planner/compaction_planner.rs每个扫描周期SCAN_AND_PLAN_INTERVAL 5s从 metastore 拉取 immature 的 published split其查询构造为第 196-223 行let query ListSplitsQuery::for_all_indexes() .with_split_state(SplitState::Published) .retain_immature(OffsetDateTime::now_utc()) .sort_by_maturity_timestamp() .with_limit(SCAN_PAGE_SIZE) .with_excluded_split_ids(excluded_split_ids);注意sort_by_maturity_timestamp()——当存在积压时最紧急的 split即将成熟的先被处理。这是基于成熟时间的紧迫性排序与窗口新旧无关。代码注释也坦承Every tick, the planner re-scans the immature published set, sorted bymaturity_timestampASC so the most-urgent splits are processed first when a backlog exists.随后待分配合并操作进入PendingOperationsquickwit/quickwit-compaction/src/planner/mod.rsPendingMerge的priority_score同样由compute_merge_score(operation.splits.len(), total_num_bytes)计算第 40-60 行Ord实现为按priority_score降序的最大堆第 77-89 行。从合并候选的产生、排队、到执行排序三个环节都没有引入window_start或窗口内 split 数量作为优先级因子。3.4 小结三处源码共同佐证 GAP-009层级代码位置当前优先级依据是否含窗口维度合并策略merge_policy/stable_log_merge_policy.rs成熟度、文档数split_num_docs_target否合并调度actors/merge_scheduler_service.rscompute_merge_score减少的 split 数 / 总字节否独立压缩规划planner/compaction_planner.rs、planner/mod.rs成熟时间戳扫描、compute_merge_score排队否GAP-009 是Status: Open的缺口分析文档Discovered: 2026-02-19其影响评估为Severity: High直接作用于近期数据查询延迟、Frequency: Constant生产负载下持续存在、Affected Areas: Merge planner、compaction scheduler、resource allocation。四、为什么这个缺口在高写入速率下最致命4.1 查询 fan-out 与 split 数成正比可观测性系统每个查询都要打开并扫描时间范围内的全部相关 split。split 越多I/O、元数据查找与 DataFusion 任务调度开销越大。前沿窗口的小 split 以最高速度累积ADR-003 的极端推算10 GiB/s 写入 10 MiB split 时每秒约产生 1,024 个 split15 分钟窗口内压缩前可累积约 92 万个如果压缩跟不上查询最近数据时 fan-out 规模会失控。4.2 新数据可见性freshnessGAP-009 明确将新数据可见性列为受影响项系统不优先让新写入数据尽快可查询。在极端情况下旧窗口的压缩应让出资源保证新数据在 ingest-to-query 延迟 SLO文档给出的示例为 30s p99.9内可见。当前实现没有任何机制实现这种让出前沿窗口与旧窗口在同一资源池中竞争。4.3 信号无关性GAP-009 的Signal Impact指出所有信号logs/traces/metrics同等受影响。前沿窗口优先级是信号无关signal-agnostic的——任何具有高写入速率与时间范围查询的信号都能受益。这意味着该缺口不是某个特定管线的特有问题而是压缩调度的通用能力缺失。五、行业现状State of the ArtHusky 与 Prometheus/Mimir 的做法GAP-009 对照了两个成熟系统的设计5.1 Husky压缩器优先前沿窗口前沿优先压缩器优先压缩最新时间桶recent time buckets旧桶排在后面自动扩缩压缩并发根据前沿窗口未压缩文件的积压量backlog自动扩缩——积压越多投入的压缩资源越多。Husky 的做法本质上是把前沿窗口积压作为资源分配与调度规模的核心输入信号。这也与 Phase 1: Sorted Splits for Parquet 中提到的 hinting mechanism提示机制一脉相承——Phase 1 设计文档第 334 行写道In the future, the compaction planner may benefit from a hinting mechanism similar to Huskys, where the system can signal that a particular window needs compaction (e.g., due to late-arriving data or a schema change). This would replace the current polling-based approach with event-driven compaction for specific windows.5.2 Prometheus/Mimir分级调度Head block 压缩按紧凑的固定节奏每 2 小时执行重叠块overlapping blocks的垂直压缩优先级更低。其思路是把必须按时完成的关键压缩与可以弹性延后的优化性压缩分离开用不同的调度节奏与优先级处理避免低价值压缩抢占关键路径资源。这两者的共同点在于压缩调度必须理解数据的时间价值——最近的数据对查询价值最高其物理组织状态也最需要及时维护而这两个项目分别用积压驱动的自动扩缩/优先与分级调度节奏实现这一点。六、候选解决方案三种思路的深度剖析GAP-009 提出了三个候选方案Potential Solutions原文均处于开放讨论状态。结合仓库现有代码结构逐一分析其可行性与落点。6.1 Option A为压缩调度引入优先级队列Priority QueueAssign priority based on window recency and split count. Recent windows with high split counts get compacted first. Older, already-compacted windows get lower priority.方案要点调度优先级 f(窗口新旧程度, 窗口内 split 数量)。最新且 split 多的窗口先压缩已充分压缩的旧窗口降级。落地分析结合源码这是改造面最小的方案直接复用现有的两处优先队列基础设施merge_scheduler_service.rs 的ScheduledMerge已实现Ord按score排序只需把score从纯compute_merge_score扩展为compute_merge_score window_recency 加权quickwit-compaction/src/planner/mod.rs 的PendingMerge.priority_score同理在计算时引入window_start越新越高分与窗口内 split 数量。实现上需要SplitMetadata携带window_start这正是 ADR-003 提出的 split 元数据扩展 中的window_start: i64字段与metrics_splits表及 Parquetkey_value_metadata双写。注意在 ADR-003 尚未落地前Tantivy 管线的 split 元数据尚无window_startOption A 若要覆盖既有 logs/traces 管线可能需要以time_range.end近似窗口新旧。优点改动集中、可渐进灰度风险优先级计算公式需要实验校准窗口新旧与 split 数的权重、防止旧窗口饿死。6.2 Option B前沿压缩与后台压缩分离资源切分Separate leading-edge compaction from background compaction. Dedicate a portion of compaction resources to the most recent N windows (e.g., last 1 hour), with remaining resources for background compaction of older windows.方案要点把压缩资源分成两份——一份固定配额给最近 N 个窗口示例最近 1 小时另一份做旧窗口后台压缩。落地分析这直接对应MergeSchedulerService中的并发控制。当前实现用单一Semaphore以merge_concurrency限流merge_scheduler_service.rs 第 191 行Tantivy 合并与 Parquet 合并共享同一信号量第 244-247 行注释Shares the same semaphore as Tantivy merges so the node doesnt exceed its merge concurrency limit。Option B 需要把单一信号量拆分为两个如前沿池 70% 后台池 30%或引入基于窗口的令牌桶window_start落在最近 1 小时内的合并从高配额池取令牌否则从低配额池取。优点直观地保证前沿窗口有最低资源保障天然实现旧窗口压缩让位风险配额比例需要随写入速率动态调整否则写入飙升时前沿配额不足写入低谷时前沿配额浪费可能仍需结合积压信号做自适应。6.3 Option C事件驱动的压缩提示Event-driven Compaction HintsWhen a windows split count exceeds a threshold (e.g., affecting query latency), emit a compaction hint that bumps that windows priority. Similar to Huskys hinting mechanism.方案要点当某窗口的 split 数超过阈值例如已影响查询延迟时发出一个压缩提示hint把该窗口的优先级临时抬高。这类似 Phase 1 设计文档中提到的 Husky 式 hinting 机制见 phase-1-sorted-splits.md 的Compaction Policy一节。落地分析这是从轮询式polling-based到事件驱动event-driven的架构转变。当前CompactionPlanner每 5 秒轮询 metastore 扫描 immature splitsSCAN_AND_PLAN_INTERVAL本质上是被动发现积压。Option C 引入主动信号例如在 split 发布路径上统计每个window_start的活跃 split 数超过阈值时向 planner 发送带窗口标识的 hintplanner 据此将该窗口的待合并操作在PendingOperations堆中插队。ADR-003 的Merge correctness invariantsMC-1~MC-4表明压缩是纯物理重排hint 机制不会破坏正确性只需保证 hint 驱动的合并仍遵守六维兼容作用域index_uid, source_id, partition_id, doc_mapping_uid, sort_schema, window_duration。优点资源利用最精准——只有真正生病的窗口才被提升优先级避免固定配额在低负载时的浪费风险hint 阈值split 数 vs 查询延迟的关系需要实验标定且 hint 风暴大量窗口同时超阈需要背压保护。6.4 三个选项并非互斥从工程实践看三者可以组合Option A 提供基础的分级排序Option B 保证前沿窗口的资源下限Option C 提供对异常窗口的快速响应。GAP-009 将其列为平行候选最终取舍取决于前沿窗口 split 累积速率的实测数据见下一节 Next Steps 的测量任务。七、落地路径Next Steps 与评估指标GAP-009 给出的后续步骤原文为 Open 状态的任务清单为在代表性写入速率下测量前沿窗口的 split 数量累积速率设计基于优先级的压缩调度窗口新旧 split 数量定义前沿窗口压缩 SLO例如每窗口最大 split 数、首次压缩前最大窗口年龄评估事件驱动压缩提示 vs 轮询式优先级的取舍。其中测量是第一优先级——任何方案都依赖对前沿窗口 split 累积曲线的量化理解。ADR-003 的Compaction Policy一节同样强调实验先行建议先做基线测量每 15 分钟窗口的 split 数、单个 split 大小、窗口总数据量再做 merge fanin 扫描4/8/16与目标 split 尺寸扫描64MB/128MB/256MB/512MB。落地时可关注的监控指标源自 Phase 1 设计文档 的 Monitoring 一节split_size_bytes压缩前后对比、indexer_cpu_usage、compaction_duration以及parquet_pages_scanned反映查询阶段页级剪枝效果。若实现 GAP-009 方案还应新增窗口级积压指标如 per-window_start的活跃 split 数、前沿窗口合并等待时长作为优先级计算与 SLO 告警的数据源。八、结论GAP-009 揭示的是 Quickwit 压缩调度中的一个系统性能力缺失调度只优化合并自身的效率减少的 split 数 / 字节数而不优化合并对象的时间价值窗口新旧、查询热度、可见性紧迫度。仓库源码在策略、调度、规划三个层面均证实了这一点——StableLogMergePolicy只认成熟度与文档数compute_merge_score只认分裂收益与字节数CompactionPlanner只按成熟时间戳扫描。在高写入速率、查询高度集中于近期数据的可观测性负载下这个缺口直接转化为前沿窗口小 split 堆积失控、最近数据查询延迟劣化、以及新数据可见性延迟。行业方案Husky 的前沿优先积压扩缩、Prometheus/Mimir 的分级调度提供了两条可借鉴的路径而 GAP-009 的三种候选方案优先级队列、资源切分、事件驱动 hint给出了具体的改造蓝图——它们与 ADR-003 的时间窗口化压缩、Phase 1 的 sorted splits 基础设施天然衔接是 Quickwit 压缩架构从物理效率优先走向查询价值优先的关键一步。延伸阅读GAP-009: No Leading Edge Prioritization本文主体文档ADR-003: Time-Windowed Sorted Compaction for Parquet时间窗口化压缩设计GAP-009 的直接上下文Phase 1: Sorted Splits for Parquet含 hinting mechanism 前瞻讨论StableLogMergePolicy 实现无窗口优先级的合并策略MergePolicy trait 与 compute_merge_score优先级打分的实现源头MergeSchedulerService合并执行队列的排序逻辑CompactionPlanner 与 PendingMerge独立压缩器侧的扫描与排队逻辑【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价