Allow single-valued updates for SortedNumericDocValues - #16581
HoustonPutman merged 10 commits into
Conversation
| if (type == DocValuesType.SORTED_NUMERIC && existingOverlay == null) { | ||
| SortedNumericDocValues baseDV = reader.getSortedNumericDocValues(field); | ||
| if (baseDV != null && DocValues.unwrapSingleton(baseDV) == null) { | ||
| throw new IllegalArgumentException( |
There was a problem hiding this comment.
This rejection happens at write time, but writeFieldUpdates only prunes pendingDVUpdates after handleDVUpdates succeeds. The catch block rethrows and the pruning loop below it never runs, so the offending DocValuesFieldUpdates stays in pendingDVUpdates and nothing ever removes it. Every subsequent commit, NRT reopen, merge, and RAM-triggered flush re-enters this loop and throws again. The writer can't commit, refresh, or close(), and the only recovery is rollback(), which discards everything since the last commit. Since handleDVUpdates iterates all fields under one DocValuesConsumer, unrelated valid NUMERIC/BINARY updates on the segment are blocked permanently too.
testUpdateOverMultiValuedBaseRejected encodes this as expected behaviour, but from the caller's side it's a data-loss trap: updateSortedNumericDocValue returned a seqNo successfully, and the failure surfaces later in an unrelated operation. In Solr/ES it's reachable from ordinary traffic, since one document anywhere in the segment with two values wedges the writer. The check is also per-segment, so the same update can be written to segment A and then throw on segment B (partial application, no undo), and it can first fire during a background merge via commitMergedDeletesAndUpdates.
I think validation needs to move to the IndexWriter.updateSortedNumericDocValue / updateDocValues boundary so the caller fails fast and nothing is ever buffered. If it has to stay here, the minimum is dropping the offending entry from pendingDVUpdates before throwing so the writer stays usable.
|
Nice change, and I think this is the right direction. Routing a single-valued sorted-numeric update through the existing numeric update object and buffer keeps it small, and the singleton unwrap in 1. Interaction with #16570 (merge carry-over): sorted-numeric updates that resolve during a merge are dropped or mis-typed. #16570 removed the in-heap
Net effect: an To fix on rebase: add SORTED_NUMERIC to the updated-field type filter; in 2. The multi-valued-base merge is the trickiest new code and is barely covered. 3. The removal branch in 4. 5. Nit: |
Yeah, more tests were certainly needed. Added more.
I've merged and now it should support sorted-numeric updates. Also changed |
|
Thanks Houston, this all looks good now. The per-type split in Two small things. The carry-over fix itself doesn't have a test yet. The new multi-valued tests cover the fold and dense rewrite, but nothing resolves an update while a merge is running, so /**
* A single-valued sorted-numeric update that resolves onto a segment while it is being merged must
* be carried over, and an untouched multi-valued doc must keep its whole value set. Exercises the
* sorted-numeric branch of the disk carry-over (addSortedNumericDiskDiffToPacket).
*/
public void testSortedNumericUpdateResolvedDuringMergeIsCarriedOver() throws Exception {
MergePausingDirectory dir = new MergePausingDirectory(newDirectory());
IndexWriterConfig conf =
newIndexWriterConfig(new MockAnalyzer(random()))
.setMergeScheduler(new ConcurrentMergeScheduler());
IndexWriter writer = new IndexWriter(dir, conf);
// First segment: doc 0 single-valued, doc 1 genuinely multi-valued (never updated).
Document d0 = new Document();
d0.add(new StringField("id", "0", StringField.Store.NO));
d0.add(new SortedNumericDocValuesField("snv", 10));
writer.addDocument(d0);
Document d1 = new Document();
d1.add(new StringField("id", "1", StringField.Store.NO));
d1.add(new SortedNumericDocValuesField("snv", 20));
d1.add(new SortedNumericDocValuesField("snv", 21));
writer.addDocument(d1);
writer.commit();
// Second segment so the merge has two sources.
for (int i = 2; i < 6; i++) {
Document d = new Document();
d.add(new StringField("id", Integer.toString(i), StringField.Store.NO));
d.add(new SortedNumericDocValuesField("snv", i * 10L));
writer.addDocument(d);
}
writer.commit();
Thread merger =
new Thread(
() -> {
try {
writer.forceMerge(1);
} catch (Throwable t) {
dir.failure.compareAndSet(null, t);
}
},
"forceMerge");
merger.start();
dir.mergeStarted.await();
// Update a single-valued doc in each merging segment, then resolve them to disk.
writer.updateSortedNumericDocValue(new Term("id", "0"), "snv", 100);
writer.updateSortedNumericDocValue(new Term("id", "4"), "snv", 104);
try (DirectoryReader r = DirectoryReader.open(writer)) {
assertNotNull(r);
}
dir.resumeMerge.countDown();
merger.join();
assertNull("forceMerge failed: " + dir.failure.get(), dir.failure.get());
try (DirectoryReader reader = DirectoryReader.open(writer)) {
assertEquals(1, reader.leaves().size()); // single merged segment
LeafReader leaf = reader.leaves().get(0).reader();
SortedNumericDocValues snv = leaf.getSortedNumericDocValues("snv");
Terms idTerms = leaf.terms("id");
TermsEnum te = idTerms.iterator();
java.util.Map<String, long[]> byId = new java.util.HashMap<>();
BytesRef t;
while ((t = te.next()) != null) {
PostingsEnum pe = te.postings(null, PostingsEnum.NONE);
int docId = pe.nextDoc();
assertTrue(snv.advanceExact(docId));
long[] vals = new long[snv.docValueCount()];
for (int i = 0; i < vals.length; i++) {
vals[i] = snv.nextValue();
}
byId.put(t.utf8ToString(), vals);
}
// The two updates resolved during the merge were carried over.
assertArrayEquals("carried update for doc 0", new long[] {100}, byId.get("0"));
assertArrayEquals("carried update for doc 4", new long[] {104}, byId.get("4"));
// The untouched multi-valued doc kept its whole value set through the merge.
assertArrayEquals("untouched multi-valued doc preserved", new long[] {20, 21}, byId.get("1"));
// An untouched single-valued doc is unchanged.
assertArrayEquals(new long[] {30}, byId.get("3"));
}
writer.close();
dir.close();
}Also the comments in |
|
Fair enough @jimczi . Instead, I just added Also good call on the comments. Updated them throughout the file. |
jimczi
left a comment
There was a problem hiding this comment.
LGTM, thanks for the thorough follow-ups. The extended TestMergeCarryOverFromDisk covers the sorted-numeric merge carry-over well now, single and multi-valued base across the plain, fold and soft-delete paths, and it is green here over iterations along with the full index package.
One note for @AdityaTeltia: the multi-valued rejection your comment pointed at is gone, the PR now supports a multi-valued base rather than throwing inside handleDVUpdates, so the stuck-packet path no longer exists. I think that thread can be resolved.
|
Yes, we can mark it resolved now. Apparently resolving a review thread requires either write access to the repo or being the PR author. Thus, I am unable to see "Resolve Conversation/Comment" option. |
|
This PR has not had activity in the past 2 weeks, labeling it as stale. If the PR is waiting for review, notify the dev@lucene.apache.org list. Thank you for your contribution! |
Description
As mentioned in #16580, there are good reasons to want to use
SortedNumericDocValuesfor singleValued fields. This PR adds support for issuing single-value updates forSortedNumericDocValues. Originally this was only to support single-valuedSortedNumericDocValues, but really it's not hard to support multi-valuedSortedNumericDocValues. Supporting multi-value updates is harder, so that will be left for a future enhancement.