Skip to content

Commit 32a7a6d

Browse files
adelapenadriftx
authored andcommitted
CNDB-13534: Add RequestFailureReason.FEATURE_NEEDS_INDEX_REBUILD
The new request failure reason should be used when the on-disk format of an index in a replica doesn't support a requested feature. Also, rename org.apache.cassandra.index.sai.disk.format.Version.latest() to Version.current(), and add separate Version.CURRENT and Version.LATEST constants.
1 parent 17b4ad7 commit 32a7a6d

File tree

65 files changed

+941
-143
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+941
-143
lines changed

src/java/org/apache/cassandra/config/CassandraRelevantProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,8 @@ public enum CassandraRelevantProperties
678678

679679
/** Class used to discover/load the proper SAI index components file for a given sstable. */
680680
SAI_ANN_USE_SYNTHETIC_SCORE("cassandra.sai.ann_use_synthetic_score", "false"),
681+
/** The current version of the SAI on-disk index format. */
682+
SAI_CURRENT_VERSION("cassandra.sai.latest.version", "dc"),
681683
SAI_CUSTOM_COMPONENTS_DISCOVERY_CLASS("cassandra.sai.custom_components_discovery_class"),
682684
SAI_ENABLE_EDGES_CACHE("cassandra.sai.enable_edges_cache", "false"),
683685
SAI_ENABLE_GENERAL_ORDER_BY("cassandra.sai.general_order_by", "true"),

src/java/org/apache/cassandra/exceptions/RequestFailureReason.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.cassandra.db.filter.TombstoneOverwhelmingException;
2525
import org.apache.cassandra.index.IndexBuildInProgressException;
2626
import org.apache.cassandra.index.IndexNotAvailableException;
27+
import org.apache.cassandra.index.FeatureNeedsIndexRebuildException;
2728
import org.apache.cassandra.index.sai.utils.AbortedOperationException;
2829
import org.apache.cassandra.io.IVersionedSerializer;
2930
import org.apache.cassandra.io.util.DataInputPlus;
@@ -47,7 +48,8 @@ public enum RequestFailureReason
4748
UNKNOWN_COLUMN (500),
4849
UNKNOWN_TABLE (501),
4950
REMOTE_STORAGE_FAILURE (502),
50-
INDEX_BUILD_IN_PROGRESS (503);
51+
INDEX_BUILD_IN_PROGRESS (503),
52+
FEATURE_NEEDS_INDEX_REBUILD(504); // The index uses an old version that doesn't support the requested feature
5153

5254
public static final Serializer serializer = new Serializer();
5355

@@ -85,6 +87,7 @@ public int codeForNativeProtocol()
8587
exceptionToReasonMap.put(UnknownColumnException.class, UNKNOWN_COLUMN);
8688
exceptionToReasonMap.put(UnknownTableException.class, UNKNOWN_TABLE);
8789
exceptionToReasonMap.put(IndexBuildInProgressException.class, INDEX_BUILD_IN_PROGRESS);
90+
exceptionToReasonMap.put(FeatureNeedsIndexRebuildException.class, FEATURE_NEEDS_INDEX_REBUILD);
8891

8992
if (exceptionToReasonMap.size() != reasons.length-5)
9093
throw new RuntimeException("A new RequestFailureReasons was probably added and you may need to update the exceptionToReasonMap");
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright DataStax, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.apache.cassandra.index;
18+
19+
import org.apache.cassandra.exceptions.InternalRequestExecutionException;
20+
import org.apache.cassandra.exceptions.RequestFailureReason;
21+
22+
/**
23+
* Thrown if a secondary index is still in an old version that doesn't support the requested feature.
24+
* </p>
25+
* Users hitting this exception should probably rebuild their index to a newer version.
26+
*/
27+
public final class FeatureNeedsIndexRebuildException extends RuntimeException implements InternalRequestExecutionException
28+
{
29+
/**
30+
* Creates a new {@link FeatureNeedsIndexRebuildException} for the specified message.
31+
*
32+
* @param message the explanation of the error
33+
*/
34+
public FeatureNeedsIndexRebuildException(String message)
35+
{
36+
super(message);
37+
}
38+
39+
@Override
40+
public RequestFailureReason getReason()
41+
{
42+
return RequestFailureReason.FEATURE_NEEDS_INDEX_REBUILD;
43+
}
44+
}

