2021SC@SDUSC
/**
* 删除索引中的所有文档。
*
* <p>
* 此方法将删除所有缓冲的文档并删除所有段
* 来自索引。直到 {@link #commit()}
* 已被调用。可以使用 {@link #rollback()} 回滚此方法。
* </p>
*
* <p>
* 注意:此方法比使用 deleteDocuments( new
* MatchAllDocsQuery() )。然而,这种方法也有不同的语义
* 与 {@link #deleteDocuments(Query...)} 相比,因为内部
* 清除数据结构以及所有段信息
* 强行删除的抗病毒语义,如忽略规范被重置或
* doc 值类型被清除。基本上调用 {@link #deleteAll()} 是
* 相当于创建一个新的 {@link IndexWriter}
* {@link OpenMode#CREATE} 删除查询仅将文档标记为
* 删除。
* </p>
*
* <p>
* 注意:此方法将强制中止所有正在进行的合并。如果其他
* 线程正在运行 {@link #forceMerge},{@link #addIndexes(CodecReader[])}
* 或 {@link #forceMergeDeletes} 方法,他们可能会收到
* {@link MergePolicy.MergeAbortedException}s。
*
* @return <a href="#sequence_number">序列号</a>
* 对于这个操作
*/
@SuppressWarnings("try")
public long deleteAll() throws IOException {
ensureOpen();
// Remove any buffered docs
boolean success = false;
/*
* 我们首先中止并丢弃内存中的所有内容
* 并保持线程状态锁定,lockAndAbortAll 操作
* 所以我们中止内存结构中的所有内容
* 在中止之前删除全局字段编号确定它就像一个新的索引。
*/
try {
synchronized (fullFlushLock) {
try (Closeable finalizer = docWriter.lockAndAbortAll()) {
processEvents(false);
synchronized (this) {
try {
// Abort any running merges
try {
abortMerges();
assert merges.areEnabled() == false : "merges should be disabled - who enabled them?";
assert mergingSegments.isEmpty() : "found merging segments but merges are disabled: " + mergingSegments;
} finally {
// abortMerges 禁用所有合并,我们需要在此处重新启用它们以确保
// IW 可以正常运行。 abortMerges() 中的异常对于 IW 可能是致命的,但只是为了确定让我们重新启用合并。
merges.enable();
}
adjustPendingNumDocs(-segmentInfos.totalMaxDoc());
// Remove all segments
segmentInfos.clear();
// Ask deleter to locate unreferenced files & remove them:
deleter.checkpoint(segmentInfos, false);
/* 不要在这里刷新删除器,因为可能有
* 是开放的并发索引请求
* 调用 DW#abort() 后目录中的文件
* 如果我们这样做,这些索引请求可能会遇到 FNF 异常。
* 我们将逐步删除文件...
*/
// 不要费心在我们的 segmentInfos 中保存任何更改
readerPool.dropAll();
// Mark that the index has changed
changeCount.incrementAndGet();
segmentInfos.changed();
globalFieldNumberMap.clear();
success = true;
long seqNo = docWriter.getNextSequenceNumber();
return seqNo;
} finally {
if (success == false) {
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "hit exception during deleteAll");
}
}
}
}
}
}
} catch (VirtualMachineError tragedy) {
tragicEvent(tragedy, "deleteAll");
throw tragedy;
}
}
/** 中止正在运行的合并。 使用这个时要小心
* 方法:当你中止一个长时间运行的合并时,你就输了
* 很多工作必须在以后重做。 */
private synchronized void abortMerges() throws IOException {
merges.disable();
// Abort all pending & running merges:
IOUtils.applyToAll(pendingMerges, merge -> {
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "now abort pending merge " + segString(merge.segments));
}
abortOneMerge(merge);
mergeFinish(merge);
});
pendingMerges.clear();
for (final MergePolicy.OneMerge merge : runningMerges) {
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "now abort running merge " + segString(merge.segments));
}
merge.setAborted();
}
// 我们在这里等待让所有合并停止。 它不应该
// 需要很长时间,因为他们会定期检查是否
// 他们被中止了。
while (runningMerges.size() + runningAddIndexesMerges.size() != 0) {
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "now wait for " + runningMerges.size()
+ " running merge/s to abort; currently running addIndexes: " + runningAddIndexesMerges.size());
}
doWait();
}
notifyAll();
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "all running merges have aborted");
}
}
/**
* 等待任何当前未完成的合并完成。
*
* <p>保证在调用此方法之前开始任何合并
* 将在此方法完成后完成。</p>
*/
void waitForMerges() throws IOException {
// 给合并调度程序最后一次运行的机会,以防万一
// 任何挂起的合并都在等待。 我们无法抓住信息战的锁
// 当进入合并时,因为它可能导致死锁。
mergeScheduler.merge(mergeSource, MergeTrigger.CLOSING);
synchronized (this) {
ensureOpen(false);
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "waitForMerges");
}
while (pendingMerges.size() > 0 || runningMerges.size() > 0) {
doWait();
}
// sanity check
assert 0 == mergingSegments.size();
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "waitForMerges done");
}
}
}
/**
* 每当 SegmentInfos 更新和
* 引用的索引文件(正确)存在于
* 索引目录。
*/
private synchronized void checkpoint() throws IOException {
changed();
deleter.checkpoint(segmentInfos, false);
}
/** 检查点与 IndexFileDeleter,所以它知道
* 新文件,并增加changeCount,依此类推
* close/commit 我们会写一个新的segments文件,但是
* 不会碰撞 segmentInfos.version。 */
private synchronized void checkpointNoSIS() throws IOException {
changeCount.incrementAndGet();
deleter.checkpoint(segmentInfos, false);
}
/** 如果任何索引状态已更改,则在内部调用。 */
private synchronized void changed() {
changeCount.incrementAndGet();
segmentInfos.changed();
}
private synchronized long publishFrozenUpdates(FrozenBufferedUpdates packet) {
assert packet != null && packet.any();
long nextGen = bufferedUpdatesStream.push(packet);
// 将此作为一个事件执行,以便当我们不持有 DocumentsWriterFlushQueue.purgeLock 时它在堆栈中应用更高:
eventQueue.add(w -> {
try {
// 我们在这里调用 tryApply 因为我们不想阻塞如果刷新或刷新已经应用
//数据包。 无论如何刷新都会重试这个数据包以确保所有这些数据包都被应用
tryApply(packet);
} catch (Throwable t) {
try {
w.onTragicEvent(t, "applyUpdatesPacket");
} catch (Throwable t1) {
t.addSuppressed(t1);
}
throw t;
}
w.flushDeletesCount.incrementAndGet();
});
return nextGen;
}
/**
* 原子地添加segment私有删除包并发布flush
* 将 SegmentInfo 分段到索引编写器。
*/
private synchronized void publishFlushedSegment(SegmentCommitInfo newSegment, FieldInfos fieldInfos,
FrozenBufferedUpdates packet, FrozenBufferedUpdates globalPacket,
Sorter.DocMap sortMap) throws IOException {
boolean published = false;
try {
// Lock order IW -> BDS
ensureOpen(false);
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "publishFlushedSegment " + newSegment);
}
if (globalPacket != null && globalPacket.any()) {
publishFrozenUpdates(globalPacket);
}
// 发布段必须在 IW -> BDS 上同步以确保
// 没有合并会删除段。 私人删除包
final long nextGen;
if (packet != null && packet.any()) {
nextGen = publishFrozenUpdates(packet);
} else {
// 因为我们没有要应用的删除包,所以我们可以得到一个新的
// 立即生成
nextGen = bufferedUpdatesStream.getNextGen();
// No deletes/updates here, so marked finished immediately:
bufferedUpdatesStream.finishedSegment(nextGen);
}
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "publish sets newSegment delGen=" + nextGen + " seg=" + segString(newSegment));
}
newSegment.setBufferedDeletesGen(nextGen);
segmentInfos.add(newSegment);
published = true;
checkpoint();
if (packet != null && packet.any() && sortMap != null) {
// TODO:我们在持有 IW 的监视器锁的同时执行这个繁重的操作,这不是很好,
// 但它仅适用于使用排序索引和更新文档值的情况:
ReadersAndUpdates rld = getPooledInstance(newSegment, true);
rld.sortMap = sortMap;
// 不要释放这个 ReadersAndUpdates 我们需要坚持那个 sortMap
}
FieldInfo fieldInfo = fieldInfos.fieldInfo(config.softDeletesField); // 如果不存在软删除,将返回 null
// 这是一个极端情况,文档使用软删除来删除它们自己。 这用于
// 构建删除墓碑等。在这种情况下,我们在这个新刷新的片段中没有看到对 DV 的任何更新。
// 如果我们看到了更新,则更新代码检查该段是否已完全删除。
boolean hasInitialSoftDeleted = (fieldInfo != null
&& fieldInfo.getDocValuesGen() == -1
&& fieldInfo.getDocValuesType() != DocValuesType.NONE);
final boolean isFullyHardDeleted = newSegment.getDelCount() == newSegment.info.maxDoc();
// 我们要么有一个完全硬删除的片段,要么一个或多个文档被软删除。 在这两种情况下,我们都需要
// 去检查它们是否被完全删除。 这有一个很好的副作用,我们现在有了准确的数字
// 用于在我们刷新到磁盘后立即进行软删除。
if (hasInitialSoftDeleted || isFullyHardDeleted){
// 此操作仅在需要时才真正执行,如果未配置软删除,则仅执行
// 如果我们删除了这个新刷新的段中的所有文档。
ReadersAndUpdates rld = getPooledInstance(newSegment, true);
try {
if (isFullyDeleted(rld)) {
dropDeletedSegment(newSegment);
checkpoint();
}
} finally {
release(rld);
}
}
} finally {
if (published == false) {
adjustPendingNumDocs(-newSegment.info.maxDoc());
}
flushCount.incrementAndGet();
doAfterFlush();
}
}
private synchronized void resetMergeExceptions() {
mergeExceptions.clear();
mergeGen++;
}
private void noDupDirs(Directory... dirs) {
HashSet<Directory> dups = new HashSet<>();
for(int i=0;i<dirs.length;i++) {
if (dups.contains(dirs[i]))
throw new IllegalArgumentException("Directory " + dirs[i] + " appears more than once");
if (dirs[i] == directoryOrig)
throw new IllegalArgumentException("Cannot add directory to itself");
dups.add(dirs[i]);
}
}
/** 获取所有目录的写锁; 确定
* 与调用 {@link IOUtils#close} 相匹配
* 最后条款。 */
private List<Lock> acquireWriteLocks(Directory... dirs) throws IOException {
List<Lock> locks = new ArrayList<>(dirs.length);
for(int i=0;i<dirs.length;i++) {
boolean success = false;
try {
Lock lock = dirs[i].obtainLock(WRITE_LOCK_NAME);
locks.add(lock);
success = true;
} finally {
if (success == false) {
// Release all previously acquired locks:
// TODO: addSuppressed? it could be many...
IOUtils.closeWhileHandlingException(locks);
}
}
}
return locks;
}
/**
* 将索引数组中的所有段添加到该索引中。
*
* <p>这可用于并行化批量索引。一个大文件
* 集合可以分解为子集合。每个子集都可以
* 在不同的线程、进程或机器上并行索引。这
* 然后可以通过合并子集合索引来创建完整索引
* 用这种方法。
*
* <p>
* <b>注意:</b> 此方法获取写锁
* 每个目录,确保没有{@code IndexWriter}
* 当前已打开或在此期间尝试打开
* 跑步。
*
* <p>此方法是事务性的异常如何
* 处理:它不会提交新的segments_N 文件,直到
* 添加所有索引。这意味着如果一个异常
* 发生(例如磁盘已满),然后没有索引
* 将被添加,或者它们都将被添加。
*
* <p>注意这需要临时的空闲空间
* {@link Directory} 高达所有输入索引总和的 2 倍
*(包括起始索引)。如果读者/搜索者
* 针对起始索引打开,然后是临时的
* 所需的可用空间会因大小而增加
* 起始索引(详见 {@link #forceMerge(int)})。
*
* <p>这要求该索引不在要添加的索引中。
*
* <p>所有添加的索引必须由相同的
* Lucene 版本作为此索引。
*
* @return <a href="#sequence_number">序列号</a>
* 对于这个操作
*
* @throws CorruptIndexException 如果索引损坏
* @throws IOException 如果存在低级 IO 错误
* @throws IllegalArgumentException 如果 addIndexes 会导致
* 索引超过 {@link #MAX_DOCS},或者如果indoming
* 索引排序与该索引的索引排序不匹配
*/
public long addIndexes(Directory... dirs) throws IOException {
ensureOpen();
noDupDirs(dirs);
List<Lock> locks = acquireWriteLocks(dirs);
Sort indexSort = config.getIndexSort();
boolean successTop = false;
long seqNo;
try {
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "flush at addIndexes(Directory...)");
}
flush(false, true);
List<SegmentCommitInfo> infos = new ArrayList<>();
// long so we can detect int overflow:
long totalMaxDoc = 0;
List<SegmentInfos> commits = new ArrayList<>(dirs.length);
for (Directory dir : dirs) {
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "addIndexes: process directory " + dir);
}
SegmentInfos sis = SegmentInfos.readLatestCommit(dir); // read infos from dir
if (segmentInfos.getIndexCreatedVersionMajor() != sis.getIndexCreatedVersionMajor()) {
throw new IllegalArgumentException("Cannot use addIndexes(Directory) with indexes that have been created "
+ "by a different Lucene version. The current index was generated by Lucene "
+ segmentInfos.getIndexCreatedVersionMajor()
+ " while one of the directories contains an index that was generated with Lucene "
+ sis.getIndexCreatedVersionMajor());
}
totalMaxDoc += sis.totalMaxDoc();
commits.add(sis);
}
// Best-effort up front check:
testReserveDocs(totalMaxDoc);
boolean success = false;
try {
for (SegmentInfos sis : commits) {
for (SegmentCommitInfo info : sis) {
assert !infos.contains(info): "dup info dir=" + info.info.dir + " name=" + info.info.name;
Sort segmentIndexSort = info.info.getIndexSort();
if (indexSort != null && (segmentIndexSort == null || isCongruentSort(indexSort, segmentIndexSort) == false)) {
throw new IllegalArgumentException("cannot change index sort from " + segmentIndexSort + " to " + indexSort);
}
String newSegName = newSegmentName();
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "addIndexes: process segment origName=" + info.info.name + " newName=" + newSegName + " info=" + info);
}
IOContext context = new IOContext(new FlushInfo(info.info.maxDoc(), info.sizeInBytes()));
FieldInfos fis = readFieldInfos(info);
for(FieldInfo fi : fis) {
// 如果任何传入字段具有非法架构更改,这将引发异常:
globalFieldNumberMap.addOrGet(fi.name, fi.number, fi.getIndexOptions(), fi.getDocValuesType(), fi.getPointDimensionCount(), fi.getPointIndexDimensionCount(), fi.getPointNumBytes(), fi.isSoftDeletesField());
}
infos.add(copySegmentAsIs(info, newSegName, context));
}
}
success = true;
} finally {
if (!success) {
for(SegmentCommitInfo sipc : infos) {
// Safe: these files must exist
deleteNewFiles(sipc.files());
}
}
}
synchronized (this) {
success = false;
try {
ensureOpen();
// 现在保留文档,就在我们更新 SIS 之前:
reserveDocs(totalMaxDoc);
seqNo = docWriter.getNextSequenceNumber();
success = true;
} finally {
if (!success) {
for(SegmentCommitInfo sipc : infos) {
// Safe: these files must exist
deleteNewFiles(sipc.files());
}
}
}
segmentInfos.addAll(infos);
checkpoint();
}
successTop = true;
} catch (VirtualMachineError tragedy) {
tragicEvent(tragedy, "addIndexes(Directory...)");
throw tragedy;
} finally {
if (successTop) {
IOUtils.close(locks);
} else {
IOUtils.closeWhileHandlingException(locks);
}
}
maybeMerge();
return seqNo;
}
private void validateMergeReader(CodecReader leaf) {
LeafMetaData segmentMeta = leaf.getMetaData();
if (segmentInfos.getIndexCreatedVersionMajor() != segmentMeta.getCreatedVersionMajor()) {
throw new IllegalArgumentException("Cannot merge a segment that has been created with major version "
+ segmentMeta.getCreatedVersionMajor() + " into this index which has been created by major version "
+ segmentInfos.getIndexCreatedVersionMajor());
}
if (segmentInfos.getIndexCreatedVersionMajor() >= 7 && segmentMeta.getMinVersion() == null) {
throw new IllegalStateException("Indexes created on or after Lucene 7 must record the created version major, but " + leaf + " hides it");
}
Sort leafIndexSort = segmentMeta.getSort();
if (config.getIndexSort() != null &&
(leafIndexSort == null || isCongruentSort(config.getIndexSort(), leafIndexSort) == false)) {
throw new IllegalArgumentException("cannot change index sort from " + leafIndexSort + " to " + config.getIndexSort());
}
}
/**
* 将提供的索引合并到该索引中。
*
* <p>
* 提供的 IndexReaders 未关闭。
*
* <p>
* 有关事务语义的详细信息,请参阅 {@link #addIndexes},临时
* 目录中所需的可用空间,以及异常上的非 CFS 段。
*
* <p>
* <b>注意:</b> 空段被这个方法删除,而不是添加到这个
* 指数。
*
* <p>
* <b>注意:</b> 这将所有给定的 {@link LeafReader} 合并为一个
* 合并。如果你打算合并大量的读者,可能会更好
* 多次调用此方法,每次使用一小组读者。
* 原则上,如果您使用带有 {@code mergeFactor} 的合并策略或
* {@code maxMergeAtOnce} 参数,你应该一次传递那么多读者
* 称呼。
*
* <p>
* <b>注意:</b> 此方法不会调用或使用 {@link MergeScheduler},
* 因此任何自定义带宽限制目前都被忽略。
*
* @return <a href="#sequence_number">序列号</a>
* 对于这个操作
*
* @throws CorruptIndexException
* 如果索引损坏
* @throws IOException
* 如果存在低级 IO 错误
* @throws IllegalArgumentException
* 如果 addIndexes 会导致索引超过 {@link #MAX_DOCS}
*/
public long addIndexes(CodecReader... readers) throws IOException {
ensureOpen();
// long so we can detect int overflow:
long numDocs = 0;
long seqNo;
try {
if (infoStream.isEnabled("IW")) {
infoStream.message("IW", "flush at addIndexes(CodecReader...)");
}
flush(false, true);
String mergedName = newSegmentName();
int numSoftDeleted = 0;
for (CodecReader leaf : readers) {
numDocs += leaf.numDocs();
validateMergeReader(leaf);
if (softDeletesEnabled) {
Bits liveDocs = leaf.getLiveDocs();
numSoftDeleted += PendingSoftDeletes.countSoftDeletes(
DocValuesFieldExistsQuery.getDocValuesDocIdSetIterator(config.getSoftDeletesField(), leaf), liveDocs);
}
}
// Best-effort up front check:
testReserveDocs(numDocs);
final IOContext context = new IOContext(new MergeInfo(Math.toIntExact(numDocs), -1, false, UNBOUNDED_MAX_MERGE_SEGMENTS));
// TODO:我们应该以某种方式修复这个合并,所以它是
// 可中止,以便 IW.close(false) 能够停止它
TrackingDirectoryWrapper trackingDir = new TrackingDirectoryWrapper(directory);
Codec codec = config.getCodec();
// 我们暂时将最小版本设置为 null,稍后由 SegmentMerger 设置
SegmentInfo info = new SegmentInfo(directoryOrig, Version.LATEST, null, mergedName, -1,
false, codec, Collections.emptyMap(), StringHelper.randomId(), Collections.emptyMap(), config.getIndexSort());
SegmentMerger merger = new SegmentMerger(Arrays.asList(readers), info, infoStream, trackingDir,
globalFieldNumberMap,
context);
if (!merger.shouldMerge()) {
return docWriter.getNextSequenceNumber();
}
synchronized (this) {
ensureOpen();
assert merges.areEnabled();
runningAddIndexesMerges.add(merger);
}
try {
merger.merge(); // merge 'em
} finally {
synchronized (this) {
runningAddIndexesMerges.remove(merger);
notifyAll();
}
}
SegmentCommitInfo infoPerCommit = new SegmentCommitInfo(info, 0, numSoftDeleted, -1L, -1L, -1L, StringHelper.randomId());
info.setFiles(new HashSet<>(trackingDir.getCreatedFiles()));
trackingDir.clearCreatedFiles();
setDiagnostics(info, SOURCE_ADDINDEXES_READERS);
final MergePolicy mergePolicy = config.getMergePolicy();
boolean useCompoundFile;
synchronized(this) { // Guard segmentInfos
if (merges.areEnabled() == false) {
// Safe: these files must exist
deleteNewFiles(infoPerCommit.files());
return docWriter.getNextSequenceNumber();
}
ensureOpen();
useCompoundFile = mergePolicy.useCompoundFile(segmentInfos, infoPerCommit, this);
}
// Now create the compound file if needed
if (useCompoundFile) {
Collection<String> filesToDelete = infoPerCommit.files();
TrackingDirectoryWrapper trackingCFSDir = new TrackingDirectoryWrapper(directory);
// 待办事项:与合并不同,例外的是我们不会在这里狙击任何垃圾 cfs 文件?
// createCompoundFile 尝试清理,但它可能并不总是能够...
try {
createCompoundFile(infoStream, trackingCFSDir, info, context, this::deleteNewFiles);
} finally {
// delete new non cfs files directly: they were never
// registered with IFD
deleteNewFiles(filesToDelete);
}
info.setUseCompoundFile(true);
}
// 让编解码器写入 SegmentInfo。 之后必须这样做
// 创建 CFS 以便 1) .si 不会被混入 CFS,
// 和 2) .si 反映了 useCompoundFile=true 的变化
// 多于:
codec.segmentInfoFormat().write(trackingDir, info, context);
info.addFiles(trackingDir.getCreatedFiles());
// Register the new segment
synchronized(this) {
if (merges.areEnabled() == false) {
// Safe: these files must exist
deleteNewFiles(infoPerCommit.files());
return docWriter.getNextSequenceNumber();
}
ensureOpen();
// Now reserve the docs, just before we update SIS:
reserveDocs(numDocs);
segmentInfos.add(infoPerCommit);
seqNo = docWriter.getNextSequenceNumber();
checkpoint();
}
} catch (VirtualMachineError tragedy) {
tragicEvent(tragedy, "addIndexes(CodecReader...)");
throw tragedy;
}
maybeMerge();
return seqNo;
}