一句话
`DocumentsWriterFlushControl` 是 Lucene 在 多线程写入 场景下的 “全局刷盘调度器”——负责决定 何时、由哪些 DWPT(DocumentsWriterPerThread) 去 flush,并保证 内存不会爆掉、线程不会饿死。
---
它到底管哪些事?
1. 触发时机
• 每个线程写完文档后,都会把数据放到自己的 DWPT 缓冲区。
• `DocumentsWriterFlushControl` 内部持有一个 `FlushPolicy`(默认是 `FlushByRamOrCountsPolicy`),根据
‑ RAMBufferSizeMB(内存阈值)
‑ MaxBufferedDocs(文档数阈值)
‑ MaxBufferedDeleteTerms(删除项阈值)
来判断 “这个 DWPT 是否该 flush”。
2. 并发调度
• 当多线程同时达到阈值时,`FlushControl` 会把需要刷新的 DWPT 标记为 `flushPending`,然后放进一个 flushQueue。
• 内部用 `DocumentsWriterStallControl` 做 “健康管理”:如果 flush 线程赶不上写线程,写线程会被阻塞,防止 OOM。
“flush 线程赶不上写线程”指的是:
写入线程把数据塞进 DWPT 的速度,比后台真正把 DWPT 刷到磁盘的速度要快得多,导致 所有可用 DWPT 都被占满、总内存用量逼近上限,如果不刹车就会 OOM。
具体过程
1. 每个 DWPT 是一块独立的内存缓冲区。
2. 当 DWPT 达到阈值(RAMBufferSizeMB 或 doc 数)时,会被标记为 flushPending,等待后台线程把它写成一个 segment。
3. 如果后台 flush 线程(或磁盘 I/O)太慢,flushPending 的 DWPT 迟迟得不到释放。
4. 活跃的写线程还在不断创建 新的 DWPT 或继续往已有 DWPT 里添加文档。
5. 结果:
• 活跃的 DWPT 数量越来越多;
• 总内存占用持续上升;
• 最终触发 `DocumentsWriterFlushControl` 的 stall 保护——写线程被 主动阻塞(stall),直到 flush 线程腾出一个 DWPT 为止。
一句话总结
“flush 线程赶不上写线程”就是 写得太快、刷得太慢,Lucene 通过 stall 机制 强制写线程 暂停,防止把 JVM 内存撑爆。
3. full-flush vs 单 DWPT flush
• `commit/close/forceMerge` 等操作会触发 full flush → `flushAllThreads()` → `markForFullFlush()` 把所有 active DWPT 一次性刷盘并生成 commit point。
• 普通的 `updateDocument` 只是 单个 DWPT flush,不会全局落盘。
4. 线程同步
• 使用 Balking + TicketQueue 模式:
‑ `markForFullFlush` 把 DWPT 状态从 active → flushing,并放入 `flushingWriters` Map,相当于“轻量级锁”。
‑ flush 线程每完成一个 DWPT 就“交票”,主线程 `waitForFlush()` 会感知并继续。
---
再简单一点
`DocumentsWriterFlushControl` 就是 IndexWriter 的 “内存管家 + 刷盘调度器”:
- 写线程只管往 DWPT 里塞数据;
- 它来决定“谁、什么时候”把 DWPT 刷成 segment;
- 同时保证 内存可控、并发安全、不丢数据。