-
Notifications
You must be signed in to change notification settings - Fork 3.9k
CASSANDRA-20243 Avoid fetching entire partitions on unresolved static rows in RFP when no static column predicates exist #3890
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -427,12 +427,12 @@ private void addToFetch(Row row) | |
| if (toFetch == null) | ||
| toFetch = BTreeSet.builder(command.metadata().comparator); | ||
|
|
||
| // Note that for static, we shouldn't add the clustering to the clustering set (the | ||
| // ClusteringIndexNamesFilter we'll build from this later does not expect it), but the fact | ||
| // we created a builder in the first place will act as a marker that the static row must be | ||
| // fetched, even if no other rows are added for this partition. | ||
| if (row.isStatic()) | ||
| unresolvedStatic = true; | ||
| // If there is an expression on a static column, the static row must be marked unresolved and the | ||
| // partition fetched, as completing the static row could produce matches across the entire partition. | ||
| // The static row itself will still be retrieved and completed if there is any unresolved non-static | ||
| // row, however, ensuring the latest static values are returned from the query. | ||
| unresolvedStatic = command.rowFilter().hasStaticExpression(); | ||
|
||
| else | ||
| toFetch.add(row.clustering()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,6 @@ | |
|
|
||
| import org.apache.cassandra.distributed.Cluster; | ||
| import org.apache.cassandra.distributed.api.ConsistencyLevel; | ||
| import org.apache.cassandra.distributed.shared.AssertUtils; | ||
| import org.apache.cassandra.distributed.test.TestBaseImpl; | ||
|
|
||
| import static org.apache.cassandra.distributed.api.Feature.GOSSIP; | ||
|
|
@@ -51,6 +50,26 @@ public static void setUpCluster() throws IOException | |
| CLUSTER = init(Cluster.build(2).withConfig(config -> config.set("hinted_handoff_enabled", false).with(GOSSIP).with(NETWORK)).start()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testMissingStaticRowWithNonStaticExpression() | ||
| { | ||
| CLUSTER.schemaChange(withKeyspace("CREATE TABLE %s.single_predicate (pk0 int, ck0 int, ck1 int, s0 int static, s1 int static, v0 int, PRIMARY KEY (pk0, ck0, ck1)) " + | ||
| "WITH CLUSTERING ORDER BY (ck0 ASC, ck1 DESC) AND read_repair = 'NONE'")); | ||
| CLUSTER.schemaChange(withKeyspace("CREATE INDEX ON %s.single_predicate(ck1) USING 'sai'")); | ||
| SAIUtil.waitForIndexQueryable(CLUSTER, KEYSPACE); | ||
|
|
||
| CLUSTER.get(1).executeInternal(withKeyspace("INSERT INTO %s.single_predicate (pk0, ck0, ck1, s0, s1, v0) " + | ||
| "VALUES (0, 1, 2, 3, 4, 5) USING TIMESTAMP 1")); | ||
| CLUSTER.get(2).executeInternal(withKeyspace("UPDATE %s.single_predicate USING TIMESTAMP 2 SET s0 = 6, s1 = 7, v0 = 8 " + | ||
| "WHERE pk0 = 0 AND ck0 = 9 AND ck1 = 10")); | ||
|
|
||
| // Node 2 will not produce a match for the static row. Make sure that replica filtering protection does not | ||
| // fetch the entire partition, which could let non-matching rows slip through combined with the fact that we | ||
| // don't post-filter at the coordinator with no regular column predicates in the query. | ||
| String select = withKeyspace("SELECT pk0, ck0, ck1, s0, s1 FROM %s.single_predicate WHERE ck1 = 2 ALLOW FILTERING"); | ||
| assertRows(CLUSTER.coordinator(1).execute(select, ConsistencyLevel.ALL), row(0, 1, 2, 6, 7)); | ||
|
||
| } | ||
|
|
||
| @Test | ||
| public void shouldDegradeToUnionOnSingleStatic() | ||
| { | ||
|
|
@@ -251,7 +270,7 @@ public void testTimestampCollision() | |
|
|
||
| String select = withKeyspace("SELECT * FROM %s.timestamp_collision WHERE a = 2 AND b = 2"); | ||
| Object[][] initialRows = CLUSTER.coordinator(1).execute(select, ConsistencyLevel.ALL); | ||
| assertRows(initialRows, AssertUtils.row(0, 2, 2)); | ||
| assertRows(initialRows, row(0, 2, 2)); | ||
| } | ||
|
|
||
| @Test | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.function.Consumer; | ||
| import java.util.function.Supplier; | ||
|
|
||
|
|
@@ -62,11 +63,11 @@ public abstract class SingleNodeSAITestBase extends TestBaseImpl | |
| private static final int VALIDATION_SKIP = 739; | ||
| private static final int QUERIES_PER_VALIDATION = 8; | ||
|
|
||
| private static final int FLUSH_SKIP = 1499; | ||
| private static final int COMPACTION_SKIP = 1503; | ||
| private static final int DEFAULT_REPAIR_SKIP = 6101; | ||
| private static final int FLUSH_SKIP = 2217; | ||
| private static final int COMPACTION_SKIP = 4435; | ||
| private static final int DEFAULT_REPAIR_SKIP = 8869; | ||
|
||
|
|
||
| private static final int OPERATIONS_PER_RUN = DEFAULT_REPAIR_SKIP * 5; | ||
| private static final int OPERATIONS_PER_RUN = 30_000; | ||
|
|
||
| private static final int NUM_PARTITIONS = 64; | ||
| private static final int NUM_VISITED_PARTITIONS = 16; | ||
|
|
@@ -162,27 +163,30 @@ private void saiTest(EntropySource rng, SchemaSpec schema, Supplier<Boolean> cre | |
| Generator<Integer> globalPkGen = Generators.int32(0, Math.min(NUM_PARTITIONS, schema.valueGenerators.pkPopulation())); | ||
| Generator<Integer> ckGen = Generators.int32(0, schema.valueGenerators.ckPopulation()); | ||
|
|
||
| CassandraRelevantProperties.SAI_INTERSECTION_CLAUSE_LIMIT.setInt(100); | ||
| beforeEach(); | ||
| cluster.forEach(i -> i.nodetool("disableautocompaction")); | ||
|
|
||
| cluster.schemaChange(schema.compile()); | ||
| cluster.schemaChange(schema.compile().replace(schema.keyspace + '.' + schema.table, schema.keyspace + ".debug_table")); | ||
|
|
||
| AtomicInteger indexCount = new AtomicInteger(); | ||
|
|
||
| Streams.concat(schema.clusteringKeys.stream(), schema.regularColumns.stream(), schema.staticColumns.stream()) | ||
| .forEach(column -> { | ||
| if (createIndex.get()) | ||
| { | ||
| logger.info("Adding index to column {}...", column.name); | ||
| cluster.schemaChange(String.format("CREATE INDEX %s_sai_idx ON %s.%s (%s) USING 'sai' ", | ||
| column.name, schema.keyspace, schema.table, column.name)); | ||
| column.name, schema.keyspace, schema.table, column.name)); | ||
| indexCount.incrementAndGet(); | ||
| } | ||
| else | ||
| { | ||
| logger.info("Leaving column {} unindexed...", column.name); | ||
| } | ||
| }); | ||
|
|
||
| CassandraRelevantProperties.SAI_INTERSECTION_CLAUSE_LIMIT.setInt(indexCount.get()); | ||
| waitForIndexesQueryable(schema); | ||
|
|
||
| HistoryBuilder history = new ReplayingHistoryBuilder(schema.valueGenerators, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This guardrail was designed to make sure individual column predicates don't involve index queries that hit too many SSTables rather than worrying about the total number of SSTables across all column predicates. The Harry tests can have 10+ column predicates, and that makes it much more likely we'll hit this and give in-JVM tests trouble.