This is an automated email from the ASF dual-hosted git repository.

jerry-024 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 182934c7 fix(vindex): avoid full index reads during refine (#708)
182934c7 is described below

commit 182934c7fe473806c78804c5cae439e5642d9344
Author: jerry <[email protected]>
AuthorDate: Fri Aug 14 11:23:49 2026 +0800

    fix(vindex): avoid full index reads during refine (#708)
---
 crates/paimon/src/table/vector_search_builder.rs | 105 +++++++++++++++++++++--
 1 file changed, 100 insertions(+), 5 deletions(-)

diff --git a/crates/paimon/src/table/vector_search_builder.rs 
b/crates/paimon/src/table/vector_search_builder.rs
index 1295db93..21c308bf 100644
--- a/crates/paimon/src/table/vector_search_builder.rs
+++ b/crates/paimon/src/table/vector_search_builder.rs
@@ -17,7 +17,7 @@
 
 use crate::arrow::format::FilePredicates;
 use crate::arrow::residual::{evaluate_predicates_mask, widen_scan_fields};
-use crate::io::FileIO;
+use crate::io::{FileIO, FileRead};
 use crate::lumina::reader::LuminaVectorGlobalIndexReader;
 use crate::lumina::{
     is_lumina_index_type, LuminaIndexMeta, LuminaVectorIndexOptions, 
LuminaVectorMetric,
@@ -64,6 +64,7 @@ use crate::vindex::{is_vindex_index_type, 
VindexVectorIndexOptions};
 use arrow_array::{Array, FixedSizeListArray, Float32Array, Int64Array, 
ListArray, RecordBatch};
 use arrow_select::interleave::interleave_record_batch;
 use futures::{stream, TryStreamExt};
+use paimon_vindex_core::diskann_io::DISKANN_HEADER_SIZE;
 use paimon_vindex_core::distance::MetricType;
 use paimon_vindex_core::index::VectorIndexReader as VIndexReader;
 use paimon_vindex_core::io::SeekRead;
@@ -2534,16 +2535,41 @@ async fn resolve_raw_vector_metric(
                 }
             }
             VectorIndexBackend::Vindex => {
+                if let Some(index_meta) = global_meta.index_meta.as_ref() {
+                    if let Ok(options) =
+                        serde_json::from_slice::<HashMap<String, 
String>>(index_meta)
+                    {
+                        if let Some(metric) = options.get("metric") {
+                            if let Some(metric) =
+                                
RawVectorMetric::parse_normalized(&normalize_metric(metric))
+                            {
+                                return Ok(metric);
+                            }
+                        }
+                    }
+                }
                 let path = format!("{table_path}/{INDEX_DIR}/{}", 
entry.index_file.file_name);
                 let input = file_io.new_input(&path)?;
-                let bytes = input.read().await.map_err(|e| 
crate::Error::DataInvalid {
+                let read_error = |e| crate::Error::DataInvalid {
                     message: format!(
                         "Failed to read vindex index file '{}' for raw search 
metric: {}",
                         entry.index_file.file_name, e
                     ),
-                    source: None,
-                })?;
-                let reader = 
VIndexReader::open(Cursor::new(bytes.to_vec())).map_err(|e| {
+                    source: Some(Box::new(e)),
+                };
+                let header_size = if entry.index_file.file_size > 0 {
+                    (entry.index_file.file_size as 
u64).min(DISKANN_HEADER_SIZE as u64)
+                } else {
+                    input
+                        .metadata()
+                        .await
+                        .map_err(&read_error)?
+                        .size
+                        .min(DISKANN_HEADER_SIZE as u64)
+                };
+                let file_reader = input.reader().await.map_err(&read_error)?;
+                let bytes = 
file_reader.read(0..header_size).await.map_err(read_error)?;
+                let reader = 
VIndexReader::open(Cursor::new(bytes)).map_err(|e| {
                     crate::Error::DataInvalid {
                         message: format!(
                             "Failed to open paimon-vindex-core reader for raw 
search metric: {}",
@@ -3167,6 +3193,75 @@ mod tests {
         );
     }
 
+    #[tokio::test]
+    async fn test_resolve_raw_vector_metric_uses_vindex_manifest_metadata() {
+        let file_io = FileIOBuilder::new("memory").build().unwrap();
+        let mut entry = make_lumina_entry("missing.idx", IVF_FLAT_IDENTIFIER, 
FileKind::Add, 2);
+        let index_meta = serde_json::to_vec(&HashMap::from([(
+            "metric".to_string(),
+            "cosine".to_string(),
+        )]))
+        .unwrap();
+        entry
+            .index_file
+            .global_index_meta
+            .as_mut()
+            .unwrap()
+            .index_meta = Some(index_meta);
+
+        let metric = resolve_raw_vector_metric(
+            &file_io,
+            "memory:///test_table",
+            &HashMap::new(),
+            &[entry],
+            2,
+            "embedding",
+        )
+        .await
+        .unwrap();
+
+        assert_eq!(metric, RawVectorMetric::Cosine);
+    }
+
+    #[tokio::test]
+    async fn test_resolve_raw_vector_metric_falls_back_to_vindex_header() {
+        let file_io = FileIOBuilder::new("memory").build().unwrap();
+        let index = build_vindex_segment_bytes("inner_product");
+        file_io
+            .new_output("memory:///test_table/index/test.idx")
+            .unwrap()
+            .write(bytes::Bytes::from(index.clone()))
+            .await
+            .unwrap();
+        for (file_size, index_meta) in [
+            (index.len() as i64, br#"{"metric":"euclidean"}"#.to_vec()),
+            (0, b"{}".to_vec()),
+            (-1, b"{}".to_vec()),
+        ] {
+            let mut entry = make_lumina_entry("test.idx", IVF_FLAT_IDENTIFIER, 
FileKind::Add, 2);
+            entry.index_file.file_size = file_size;
+            entry
+                .index_file
+                .global_index_meta
+                .as_mut()
+                .unwrap()
+                .index_meta = Some(index_meta);
+
+            let metric = resolve_raw_vector_metric(
+                &file_io,
+                "memory:///test_table",
+                &HashMap::new(),
+                &[entry],
+                2,
+                "embedding",
+            )
+            .await
+            .unwrap();
+
+            assert_eq!(metric, RawVectorMetric::InnerProduct);
+        }
+    }
+
     #[test]
     fn test_configured_refine_factor_precedence_and_aliases() {
         let table_options = HashMap::from([(

Reply via email to