Skip to content

Commit de92f11

Browse files
authored
Defer Sorter.DocMap packing until after flush (apache#16048)
* Defer Sorter.DocMap packing until after flush IndexingChain calls sortMap.oldToNew heavily during flush — per posting in FreqProxTermsWriter, per (field, doc) in DV writers, per vector in KNN writers. The previous implementation built oldToNew as a PackedLongValues, which is slow for that random-access hot path. Build oldToNew as an int[] during flush, then pack to PackedLongValues before storing on FlushedSegment, which is retained long-term on ReadersAndUpdates for sorted segments with seg-private updates. * Changes
1 parent a16bca0 commit de92f11

4 files changed

Lines changed: 75 additions & 33 deletions

File tree

lucene/CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ Optimizations
379379
* GITHUB#16061, GITHUB#16070: Improve cost estimation in SortedSetDocValuesRangeQuery when using DocValuesSkipper
380380
and the field is dense and is the primary sort of the index to reduce the number of doc values visited. (Ignacio Vera)
381381

382+
* GITHUB#16048: Defer Sorter.DocMap packing until after flush (Tim Brooks)
382383

383384
Bug Fixes
384385
---------------------

lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -481,15 +481,15 @@ FlushedSegment flush(DocumentsWriter.FlushNotifications flushNotifications) thro
481481
"DWPT",
482482
"flush postings as segment " + flushState.segmentInfo.name + " numDocs=" + numDocsInRAM);
483483
}
484-
final Sorter.DocMap sortMap;
484+
final Sorter.PackableDocMap packableSortMap;
485485
try {
486486
DocIdSetIterator softDeletedDocs;
487487
if (indexWriterConfig.getSoftDeletesField() != null) {
488488
softDeletedDocs = indexingChain.getHasDocValues(indexWriterConfig.getSoftDeletesField());
489489
} else {
490490
softDeletedDocs = null;
491491
}
492-
sortMap = indexingChain.flush(flushState);
492+
packableSortMap = indexingChain.flush(flushState);
493493
if (softDeletedDocs == null) {
494494
flushState.softDelCountOnFlush = 0;
495495
} else {
@@ -570,8 +570,10 @@ FlushedSegment flush(DocumentsWriter.FlushNotifications flushNotifications) thro
570570
segmentDeletes,
571571
flushState.liveDocs,
572572
flushState.delCountOnFlush,
573-
sortMap);
574-
sealFlushedSegment(fs, sortMap, flushNotifications);
573+
packableSortMap != null
574+
? packableSortMap.pack()
575+
: null); // Use a packed version as the lifetime of FlushedSegment is long.
576+
sealFlushedSegment(fs, packableSortMap, flushNotifications);
575577
if (infoStream.isEnabled("DWPT")) {
576578
infoStream.message(
577579
"DWPT",

lucene/core/src/java/org/apache/lucene/index/IndexingChain.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public FieldInfos getFieldInfos() {
237237
};
238238
}
239239

240-
private Sorter.DocMap maybeSortSegment(SegmentWriteState state) throws IOException {
240+
private Sorter.PackableDocMap maybeSortSegment(SegmentWriteState state) throws IOException {
241241
Sort indexSort = state.segmentInfo.getIndexSort();
242242
if (indexSort == null) {
243243
return null;
@@ -283,15 +283,15 @@ private Sorter.DocMap maybeSortSegment(SegmentWriteState state) throws IOExcepti
283283
}
284284
Sorter sorter = new Sorter(indexSort);
285285
// returns null if the documents are already sorted
286-
return sorter.sort(
286+
return sorter.sortAndLeaveUnpacked(
287287
state.segmentInfo.maxDoc(), comparators.toArray(IndexSorter.DocComparator[]::new));
288288
}
289289

290-
Sorter.DocMap flush(SegmentWriteState state) throws IOException {
290+
Sorter.PackableDocMap flush(SegmentWriteState state) throws IOException {
291291

292292
// NOTE: caller (DocumentsWriterPerThread) handles
293293
// aborting on any exception from this method
294-
Sorter.DocMap sortMap = maybeSortSegment(state);
294+
Sorter.PackableDocMap sortMap = maybeSortSegment(state);
295295
int maxDoc = state.segmentInfo.maxDoc();
296296
long t0 = System.nanoTime();
297297
writeNorms(state, sortMap);

lucene/core/src/java/org/apache/lucene/index/Sorter.java

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ protected int compareSaved(int i, int j) {
129129
}
130130

131131
/** Computes the old-to-new permutation over the given comparator. */
132-
private static Sorter.DocMap sort(final int maxDoc, IndexSorter.DocComparator comparator) {
132+
private static PackableDocMap sort(final int maxDoc, IndexSorter.DocComparator comparator) {
133133
// check if the index is sorted
134134
boolean sorted = true;
135135
for (int i = 1; i < maxDoc; ++i) {
@@ -168,30 +168,7 @@ private static Sorter.DocMap sort(final int maxDoc, IndexSorter.DocComparator co
168168
docs[(int) newToOld.get(i)] = i;
169169
} // docs is now the oldToNew mapping
170170

171-
final PackedLongValues.Builder oldToNewBuilder =
172-
PackedLongValues.monotonicBuilder(PackedInts.COMPACT);
173-
for (int i = 0; i < maxDoc; ++i) {
174-
oldToNewBuilder.add(docs[i]);
175-
}
176-
final PackedLongValues oldToNew = oldToNewBuilder.build();
177-
178-
return new Sorter.DocMap() {
179-
180-
@Override
181-
public int oldToNew(int docID) {
182-
return (int) oldToNew.get(docID);
183-
}
184-
185-
@Override
186-
public int newToOld(int docID) {
187-
return (int) newToOld.get(docID);
188-
}
189-
190-
@Override
191-
public int size() {
192-
return maxDoc;
193-
}
194-
};
171+
return new PackableDocMap(docs, newToOld, maxDoc);
195172
}
196173

197174
/**
@@ -241,6 +218,12 @@ DocMap sort(LeafReader reader) throws IOException {
241218
}
242219

243220
DocMap sort(int maxDoc, IndexSorter.DocComparator[] comparators) throws IOException {
221+
PackableDocMap packableDocMap = sortAndLeaveUnpacked(maxDoc, comparators);
222+
return packableDocMap != null ? packableDocMap.pack() : packableDocMap;
223+
}
224+
225+
PackableDocMap sortAndLeaveUnpacked(int maxDoc, IndexSorter.DocComparator[] comparators)
226+
throws IOException {
244227
final IndexSorter.DocComparator comparator =
245228
(docID1, docID2) -> {
246229
for (int i = 0; i < comparators.length; i++) {
@@ -270,4 +253,60 @@ public String getID() {
270253
public String toString() {
271254
return getID();
272255
}
256+
257+
/** A {@link DocMap} that can keep oldToNew in either packed or unpacked form. */
258+
static final class PackableDocMap extends DocMap {
259+
260+
private final PackedLongValues newToOld;
261+
private final int maxDoc;
262+
263+
private final int[] oldToNewUnpacked;
264+
private final PackedLongValues oldToNewPacked;
265+
266+
private PackableDocMap(int[] oldToNewUnpacked, PackedLongValues newToOld, int maxDoc) {
267+
this.oldToNewUnpacked = oldToNewUnpacked;
268+
this.oldToNewPacked = null;
269+
this.newToOld = newToOld;
270+
this.maxDoc = maxDoc;
271+
}
272+
273+
private PackableDocMap(PackedLongValues oldToNewPacked, PackedLongValues newToOld, int maxDoc) {
274+
this.oldToNewUnpacked = null;
275+
this.oldToNewPacked = oldToNewPacked;
276+
this.newToOld = newToOld;
277+
this.maxDoc = maxDoc;
278+
}
279+
280+
@Override
281+
public int oldToNew(int docID) {
282+
if (oldToNewUnpacked != null) {
283+
return oldToNewUnpacked[docID];
284+
} else {
285+
return (int) oldToNewPacked.get(docID);
286+
}
287+
}
288+
289+
@Override
290+
public int newToOld(int docID) {
291+
return (int) newToOld.get(docID);
292+
}
293+
294+
/** Pack the internal representations into more compact forms. No-op if already packed. */
295+
DocMap pack() {
296+
if (oldToNewPacked != null) {
297+
return this;
298+
}
299+
final PackedLongValues.Builder oldToNewBuilder =
300+
PackedLongValues.monotonicBuilder(PackedInts.COMPACT);
301+
for (int i = 0; i < maxDoc; ++i) {
302+
oldToNewBuilder.add(oldToNewUnpacked[i]);
303+
}
304+
return new PackableDocMap(oldToNewBuilder.build(), newToOld, maxDoc);
305+
}
306+
307+
@Override
308+
public int size() {
309+
return maxDoc;
310+
}
311+
}
273312
}

0 commit comments

Comments
 (0)