Skip to content

Commit 1729cba

Browse files
author
Piotr Kołaczkowski
committed
CNDB-15508: Address review comments
1 parent 52e877c commit 1729cba

File tree

5 files changed

+80
-75
lines changed

5 files changed

+80
-75
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,13 @@ public enum CassandraRelevantProperties
629629
*/
630630
SAI_QUERY_KIND_PER_QUERY_METRICS_ENABLED("cassandra.sai.metrics.query_kind.per_query.enabled", "false"),
631631

632+
/**
633+
* Whether to enable SAI query plan metrics such as the estimated cost, estimated number of rows,
634+
* number of indexes used in the original and optimized query plan, etc.
635+
* These metrics are counters and histograms.
636+
*/
637+
SAI_QUERY_PLAN_METRICS_ENABLED("cassandra.sai.metrics.query_plan.enabled", "true"),
638+
632639
/**
633640
* Whether to enable SAI index metrics such as memtable flush metrics, compaction metrics, and disk usage metrics.
634641
* These metrics include timers, histograms, counters, and gauges for index operations.

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

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import com.google.common.annotations.VisibleForTesting;
2828

29+
import org.apache.cassandra.config.CassandraRelevantProperties;
2930
import org.apache.cassandra.config.DatabaseDescriptor;
3031
import org.apache.cassandra.index.sai.plan.Plan;
3132
import org.apache.cassandra.index.sai.utils.AbortedOperationException;
@@ -69,10 +70,7 @@ public class QueryContext
6970

7071
private final LongAdder shadowedPrimaryKeyCount = new LongAdder();
7172

72-
private Plan originalPlan = null;
73-
74-
75-
private Plan optimizedPlan = null;
73+
private PlanInfo queryPlanInfo;
7674

7775
@VisibleForTesting
7876
public QueryContext()
@@ -207,18 +205,10 @@ public long annGraphSearchLatency()
207205
return annGraphSearchLatency.longValue();
208206
}
209207

210-
/** Can return null when query plan hasn't been prepared and optimized yet */
211-
@Nullable
212-
public Plan optimizedPlan()
213-
{
214-
return optimizedPlan;
215-
}
216-
217-
/** Can return null when query plan hasn't been prepared yet */
218208
@Nullable
219-
public Plan originalPlan()
209+
public PlanInfo queryPlanInfo()
220210
{
221-
return originalPlan;
211+
return queryPlanInfo;
222212
}
223213

224214
public void checkpoint()
@@ -254,14 +244,10 @@ public void updateAnnRerankFloor(float observedFloor)
254244
annRerankFloor = max(annRerankFloor, observedFloor);
255245
}
256246

257-
public void setOriginalPlan(Plan originalPlan)
247+
public void recordQueryPlan(Plan.RowsIteration originalPlan, Plan.RowsIteration.RowsIteration optimizedPlan)
258248
{
259-
this.originalPlan = originalPlan;
260-
}
261-
262-
public void setOptimizedPlan(Plan optimizedPlan)
263-
{
264-
this.optimizedPlan = optimizedPlan;
249+
if (CassandraRelevantProperties.SAI_QUERY_PLAN_METRICS_ENABLED.getBoolean())
250+
this.queryPlanInfo = new PlanInfo(originalPlan, optimizedPlan);
265251
}
266252

267253
public Snapshot snapshot()
@@ -297,7 +283,7 @@ public static class Snapshot
297283
public final long shadowedPrimaryKeyCount;
298284

299285
@Nullable
300-
public final QueryPlanInfo queryPlanInfo;
286+
public final PlanInfo queryPlanInfo;
301287

