Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CNDB-12425: A few reproduction tests and a preliminary patch, WIP #1529

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ekaterinadimitrova2
Copy link

@ekaterinadimitrova2 ekaterinadimitrova2 commented Jan 23, 2025

What is the issue

...
Queries with ALLOW FILTERING are rejected while we have an index that can be used in the future for the same query, still building

What does this PR fix and why was it fixed

CNDB-12425: A preliminary patch to facilitate discussions. It does not include:

  • feature flag
  • checks we are on the new messaging version added for ANNOptions
  • we fall back to allow filtering only on Index Creation. Currently, we also fall back to ALLOW FILTERING if we use nodetool to rebuild indexes

Checklist before you submit for review

  • Make sure there is a PR in the CNDB project updating the Converged Cassandra version
  • Use NoSpamLogger for log lines that may appear frequently in the logs
  • Verify test results on Butler
  • Test coverage for new/modified code is > 80%
  • Proper code formatting
  • Proper title for each commit staring with the project-issue number, like CNDB-1234
  • Each commit has a meaningful description
  • Each commit is not very long and contains related changes
  • Renames, moves and reformatting are in distinct commits

@ekaterinadimitrova2 ekaterinadimitrova2 marked this pull request as draft January 23, 2025 03:20
@ekaterinadimitrova2 ekaterinadimitrova2 self-assigned this Jan 23, 2025
…t include:

- feature flag
- checks we are on the new messaging version added for ANNOptions
- we fall back to allow filtering only on Index Creation. Currently we also fall back to ALLOW FILTERING if we use nodetool to rebuild indexes
@ekaterinadimitrova2 ekaterinadimitrova2 marked this pull request as ready for review February 9, 2025 17:53
@ekaterinadimitrova2 ekaterinadimitrova2 marked this pull request as draft February 9, 2025 17:53
@ekaterinadimitrova2 ekaterinadimitrova2 marked this pull request as ready for review February 9, 2025 17:55
@ekaterinadimitrova2 ekaterinadimitrova2 marked this pull request as draft February 9, 2025 17:57
@ekaterinadimitrova2 ekaterinadimitrova2 marked this pull request as ready for review February 9, 2025 18:01
Copy link

sonarqubecloud bot commented Feb 9, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
79.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@cassci-bot
Copy link

✔️ Build ds-cassandra-pr-gate/PR-1529 approved by Butler


Approved by Butler
See build details here

@ekaterinadimitrova2 ekaterinadimitrova2 marked this pull request as draft February 9, 2025 21:10
@@ -382,6 +396,17 @@ public boolean isIndexQueryable(Index index)
return isIndexQueryable(index.getIndexMetadata().name);
}

/**
+ * Checks if the specified index is building.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting issue, I will fix it

@@ -77,7 +76,7 @@ public void tesConcurrencyFactor()
// verify that a low concurrency factor is not capped by the max concurrency factor
PartitionRangeReadCommand command = command(cfs, 50, 50);
try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, System.nanoTime(), ReadTracker.NOOP);
ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, ONE))
ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, ONE, false))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be command.rowFilter().allowFiltering

searcher = indexSearcher();
Index index = indexQueryPlan.getFirst();
Tracing.trace("Executing read on {}.{} using index {}", cfs.metadata.keyspace, cfs.metadata.name, index.getIndexMetadata().name);
if (cfs.indexManager.isQueryableThroughIndex(indexQueryPlan, rowFilter().allowFiltering))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collapsed the two ifs, no need of nesting here

@@ -362,13 +365,24 @@ public synchronized Future<?> addIndex(IndexMetadata indexDef, boolean isNewCF)
* @param queryPlan a query plan
* @throws IndexNotAvailableException if the query plan has any index that is not queryable
*/
public void checkQueryability(Index.QueryPlan queryPlan)
public boolean isQueryableThroughIndex(Index.QueryPlan queryPlan, boolean allowsFiltering)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method can now return true or false, or throw an exception, and it also throws a client warning. That looks like too many side effects for a method starting with boolean is..., which might suggest a simpler behaviour. I would either:
a) Split it into two simpler separate boolean methods to know if all the indexes in the plan are building/queryable, and let ReadCommand#executeLocally do the AF check and throw exceptions and warnings.
b) Transform it into a SecondaryIndexManger#searcherFor(Index.QueryPlan, boolean) method keeping most of it's responsibilities, returning the searcher if it's possible to build it, null if it's building, and exception if it's not queryable.