src/java/org/apache/cassandra/index/Index.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,7 @@ default void unload() { }
910910
* Returns the set of sstable-attached components that this group will create for a newly flushed sstable.
911911
*
912912
* Note that the result of this method is only valid for newly flushed/written sstables as the components
913-
* returned will assume a version of {@link Version#latest()} and a generation of 0. SSTables for which some
913+
* returned will assume a version of {@link Version#current()} and a generation of 0. SSTables for which some
914914
* index have been rebuild may have index components that do not match what this method return in particular.
915915
*/
916916
Set<Component> componentsForNewSSTable();

src/java/org/apache/cassandra/index/sai/IndexContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public IndexContext(@Nonnull String keyspace,
181181
this.viewManager = new IndexViewManager(this);
182182
this.validator = TypeUtil.cellValueType(column, indexType);
183183
this.cfs = cfs;
184-
this.primaryKeyFactory = Version.latest().onDiskFormat().newPrimaryKeyFactory(clusteringComparator);
184+
this.primaryKeyFactory = Version.current().onDiskFormat().newPrimaryKeyFactory(clusteringComparator);
185185

186186
String columnName = column.name.toString();
187187

@@ -662,7 +662,7 @@ public View getView()
662662
*/
663663
public int openPerIndexFiles()
664664
{
665-
return viewManager.getView().size() * Version.latest().onDiskFormat().openFilesPerIndex(this);
665+
return viewManager.getView().size() * Version.current().onDiskFormat().openFilesPerIndex(this);
666666
}
667667

668668
public void prepareSSTablesForRebuild(Collection<SSTableReader> sstablesToRebuild)

src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.apache.cassandra.index.Index;
7777
import org.apache.cassandra.index.IndexBuildDecider;
7878
import org.apache.cassandra.index.IndexRegistry;
79+
import org.apache.cassandra.index.FeatureNeedsIndexRebuildException;
7980
import org.apache.cassandra.index.SecondaryIndexBuilder;
8081
import org.apache.cassandra.index.SecondaryIndexManager;
8182
import org.apache.cassandra.index.TargetParser;
@@ -85,6 +86,7 @@
8586
import org.apache.cassandra.index.sai.analyzer.NonTokenizingOptions;
8687
import org.apache.cassandra.index.sai.disk.StorageAttachedIndexWriter;
8788
import org.apache.cassandra.index.sai.disk.format.IndexDescriptor;
89+
import org.apache.cassandra.index.sai.disk.format.Version;
8890
import org.apache.cassandra.index.sai.disk.v1.IndexWriterConfig;
8991
import org.apache.cassandra.index.sai.utils.TypeUtil;
9092
import org.apache.cassandra.index.sai.view.View;
@@ -447,7 +449,17 @@ private Future<?> startInitialBuild(ColumnFamilyStore baseCfs, boolean validate,
447449
return ImmediateFuture.success(null);
448450
}
449451

450-
// stop in-progress compaction tasks to prevent compacted sstable not being index.
452+
if (indexContext.isVector() && Version.current().compareTo(Version.JVECTOR_EARLIEST) < 0)
453+
{
454+
throw new FeatureNeedsIndexRebuildException(String.format("The current configured on-disk format version %s does not support vector indexes. " +
455+
"The minimum version that supports vectors is %s. " +
456+
"The on-disk format version can be set via the -D%s system property.",
457+
Version.current(),
458+
Version.JVECTOR_EARLIEST,
459+
CassandraRelevantProperties.SAI_CURRENT_VERSION.name()));
460+
}
461+
462+
// stop in-progress compaction tasks to prevent compacted sstables not being indexed.
451463
logger.debug(indexContext.logMessage("Stopping active compactions to make sure all sstables are indexed after initial build."));
452464
CompactionManager.instance.interruptCompactionFor(Collections.singleton(baseCfs.metadata()),
453465
OperationType.REWRITES_SSTABLES,

src/java/org/apache/cassandra/index/sai/StorageAttachedIndexBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,10 @@ private static void prepareForRebuild(IndexComponents.ForRead components, Set<Co
290290
// The current components are "replaced" (by "other" components) if the build create different components than
291291
// the existing ones. This will happen in the following cases:
292292
// 1. if we use immutable components, that's the point of immutable components.
293-
// 2. when we do not use immutable components, the rebuild components will always be for the latest version and
293+
// 2. when we do not use immutable components, the rebuild components will always be for the current version and
294294
// for generation 0, so if the current components are not for that specific built, then we won't be rebuilding
295295
// the exact same components, and we're "replacing", not "overwriting" ()
296-
// a) the old components are from an older version: a new build will alawys be for `Version.latest()` and
296+
// a) the old components are from an older version: a new build will alawys be for `Version.current()` and
297297
// so will create new files in that case (Note that "normally" we should not have non-0 generation in the
298298
// first place if immutable components are not used, but we handle this case to better support "downgrades"
299299
// where immutable components was enabled, but then disabled for some reason. If that happens, we still

src/java/org/apache/cassandra/index/sai/disk/StorageAttachedIndexWriter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ public StorageAttachedIndexWriter(IndexDescriptor indexDescriptor,
9090
TableMetrics tableMetrics) throws IOException
9191
{
9292
// We always write at the latest version (through what that version is can be configured for specific cases)
93-
var onDiskFormat = Version.latest().onDiskFormat();
93+
var onDiskFormat = Version.current().onDiskFormat();
9494
this.indexDescriptor = indexDescriptor;
95-
// Note: I think there is a silent assumption here. That is, the PK factory we use here must be for the latest
95+
// Note: I think there is a silent assumption here. That is, the PK factory we use here must be for the current
9696
// format version, because that is what `IndexContext.keyFactory` always uses (see ctor)
9797
this.primaryKeyFactory = onDiskFormat.newPrimaryKeyFactory(tableMetadata.comparator);
9898
this.indices = indices;

src/java/org/apache/cassandra/index/sai/disk/format/ComponentsBuildId.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*/
3232
public class ComponentsBuildId implements Comparable<ComponentsBuildId>
3333
{
34-
private static final ComponentsBuildId FOR_NEW_SSTABLE = ComponentsBuildId.latest(0);
34+
private static final ComponentsBuildId FOR_NEW_SSTABLE = ComponentsBuildId.current(0);
3535

3636
private final Version version;
3737
private final int generation;
@@ -47,9 +47,9 @@ public static ComponentsBuildId of(Version version, int generation)
4747
return new ComponentsBuildId(version, generation);
4848
}
4949

50-
public static ComponentsBuildId latest(int generation)
50+
public static ComponentsBuildId current(int generation)
5151
{
52-
return of(Version.latest(), generation);
52+
return of(Version.current(), generation);
5353
}
5454

5555
public static ComponentsBuildId forNewSSTable()
@@ -59,7 +59,7 @@ public static ComponentsBuildId forNewSSTable()
5959

6060
public static ComponentsBuildId forNewBuild(@Nullable ComponentsBuildId previousBuild, Predicate<ComponentsBuildId> newBuildIsUsablePredicate)
6161
{
62-
Version version = Version.latest();
62+
Version version = Version.current();
6363
// If we're not using immutable components, we always use generation 0, and we're fine if that overrides existing files
6464
if (!version.useImmutableComponentFiles())
6565
return new ComponentsBuildId(version, 0);

src/java/org/apache/cassandra/index/sai/disk/format/IndexComponents.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
* of the completion marker, but not of the other component of the group). The bumping of generations takes incomplete
7777
* groups into account, and so incomplete groups are not overridden either. Essentially, the generation used by a new
7878
* build is always one more than the highest generation of any component found on disk (for the group in question, and
79-
* the version we writting, usually {@link Version#latest()}).
79+
* the version we writting, usually {@link Version#current()}).
8080
*/
8181
public interface IndexComponents
8282
{

src/java/org/apache/cassandra/index/sai/disk/format/IndexDescriptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public class IndexDescriptor
7979
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
8080
private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 1, TimeUnit.MINUTES);
8181

82-
private static final ComponentsBuildId EMPTY_GROUP_MARKER = ComponentsBuildId.latest(-1);
82+
private static final ComponentsBuildId EMPTY_GROUP_MARKER = ComponentsBuildId.current(-1);
8383

8484
// TODO Because indexes can be added at any time to existing data, the Version of a column index
8585
// may not match the Version of the base sstable. OnDiskFormat + IndexFeatureSet + IndexDescriptor

src/java/org/apache/cassandra/index/sai/disk/format/IndexFeatureSet.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ public interface IndexFeatureSet
5252
* multiple sources. This will include all the SSTables included in a query and all the indexes
5353
* attached to those SSTables, added using {@link Accumulator#accumulate}.
5454
* <p>
55-
* The feature set of the latest version denoted by {@link Version#latest()}
55+
* The feature set of the current version denoted by {@link Version#current()}
5656
* is implicitly added, so the result feature set will include only the features supported by the
57-
* latest version.
57+
* current version.
5858
* <p>
5959
* The {@code Accumulator} creates an {@code IndexFeatureSet} this contains the features from
6060
* all the associated feature sets where {@code false} is the highest priority. This means if any
@@ -70,7 +70,7 @@ class Accumulator
7070

7171
public Accumulator()
7272
{
73-
accumulate(Version.latest().onDiskFormat().indexFeatureSet());
73+
accumulate(Version.current().onDiskFormat().indexFeatureSet());
7474
}
7575

7676
/**

src/java/org/apache/cassandra/index/sai/disk/format/Version.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,22 @@ public class Version implements Comparable<Version>
6161
public static final Version DC = new Version("dc", V5OnDiskFormat.instance, (c, i, g) -> stargazerFileNameFormat(c, i, g, "dc"));
6262
// histograms in index metadata
6363
public static final Version EB = new Version("eb", V6OnDiskFormat.instance, (c, i, g) -> stargazerFileNameFormat(c, i, g, "eb"));
64-
// term frequencies index component
64+
// term frequencies index component (support for BM25)
6565
public static final Version EC = new Version("ec", V7OnDiskFormat.instance, (c, i, g) -> stargazerFileNameFormat(c, i, g, "ec"));
6666

6767
// These are in reverse-chronological order so that the latest version is first. Version matching tests
68-
// are more likely to match the latest version so we want to test that one first.
68+
// are more likely to match the latest version, so we want to test that one first.
6969
public static final List<Version> ALL = Lists.newArrayList(EC, EB, DC, DB, CA, BA, AA);
7070

7171
public static final Version EARLIEST = AA;
7272
public static final Version VECTOR_EARLIEST = BA;
73-
// The latest version can be configured to be an earlier version to support partial upgrades that don't
73+
public static final Version JVECTOR_EARLIEST = CA;
74+
public static final Version BM25_EARLIEST = EC;
75+
public static final Version LATEST = EC;
76+
// The current version can be configured to be an earlier version to support partial upgrades that don't
7477
// write newer versions of the on-disk formats. This is volatile rather than final so that tests may
7578
// use reflection to change it and safely publish across threads.
76-
private static volatile Version LATEST = parse(CassandraRelevantProperties.SAI_LATEST_VERSION.getString());
79+
public static volatile Version CURRENT = parse(currentVersionProperty());
7780

7881
private static final Pattern GENERATION_PATTERN = Pattern.compile("\\d+");
7982

@@ -88,6 +91,11 @@ private Version(String version, OnDiskFormat onDiskFormat, FileNameFormatter fil
8891
this.fileNameFormatter = fileNameFormatter;
8992
}
9093

94+
private static String currentVersionProperty()
95+
{
96+
return CassandraRelevantProperties.SAI_CURRENT_VERSION.getString();
97+
}
98+
9199
public static Version parse(String input)
92100
{
93101
checkArgument(input != null);
@@ -99,9 +107,9 @@ public static Version parse(String input)
99107
throw new IllegalArgumentException("Unrecognized SAI version string " + input);
100108
}
101109

102-
public static Version latest()
110+
public static Version current()
103111
{
104-
return LATEST;
112+
return CURRENT;
105113
}
106114

107115
/**
@@ -125,7 +133,7 @@ public static int calculateIndexNameAllowedLength()
125133
private static int getAddedLengthFromDescriptorAndVersion()
126134
{
127135
// Prefixes and suffixes constructed by Version.stargazerFileNameFormat
128-
int versionNameLength = latest().toString().length();
136+
int versionNameLength = current().toString().length();
129137
// room for up to 999 generations
130138
int generationLength = 3 + SAI_SEPARATOR.length();
131139
int addedLength = SAI_DESCRIPTOR.length()

src/java/org/apache/cassandra/index/sai/disk/v1/InvertedIndexSearcher.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.stream.Collectors;
3131

3232
import com.google.common.base.MoreObjects;
33+
import org.apache.cassandra.index.sai.plan.QueryController;
3334
import org.slf4j.Logger;
3435
import org.slf4j.LoggerFactory;
3536

@@ -42,6 +43,7 @@
4243
import org.apache.cassandra.db.rows.Row;
4344
import org.apache.cassandra.dht.AbstractBounds;
4445
import org.apache.cassandra.exceptions.InvalidRequestException;
46+
import org.apache.cassandra.index.FeatureNeedsIndexRebuildException;
4547
import org.apache.cassandra.index.sai.IndexContext;
4648
import org.apache.cassandra.index.sai.QueryContext;
4749
import org.apache.cassandra.index.sai.SSTableContext;
@@ -187,7 +189,10 @@ public CloseableIterator<PrimaryKeyWithSortKey> orderBy(Orderer orderer, Express
187189
return toMetaSortedIterator(iter, queryContext);
188190
}
189191
if (docLengthsMeta == null)
190-
throw new InvalidRequestException(indexContext.getIndexName() + " does not support BM25 scoring until it is rebuilt");
192+
{
193+
throw new FeatureNeedsIndexRebuildException(String.format(QueryController.INDEX_VERSION_DOES_NOT_SUPPORT_BM25,
194+
indexContext.getIndexName()));
195+
}
191196

192197
// find documents that match each term
193198
var queryTerms = orderer.getQueryTerms();
@@ -264,8 +269,12 @@ public CloseableIterator<PrimaryKeyWithSortKey> orderResultsBy(SSTableReader rea
264269
{
265270
if (!orderer.isBM25())
266271
return super.orderResultsBy(reader, queryContext, keys, orderer, limit);
272+
267273
if (docLengthsMeta == null)
268-
throw new InvalidRequestException(indexContext.getIndexName() + " does not support BM25 scoring until it is rebuilt");
274+
{
275+
throw new InvalidRequestException(String.format(QueryController.INDEX_VERSION_DOES_NOT_SUPPORT_BM25,
276+
indexContext.getIndexName()));
277+
}
269278

270279
var queryTerms = orderer.getQueryTerms();
271280
// compute documentFrequencies from either histogram or an index search

src/java/org/apache/cassandra/index/sai/disk/v1/MemtableIndexWriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ private long flush(DecoratedKey minKey, DecoratedKey maxKey, AbstractType<?> ter
212212

213213
private boolean writeFrequencies()
214214
{
215-
return indexContext().isAnalyzed() && Version.latest().onOrAfter(Version.EC);
215+
return indexContext().isAnalyzed() && Version.current().onOrAfter(Version.BM25_EARLIEST);
216216
}
217217

218218
private void flushVectorIndex(DecoratedKey minKey, DecoratedKey maxKey, long startTime, Stopwatch stopwatch) throws IOException

src/java/org/apache/cassandra/index/sai/disk/v1/SegmentBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public static class RAMStringSegmentBuilder extends SegmentBuilder
202202

203203
private boolean writeFrequencies()
204204
{
205-
return !(analyzer instanceof NoOpAnalyzer) && Version.latest().onOrAfter(Version.EC);
205+
return !(analyzer instanceof NoOpAnalyzer) && Version.current().onOrAfter(Version.BM25_EARLIEST);
206206
}
207207

208208
public boolean isEmpty()
@@ -499,8 +499,8 @@ private long add(List<ByteBuffer> terms, PrimaryKey key, long sstableRowId)
499499
// Update term boundaries for all terms in this row
500500
for (ByteBuffer term : terms)
501501
{
502-
minTerm = TypeUtil.min(term, minTerm, termComparator, Version.latest());
503-
maxTerm = TypeUtil.max(term, maxTerm, termComparator, Version.latest());
502+
minTerm = TypeUtil.min(term, minTerm, termComparator, Version.current());
503+
maxTerm = TypeUtil.max(term, maxTerm, termComparator, Version.current());
504504
}
505505

506506
// segmentRowIdOffset should encode sstableRowId into Integer

0 commit comments

Comments
 (0)