302288
/**
303289
* Creates a snapshot of all the metrics in the given {@link QueryContext}.
@@ -322,40 +308,34 @@ private Snapshot(QueryContext context)
322308
queryTimeouts = context.queryTimeouts();
323309
annGraphSearchLatency = context.annGraphSearchLatency();
324310
shadowedPrimaryKeyCount = context.getShadowedPrimaryKeyCount();
325-
326-
Plan originalPlan = context.originalPlan();
327-
Plan optimizedPlan = context.optimizedPlan();
328-
if (originalPlan != null && optimizedPlan != null) {
329-
queryPlanInfo = new QueryPlanInfo(originalPlan, optimizedPlan);
330-
} else {
331-
queryPlanInfo = null;
332-
}
311+
queryPlanInfo = context.queryPlanInfo();
333312
}
313+
}
314+
315+
/**
316+
* Captures relevant information about a query plan, both original and optimized.
317+
*/
318+
public static class PlanInfo
319+
{
320+
public final boolean searchExecutedBeforeOrder;
321+
public final boolean filterExecutedAfterOrderedScan;
334322

335-
public static class QueryPlanInfo
323+
public final double rowsEstimated;
324+
public final double selectivityEstimated;
325+
public final double costEstimated;
326+
327+
public final int indexReferencesInQuery;
328+
public final int indexReferencesInPlan;
329+
330+
public PlanInfo(@Nonnull Plan.RowsIteration originalPlan, @Nonnull Plan.RowsIteration optimizedPlan)
336331
{
337-
public final boolean searchExecutedBeforeOrder;
338-
public final boolean filterExecutedAfterOrderedScan;
339-
340-
public final double rowsEstimated;
341-
public final double selectivityEstimated;
342-
public final double costEstimated;
343-
344-
public final int indexReferencesInQuery;
345-
public final int indexReferencesInPlan;
346-
347-
public QueryPlanInfo(@Nonnull Plan originalPlan, @Nonnull Plan optimizedPlan)
348-
{
349-
this.costEstimated = optimizedPlan.fullCost();
350-
this.rowsEstimated = optimizedPlan.expectedRows();
351-
this.selectivityEstimated = optimizedPlan.selectivity();
352-
this.indexReferencesInQuery = originalPlan.referencedIndexCount();
353-
this.indexReferencesInPlan = optimizedPlan.referencedIndexCount();
354-
this.searchExecutedBeforeOrder = optimizedPlan.isSearchThenOrderHybrid();
355-
this.filterExecutedAfterOrderedScan = optimizedPlan.isOrderedScanThenFilterHybrid();
356-
}
332+
this.costEstimated = optimizedPlan.fullCost();
333+
this.rowsEstimated = optimizedPlan.expectedRows();
334+
this.selectivityEstimated = optimizedPlan.selectivity();
335+
this.indexReferencesInQuery = originalPlan.referencedIndexCount();
336+
this.indexReferencesInPlan = optimizedPlan.referencedIndexCount();
337+
this.searchExecutedBeforeOrder = optimizedPlan.isSearchThenOrderHybrid();
338+
this.filterExecutedAfterOrderedScan = optimizedPlan.isOrderedScanThenFilterHybrid();
357339
}
358-
359340
}
360-
361341
}

src/java/org/apache/cassandra/index/sai/metrics/TableQueryMetrics.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public void record(QueryContext.Snapshot snapshot)
233233
totalPartitionReads.inc(snapshot.partitionsRead);
234234
totalRowsFiltered.inc(snapshot.rowsFiltered);
235235