+ */
public boolean isIndexBuilding(Index index)
{
return isIndexBuilding(index.getIndexMetadata().name);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: double tab

+ * @param index the index
+ * @return <code>true</code> if the specified index is building, <code>false</code> otherwise
+ */
public boolean isIndexBuilding(Index index)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we put the two overloads of isIndexBuilding together?

@@ -1814,13 +1839,16 @@ public static void shutdownAndWait(long timeout, TimeUnit units) throws Interrup
* @param indexQueryPlan index query plan used in the read command
* @param level consistency level of read command
*/
public static <E extends Endpoints<E>> E filterForQuery(E liveEndpoints, Keyspace keyspace, Index.QueryPlan indexQueryPlan, ConsistencyLevel level)
public static <E extends Endpoints<E>> E filterForQuery(E liveEndpoints, Keyspace keyspace, Index.QueryPlan indexQueryPlan, ConsistencyLevel level, boolean allowsFiltering)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: break the long line and add the new allowsFiltering param to the method's JavaDoc.

@@ -723,11 +723,11 @@ public static ReplicaPlan.ForRangeRead forSingleReplicaRead(Keyspace keyspace, A
* The candidate collection can be used for speculation, although at present
* it would break EACH_QUORUM to do so without further filtering
*/
public static ReplicaPlan.ForTokenRead forRead(Keyspace keyspace, Token token, Index.QueryPlan indexQueryPlan, ConsistencyLevel consistencyLevel, SpeculativeRetryPolicy retry)
public static ReplicaPlan.ForTokenRead forRead(Keyspace keyspace, Token token, Index.QueryPlan indexQueryPlan, ConsistencyLevel consistencyLevel, SpeculativeRetryPolicy retry, boolean allowsFiltering)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: break the long line.

@@ -743,11 +743,11 @@ public static ReplicaPlan.ForTokenRead forRead(Keyspace keyspace, Token token, I
*
* There is no speculation for range read queries at present, so we never 'always speculate' here, and a failed response fails the query.
*/
public static ReplicaPlan.ForRangeRead forRangeRead(Keyspace keyspace, Index.QueryPlan indexQueryPlan, ConsistencyLevel consistencyLevel, AbstractBounds<PartitionPosition> range, int vnodeCount)
public static ReplicaPlan.ForRangeRead forRangeRead(Keyspace keyspace, Index.QueryPlan indexQueryPlan, ConsistencyLevel consistencyLevel, AbstractBounds<PartitionPosition> range, int vnodeCount, boolean allowsFiltering)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: break the long line.


private final FilterElement root;

protected RowFilter(FilterElement root)
public final boolean allowFiltering;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a mixed use of allowFiltering and allowsFiltering around. I would homogenize it, so it's easier to search.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ALLOW FILTERING has always been kind of a transversal concept while it was a CQL thing. Putting it in the RowFilter somehow feels like it finally has a clear home. I think it would be super nice to add some brief JavaDoc here saying what ALLOW FILTERING is, for the sake of newcomers.

protected RowFilter(FilterElement root)
public final boolean allowFiltering;

protected RowFilter(FilterElement root, boolean allowFitlering)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: s/allowFitlering/allowFiltering

@@ -1836,19 +1841,22 @@ public void serialize(RowFilter filter, DataOutputPlus out, int version) throws
{
out.writeBoolean(false); // Old "is for thrift" boolean
FilterElement.serializer.serialize(filter.root, out, version);
out.writeBoolean(filter.allowFiltering);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should depend on the messaging version

}

public RowFilter deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException
{
in.readBoolean(); // Unused
FilterElement operation = FilterElement.serializer.deserialize(in, version, metadata);
return new RowFilter(operation);
boolean allowFiltering = in.readBoolean();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should depend on the messaging version

}

public long serializedSize(RowFilter filter, int version)
{
return 1 // unused boolean
+ FilterElement.serializer.serializedSize(filter.root, version);
+ FilterElement.serializer.serializedSize(filter.root, version)
+ 1; // for allowFiltering

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should depend on the messaging version. Also, it's probably better to use TypeSizes.BOOL_SIZE

}

// Verify that all provided indexes belong to this group
assert indexes.size() < indices.size() && indices.containsAll(saiIndexes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simply add this to the loop above:

assert indices.contains(index) : "Index does not belong to this group";

* @return a new query plan for the specified {@link RowFilter} and {@link Index}, {@code null} otherwise
*/
@Nullable
QueryPlan queryPlanForIndices(RowFilter rowFilter, Set<Index> indexes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still thinking about how could make this the only method and get rid of queryPlanFor(RowFilter)

@@ -120,6 +120,25 @@ public Index.QueryPlan queryPlanFor(RowFilter rowFilter)
return null;
}

@Override
public Index.QueryPlan queryPlanForIndices(RowFilter rowFilter, Set<Index> indexes)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one looks like almost a duplicate of Index.QueryPlan queryPlanFor, which kinds of reinforces the idea that we could have a single signature for this.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is wrong in my opinion. This class is about single index, we can never build plan of a subset which would be 0 indexes... I have it throwing unsupported in my next changes. But I agree we may want to combine into one method, though it may be a bit confusing and overloaded.
As I mentioned - the current state is draft with main functionality for feedback - the actual polishing is in the making :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants