Skip to content

Commit 6bf9c57

Browse files
committed
lower query optimizer overhead
1 parent 0675c7c commit 6bf9c57

9 files changed

Lines changed: 376 additions & 26 deletions

File tree

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/FilterSelectivityTelemetry.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ private FilterSelectivityTelemetry() {
2323
}
2424

2525
static void annotate(Filter filter, EvaluationStatistics statistics) {
26-
if (statistics == null) {
26+
if (filter == null || statistics == null) {
27+
return;
28+
}
29+
double passRatio = filter.getDoubleMetricPlanned(TelemetryMetricNames.PLANNED_FILTER_PASS_RATIO);
30+
if (isValidPassRatio(passRatio)) {
31+
annotate(filter, statistics, null);
2732
return;
2833
}
2934
annotate(filter, statistics, statistics.estimateFilterPass(filter));
@@ -35,8 +40,12 @@ static void annotate(Filter filter, EvaluationStatistics statistics,
3540
return;
3641
}
3742

38-
double passRatio = estimate == null ? -1.0d : estimate.getPassRatio();
39-
String source = estimate == null ? null : estimate.getSource().name().toLowerCase();
43+
double passRatio = filter.getDoubleMetricPlanned(TelemetryMetricNames.PLANNED_FILTER_PASS_RATIO);
44+
String source = filter.getStringMetricPlanned(TelemetryMetricNames.FILTER_SELECTIVITY_SOURCE);
45+
if (!isValidPassRatio(passRatio)) {
46+
passRatio = estimate == null ? -1.0d : estimate.getPassRatio();
47+
source = estimate == null ? null : estimate.getSource().name().toLowerCase();
48+
}
4049
if (!isValidPassRatio(passRatio)) {
4150
double cardinalityPassRatio = estimateCardinalityPassRatio(filter, statistics);
4251
if (isValidPassRatio(cardinalityPassRatio)) {

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/QueryJoinOptimizer.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,19 @@ private static int filterConditionCost(ValueExpr condition) {
169169
*/
170170
@Override
171171
public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings) {
172-
tupleExpr.visit(new JoinVisitor());
172+
QueryOptimizationScopeProvider.QueryOptimizationScope scope = beginQueryOptimizationScope();
173+
try {
174+
tupleExpr.visit(new JoinVisitor());
175+
} finally {
176+
scope.close();
177+
}
178+
}
179+
180+
private QueryOptimizationScopeProvider.QueryOptimizationScope beginQueryOptimizationScope() {
181+
if (statistics instanceof QueryOptimizationScopeProvider) {
182+
return ((QueryOptimizationScopeProvider) statistics).beginQueryOptimizationScope();
183+
}
184+
return QueryOptimizationScopeProvider.NO_OP_SCOPE;
173185
}
174186

175187
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
// Some portions generated by Codex
12+
package org.eclipse.rdf4j.query.algebra.evaluation.optimizer;
13+
14+
/**
15+
* Optional hook for statistics implementations that can share short-lived caches during one optimizer pass.
16+
*/
17+
public interface QueryOptimizationScopeProvider {
18+
19+
QueryOptimizationScope NO_OP_SCOPE = () -> {
20+
};
21+
22+
QueryOptimizationScope beginQueryOptimizationScope();
23+
24+
@FunctionalInterface
25+
interface QueryOptimizationScope extends AutoCloseable {
26+
27+
@Override
28+
void close();
29+
}
30+
}

core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/QueryJoinOptimizerTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinFactorCostModel;
5757
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinOrderPlanner;
5858
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.QueryJoinOptimizer;
59+
import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.QueryOptimizationScopeProvider;
5960
import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor;
6061
import org.eclipse.rdf4j.query.algebra.helpers.QueryModelTreeToGenericPlanNode;
6162
import org.eclipse.rdf4j.query.algebra.helpers.TupleExprs;
@@ -75,6 +76,34 @@ public class QueryJoinOptimizerTest extends QueryOptimizerTest {
7576

7677
private static final ValueFactory VF = SimpleValueFactory.getInstance();
7778

79+
@Test
80+
public void optimizerOpensOneStatisticsOptimizationScope() {
81+
ScopedStatistics statistics = new ScopedStatistics();
82+
QueryRoot root = new QueryRoot(new Join(
83+
statementPattern("s", "o1", ex("pA")),
84+
statementPattern("s", "o2", ex("pB"))));
85+
86+
new QueryJoinOptimizer(statistics, new EmptyTripleSource()).optimize(root, null, null);
87+
88+
assertEquals(1, statistics.openedScopes);
89+
assertEquals(1, statistics.closedScopes);
90+
assertEquals(0, statistics.activeScopes);
91+
assertTrue(statistics.cardinalityCalls > 0, "Expected optimizer to use statistics inside the opened scope");
92+
}
93+
94+
@Test
95+
public void filterTelemetryUsesPlannedPassRatioWithoutCardinalityFallback() {
96+
CardinalityCountingStatistics statistics = new CardinalityCountingStatistics();
97+
Filter filter = filter(statementPattern("s", "o", ex("pA")), "s", "o");
98+
filter.setDoubleMetricPlanned(TelemetryMetricNames.PLANNED_FILTER_PASS_RATIO, 0.25d);
99+
100+
new QueryJoinOptimizer(statistics, new EmptyTripleSource()).optimize(new QueryRoot(filter), null, null);
101+
102+
assertEquals(0, statistics.filterCardinalityCalls,
103+
"Existing planned filter pass ratio should avoid recursive full filter cardinality estimation");
104+
assertEquals(0.25d, filter.getDoubleMetricPlanned(TelemetryMetricNames.PLANNED_FILTER_PASS_RATIO), 0.0d);
105+
}
106+
78107
@Test
79108
public void testBindingSetAssignmentOptimization() throws RDF4JException {
80109
String query = "prefix ex: <ex:>" + "select ?s ?p ?o ?x where {" + " ex:s1 ex:pred ?v. "
@@ -1350,6 +1379,42 @@ private String predicate(TupleExpr expr) {
13501379
}
13511380
}
13521381

1382+
private static final class ScopedStatistics extends EvaluationStatistics implements QueryOptimizationScopeProvider {
1383+
private int openedScopes;
1384+
private int closedScopes;
1385+
private int activeScopes;
1386+
private int cardinalityCalls;
1387+
1388+
@Override
1389+
public QueryOptimizationScope beginQueryOptimizationScope() {
1390+
openedScopes++;
1391+
activeScopes++;
1392+
return () -> {
1393+
closedScopes++;
1394+
activeScopes--;
1395+
};
1396+
}
1397+
1398+
@Override
1399+
public double getCardinality(TupleExpr expr) {
1400+
cardinalityCalls++;
1401+
assertTrue(activeScopes > 0, "Cardinality estimation should run inside the optimization scope");
1402+
return super.getCardinality(expr);
1403+
}
1404+
}
1405+
1406+
private static final class CardinalityCountingStatistics extends EvaluationStatistics {
1407+
private int filterCardinalityCalls;
1408+
1409+
@Override
1410+
public double getCardinality(TupleExpr expr) {
1411+
if (expr instanceof Filter) {
1412+
filterCardinalityCalls++;
1413+
}
1414+
return super.getCardinality(expr);
1415+
}
1416+
}
1417+
13531418
private static final class ParsedQueryPairwiseJoinStatistics extends EvaluationStatistics {
13541419
private final Map<String, Double> joinCosts;
13551420

0 commit comments

Comments
 (0)