236-
QueryContext.Snapshot.QueryPlanInfo queryPlanInfo = snapshot.queryPlanInfo;
236+
QueryContext.PlanInfo queryPlanInfo = snapshot.queryPlanInfo;
237237
if (queryPlanInfo != null)
238238
{
239239
totalCostEstimated.inc(Math.round(queryPlanInfo.costEstimated));
@@ -381,7 +381,7 @@ public void record(QueryContext.Snapshot snapshot)
381381

382382
shadowedKeysScannedHistogram.update(snapshot.shadowedPrimaryKeyCount);
383383

384-
QueryContext.Snapshot.QueryPlanInfo queryPlanInfo = snapshot.queryPlanInfo;
384+
QueryContext.PlanInfo queryPlanInfo = snapshot.queryPlanInfo;
385385
if (queryPlanInfo != null)
386386
{
387387
costEstimated.update(Math.round(queryPlanInfo.costEstimated));

src/java/org/apache/cassandra/index/sai/plan/Plan.java

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.apache.cassandra.db.filter.IndexHints;
3737
import org.apache.cassandra.db.filter.RowFilter;
3838
import org.apache.cassandra.index.sai.IndexContext;
39-
import org.apache.cassandra.index.sai.QueryContext;
4039
import org.apache.cassandra.index.sai.disk.format.Version;
4140
import org.apache.cassandra.index.sai.iterators.KeyRangeIntersectionIterator;
4241
import org.apache.cassandra.index.sai.iterators.KeyRangeIterator;
@@ -395,7 +394,7 @@ protected void visitIndexes(Consumer<IndexContext> consumer)
395394
* and recomputes the nodes above it. Then it returns the best plan from candidates obtained that way.
396395
* The expected running time is proportional to the height of the plan tree multiplied by the number of the leaves.
397396
*/
398-
public final Plan optimize()
397+
protected Plan optimize()
399398
{
400399
if (logger.isTraceEnabled())
401400
logger.trace("Optimizing plan:\n{}", toRedactedStringRecursive());
@@ -428,7 +427,7 @@ public final Plan optimize()
428427
* Modifies all intersections to not intersect more clauses than the given limit.
429428
* Retains the most selective clauses.
430429
*/
431-
public final Plan limitIntersectedClauses(int clauseLimit)
430+
protected Plan limitIntersectedClauses(int clauseLimit)
432431
{
433432
Plan result = this;
434433
if (result instanceof Intersection)
@@ -529,19 +528,6 @@ public final double selectivity()
529528
return selectivity;
530529
}
531530

532-
/**
533-
* Returns the number of rows produced by this plan.
534-
* This can be only called on plans producing rows.
535-
*/
536-
public final double expectedRows()
537-
{
538-
Cost cost = cost();
539-
if (!(cost instanceof RowsIterationCost))
540-
throw new UnsupportedOperationException("Expected rows is only supported for plans returning rows (called on " + this.getClass() + ')');
541-
542-
return ((RowsIterationCost) cost).expectedRows;
543-
}
544-
545531
/**
546532
* Returns the number of indexes referenced by this plan.
547533
*/
@@ -729,6 +715,18 @@ final double costPerKey()
729715
return cost().costPerKey();
730716
}
731717

718+
@Override
719+
public final KeysIteration optimize()
720+
{
721+
return (KeysIteration) super.optimize();
722+
}
723+
724+
@Override
725+
public final KeysIteration limitIntersectedClauses(int clauseLimit)
726+
{
727+
return (KeysIteration) super.limitIntersectedClauses(clauseLimit);
728+
}
729+
732730
protected abstract boolean usesIncludedIndex();
733731
}
734732

@@ -1639,6 +1637,26 @@ final double costPerRow()
16391637
{
16401638
return cost().costPerRow();
16411639
}
1640+
1641+
/**
1642+
* Returns the number of rows produced by this plan.
1643+
*/
1644+
public final double expectedRows()
1645+
{
1646+
return cost().expectedRows;
1647+
}
1648+
1649+
@Override
1650+
public final RowsIteration optimize()
1651+
{
1652+
return (RowsIteration) super.optimize();
1653+
}
1654+
1655+
@Override
1656+
public final RowsIteration limitIntersectedClauses(int clauseLimit)
1657+
{
1658+
return (RowsIteration) super.limitIntersectedClauses(clauseLimit);
1659+
}
16421660
}
16431661

16441662
/**

src/java/org/apache/cassandra/index/sai/plan/QueryController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,14 +394,14 @@ Plan buildPlan()
394394
// in which predicates it leaves in the plan and the probability of accidentally removing a good branch
395395
// here is even lower.
396396
int intersectionClauseLimit = CassandraRelevantProperties.SAI_INTERSECTION_CLAUSE_LIMIT.getInt();
397-
Plan plan = rowsIteration.limitIntersectedClauses(intersectionClauseLimit * 3);
398-
queryContext.setOriginalPlan(plan);
397+
Plan.RowsIteration origPlan = rowsIteration.limitIntersectedClauses(intersectionClauseLimit * 3);
398+
Plan.RowsIteration plan = origPlan;
399399

400400
if (QUERY_OPT_LEVEL > 0)
401-
plan = plan.optimize();
401+
plan = origPlan.optimize();
402402

403403
plan = plan.limitIntersectedClauses(intersectionClauseLimit);
404-
queryContext.setOptimizedPlan(plan);
404+
queryContext.recordQueryPlan(origPlan, plan);
405405
updateIndexMetricsQueriesCount(plan);
406406

407407
if (logger.isTraceEnabled())

0 commit comments

Comments
 (0)