当前位置: 首页 > news >正文

DocumentsWriterDeleteQueue

Lucene 核心索引模块中的DocumentsWriterDeleteQueue,它是 Lucene 实现高并发、强一致性删除(delete)和更新(update)语义的关键基础设施。

我们可以用一句话概括它的作用:

DocumentsWriterDeleteQueue是一个全局的、非阻塞的、单向链表结构的“待处理删除操作队列”,用于在多线程写入(add/update/delete)场景下,保证所有删除操作按提交顺序被正确应用到每个即将 flush 的 segment 上。


🔍 一、为什么需要它?——问题背景

在 Lucene 中:

  • 多个线程可以同时调用addDocument()updateDocument()deleteDocuments()
  • 每个线程有自己的DocumentsWriterPerThread(DWPT),独立缓冲文档
  • 但删除操作是全局的:一个delete(term)应该影响所有 segment(包括未来 flush 出来的)

挑战

如何让每个 DWPT 在 flush 时,知道“截至此刻,哪些 delete 已经发生”?
并且要保证:先 add 后 delete → 文档不出现;先 delete 后 add → 文档出现

这就需要一个全局有序的 delete 日志,而DocumentsWriterDeleteQueue就是这个日志。


🧱 二、核心设计:链表 + DeleteSlice

1.全局链表结构
private volatile Node<?> tail;
  • 所有 delete 操作(term/query/doc-values update)都被封装为Node,追加到链表尾部
  • 链表是单向、无头(只有 tail)、带哨兵节点(sentinel)
  • 追加操作是synchronized的,保证严格顺序
2.每个消费者持有一个 DeleteSlice
class DeleteSlice { Node<?> sliceHead; // 上次处理到的位置(不包含) Node<?> sliceTail; // 本次需要处理到的位置(包含) }
  • 每个 DWPT有自己的DeleteSlice
  • 全局 delete pool也有一个globalSlice
  • slice表示“我需要处理从 head 到 tail 之间的 delete”

💡 这种设计让 GC 自动回收已处理的节点(只要没有 slice 引用它)


⚙️ 三、关键方法解析

方法作用
add(Node, DeleteSlice)用于updateDocument(doc, delTerm)
1. 将 delTerm 加入全局队列
2.原子性地更新调用者的 slice.tail = 新节点
→ 保证该 delete 会被本 DWPT 在 flush 时应用
freezeGlobalBuffer(DeleteSlice callerSlice)DWPT flush 前调用:
1. 锁住全局 buffer
2. 将 callerSlice.tail 推进到当前 tail
3. 返回一个FrozenBufferedUpdates快照(包含所有 delete)
→ 用于构建FlushedSegment
tryApplyGlobalSlice()异步尝试将新 delete 应用到globalBufferedUpdates
(供后续 merge 或 searcher 使用)
newSlice()为新 DWPT 创建初始 slice(指向当前 tail)

🔄 四、典型流程:updateDocument(doc, delTerm)

这是最能体现其价值的场景:

indexWriter.updateDocument(new Term("id", "123"), doc);

内部步骤:

  1. DWPT-A 处理 doc
  2. 调用deleteQueue.add(delTermNode, dwptSlice)
    • 全局队列追加delTermNode
    • dwptSlice.sliceTail = delTermNode← 关键!
  3. DWPT-A flush 时:
    • 调用freezeGlobalBuffer(dwptSlice)
      • 将 slice.tail 推进到最新 tail(可能包含其他 delete)
    • dwptSlice.apply(...)→ 应用所有 delete(包括自己的 delTerm)
    • 如果 doc 匹配delTerm→ 不写入 segment

✅ 即使多个线程同时 update 同一个 term,也能保证只有一个成功写入(因为 slice.tail 原子更新)


🌐 五、全局 vs 局部视角

视角说明
全局队列所有 delete 的“事实来源”,严格有序
DWPT 的 slice“我关心哪些 delete” —— 从上次 flush 到现在
globalSlice“哪些 delete 还没应用到已提交的 segments”
→ 用于生成.del文件或 merge 时应用

🧠 六、类比理解

想象一个多人协作编辑的 Google Docs

