From f5f7b5c600afb3fd01c6fb8be9cfe94158ae1f62 Mon Sep 17 00:00:00 2001 From: Olivier Boudet Date: Thu, 6 Aug 2026 21:39:57 +0200 Subject: [PATCH 1/2] SOLR-18335 : enable shard splitting by point fields Previously, shard splitting could not utilize numeric point fields as router fields. This change introduces the necessary logic to extract routing values from point fields, either via docValues or stored fields, thus expanding the types of fields available for shard splitting. --- ...35-splitshard-router-field-point-field.yml | 8 + .../apache/solr/update/SolrIndexSplitter.java | 176 +++++++++++++++--- .../solr/collection1/conf/schema15.xml | 2 + .../cloud/api/collections/ShardSplitTest.java | 70 +++++++ .../solr/update/SolrIndexSplitterTest.java | 87 +++++++++ 5 files changed, 318 insertions(+), 25 deletions(-) create mode 100644 changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml diff --git a/changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml b/changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml new file mode 100644 index 000000000000..78cb96be26ba --- /dev/null +++ b/changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml @@ -0,0 +1,8 @@ +title: > + SPLITSHARD fails to migrate documents when using a numeric PointField as router.field +type: fixed +authors: + - name: Olivier Boudet +links: + - name: SOLR-18335 + url: https://issues.apache.org/jira/browse/SOLR-18335 diff --git a/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java b/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java index 9312b831e75c..d9f404eced08 100644 --- a/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java +++ b/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java @@ -28,14 +28,18 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.lucene.document.Document; import org.apache.lucene.index.CodecReader; +import org.apache.lucene.index.DocValues; import org.apache.lucene.index.FilterCodecReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.NoMergePolicy; +import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.PostingsEnum; import org.apache.lucene.index.SlowCodecReaderWrapper; import org.apache.lucene.index.Terms; @@ -70,6 +74,7 @@ import org.apache.solr.handler.IndexFetcher; import org.apache.solr.handler.SnapShooter; import org.apache.solr.schema.IndexSchema; +import org.apache.solr.schema.NumberType; import org.apache.solr.schema.SchemaField; import org.apache.solr.search.BitsFilteredPostingsEnum; import org.apache.solr.search.SolrIndexSearcher; @@ -677,6 +682,20 @@ static FixedBitSet[] split( } } + if (field.getType().isPointField()) { + return splitPointField( + reader, + numPieces, + field, + rangesArr, + splitKey, + hashRouter, + delete, + docSets, + liveDocs, + currentPartition); + } + Terms terms = reader.terms(field.getName()); TermsEnum termsEnum = terms == null ? null : terms.iterator(); if (termsEnum == null) return docSets; @@ -746,38 +765,145 @@ static FixedBitSet[] split( } if (docsMatchingRanges != null) { - for (int ii = 0; ii < docsMatchingRanges.length; ii++) { - if (0 == docsMatchingRanges[ii]) continue; - switch (ii) { - case 0: - // document loss - log.error( - "Splitting {}: {} documents belong to no shards and will be dropped", - reader, - docsMatchingRanges[ii]); - break; - case 1: - // normal case, each document moves to one of the sub-shards - log.info( - "Splitting {}: {} documents will move into a sub-shard", - reader, - docsMatchingRanges[ii]); - break; - default: - // document duplication - log.error( - "Splitting {}: {} documents will be moved to multiple ({}) sub-shards", - reader, - docsMatchingRanges[ii], - ii); - break; + logDocsMatchingRanges(reader, docsMatchingRanges); + } + + return docSets; + } + + private static FixedBitSet[] splitPointField( + LeafReader reader, + int numPieces, + SchemaField field, + DocRouter.Range[] rangesArr, + String splitKey, + HashBasedRouter hashRouter, + boolean delete, + FixedBitSet[] docSets, + Bits liveDocs, + AtomicInteger currentPartition) + throws IOException { + NumericDocValues numericDocValues = + field.hasDocValues() ? DocValues.getNumeric(reader, field.getName()) : null; + + int[] docsMatchingRanges = null; + if (rangesArr != null) { + docsMatchingRanges = new int[rangesArr.length + 1]; + } + + for (int doc = 0; doc < reader.maxDoc(); doc++) { + if (liveDocs != null && !liveDocs.get(doc)) { + continue; + } + + String routeValue = getRouteFieldValue(reader, doc, field, numericDocValues); + if (splitKey != null) { + String part1 = ((CompositeIdRouter) hashRouter).getRouteKeyNoSuffix(routeValue); + if (part1 == null || !splitKey.equals(part1)) { + continue; } } + + if (rangesArr == null) { + if (delete) { + docSets[currentPartition.get()].clear(doc); + } else { + docSets[currentPartition.get()].set(doc); + } + currentPartition.set((currentPartition.get() + 1) % numPieces); + } else { + int hash = hashRouter.sliceHash(routeValue, null, null, null); + int matchingRangesCount = 0; + for (int i = 0; i < rangesArr.length; i++) { + if (rangesArr[i].includes(hash)) { + if (delete) { + docSets[i].clear(doc); + } else { + docSets[i].set(doc); + } + ++matchingRangesCount; + } + } + docsMatchingRanges[matchingRangesCount]++; + } } + if (docsMatchingRanges != null) { + logDocsMatchingRanges(reader, docsMatchingRanges); + } return docSets; } + private static String getRouteFieldValue( + LeafReader reader, int doc, SchemaField field, NumericDocValues numericDocValues) + throws IOException { + if (numericDocValues != null && numericDocValues.advanceExact(doc)) { + return numericRouteValueToString(field, numericDocValues.longValue()); + } + + if (field.stored()) { + Document storedDocument = reader.storedFields().document(doc); + IndexableField storedField = storedDocument.getField(field.getName()); + if (storedField != null) { + Object routeValue = field.getType().toObject(storedField); + if (routeValue != null) { + return routeValue.toString(); + } + } + } + + throw new SolrException( + SolrException.ErrorCode.SERVER_ERROR, + "Unable to read route field '" + + field.getName() + + "' for shard splitting. Point-based route fields must expose docValues or be stored."); + } + + private static String numericRouteValueToString(SchemaField field, long value) { + NumberType numberType = field.getType().getNumberType(); + if (numberType == null) { + return Long.toString(value); + } + + return switch (numberType) { + case INTEGER -> Integer.toString((int) value); + case LONG -> Long.toString(value); + case FLOAT -> Float.toString(Float.intBitsToFloat((int) value)); + case DOUBLE -> Double.toString(Double.longBitsToDouble(value)); + case DATE -> Long.toString(value); + }; + } + + private static void logDocsMatchingRanges(LeafReader reader, int[] docsMatchingRanges) { + for (int ii = 0; ii < docsMatchingRanges.length; ii++) { + if (0 == docsMatchingRanges[ii]) continue; + switch (ii) { + case 0: + // document loss + log.error( + "Splitting {}: {} documents belong to no shards and will be dropped", + reader, + docsMatchingRanges[ii]); + break; + case 1: + // normal case, each document moves to one of the sub-shards + log.info( + "Splitting {}: {} documents will move into a sub-shard", + reader, + docsMatchingRanges[ii]); + break; + default: + // document duplication + log.error( + "Splitting {}: {} documents will be moved to multiple ({}) sub-shards", + reader, + docsMatchingRanges[ii], + ii); + break; + } + } + } + private static void checkRouterSupportsSplitKey(HashBasedRouter hashRouter, String splitKey) { if (splitKey != null && !(hashRouter instanceof CompositeIdRouter)) { throw new IllegalStateException( diff --git a/solr/core/src/test-files/solr/collection1/conf/schema15.xml b/solr/core/src/test-files/solr/collection1/conf/schema15.xml index 87fdad981d67..590ea78aab0e 100644 --- a/solr/core/src/test-files/solr/collection1/conf/schema15.xml +++ b/solr/core/src/test-files/solr/collection1/conf/schema15.xml @@ -35,6 +35,7 @@ + @@ -595,6 +596,7 @@ + diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java index c55843dab9d9..661de75923cf 100644 --- a/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java +++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java @@ -107,6 +107,7 @@ public void test() throws Exception { incompleteOrOverlappingCustomRangeTest(); splitByUniqueKeyTest(); splitByRouteFieldTest(); + splitByNumericRouteFieldTest(); splitByRouteKeyTest(); // todo can't call waitForThingsToLevelOut because it looks for jettys of all shards @@ -1009,6 +1010,75 @@ public void splitByRouteFieldTest() throws Exception { .query(new SolrQuery("*:*").setParam("shards", "shard1_1")) .getResults() .getNumFound()); + assertEquals(101, collectionClient.query(new SolrQuery("*:*")).getResults().getNumFound()); + } + } + + public void splitByNumericRouteFieldTest() throws Exception { + log.info("Starting splitByNumericRouteFieldTest"); + String collectionName = "numericRouteFieldColl"; + int numShards = 4; + int replicationFactor = 2; + + HashMap> collectionInfos = new HashMap<>(); + String shardField = "shard_pl"; + try (CloudSolrClient client = createCloudClient(null)) { + Map props = + Map.of( + REPLICATION_FACTOR, + replicationFactor, + CollectionHandlingUtils.NUM_SLICES, + numShards, + "router.field", + shardField); + + createCollection(collectionInfos, collectionName, props, client); + } + + List list = collectionInfos.get(collectionName); + checkForCollection(collectionName, list); + + waitForRecoveriesToFinish(false); + + getCommonCloudSolrClient(); + String baseUrl = getBaseUrlFromZk(cloudClient.getClusterState(), collectionName); + + try (SolrClient collectionClient = getHttpSolrClient(baseUrl, collectionName)) { + ClusterState clusterState = cloudClient.getClusterState(); + final DocRouter router = clusterState.getCollection(collectionName).getRouter(); + Slice shard1 = clusterState.getCollection(collectionName).getSlice(SHARD1); + DocRouter.Range shard1Range = + shard1.getRange() != null ? shard1.getRange() : router.fullRange(); + final List ranges = router.partitionRange(2, shard1Range); + final int[] docCounts = new int[ranges.size()]; + + for (int i = 100; i <= 200; i++) { + collectionClient.add(getDoc(id, i, "n_ti", i, shardField, i)); + int idx = getHashRangeIdx(router, ranges, Integer.toString(i)); + if (idx != -1) { + docCounts[idx]++; + } + } + + collectionClient.commit(); + + trySplit(collectionName, null, SHARD1, 3); + + waitForRecoveriesToFinish(collectionName, false); + + assertEquals( + docCounts[0], + collectionClient + .query(new SolrQuery("*:*").setParam("shards", "shard1_0")) + .getResults() + .getNumFound()); + assertEquals( + docCounts[1], + collectionClient + .query(new SolrQuery("*:*").setParam("shards", "shard1_1")) + .getResults() + .getNumFound()); + assertEquals(101, collectionClient.query(new SolrQuery("*:*")).getResults().getNumFound()); } } diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java index d7f7fa4f2e11..967dd8853e03 100644 --- a/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java +++ b/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java @@ -413,6 +413,16 @@ public void testSplitByRouteKeyLink() throws Exception { doTestSplitByRouteKey(SolrIndexSplitter.SplitMethod.LINK); } + @Test + public void testSplitByNumericRouteField() throws Exception { + doTestSplitByNumericRouteField(SolrIndexSplitter.SplitMethod.REWRITE); + } + + @Test + public void testSplitByNumericRouteFieldLink() throws Exception { + doTestSplitByNumericRouteField(SolrIndexSplitter.SplitMethod.LINK); + } + private void doTestSplitByRouteKey(SolrIndexSplitter.SplitMethod splitMethod) throws Exception { Path indexDir = createTempDir(); @@ -480,6 +490,83 @@ private void doTestSplitByRouteKey(SolrIndexSplitter.SplitMethod splitMethod) th } } + private void doTestSplitByNumericRouteField(SolrIndexSplitter.SplitMethod splitMethod) + throws Exception { + CompositeIdRouter router = new CompositeIdRouter(); + List ranges = router.partitionRange(2, router.fullRange()); + int[] expectedDocCounts = new int[ranges.size()]; + + for (int i = 100; i < 140; i++) { + String routeValue = Integer.toString(i); + assertU(adoc("id", "doc-" + i, "route_pl", routeValue)); + + int hash = router.sliceHash(routeValue, null, null, null); + for (int rangeIndex = 0; rangeIndex < ranges.size(); rangeIndex++) { + if (ranges.get(rangeIndex).includes(hash)) { + expectedDocCounts[rangeIndex]++; + break; + } + } + } + + assertU(commit()); + assertJQ(req("q", "*:*"), "/response/numFound==40"); + + SolrQueryRequestBase request = null; + Directory directory = null; + try { + request = lrf.makeRequest("q", "dummy"); + SolrQueryResponse rsp = new SolrQueryResponse(); + SplitIndexCommand command = + new SplitIndexCommand( + request, + rsp, + List.of(indexDir1.toString(), indexDir2.toString()), + null, + ranges, + router, + "route_pl", + null, + splitMethod); + doSplit(command); + + directory = + h.getCore() + .getDirectoryFactory() + .get( + indexDir1.toString(), + DirectoryFactory.DirContext.DEFAULT, + h.getCore().getSolrConfig().indexConfig.lockType); + DirectoryReader reader = DirectoryReader.open(directory); + assertEquals( + "split index1 has wrong number of documents", expectedDocCounts[0], reader.numDocs()); + reader.close(); + h.getCore().getDirectoryFactory().release(directory); + directory = null; + + directory = + h.getCore() + .getDirectoryFactory() + .get( + indexDir2.toString(), + DirectoryFactory.DirContext.DEFAULT, + h.getCore().getSolrConfig().indexConfig.lockType); + reader = DirectoryReader.open(directory); + assertEquals( + "split index2 has wrong number of documents", expectedDocCounts[1], reader.numDocs()); + reader.close(); + h.getCore().getDirectoryFactory().release(directory); + directory = null; + } finally { + if (request != null) { + request.close(); + } + if (directory != null) { + h.getCore().getDirectoryFactory().release(directory); + } + } + } + @Test public void testSplitWithChildDocs() throws Exception { doTestSplitWithChildDocs(SolrIndexSplitter.SplitMethod.REWRITE); From dd20bb8a36f98e57ee4f99c972166ab9deba6a0e Mon Sep 17 00:00:00 2001 From: David Smiley Date: Sat, 5 Sep 2026 21:55:20 -0400 Subject: [PATCH 2/2] Refactor: this is about docValues vs terms index SOLR-18335: derive numeric route field values via docValues generically, not raw bit decoding getRouteFieldValue hand-decoded NumericDocValues bits with a NumberType switch, plus a stored-field fallback. Rework it to ask the field's FieldType for a ValueSource/FunctionValues instead, so each field type decodes its own docValues rather than SolrIndexSplitter guessing at the encoding. Generalize the branch in split(): the deciding factor isn't "is this a PointField" but "does this field have a term index to iterate" -- fields without one (PointFields, or any indexed=false docValues-only field) now fall back to reading docValues, regardless of type. Drop the stored-field fallback; docValues are now required for a route field with no term index, and a missing-docValues field now fails with a clear SolrException instead of a confusing Lucene IllegalStateException surfaced from the raw (unwrapped) reader. Test coverage reuses the schema's existing parameterized numeric field type (randomized between Trie/Point) rather than adding a dedicated Point-only field type, forcing docValues on when Points are randomly selected so the test is deterministic either way. --- ...35-splitshard-router-field-point-field.yml | 6 +- .../apache/solr/update/SolrIndexSplitter.java | 78 +++++++------------ .../solr/collection1/conf/schema15.xml | 2 - .../cloud/api/collections/ShardSplitTest.java | 2 +- .../solr/update/SolrIndexSplitterTest.java | 8 +- .../pages/collection-management.adoc | 5 +- 6 files changed, 42 insertions(+), 59 deletions(-) diff --git a/changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml b/changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml index 78cb96be26ba..dab08ed0639f 100644 --- a/changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml +++ b/changelog/unreleased/SOLR-18335-splitshard-router-field-point-field.yml @@ -1,8 +1,10 @@ title: > - SPLITSHARD fails to migrate documents when using a numeric PointField as router.field -type: fixed + SPLITSHARD has expanded support for collection router.field, not only supporting indexed fields but also docValues. + Note: "Point" numeric fields only work in this case with docValues. +type: added authors: - name: Olivier Boudet + - name: David Smiley links: - name: SOLR-18335 url: https://issues.apache.org/jira/browse/SOLR-18335 diff --git a/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java b/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java index d9f404eced08..b9e4c4722d1d 100644 --- a/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java +++ b/solr/core/src/java/org/apache/solr/update/SolrIndexSplitter.java @@ -28,23 +28,21 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.lucene.document.Document; import org.apache.lucene.index.CodecReader; -import org.apache.lucene.index.DocValues; import org.apache.lucene.index.FilterCodecReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.NoMergePolicy; -import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.PostingsEnum; import org.apache.lucene.index.SlowCodecReaderWrapper; import org.apache.lucene.index.Terms; import org.apache.lucene.index.TermsEnum; import org.apache.lucene.misc.store.HardlinkCopyDirectoryWrapper; +import org.apache.lucene.queries.function.FunctionValues; +import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.search.ConstantScoreScorer; import org.apache.lucene.search.ConstantScoreWeight; import org.apache.lucene.search.DocIdSetIterator; @@ -74,7 +72,6 @@ import org.apache.solr.handler.IndexFetcher; import org.apache.solr.handler.SnapShooter; import org.apache.solr.schema.IndexSchema; -import org.apache.solr.schema.NumberType; import org.apache.solr.schema.SchemaField; import org.apache.solr.search.BitsFilteredPostingsEnum; import org.apache.solr.search.SolrIndexSearcher; @@ -682,9 +679,13 @@ static FixedBitSet[] split( } } - if (field.getType().isPointField()) { - return splitPointField( - reader, + Terms terms = reader.terms(field.getName()); + TermsEnum termsEnum = terms == null ? null : terms.iterator(); + if (termsEnum == null) { + // No term dictionary for this field (e.g. a PointField, which has no Terms; or a + // docValues-only field that isn't indexed). Derive route values from docValues instead. + return splitUsingDocValues( + readerContext, numPieces, field, rangesArr, @@ -696,10 +697,6 @@ static FixedBitSet[] split( currentPartition); } - Terms terms = reader.terms(field.getName()); - TermsEnum termsEnum = terms == null ? null : terms.iterator(); - if (termsEnum == null) return docSets; - BytesRef term = null; PostingsEnum postingsEnum = null; @@ -771,8 +768,8 @@ static FixedBitSet[] split( return docSets; } - private static FixedBitSet[] splitPointField( - LeafReader reader, + private static FixedBitSet[] splitUsingDocValues( + LeafReaderContext readerContext, int numPieces, SchemaField field, DocRouter.Range[] rangesArr, @@ -783,8 +780,16 @@ private static FixedBitSet[] splitPointField( Bits liveDocs, AtomicInteger currentPartition) throws IOException { - NumericDocValues numericDocValues = - field.hasDocValues() ? DocValues.getNumeric(reader, field.getName()) : null; + if (!field.hasDocValues()) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "Route field '" + + field.getName() + + "' has no term index to split by and lacks docValues; unable to split shard."); + } + LeafReader reader = readerContext.reader(); + ValueSource valueSource = field.getType().getValueSource(field, null); + FunctionValues routeFieldValues = valueSource.getValues(Map.of(), readerContext); int[] docsMatchingRanges = null; if (rangesArr != null) { @@ -796,10 +801,10 @@ private static FixedBitSet[] splitPointField( continue; } - String routeValue = getRouteFieldValue(reader, doc, field, numericDocValues); + String routeValue = getRouteFieldValue(routeFieldValues, doc, field); if (splitKey != null) { String part1 = ((CompositeIdRouter) hashRouter).getRouteKeyNoSuffix(routeValue); - if (part1 == null || !splitKey.equals(part1)) { + if (!splitKey.equals(part1)) { continue; } } @@ -835,43 +840,14 @@ private static FixedBitSet[] splitPointField( } private static String getRouteFieldValue( - LeafReader reader, int doc, SchemaField field, NumericDocValues numericDocValues) - throws IOException { - if (numericDocValues != null && numericDocValues.advanceExact(doc)) { - return numericRouteValueToString(field, numericDocValues.longValue()); - } - - if (field.stored()) { - Document storedDocument = reader.storedFields().document(doc); - IndexableField storedField = storedDocument.getField(field.getName()); - if (storedField != null) { - Object routeValue = field.getType().toObject(storedField); - if (routeValue != null) { - return routeValue.toString(); - } - } + FunctionValues routeFieldValues, int doc, SchemaField field) throws IOException { + if (routeFieldValues.exists(doc)) { + return routeFieldValues.strVal(doc); } throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, - "Unable to read route field '" - + field.getName() - + "' for shard splitting. Point-based route fields must expose docValues or be stored."); - } - - private static String numericRouteValueToString(SchemaField field, long value) { - NumberType numberType = field.getType().getNumberType(); - if (numberType == null) { - return Long.toString(value); - } - - return switch (numberType) { - case INTEGER -> Integer.toString((int) value); - case LONG -> Long.toString(value); - case FLOAT -> Float.toString(Float.intBitsToFloat((int) value)); - case DOUBLE -> Double.toString(Double.longBitsToDouble(value)); - case DATE -> Long.toString(value); - }; + "Unable to read route field '" + field.getName() + "' for shard splitting."); } private static void logDocsMatchingRanges(LeafReader reader, int[] docsMatchingRanges) { diff --git a/solr/core/src/test-files/solr/collection1/conf/schema15.xml b/solr/core/src/test-files/solr/collection1/conf/schema15.xml index 590ea78aab0e..87fdad981d67 100644 --- a/solr/core/src/test-files/solr/collection1/conf/schema15.xml +++ b/solr/core/src/test-files/solr/collection1/conf/schema15.xml @@ -35,7 +35,6 @@ - @@ -596,7 +595,6 @@ - diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java index 661de75923cf..be142d5583fa 100644 --- a/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java +++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java @@ -1021,7 +1021,7 @@ public void splitByNumericRouteFieldTest() throws Exception { int replicationFactor = 2; HashMap> collectionInfos = new HashMap<>(); - String shardField = "shard_pl"; + String shardField = "shard_l"; try (CloudSolrClient client = createCloudClient(null)) { Map props = Map.of( diff --git a/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java b/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java index 967dd8853e03..9ae688f377ac 100644 --- a/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java +++ b/solr/core/src/test/org/apache/solr/update/SolrIndexSplitterTest.java @@ -55,6 +55,10 @@ public static void beforeClass() throws Exception { // _version_ System.setProperty("solr.directoryFactory", "solr.NRTCachingDirectoryFactory"); System.setProperty("solr.tests.lockType", DirectoryFactory.LOCK_TYPE_SIMPLE); + // route_l needs docValues to be usable as a router.field when numerics are Point-based + if (Boolean.getBoolean(NUMERIC_POINTS_SYSPROP)) { + System.setProperty(NUMERIC_DOCVALUES_SYSPROP, "true"); + } initCore("solrconfig.xml", "schema15.xml"); } @@ -498,7 +502,7 @@ private void doTestSplitByNumericRouteField(SolrIndexSplitter.SplitMethod splitM for (int i = 100; i < 140; i++) { String routeValue = Integer.toString(i); - assertU(adoc("id", "doc-" + i, "route_pl", routeValue)); + assertU(adoc("id", "doc-" + i, "route_l", routeValue)); int hash = router.sliceHash(routeValue, null, null, null); for (int rangeIndex = 0; rangeIndex < ranges.size(); rangeIndex++) { @@ -525,7 +529,7 @@ private void doTestSplitByNumericRouteField(SolrIndexSplitter.SplitMethod splitM null, ranges, router, - "route_pl", + "route_l", null, splitMethod); doSplit(command); diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc index 46452619f7c7..45b26e0af47c 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc @@ -232,6 +232,8 @@ For nested documents, the route field must match among all the documents in the + Please note that xref:configuration-guide:realtime-get.adoc[] or retrieval by document ID would also require the parameter `\_route_` (or `shard.keys`) to avoid a distributed search. +Use of docValues is highly encouraged on this field. + `perReplicaState`:: + [%autowidth,frame=none] @@ -1647,7 +1649,8 @@ s|Required |Default: none What to name the backup that is created. Provided as a query parameter for v1 requests, or as a path segment for v2 requests. + -For incremental backups, the backup name should be reused to add new backup points to the existing backup. For non-incremental backups (deprecated), this name is checked to ensure it doesn't already exist, and an error message is raised if it does. +For incremental backups, the backup name should be reused to add new backup points to the existing backup. +For non-incremental backups (deprecated), this name is checked to ensure it doesn't already exist, and an error message is raised if it does. `location`:: +