You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by al...@apache.org on 2026/09/28 01:34:44 UTC
(asterixdb) branch master updated: [ASTERIXDB-3900][COMP] Index-only ANN plan with a LET-bound sort key
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git
The following commit(s) were added to refs/heads/master by this push:
new 3703faab79 [ASTERIXDB-3900][COMP] Index-only ANN plan with a LET-bound sort key
3703faab79 is described below
commit 3703faab7941f2f36ddc93c7e96165b68ab52654
Author: Le0shy <le...@gmail.com>
AuthorDate: Fri Sep 25 11:49:58 2026 -0700
[ASTERIXDB-3900][COMP] Index-only ANN plan with a LET-bound sort key
- user model changes: no
- storage format changes: no
- interface changes: no
Details:
When the ORDER BY key is a variable, PushSortDownRule pushes the ORDER below the projection's ASSIGN, which leaves that ASSIGN between the LIMIT and the ORDER, outside the subtree the index-only check read its variable bindings from. A projection of nothing but the primary key then looked uncovered and the query took the lookup-and-rerank plan. The check now reads bindings from the LIMIT.
Ext-ref: MB-74258
Generated-by: Claude Fable 5.1 <no...@anthropic.com>
Change-Id: I3f367c26c079a3b85e39c6f45ed7967bb424a9df
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21906
Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
Reviewed-by: Hongyu Shi <le...@gmail.com>
Reviewed-by: Ali Alsuliman <al...@gmail.com>
Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
---
.../rules/am/IntroduceTopKAccessMethodRule.java | 6 ++--
.../create-index-vtree.13.plans.sqlpp | 31 ++++++++++++++++
.../create-index-vtree.14.query.sqlpp | 27 ++++++++++++++
.../create-index-vtree.15.plans.sqlpp | 31 ++++++++++++++++
.../create-index-vtree/create-index-vtree.13.plan | 26 ++++++++++++++
.../create-index-vtree/create-index-vtree.14.adm | 3 ++
.../create-index-vtree/create-index-vtree.15.plan | 42 ++++++++++++++++++++++
7 files changed, 163 insertions(+), 3 deletions(-)
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceTopKAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceTopKAccessMethodRule.java
index 2e283c9f9c..b1cf5f625f 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceTopKAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceTopKAccessMethodRule.java
@@ -876,9 +876,9 @@ public class IntroduceTopKAccessMethodRule extends AbstractIntroduceAccessMethod
pathContext(subTree.getRecordType(), dataRecordVar == null ? Set.of() : Set.of(dataRecordVar)),
pathContext(subTree.getMetaRecordType(), metaRecordVar == null ? Set.of() : Set.of(metaRecordVar)));
- // Collect ASSIGN bindings throughout the entire subtree.
- Map<LogicalVariable, ILogicalExpression> bindings =
- VectorIncludeFilterPushdown.collectAssignBindings(subTree.getRoot());
+ // With a variable sort key, PushSortDownRule leaves the projection's ASSIGN between the LIMIT and the
+ // ORDER, above the subtree root.
+ Map<LogicalVariable, ILogicalExpression> bindings = VectorIncludeFilterPushdown.collectAssignBindings(limitOp);
// Gather variables used above the LIMIT.
Set<LogicalVariable> liveOut = new HashSet<>();
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.13.plans.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.13.plans.sqlpp
new file mode 100644
index 0000000000..c8549791c1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.13.plans.sqlpp
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+-- param compile-only:string=true
+-- param optimized-logical-plan:string=true
+
+// CBO costs depend on the cluster shape, so it is off for a stable plan.
+SET `compiler.cbo` "false";
+
+// Step 7 with a LET-bound distance stays index-only: index-search flag true, no BTREE_SEARCH.
+FROM test.MovieSmall m
+LET dist = ann_distance(m.embedding, [0.0, 0.0, 0.0, 0.0], "euclidean", 1.0)
+SELECT VALUE m.id
+ORDER BY dist
+LIMIT 3;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.14.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.14.query.sqlpp
new file mode 100644
index 0000000000..47468eb149
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.14.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+USE test;
+
+// Step 13 executed: the index-only plan over a LET-bound distance returns the exact top-3 of step 6.
+FROM MovieSmall m
+LET dist = ann_distance(m.embedding, [0.0, 0.0, 0.0, 0.0], "euclidean", 1.0)
+SELECT VALUE m.id
+ORDER BY dist
+LIMIT 3;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.15.plans.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.15.plans.sqlpp
new file mode 100644
index 0000000000..a838d51ad5
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree/create-index-vtree.15.plans.sqlpp
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+-- param compile-only:string=true
+-- param optimized-logical-plan:string=true
+
+// CBO costs depend on the cluster shape, so it is off for a stable plan.
+SET `compiler.cbo` "false";
+
+// Step 13 also projecting the embedding, which the index does not emit: BTREE_SEARCH follows the search.
+FROM test.MovieSmall m
+LET dist = ann_distance(m.embedding, [0.0, 0.0, 0.0, 0.0], "euclidean", 1.0)
+SELECT m.id, m.embedding
+ORDER BY dist
+LIMIT 3;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.13.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.13.plan
new file mode 100644
index 0000000000..9c10d90fe2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.13.plan
@@ -0,0 +1,26 @@
+distribute result [$$40]
+-- DISTRIBUTE_RESULT |UNPARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |UNPARTITIONED|
+ limit 3
+ -- STREAM_LIMIT |UNPARTITIONED|
+ project ([$$40])
+ -- STREAM_PROJECT |PARTITIONED|
+ exchange
+ -- SORT_MERGE_EXCHANGE [$$41(ASC) ] |PARTITIONED|
+ limit 3
+ -- STREAM_LIMIT |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ order (topK: 3) (ASC, $$41)
+ -- STABLE_SORT [topK: 3] [$$41(ASC)] |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ unnest-map [$$40, $$41] <- index-search("idx_emb", 8, "Default", "test", "MovieSmall", false, false, true, 5, $$35, $$36, $$37, $$38, $$39)
+ -- VECTOR_SEARCH |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ assign [$$35, $$36, $$37, $$38, $$39] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
+ -- ASSIGN |PARTITIONED|
+ empty-tuple-source
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.14.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.14.adm
new file mode 100644
index 0000000000..01e79c32a8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.14.adm
@@ -0,0 +1,3 @@
+1
+2
+3
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.15.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.15.plan
new file mode 100644
index 0000000000..10e0d51fdc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree/create-index-vtree.15.plan
@@ -0,0 +1,42 @@
+distribute result [$$34]
+-- DISTRIBUTE_RESULT |UNPARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |UNPARTITIONED|
+ limit 3
+ -- STREAM_LIMIT |UNPARTITIONED|
+ assign [$$34] <- [{"id": $$36, "embedding": $$35}] project: [$$34]
+ -- ASSIGN |PARTITIONED|
+ project ([$$36, $$35])
+ -- STREAM_PROJECT |PARTITIONED|
+ exchange
+ -- SORT_MERGE_EXCHANGE [$$dist(ASC) ] |PARTITIONED|
+ limit 3
+ -- STREAM_LIMIT |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ order (topK: 3) (ASC, $$dist)
+ -- STABLE_SORT [topK: 3] [$$dist(ASC)] |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ assign [$$dist] <- [euclidean-distance($$35, array: [ 0.0, 0.0, 0.0, 0.0 ])]
+ -- ASSIGN |PARTITIONED|
+ assign [$$35] <- [$$m.getField(1)] project: [$$36, $$35]
+ -- ASSIGN |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ unnest-map [$$36, $$m] <- index-search("MovieSmall", 0, "Default", "test", "MovieSmall", false, false, 1, $$43, 1, $$43, true, true, true)
+ -- BTREE_SEARCH |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ order (ASC, $$43)
+ -- STABLE_SORT [$$43(ASC)] |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ unnest-map [$$43] <- index-search("idx_emb", 8, "Default", "test", "MovieSmall", false, false, false, 5, $$38, $$39, $$40, $$41, $$42)
+ -- VECTOR_SEARCH |PARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |PARTITIONED|
+ assign [$$38, $$39, $$40, $$41, $$42] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
+ -- ASSIGN |PARTITIONED|
+ empty-tuple-source
+ -- EMPTY_TUPLE_SOURCE |PARTITIONED|