  • 每个人(DWPT)在自己的草稿区写内容
  • 所有“删除某段文字”的操作(delete)被记录在一个共享操作日志(DeleteQueue)中
  • 当某人要“提交草稿”(flush)时:
    • 他查看日志:“从我上次提交后,有哪些删除?”
    • 把这些删除应用到自己的草稿上
    • 提交最终版本

DeleteSlice就是每个人的“日志阅读进度条”


✅ 七、总结:DocumentsWriterDeleteQueue的核心价值

  1. 全局顺序保证:所有 delete 严格按调用顺序执行
  2. 跨 DWPT 一致性:任何 delete 对所有 segment 可见
  3. 高效并发:无锁读(slice 是线程局部),仅写入加锁
  4. 内存安全:GC 自动回收已处理节点
  5. 支持 update 语义:通过add(node, slice)实现原子性

它是 Lucene 实现“实时更新”(先删后加)和“高吞吐写入”的基石之一。

这个“上次提交”在 Lucene 的上下文中,并不是指 segment 已经写入磁盘并对外可见,而是指:

“当前 DWPT 上一次完成 flush 准备(即调用freezeGlobalBuffer并重置 DeleteSlice)的那个时间点”

换句话说,“上次提交” =“上次 flush 时我处理 delete 的截止位置”


🔍 一、为什么不是“真正提交到索引”?

因为:

  • DWPT 的 flush 是异步的:flush 完成后,segment 还要经过 publish 才对 searcher 可见
  • DeleteSlice 是 DWPT 私有的状态,只关心“我自己缓冲的文档需要应用哪些 delete”

所以这里的“提交”是DWPT 内部视角的“逻辑提交点”,不是全局索引的提交。


🧠 二、具体解释:“上次提交”到底是什么?

每个 DWPT 持有一个DeleteSlice,它有两个指针:

Node<?> sliceHead; // ← “上次提交”的位置(即上次 flush 时处理到的 tail) Node<?> sliceTail; // ← 当前需要处理到的位置
生命周期示例:
时间操作全局队列DWPT-A 的 DeleteSlice
T0DWPT-A 创建[sentinel]head = tail = sentinel
T1Thread-1:delete(term1)[sentinel → term1]不变
T2Thread-2:add(doc1)→ DWPT-A同上不变
T3DWPT-A flush同上调用freezeGlobalBuffer()
-sliceTail推进到term1
- 应用term1
-reset()head = tail = term1
T4Thread-3:delete(term2)[... → term2]不变
T5Thread-4:add(doc2)→ DWPT-A同上不变
T6DWPT-A 再次 flush同上freezeGlobalBuffer()
- 发现sliceTail (term1) != globalTail (term2)
- 推进 tail 到term2
- 应用term2

✅ 所以,“上次提交” =上一次 flush 时reset()后的sliceHead(等于当时的sliceTail


⚙️ 三、代码中的体现

DeleteSlice.reset()中:

void reset() { sliceHead = sliceTail; // ← 关键!将 head 移到当前 tail }

reset()被调用的地方是:

// 在 freezeGlobalBuffer -> apply 之后 deleteSlice.apply(...); deleteSlice.reset(); // ← 标记“本次 flush 已处理到此处”

所以:

  • sliceHead始终指向“上次 flush 时处理完的最后一个 delete 节点”
  • 下次 flush 时,从head.next开始处理,直到新的tail

🖼️ 四、图解:“上次提交”的含义

全局删除队列: [sentinel] → [del1] → [del2] → [del3] → [del4] ↑ ↑ sliceHead (上次提交) sliceTail (本次要处理到)
  • DWPT 在 T3 flush 时处理了 del1、del2 → reset 后head = del2
  • 现在 T6 flush,发现新来了 del3、del4 → 处理它们
  • 处理完后 reset →head = del4

✅ “上次提交” =sliceHead所指的位置


❓ 五、为什么这样设计?

  1. 避免重复处理
    如果不记录head,每次 flush 都从头开始遍历 delete 队列 → 性能灾难

  2. 保证恰好一次语义
    每个 delete 节点被每个 DWPT恰好处理一次

  3. 支持并发
    每个 DWPT 独立维护自己的进度,互不影响

  4. GC 友好
    一旦所有 DWPT 的sliceHead都越过了某个节点,该节点就不可达 → 被回收


✅ 六、总结回答

“上次提交”指的是:当前 DWPT 上一次 flush 时,通过DeleteSlice.reset()记录的删除处理截止位置(即当时的sliceTail)。
它是DWPT 私有的逻辑进度标记,用于确保:

  • 不遗漏新 delete
  • 不重复处理旧 delete
  • 正确实现 update/delete 的语义顺序

这和数据库中的“LSN(Log Sequence Number)”或“游标(cursor)”概念非常相似——是一种增量同步机制

http://www.jsqmd.com/news/886567/

相关文章:

  • 翻译 GDB 官方文档
  • 2026年化妆品贴牌定制加工厂推荐榜:网红爆品、国潮风、私域品牌定制,低成本创业之选! - 资讯快报
  • Python UiAutomation实战:从网页数据抓取到桌面应用,一个库打通数据采集全链路
  • 【SRC漏洞挖掘系列】第09期:XXE与反序列化 —— 当XML和Java开始“吃”代码
  • 一个取巧但有效的方法:利用PAT报错信息反向“猜”出测试数据(附Python二分脚本)
  • 2026长沙智能家居品牌实测,这些本地老牌值得选
  • 航空螺栓螺母表面油污清洁度检测仪为何至关重要-西恩士 - 工业干货社
  • 电信运营商每月处理海量工单,如何不再出错?基于AI Agent的端到端自动化解决方案
  • # 2026年陕西热门高考补习学校盘点:哪家提分效果好?(附选型指南) - 科技焦点
  • 小学期十二周
  • 2026会计人员能力及学习提升方向指导
  • GEO生成引擎优化:当AI成为信息分发的主角,品牌如何抢占对话窗口?
  • 从游戏引擎到仿真平台:手把手教你用AirSim+UE4搭建你的第一个无人机/自动驾驶仿真环境
  • 四川小自考畜牧兽医专业代码是什么?有哪些学校可以选择?推荐这家靠谱助学点报名! - 知名不具123
  • # 2026年西安性价比高的高三补习班推荐:基于价格与师资、效果测评 - 科技焦点
  • 特斯拉与SpaceX软件开发体系
  • 欧姆龙PLC通过以太网模块实现Web远程诊断,故障排查时间缩短70%
  • 05华夏之光永存:150吨级火星EDL进入下降着陆全链条解决方案
  • 2026年ChatBI产品TOP5深度测评:行业落地能力与问数准确率全维度对比 - 科技焦点
  • Windows 11终极优化秘籍:如何使用Win11Debloat彻底清理系统垃圾和隐私追踪
  • Godot4 2D游戏开发避坑指南:TileMap绘制、节点顺序与相机设置的三个常见问题
  • CANoe诊断测试没CDD文件怎么办?手把手教你用Fault Memory窗口和CAPL脚本读取解析DTC故障码
  • ssm207基于SSM的视频播放系统的设计与实现+vue(文档+源码)_kaic
  • # 西安高考冲刺班学校推荐:2026年TOP5机构选型指南 - 科技焦点
  • Allure报告不只是好看:用@allure.feature和step让你的Python自动化测试用例更规范、更好维护
  • 电力行业设备台账与巡检报告,何时能告别手工?基于实在Agent的端到端方案
  • 2026年了,GEO生成引擎优化到底在优化什么?一文讲透底层逻辑与实战框架
  • DragonBones与Godot集成:骨骼动画的可编程化实践
  • 西恩士-航空螺栓螺母紧固件表面油污清洁度分析设备 - 工业干货社
  • 基于PPG信号与逻辑回归的急性脑卒中院前AI分诊模型研究