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|