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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new d4920972ab [index] Update paimon-vindex to 0.3.0 (#8919)
d4920972ab is described below

commit d4920972ab322fa65bdce37bc61062fb1adefd4d
Author: Jingsong Lee <[email protected]>
AuthorDate: Thu Jul 30 13:10:36 2026 +0800

    [index] Update paimon-vindex to 0.3.0 (#8919)
---
 docs/docs/flink/procedures.md                      |   2 +-
 docs/docs/multimodal-table/global-index.mdx        |   2 +-
 .../global-index/hybrid-search.mdx                 |   2 +-
 docs/docs/multimodal-table/global-index/vector.mdx | 113 ++++++++++-----------
 docs/docs/primary-key-table/global-index.mdx       |   2 +-
 docs/docs/program-api/file-cache.mdx               |   2 +-
 paimon-python/dev/requirements-dev.txt             |   2 +-
 paimon-python/dev/run_mixed_tests.sh               |   6 +-
 .../vindex/vindex_vector_global_index_reader.py    |  48 ++++++---
 .../vindex/vindex_vector_index_writer.py           | 104 +++++++++++++++++--
 .../pypaimon/tests/global_index_build_test.py      |  83 +++++++++++++--
 .../pypaimon/tests/vindex_vector_index_test.py     |  51 +++++++---
 paimon-python/setup.py                             |   2 +-
 paimon-vector/pom.xml                              |   2 +-
 ...java => DiskAnnVectorGlobalIndexerFactory.java} |   6 +-
 ...y.java => IvfRqVectorGlobalIndexerFactory.java} |   6 +-
 ...y.java => IvfSqVectorGlobalIndexerFactory.java} |   6 +-
 .../index/NativeVectorGlobalIndexReader.java       |  64 ++++++++----
 .../index/NativeVectorGlobalIndexWriter.java       |  17 +++-
 .../vector/index/NativeVectorGlobalIndexer.java    |   2 +-
 .../index/NativeVectorGlobalIndexerFactory.java    |  38 ++++++-
 .../vector/index/NativeVectorIndexLoader.java      | 112 --------------------
 ....apache.paimon.globalindex.GlobalIndexerFactory |   5 +-
 .../vector/index/NativeVectorGlobalIndexTest.java  |  88 +++++++++++++---
 .../NativeVectorGlobalIndexerFactoryTest.java      |  61 +++++++++--
 25 files changed, 539 insertions(+), 287 deletions(-)

diff --git a/docs/docs/flink/procedures.md b/docs/docs/flink/procedures.md
index 927bfe231b..d51423128c 100644
--- a/docs/docs/flink/procedures.md
+++ b/docs/docs/flink/procedures.md
@@ -1060,7 +1060,7 @@ All available procedures are listed below.
          To create a global index on a table for accelerating queries. 
Arguments:
             <li>table(required): the target table identifier.</li>
             <li>index_column(required): the column name to build index on.</li>
-            <li>index_type(required): the type of global index, supported 
types include 'btree', 'bitmap', 'ivf-flat', 'ivf-pq', 'ivf-hnsw-flat', 
'ivf-hnsw-sq', 'full-text'.</li>
+            <li>index_type(required): the type of global index, supported 
types include 'btree', 'bitmap', 'ivf-flat', 'ivf-pq', 'ivf-sq', 'ivf-rq', 
'diskann', and 'full-text'.</li>
             <li>partitions(optional): partition filter for selective index 
creation.</li>
             <li>options(optional): additional dynamic options for index 
creation.</li>
       </td>
diff --git a/docs/docs/multimodal-table/global-index.mdx 
b/docs/docs/multimodal-table/global-index.mdx
index 54aa978f1a..26ca42cc14 100644
--- a/docs/docs/multimodal-table/global-index.mdx
+++ b/docs/docs/multimodal-table/global-index.mdx
@@ -158,7 +158,7 @@ added_files = table.create_global_index(
 </Tabs>
 
 PyPaimon global index build currently supports single-column BTree indexes,
-single-column Bitmap indexes, single-column paimon-vindex IVF vector indexes,
+single-column Bitmap indexes, single-column paimon-vindex vector indexes,
 and single-column full-text indexes on tables with row tracking enabled.
 
 Drop index files:
diff --git a/docs/docs/multimodal-table/global-index/hybrid-search.mdx 
b/docs/docs/multimodal-table/global-index/hybrid-search.mdx
index 4d8192e3ab..f767488927 100644
--- a/docs/docs/multimodal-table/global-index/hybrid-search.mdx
+++ b/docs/docs/multimodal-table/global-index/hybrid-search.mdx
@@ -79,7 +79,7 @@ The second argument is an array of vector route configs 
created by `named_struct
 | `query_vector` | Yes | N/A | Query vector for this route. |
 | `limit` | No | Final limit | Top K results to retrieve from this vector 
column before ranking. |
 | `weight` | No | `1.0` | Weight for this route when ranking results. |
-| `options` | No | Empty map | Route-specific vector search options such as 
`ivf.nprobe` and `hnsw.ef_search`. |
+| `options` | No | Empty map | Route-specific vector search options such as 
`ivf.nprobe` and `diskann.l_search`. |
 
 The third argument is an array of full-text route configs created by 
`named_struct`:
 
diff --git a/docs/docs/multimodal-table/global-index/vector.mdx 
b/docs/docs/multimodal-table/global-index/vector.mdx
index d29d076769..aec9ab0cef 100644
--- a/docs/docs/multimodal-table/global-index/vector.mdx
+++ b/docs/docs/multimodal-table/global-index/vector.mdx
@@ -36,19 +36,31 @@ Supported vector index types:
 |---|---|
 | `ivf-flat` | IVF index with flat vector storage. |
 | `ivf-pq` | IVF index with product quantization. |
-| `ivf-hnsw-flat` | IVF index with HNSW flat quantizer. |
-| `ivf-hnsw-sq` | IVF index with HNSW scalar quantizer. |
-| `lumina` | Lumina DiskANN-based vector index. |
+| `ivf-sq` | IVF index with 8-bit scalar-quantized residuals. |
+| `ivf-rq` | IVF index with rotated residual quantization. |
+| `diskann` | paimon-vindex DiskANN index with graph traversal and persisted 
rerank vectors. |
 
 Choose the index type based on the trade-off you want:
 
 | Index Type | Best For |
 |---|---|
 | `ivf-flat` | Highest recall among IVF variants when storage and memory are 
acceptable. |
-| `ivf-pq` | Smaller index files and a balanced recall, latency, and storage 
trade-off. |
-| `ivf-hnsw-flat` | Better recall inside IVF partitions with raw vector 
storage. |
-| `ivf-hnsw-sq` | HNSW search quality with scalar quantization to reduce index 
size. |
-| `lumina` | Large-scale ANN search with DiskANN graph indexing and 
configurable `rawf32`, `sq8`, or `pq` encodings. |
+| `ivf-pq` | The smallest IVF files when stronger quantization loss is 
acceptable. |
+| `ivf-sq` | High compact-index throughput with one byte per vector dimension. 
|
+| `ivf-rq` | Higher compact-IVF recall when additional scan work is 
acceptable. |
+| `diskann` | High-recall immutable collections served from local SSD or a 
complete local cache. |
+
+See the [paimon-vindex 
documentation](https://paimon.apache.org/docs/vector-index/) for its index
+selection guidance, storage architecture, and native API details.
+
+:::warning Upgrading paimon-vindex indexes to 0.3.0
+
+paimon-vindex 0.3.0 does not read the experimental `ivf-hnsw-flat` and 
`ivf-hnsw-sq` files written
+by 0.2.x. Rebuild those indexes as `ivf-flat`, `ivf-pq`, `ivf-sq`, `ivf-rq`, 
or `diskann` before
+removing the old runtime. Keep the source vectors or a 0.2-compatible index 
copy until the upgrade
+is accepted.
+
+:::
 
 ## Build Vector Index
 
@@ -62,15 +74,15 @@ CALL sys.create_global_index(
     table => 'db.my_table',
     index_column => 'embedding',
     index_type => 'ivf-pq',
-    options => 'ivf-pq.distance.metric=cosine,ivf-pq.nlist=256,ivf-pq.pq.m=16'
+    options => 
'ivf-pq.distance.metric=cosine,ivf-pq.nlist=256,ivf-pq.pq.code-ratio=0.0625'
 );
 
--- Create Lumina DiskANN vector index on 'embedding' column
+-- Create a paimon-vindex DiskANN vector index
 CALL sys.create_global_index(
     table => 'db.my_table',
     index_column => 'embedding',
-    index_type => 'lumina',
-    options => 
'lumina.index.dimension=768,lumina.distance.metric=l2,lumina.encoding.type=sq8'
+    index_type => 'diskann',
+    options => 
'diskann.dimension=768,diskann.distance.metric=l2,diskann.build-preset=balanced'
 );
 ```
 
@@ -89,7 +101,7 @@ added_files = table.create_global_index(
         "ivf-pq.dimension": "768",
         "ivf-pq.distance.metric": "cosine",
         "ivf-pq.nlist": "256",
-        "ivf-pq.pq.m": "16",
+        "ivf-pq.pq.code-ratio": "0.0625",
     },
 )
 print(added_files)
@@ -111,22 +123,16 @@ added_files = table.create_global_index(
 print(added_files)
 ```
 
-PyPaimon vector index build currently supports the paimon-vindex IVF 
identifiers:
-`ivf-flat`, `ivf-pq`, `ivf-hnsw-flat`, and `ivf-hnsw-sq`. Use SQL to build
-Lumina indexes. Install `paimon-vindex` or `pypaimon[vindex]` before building
-or querying IVF vector indexes from Python.
+PyPaimon vector index build supports the paimon-vindex 0.3.0 identifiers 
`ivf-flat`, `ivf-pq`,
+`ivf-sq`, `ivf-rq`, and `diskann`. Install `paimon-vindex==0.3.0` or 
`pypaimon[vindex]` before
+building or querying paimon-vindex indexes from Python.
 
 </TabItem>
 
 </Tabs>
 
-Use `index_type => 'lumina'` for new Lumina indexes. The legacy 
`lumina-vector-ann` identifier is
-kept only so existing tables can still load old indexes.
-
 For `ARRAY<FLOAT>` vector columns, specify the vector dimension with 
`<index-type>.dimension` for
-IVF indexes or `lumina.index.dimension` for Lumina indexes. For 
`VECTOR<FLOAT>` columns, Paimon uses
-the dimension from the column type. When `lumina.index.dimension` is 
explicitly set for a
-`VECTOR<FLOAT>` column, it must match the vector type length.
+paimon-vindex indexes. For `VECTOR<FLOAT>` columns, Paimon uses the dimension 
from the column type.
 
 ## Drop Vector Index
 
@@ -170,41 +176,36 @@ print(matched_files)
 
 </Tabs>
 
-Supported IVF vector index options:
+Supported paimon-vindex options:
 
 | Option | Default | Description |
 |---|---|---|
 | `<index-type>.dimension` | `128` | Vector dimension for `ARRAY<FLOAT>` 
columns. Ignored for `VECTOR<FLOAT>` columns. |
 | `<index-type>.distance.metric` | `inner_product` | Distance metric. 
Supported values: `l2`, `cosine`, `inner_product`. |
 | `<index-type>.train.sample-ratio` | `1.0` | Ratio of vectors sampled for 
native index training. Must be greater than `0` and less than or equal to `1`. 
Lower values reduce training memory and build cost, but may reduce index 
quality. |
-| `<index-type>.nlist` | `256` | Number of IVF clusters used during index 
build. Higher values create more partitions and can improve recall for large 
datasets, but may increase build cost. |
-| `<index-type>.pq.m` | `16` | Number of PQ sub-vectors for `ivf-pq`. The 
vector dimension must be divisible by this value. Higher values usually improve 
recall with larger index files. |
-| `<index-type>.pq.use-opq` | `false` | Whether to enable OPQ for `ivf-pq`. |
-| `<index-type>.hnsw.m` | `20` | HNSW graph out-degree for `ivf-hnsw-flat` and 
`ivf-hnsw-sq`. |
-| `<index-type>.hnsw.ef-construction` | `150` | HNSW construction search width 
for `ivf-hnsw-flat` and `ivf-hnsw-sq`. |
-| `<index-type>.hnsw.max-level` | `7` | Maximum HNSW level for `ivf-hnsw-flat` 
and `ivf-hnsw-sq`. |
+| `<index-type>.nlist` | Automatic | Number of clusters for the four IVF 
types. When omitted, paimon-vindex resolves it from the shard's non-null vector 
count. |
+| `<index-type>.pq.code-ratio` | `0.0625` | Relative PQ-code budget for 
`ivf-pq` and `diskann`. |
+| `<index-type>.pq.m` | Automatic | Expert override for the PQ sub-vector 
count used by `ivf-pq` and `diskann`. |
+| `ivf-pq.pq.use-opq` | Automatic | Explicitly enables or disables OPQ. 
Without an explicit value, a `target-recall` of at least `0.9` enables it. |
+| `ivf-rq.rq.bits` | `4` | Persisted IVF-RQ residual width. Supported values 
are `1` through `8`; changing it requires rebuilding the index. |
+| `<index-type>.target-recall` | Not set | Build-policy hint used by `ivf-pq` 
and `diskann`. Validate the resulting recall on held-out queries. |
+| `<index-type>.max-bytes-per-vector` | Not set | Storage objective and 
conservative preflight bound for `ivf-pq`, `ivf-rq`, and `diskann`. |
 
-Supported Lumina vector index options:
+Additional paimon-vindex DiskANN build options:
 
 | Option | Default | Description |
 |---|---|---|
-| `lumina.index.dimension` | `128` | Vector dimension for `ARRAY<FLOAT>` 
columns. For `VECTOR<FLOAT>` columns, an explicitly configured value must match 
the type length. |
-| `lumina.distance.metric` | `inner_product` | Distance metric. Supported 
values: `l2`, `cosine`, `inner_product`. |
-| `lumina.index.type` | `diskann` | Lumina index type. Currently supports 
DiskANN. |
-| `lumina.encoding.type` | `pq` | Vector encoding type. Supported values: 
`rawf32`, `sq8`, `pq`. |
-| `lumina.pretrain.sample_ratio` | `0.2` | Sample ratio used for pretraining. |
-| `lumina.diskann.build.ef_construction` | `1024` | Size of the dynamic 
candidate list during DiskANN graph construction. |
-| `lumina.diskann.build.neighbor_count` | `64` | Maximum number of neighbors 
per node in the DiskANN graph. |
-| `lumina.diskann.build.thread_count` | `32` | Number of threads used for 
DiskANN index building. |
-| `lumina.diskann.search.list_size` | unset; search uses `max(1.5x top_k, 16)` 
| Default DiskANN search list size used when no query value is supplied. |
-| `lumina.diskann.search.beam_width` | `4` | Beam width for DiskANN search. |
-| `lumina.encoding.pq.m` | `64` | Number of sub-quantizers for PQ encoding. It 
is capped to the vector dimension when larger than the dimension. |
-| `lumina.search.parallel_number` | `5` | Parallel number for Lumina search. |
-
-Lumina PQ encoding does not support the `cosine` distance metric. Use `rawf32` 
or `sq8` encoding
-with `cosine`, or use `l2` or `inner_product` with `pq`.
-
-The Lumina native library is currently available only on x86_64 (AMD64) 
architecture.
+| `diskann.build-preset` | `balanced` | Coherent `fast_build`, `balanced`, or 
`high_recall` build policy. |
+| `diskann.deployment-profile` | Not set | Deployment objective used to choose 
a storage layout. |
+| `diskann.pq.bits` | `8` | Resident PQ-code width. Supported values are `4` 
and `8`. |
+| `diskann.max-degree` | `64` | Maximum graph out-degree. |
+| `diskann.build-search-list-size` | `max(100, max-degree)` | Candidate width 
during graph construction. |
+| `diskann.alpha` | `1.2` | Robust-prune threshold. |
+| `diskann.seed` | `42` | Reproducible initialization and build-order seed. |
+| `diskann.memory-budget-bytes` | `8 GiB` | Internal graph-build memory 
estimate used to select normal or sharded construction. |
+| `diskann.storage-layout` | `auto` | Explicit `compact` or `interleaved` 
layout override. |
+| `diskann.raw-vector-encoding` | `auto` | Explicit `f32` or `f16` persisted 
rerank-vector encoding. |
+| `diskann.build-distance` | `auto` | Explicit product-quantized or 
full-precision build traversal override. |
 
 ## Per-Field Options
 
@@ -278,27 +279,19 @@ catalog.create_table("db.my_table", schema, 
ignore_if_exists=False)
 With the properties above, `title_embedding` is indexed with `nlist=256` while 
`image_embedding`
 uses `nlist=512` and trains with half of the non-null vectors.
 
-Lumina uses the same field-level convention. For example, 
`fields.image_embedding.distance.metric`
-overrides `lumina.distance.metric` for `image_embedding`, and
-`fields.image_embedding.index.dimension` overrides `lumina.index.dimension`.
-
 ## Vector Search
 
 Search-time options are passed with each vector search request:
 
 | Option | Default | Description |
 |---|---|---|
-| `ivf.nprobe` | `16` | Number of IVF clusters to probe during search. Higher 
values usually improve recall but increase latency. |
-| `ivf.refine_factor` | Disabled | Retrieves `top_k * refine_factor` IVF 
candidates and reranks them with the original vectors stored in the Paimon 
table. It is disabled by default for every IVF variant and is most useful for 
compressed indexes such as `ivf-pq` and `ivf-hnsw-sq` when recall is more 
important than latency. |
-| `hnsw.ef_search` | `0` | HNSW search width during search. Higher values 
usually improve recall but increase latency. `0` uses the native library 
default. |
-| `diskann.search.list_size` | `max(1.5x top_k, 16)` | Lumina DiskANN search 
list size. Higher values usually improve recall but increase latency. |
-| `diskann.search.beam_width` | `4` | Lumina DiskANN search beam width. |
-| `search.parallel_number` | `5` | Lumina search parallel number. |
+| `ivf.nprobe` | Automatic | Explicit number of IVF clusters to probe. When 
omitted, paimon-vindex derives the width from the index, `top_k`, and filter 
selectivity. |
+| `ivf.refine_factor` | Disabled | Retrieves `top_k * refine_factor` IVF 
candidates and reranks them with the original vectors stored in the Paimon 
table. It is most useful for compressed indexes such as `ivf-pq`, `ivf-sq`, and 
`ivf-rq` when recall is more important than latency. |
+| `diskann.l_search` | Automatic | paimon-vindex DiskANN graph candidate 
width. The automatic value uses calibration when available, otherwise `max(100, 
2 * top_k)`. |
 
 Use the same distance metric at build time and query time. Search options can 
be passed per query,
-so you can use a larger `ivf.nprobe` or `hnsw.ef_search` for higher recall 
queries and a smaller
-value for latency-sensitive queries. Lumina query-time options use the native 
keys shown above; when
-the same options are configured as table or index options, use the `lumina.` 
prefix.
+so you can use a larger `ivf.nprobe` or `diskann.l_search` for higher recall 
queries and a smaller
+value for latency-sensitive queries. Do not set both in one query.
 
 `ivf.refine_factor` can also be configured with `refine_factor`, 
`rerank_factor`, and hyphenated
 spellings such as `ivf.refine-factor`. Setting `ivf.refine_factor=1` still 
performs the raw-vector
diff --git a/docs/docs/primary-key-table/global-index.mdx 
b/docs/docs/primary-key-table/global-index.mdx
index 04008c2db5..db6ebe00d1 100644
--- a/docs/docs/primary-key-table/global-index.mdx
+++ b/docs/docs/primary-key-table/global-index.mdx
@@ -225,7 +225,7 @@ schema validation.
 | Option | Default | Description |
 |---|---|---|
 | `pk-vector.index.columns` | Not set | Vector column to index. Exactly one 
vector column is currently supported. |
-| `fields.<column>.pk-vector.index.type` | Required | ANN implementation, such 
as `ivf-flat`, `ivf-pq`, `ivf-hnsw-flat`, `ivf-hnsw-sq`, or `lumina`. |
+| `fields.<column>.pk-vector.index.type` | Required | ANN implementation, such 
as `ivf-flat`, `ivf-pq`, `ivf-sq`, `ivf-rq`, or `diskann`. |
 | `fields.<column>.pk-vector.distance.metric` | `inner_product` | Distance 
metric: `l2`, `cosine`, or `inner_product`. |
 | `fields.<column>.pk-vector.index.options` | Not set | JSON object containing 
build options for the selected ANN implementation. |
 | `pk-full-text.index.columns` | Not set | Character column to index. Exactly 
one full-text column is currently supported. |
diff --git a/docs/docs/program-api/file-cache.mdx 
b/docs/docs/program-api/file-cache.mdx
index cbed97f57c..eff163ada8 100644
--- a/docs/docs/program-api/file-cache.mdx
+++ b/docs/docs/program-api/file-cache.mdx
@@ -40,7 +40,7 @@ The cache classifies files by type. By default, only `meta` 
and `global-index` t
 | File Type | Config Name | Examples | Default Cached |
 |-----------|-------------|----------|----------------|
 | META | meta | snapshot, schema, manifest, statistics, tag | Yes |
-| GLOBAL_INDEX | global-index | BTree, Lumina, full-text index files | Yes |
+| GLOBAL_INDEX | global-index | BTree and full-text index files | Yes |
 | BUCKET_INDEX | bucket-index | Hash, deletion vector index files | No |
 | DATA | data | Data files (ORC, Parquet, etc.) | No |
 | FILE_INDEX | file-index | Data-file level bloom filter, bitmap | No |
diff --git a/paimon-python/dev/requirements-dev.txt 
b/paimon-python/dev/requirements-dev.txt
index 4092c5158e..ed4181ffed 100644
--- a/paimon-python/dev/requirements-dev.txt
+++ b/paimon-python/dev/requirements-dev.txt
@@ -33,6 +33,6 @@ datafusion>=52; python_version >= "3.10"
 # Lumina vector search (optional, for lumina index tests)
 lumina-data>=0.1.0
 # paimon-vindex vector search (optional, for vindex index tests)
-paimon-vindex==0.1.0; python_version >= "3.9"
+paimon-vindex==0.3.0; python_version >= "3.9"
 # paimon-ftindex full-text search (optional, for full-text index tests)
 paimon-ftindex==0.1.0; python_version >= "3.8"
diff --git a/paimon-python/dev/run_mixed_tests.sh 
b/paimon-python/dev/run_mixed_tests.sh
index 40b7b22816..057e056591 100755
--- a/paimon-python/dev/run_mixed_tests.sh
+++ b/paimon-python/dev/run_mixed_tests.sh
@@ -660,14 +660,14 @@ ensure_paimon_vindex() {
     fi
 
     echo "Installing Python paimon-vindex dependency..."
-    if python -m pip install 'paimon-vindex==0.1.0'; then
+    if python -m pip install 'paimon-vindex==0.3.0'; then
         return 0
     fi
 
     echo -e "${YELLOW}Direct pip install failed; installing paimon-vindex into 
a temporary target directory...${NC}"
     local target_dir="${TMPDIR:-/tmp}/paimon-vindex-site"
     rm -rf "$target_dir"
-    if python -m pip install --target "$target_dir" 'paimon-vindex==0.1.0'; 
then
+    if python -m pip install --target "$target_dir" 'paimon-vindex==0.3.0'; 
then
         export PYTHONPATH="$target_dir:${PYTHONPATH:-}"
         return 0
     fi
@@ -675,7 +675,7 @@ ensure_paimon_vindex() {
     if python -c "import numpy" >/dev/null 2>&1; then
         echo -e "${YELLOW}Dependency install failed but numpy is already 
available; retrying paimon-vindex without dependencies...${NC}"
         rm -rf "$target_dir"
-        if python -m pip install --target "$target_dir" --no-deps 
'paimon-vindex==0.1.0'; then
+        if python -m pip install --target "$target_dir" --no-deps 
'paimon-vindex==0.3.0'; then
             export PYTHONPATH="$target_dir:${PYTHONPATH:-}"
             return 0
         fi
diff --git 
a/paimon-python/pypaimon/globalindex/vindex/vindex_vector_global_index_reader.py
 
b/paimon-python/pypaimon/globalindex/vindex/vindex_vector_global_index_reader.py
index 4006c7d534..98aa34647a 100644
--- 
a/paimon-python/pypaimon/globalindex/vindex/vindex_vector_global_index_reader.py
+++ 
b/paimon-python/pypaimon/globalindex/vindex/vindex_vector_global_index_reader.py
@@ -26,12 +26,10 @@ from pypaimon.common.file_io import pread, supports_pread
 from pypaimon.globalindex.global_index_reader import GlobalIndexReader, 
_completed_future
 from pypaimon.globalindex.vector_search_result import 
DictBasedScoredIndexResult
 
-VINDEX_IDENTIFIERS = ("ivf-flat", "ivf-pq", "ivf-hnsw-flat", "ivf-hnsw-sq")
+VINDEX_IDENTIFIERS = ("ivf-flat", "ivf-pq", "ivf-sq", "ivf-rq", "diskann")
 
 NPROBE_PARAMETER = "ivf.nprobe"
-EF_SEARCH_PARAMETER = "hnsw.ef_search"
-DEFAULT_NPROBE = 16
-DEFAULT_EF_SEARCH = 0
+L_SEARCH_PARAMETER = "diskann.l_search"
 
 
 class PaimonVindexInput:
@@ -67,6 +65,7 @@ class VindexVectorGlobalIndexReader(GlobalIndexReader):
         self._index_input = None
         self._reader = None
         self._metadata = None
+        self._search_params_type = None
         self._load_lock = threading.Lock()
 
     def visit_vector_search(self, vector_search):
@@ -97,21 +96,18 @@ class VindexVectorGlobalIndexReader(GlobalIndexReader):
             return [None] * n
 
         options = query_options or {}
-        nprobe = _int_parameter(options, NPROBE_PARAMETER, DEFAULT_NPROBE)
-        ef_search = _int_parameter(options, EF_SEARCH_PARAMETER, 
DEFAULT_EF_SEARCH)
+        params = _search_params(self._search_params_type, options, effective_k)
         filter_bytes = _filter_bytes(include_row_ids)
 
         if n == 1:
             ids, distances = self._reader.search(
-                queries[0], effective_k, nprobe, ef_search, 
filter_bytes=filter_bytes)
+                queries[0], params, filter_bytes=filter_bytes)
             return [_result_from_scores(ids, distances, self._metadata.metric)]
 
         if hasattr(self._reader, "search_batch"):
             batch_result = self._reader.search_batch(
                 np.ascontiguousarray(queries),
-                effective_k,
-                nprobe,
-                ef_search,
+                params,
                 filter_bytes=filter_bytes,
             )
             return _batch_results(batch_result, n, effective_k, 
self._metadata.metric)
@@ -119,7 +115,7 @@ class VindexVectorGlobalIndexReader(GlobalIndexReader):
         results = []
         for query in queries:
             ids, distances = self._reader.search(
-                query, effective_k, nprobe, ef_search, 
filter_bytes=filter_bytes)
+                query, params, filter_bytes=filter_bytes)
             results.append(_result_from_scores(ids, distances, 
self._metadata.metric))
         return results
 
@@ -160,24 +156,29 @@ class VindexVectorGlobalIndexReader(GlobalIndexReader):
                 return
 
             try:
-                from paimon_vindex import VectorIndexReader
+                from paimon_vindex import SearchParams, VectorIndexReader
             except ImportError as e:
                 raise ImportError(
                     "paimon-vindex is required to read vindex vector indexes. "
-                    "Install paimon-vindex==0.1.0 or pypaimon[vindex].") from e
+                    "Install paimon-vindex==0.3.0 or pypaimon[vindex].") from e
 
             file_path = (self._io_meta.external_path
                          if self._io_meta.external_path
                          else os.path.join(self._index_path, 
self._io_meta.file_name))
             stream = self._file_io.new_input_stream(file_path)
+            reader = None
             try:
                 index_input = PaimonVindexInput(stream)
                 reader = VectorIndexReader(index_input)
                 self._metadata = reader.metadata()
+                reader.optimize_for_search()
                 self._index_input = index_input
                 self._reader = reader
+                self._search_params_type = SearchParams
                 self._stream = stream
             except Exception:
+                if reader is not None:
+                    reader.close()
                 stream.close()
                 raise
 
@@ -262,12 +263,27 @@ def _convert_distance_to_score(distance, metric):
     raise ValueError("Unknown vector search metric: %s" % metric)
 
 
-def _int_parameter(options, key, default_value):
+def _search_params(search_params_type, options, top_k):
+    nprobe = _int_parameter(options, NPROBE_PARAMETER)
+    l_search = _int_parameter(options, L_SEARCH_PARAMETER)
+    if nprobe is not None and l_search is not None:
+        raise ValueError(
+            "Cannot set both '%s' and '%s'."
+            % (NPROBE_PARAMETER, L_SEARCH_PARAMETER)
+        )
+    if nprobe is not None:
+        return search_params_type.ivf(top_k=top_k, nprobe=nprobe)
+    if l_search is not None:
+        return search_params_type.diskann(top_k=top_k, l_search=l_search)
+    return search_params_type.automatic(top_k=top_k)
+
+
+def _int_parameter(options, key):
     value = options.get(key)
     if value is None:
-        return default_value
+        return None
     try:
         return int(value)
-    except ValueError as e:
+    except (TypeError, ValueError) as e:
         raise ValueError(
             "Invalid value for '%s': %s. Must be an integer." % (key, value)) 
from e
diff --git 
a/paimon-python/pypaimon/globalindex/vindex/vindex_vector_index_writer.py 
b/paimon-python/pypaimon/globalindex/vindex/vindex_vector_index_writer.py
index 2a6ed8670e..2b1f22b697 100644
--- a/paimon-python/pypaimon/globalindex/vindex/vindex_vector_index_writer.py
+++ b/paimon-python/pypaimon/globalindex/vindex/vindex_vector_index_writer.py
@@ -54,6 +54,8 @@ class VindexVectorIndexWriter:
         self._index_type = index_type
         self._native_options = native_options(
             data_type, options, index_type, field_name)
+        self._train_sample_ratio = train_sample_ratio(
+            options, index_type, field_name)
         self._dimension = int(self._native_options["dimension"])
         self._row_count = 0
         self._vector_count = 0
@@ -96,11 +98,11 @@ class VindexVectorIndexWriter:
 
             try:
                 import numpy as np
-                from paimon_vindex import VectorIndexWriter
+                from paimon_vindex import VectorIndexTrainer, VectorIndexWriter
             except ImportError as e:
                 raise ImportError(
                     "paimon-vindex is required to build vindex vector indexes. 
"
-                    "Install paimon-vindex==0.1.0 or pypaimon[vindex].") from e
+                    "Install paimon-vindex==0.3.0 or pypaimon[vindex].") from e
 
             self._close_temp_files()
             self._file_io.check_or_mkdirs(self._index_path)
@@ -109,12 +111,19 @@ class VindexVectorIndexWriter:
                 dtype=np.float32,
                 count=self._vector_count * self._dimension,
             ).reshape(self._vector_count, self._dimension)
-            with VectorIndexWriter(self._native_options) as writer:
-                writer.train(vectors)
+            training_vectors = _sample_training_vectors(
+                np, vectors, self._train_sample_ratio)
+            training = VectorIndexTrainer.train(
+                self._training_options(), training_vectors)
+            try:
+                del training_vectors
                 del vectors
-                self._add_vectors_in_batches(np, writer)
-                with self._file_io.new_output_stream(file_path) as 
output_stream:
-                    writer.write(output_stream)
+                with VectorIndexWriter(training) as writer:
+                    self._add_vectors_in_batches(np, writer)
+                    with self._file_io.new_output_stream(file_path) as 
output_stream:
+                        writer.write(output_stream)
+            finally:
+                training.close()
         except Exception:
             self._file_io.delete_quietly(file_path)
             raise
@@ -126,6 +135,17 @@ class VindexVectorIndexWriter:
     def _file_path(self) -> str:
         return "%s/%s" % (self._index_path, self.file_name)
 
+    def _training_options(self) -> Dict[str, str]:
+        options = dict(self._native_options)
+        nlist = options.get("nlist")
+        if (
+            options.get("index.type", "").startswith("ivf_")
+            and nlist in (None, "auto")
+            and "expected-vector-count" not in options
+        ):
+            options["expected-vector-count"] = str(self._vector_count)
+        return options
+
     def close(self) -> None:
         if not self._closed:
             self._closed = True
@@ -207,9 +227,35 @@ def native_options(
 
     result["index.type"] = index_type.replace('-', '_')
     result["dimension"] = str(_dimension(data_type, result, index_type))
+    result.setdefault("metric", "inner_product")
     return result
 
 
+def train_sample_ratio(
+    options: Mapping[str, object], index_type: str, field_name: str
+) -> float:
+    field_key = "fields.%s.train.sample-ratio" % field_name
+    index_key = "%s.train.sample-ratio" % index_type
+    key = field_key if field_key in options else index_key
+    if key not in options:
+        return 1.0
+
+    value = options[key]
+    try:
+        ratio = float(value)
+    except (TypeError, ValueError) as e:
+        raise ValueError(
+            "Invalid value for '%s': %s. Must be greater than 0 and less "
+            "than or equal to 1." % (key, value)
+        ) from e
+    if not math.isfinite(ratio) or ratio <= 0 or ratio > 1:
+        raise ValueError(
+            "Invalid value for '%s': %s. Must be greater than 0 and less "
+            "than or equal to 1." % (key, value)
+        )
+    return ratio
+
+
 def validate_vector_type(data_type: DataType) -> None:
     if isinstance(data_type, VectorType):
         element_type = data_type.element
@@ -237,14 +283,40 @@ def _native_option_key(option_key: str) -> Optional[str]:
         return "metric"
     if option_key in (
         "nlist",
+        "expected-vector-count",
         "pq.m",
-        "hnsw.m",
-        "hnsw.ef-construction",
-        "hnsw.max-level",
+        "pq.code-ratio",
+        "pq.bits",
+        "rq.bits",
+        "target-recall",
+        "max-bytes-per-vector",
+        "deployment-profile",
     ):
         return option_key
     if option_key in ("pq.use-opq", "use-opq"):
         return "use-opq"
+    diskann_options = {
+        "build-preset": "diskann.build-preset",
+        "diskann.build-preset": "diskann.build-preset",
+        "max-degree": "diskann.max-degree",
+        "diskann.max-degree": "diskann.max-degree",
+        "build-search-list-size": "diskann.build-search-list-size",
+        "diskann.build-search-list-size": "diskann.build-search-list-size",
+        "alpha": "diskann.alpha",
+        "diskann.alpha": "diskann.alpha",
+        "seed": "diskann.seed",
+        "diskann.seed": "diskann.seed",
+        "memory-budget-bytes": "diskann.memory-budget-bytes",
+        "diskann.memory-budget-bytes": "diskann.memory-budget-bytes",
+        "storage-layout": "diskann.storage-layout",
+        "diskann.storage-layout": "diskann.storage-layout",
+        "raw-vector-encoding": "diskann.raw-vector-encoding",
+        "diskann.raw-vector-encoding": "diskann.raw-vector-encoding",
+        "build-distance": "diskann.build-distance",
+        "diskann.build-distance": "diskann.build-distance",
+    }
+    if option_key in diskann_options:
+        return diskann_options[option_key]
     return None
 
 
@@ -271,6 +343,18 @@ def _is_float_type(data_type: DataType) -> bool:
     )
 
 
+def _sample_training_vectors(np, vectors, sample_ratio: float):
+    vector_count = vectors.shape[0]
+    train_count = max(1, min(vector_count, int(math.ceil(
+        vector_count * sample_ratio))))
+    if train_count == vector_count:
+        return vectors
+    indexes = (
+        np.arange(train_count, dtype=np.int64) * vector_count // train_count
+    )
+    return np.ascontiguousarray(vectors[indexes])
+
+
 def _materialize_vector(
     value, dimension: int, relative_row_id: int
 ) -> List[float]:
diff --git a/paimon-python/pypaimon/tests/global_index_build_test.py 
b/paimon-python/pypaimon/tests/global_index_build_test.py
index d03eb4b2f0..186270fcb7 100644
--- a/paimon-python/pypaimon/tests/global_index_build_test.py
+++ b/paimon-python/pypaimon/tests/global_index_build_test.py
@@ -41,7 +41,9 @@ from 
pypaimon.globalindex.full_text.native_full_text_index_writer import (
 )
 from pypaimon.globalindex.vindex.vindex_vector_index_writer import (
     VindexVectorIndexWriter,
+    _sample_training_vectors,
     native_options,
+    train_sample_ratio,
 )
 from pypaimon.globalindex.data_evolution_global_index_scanner import 
DataEvolutionGlobalIndexScanner
 from pypaimon.index.index_file_handler import IndexFileHandler
@@ -78,20 +80,35 @@ class _FakeSplit:
         self.raw_convertible = False
 
 
+class _FakeVectorIndexTraining:
+
+    def __init__(self, options, data):
+        self.options = dict(options)
+        self.trained = data.tolist()
+        self.closed = False
+
+    def close(self):
+        self.closed = True
+
+
+class _FakeVectorIndexTrainer:
+
+    @classmethod
+    def train(cls, options, data):
+        return _FakeVectorIndexTraining(options, data)
+
+
 class _FakeVectorIndexWriter:
     instances = []
 
-    def __init__(self, options):
-        self.options = dict(options)
-        self.trained = None
+    def __init__(self, training):
+        self.options = dict(training.options)
+        self.trained = training.trained
         self.added_ids = None
         self.added_vectors = None
         self.closed = False
         _FakeVectorIndexWriter.instances.append(self)
 
-    def train(self, data):
-        self.trained = data.tolist()
-
     def add_vectors(self, ids, data):
         self.added_ids = ids.tolist()
         self.added_vectors = data.tolist()
@@ -551,6 +568,7 @@ class GlobalIndexBuildTest(
 
         old_module = sys.modules.get("paimon_vindex")
         sys.modules["paimon_vindex"] = types.SimpleNamespace(
+            VectorIndexTrainer=_FakeVectorIndexTrainer,
             VectorIndexWriter=_FakeVectorIndexWriter)
         _FakeVectorIndexWriter.instances = []
         try:
@@ -609,6 +627,7 @@ class GlobalIndexBuildTest(
 
         old_module = sys.modules.get("paimon_vindex")
         sys.modules["paimon_vindex"] = types.SimpleNamespace(
+            VectorIndexTrainer=_FakeVectorIndexTrainer,
             VectorIndexWriter=_FakeVectorIndexWriter)
         _FakeVectorIndexWriter.instances = []
         try:
@@ -632,6 +651,13 @@ class GlobalIndexBuildTest(
             [[0, 1], [0, 1], [0]],
             [writer.added_ids for writer in _FakeVectorIndexWriter.instances],
         )
+        self.assertEqual(
+            ['2', '2', '1'],
+            [
+                writer.options['expected-vector-count']
+                for writer in _FakeVectorIndexWriter.instances
+            ],
+        )
 
         snapshot = table.snapshot_manager().get_latest_snapshot()
         entries = sorted(
@@ -669,6 +695,7 @@ class GlobalIndexBuildTest(
 
         old_module = sys.modules.get("paimon_vindex")
         sys.modules["paimon_vindex"] = types.SimpleNamespace(
+            VectorIndexTrainer=_FakeVectorIndexTrainer,
             VectorIndexWriter=_FakeVectorIndexWriter)
         _FakeVectorIndexWriter.instances = []
         options = {
@@ -963,6 +990,50 @@ class GlobalIndexBuildTest(
         self.assertEqual('16', result['pq.m'])
         self.assertEqual('true', result['use-opq'])
 
+    def test_vindex_native_options_support_030_indexes(self):
+        data_type = ArrayType(True, AtomicType('FLOAT'))
+        options = {
+            'ivf-rq.rq.bits': '5',
+            'ivf-rq.max-bytes-per-vector': '96',
+            'diskann.build-preset': 'balanced',
+            'diskann.pq.code-ratio': '0.0625',
+            'diskann.raw-vector-encoding': 'f16',
+        }
+
+        rq_result = native_options(
+            data_type, options, 'ivf-rq', 'embedding')
+        self.assertEqual('ivf_rq', rq_result['index.type'])
+        self.assertEqual('5', rq_result['rq.bits'])
+        self.assertEqual('96', rq_result['max-bytes-per-vector'])
+        self.assertEqual('inner_product', rq_result['metric'])
+
+        diskann_result = native_options(
+            data_type, options, 'diskann', 'embedding')
+        self.assertEqual('diskann', diskann_result['index.type'])
+        self.assertEqual(
+            'balanced', diskann_result['diskann.build-preset'])
+        self.assertEqual('0.0625', diskann_result['pq.code-ratio'])
+        self.assertEqual(
+            'f16', diskann_result['diskann.raw-vector-encoding'])
+
+    def test_vindex_training_sample_ratio(self):
+        options = {
+            'ivf-rq.train.sample-ratio': '0.5',
+            'fields.embedding.train.sample-ratio': '0.25',
+        }
+        self.assertEqual(
+            0.25, train_sample_ratio(options, 'ivf-rq', 'embedding'))
+        self.assertEqual(
+            0.5, train_sample_ratio(options, 'ivf-rq', 'other'))
+
+        import numpy as np
+        vectors = np.arange(20, dtype=np.float32).reshape(10, 2)
+        sampled = _sample_training_vectors(np, vectors, 0.4)
+        self.assertEqual(
+            [[0.0, 1.0], [4.0, 5.0], [10.0, 11.0], [14.0, 15.0]],
+            sampled.tolist(),
+        )
+
     def test_split_by_contiguous_row_range_matches_java_builder(self):
         split = _FakeSplit([
             _FakeFile('a', 0, 2),
diff --git a/paimon-python/pypaimon/tests/vindex_vector_index_test.py 
b/paimon-python/pypaimon/tests/vindex_vector_index_test.py
index a549c43af8..76715d253f 100644
--- a/paimon-python/pypaimon/tests/vindex_vector_index_test.py
+++ b/paimon-python/pypaimon/tests/vindex_vector_index_test.py
@@ -36,6 +36,26 @@ class _FakeMetadata:
     total_vectors = 100
 
 
+class _FakeSearchParams:
+
+    def __init__(self, kind, top_k, width):
+        self.kind = kind
+        self.top_k = top_k
+        self.width = width
+
+    @classmethod
+    def automatic(cls, top_k):
+        return cls("automatic", top_k, 0)
+
+    @classmethod
+    def ivf(cls, top_k, nprobe):
+        return cls("ivf", top_k, nprobe)
+
+    @classmethod
+    def diskann(cls, top_k, l_search):
+        return cls("diskann", top_k, l_search)
+
+
 class _FakeVectorIndexReader:
     instances = []
 
@@ -49,20 +69,22 @@ class _FakeVectorIndexReader:
     def metadata(self):
         return _FakeMetadata()
 
-    def search(self, query, effective_k, nprobe, ef_search, filter_bytes=None):
+    def optimize_for_search(self):
+        pass
+
+    def search(self, query, params, filter_bytes=None):
         self.search_calls.append(
-            (list(query), effective_k, nprobe, ef_search, filter_bytes))
-        return list(range(10, 10 + effective_k)), [float(i) for i in 
range(effective_k)]
+            (list(query), params, filter_bytes))
+        return (
+            list(range(10, 10 + params.top_k)),
+            [float(i) for i in range(params.top_k)],
+        )
 
-    def search_batch(
-        self, queries, top_k, nprobe, ef_search=0, filter_bytes=None
-    ):
+    def search_batch(self, queries, params, filter_bytes=None):
         self.batch_calls.append(
             {
                 "queries": queries,
-                "top_k": top_k,
-                "nprobe": nprobe,
-                "ef_search": ef_search,
+                "params": params,
                 "filter_bytes": filter_bytes,
             }
         )
@@ -71,7 +93,7 @@ class _FakeVectorIndexReader:
         query_count = queries.shape[0]
         for query_index in range(query_count):
             base_id = (query_index + 1) * 10
-            for rank in range(top_k):
+            for rank in range(params.top_k):
                 ids.append(base_id + rank)
                 distances.append(float(rank))
         return ids, distances
@@ -111,6 +133,7 @@ class VindexVectorIndexTest(unittest.TestCase):
     def test_batch_search_uses_native_batch_api(self):
         old_module = sys.modules.get("paimon_vindex")
         sys.modules["paimon_vindex"] = types.SimpleNamespace(
+            SearchParams=_FakeSearchParams,
             VectorIndexReader=_FakeVectorIndexReader)
         _FakeVectorIndexReader.instances = []
 
@@ -130,7 +153,7 @@ class VindexVectorIndexTest(unittest.TestCase):
                     vectors=[[1.0, 0.0], [0.0, 1.0], [0.5, 0.5]],
                     limit=3,
                     field_name="embedding",
-                    options={"ivf.nprobe": "7", "hnsw.ef_search": "11"},
+                    options={"ivf.nprobe": "7"},
                 ).with_include_row_ids(include_ids)
             ).result()
 
@@ -140,9 +163,9 @@ class VindexVectorIndexTest(unittest.TestCase):
 
             call = fake_reader.batch_calls[0]
             self.assertEqual((3, 2), call["queries"].shape)
-            self.assertEqual(2, call["top_k"])
-            self.assertEqual(7, call["nprobe"])
-            self.assertEqual(11, call["ef_search"])
+            self.assertEqual(2, call["params"].top_k)
+            self.assertEqual("ivf", call["params"].kind)
+            self.assertEqual(7, call["params"].width)
             self.assertIsNotNone(call["filter_bytes"])
 
             self.assertEqual(3, len(results))
diff --git a/paimon-python/setup.py b/paimon-python/setup.py
index f6065b4ab7..85c4efcd56 100644
--- a/paimon-python/setup.py
+++ b/paimon-python/setup.py
@@ -181,7 +181,7 @@ setup(
             'lumina-data>=0.1.0'
         ],
         'vindex': [
-            'paimon-vindex==0.1.0; python_version>="3.9"',
+            'paimon-vindex==0.3.0; python_version>="3.9"',
         ],
         'full-text': [
             'paimon-ftindex==0.1.0; python_version>="3.8"',
diff --git a/paimon-vector/pom.xml b/paimon-vector/pom.xml
index fbe31c2fd5..dc422b7cd8 100644
--- a/paimon-vector/pom.xml
+++ b/paimon-vector/pom.xml
@@ -32,7 +32,7 @@ under the License.
     <name>Paimon : Vector Index</name>
 
     <properties>
-        
<paimon-vector-index-java.version>0.2.0</paimon-vector-index-java.version>
+        
<paimon-vector-index-java.version>0.3.0</paimon-vector-index-java.version>
     </properties>
 
     <dependencies>
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswSqVectorGlobalIndexerFactory.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/DiskAnnVectorGlobalIndexerFactory.java
similarity index 80%
copy from 
paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswSqVectorGlobalIndexerFactory.java
copy to 
paimon-vector/src/main/java/org/apache/paimon/vector/index/DiskAnnVectorGlobalIndexerFactory.java
index 3f09984db5..f8b5c07e98 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswSqVectorGlobalIndexerFactory.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/DiskAnnVectorGlobalIndexerFactory.java
@@ -18,10 +18,10 @@
 
 package org.apache.paimon.vector.index;
 
-/** Factory for the {@code ivf-hnsw-sq} vector index identifier. */
-public class IvfHnswSqVectorGlobalIndexerFactory extends 
NativeVectorGlobalIndexerFactory {
+/** Factory for the {@code diskann} vector index identifier. */
+public class DiskAnnVectorGlobalIndexerFactory extends 
NativeVectorGlobalIndexerFactory {
 
-    public static final String IDENTIFIER = "ivf-hnsw-sq";
+    public static final String IDENTIFIER = "diskann";
 
     @Override
     public String identifier() {
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswFlatVectorGlobalIndexerFactory.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfRqVectorGlobalIndexerFactory.java
similarity index 80%
rename from 
paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswFlatVectorGlobalIndexerFactory.java
rename to 
paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfRqVectorGlobalIndexerFactory.java
index 764920d5d7..2b0e60a19b 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswFlatVectorGlobalIndexerFactory.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfRqVectorGlobalIndexerFactory.java
@@ -18,10 +18,10 @@
 
 package org.apache.paimon.vector.index;
 
-/** Factory for the {@code ivf-hnsw-flat} vector index identifier. */
-public class IvfHnswFlatVectorGlobalIndexerFactory extends 
NativeVectorGlobalIndexerFactory {
+/** Factory for the {@code ivf-rq} vector index identifier. */
+public class IvfRqVectorGlobalIndexerFactory extends 
NativeVectorGlobalIndexerFactory {
 
-    public static final String IDENTIFIER = "ivf-hnsw-flat";
+    public static final String IDENTIFIER = "ivf-rq";
 
     @Override
     public String identifier() {
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswSqVectorGlobalIndexerFactory.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfSqVectorGlobalIndexerFactory.java
similarity index 80%
rename from 
paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswSqVectorGlobalIndexerFactory.java
rename to 
paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfSqVectorGlobalIndexerFactory.java
index 3f09984db5..79dbe95c17 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfHnswSqVectorGlobalIndexerFactory.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/IvfSqVectorGlobalIndexerFactory.java
@@ -18,10 +18,10 @@
 
 package org.apache.paimon.vector.index;
 
-/** Factory for the {@code ivf-hnsw-sq} vector index identifier. */
-public class IvfHnswSqVectorGlobalIndexerFactory extends 
NativeVectorGlobalIndexerFactory {
+/** Factory for the {@code ivf-sq} vector index identifier. */
+public class IvfSqVectorGlobalIndexerFactory extends 
NativeVectorGlobalIndexerFactory {
 
-    public static final String IDENTIFIER = "ivf-hnsw-sq";
+    public static final String IDENTIFIER = "ivf-sq";
 
     @Override
     public String identifier() {
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
index b525fdd226..4056212e92 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexReader.java
@@ -31,6 +31,7 @@ import org.apache.paimon.index.vector.VectorIndexInput;
 import org.apache.paimon.index.vector.VectorIndexMetadata;
 import org.apache.paimon.index.vector.VectorIndexReader;
 import org.apache.paimon.index.vector.VectorSearchBatchResult;
+import org.apache.paimon.index.vector.VectorSearchParams;
 import org.apache.paimon.index.vector.VectorSearchResult;
 import org.apache.paimon.predicate.BatchVectorSearch;
 import org.apache.paimon.predicate.FieldRef;
@@ -63,9 +64,7 @@ import static 
org.apache.paimon.utils.Preconditions.checkArgument;
 public class NativeVectorGlobalIndexReader implements GlobalIndexReader {
 
     private static final String NPROBE_PARAMETER = "ivf.nprobe";
-    private static final String EF_SEARCH_PARAMETER = "hnsw.ef_search";
-    private static final int DEFAULT_NPROBE = 16;
-    private static final int DEFAULT_EF_SEARCH = 0;
+    private static final String L_SEARCH_PARAMETER = "diskann.l_search";
     private static final int VECTOR_INDEX_MIN_SEEK_FOR_VECTOR_READS = 16 * 
1024;
     private static final int VECTOR_INDEX_PARALLELISM_FOR_VECTOR_READS = 32;
 
@@ -145,8 +144,6 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
             validateSearchVector(vector);
         }
         int dim = nativeMeta.dimension();
-        int nprobe = nprobe(batchVectorSearch.options());
-        int efSearch = efSearch(batchVectorSearch.options());
         String metric = nativeMeta.metric();
 
         SearchScope scope =
@@ -164,8 +161,14 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
         VectorSearchBatchResult batchResult =
                 scope.filterBytes != null
                         ? vectorReader.searchBatch(
-                                queries, n, scope.effectiveK, nprobe, 
efSearch, scope.filterBytes)
-                        : vectorReader.searchBatch(queries, n, 
scope.effectiveK, nprobe, efSearch);
+                                queries,
+                                n,
+                                searchParams(batchVectorSearch.options(), 
scope.effectiveK),
+                                scope.filterBytes)
+                        : vectorReader.searchBatch(
+                                queries,
+                                n,
+                                searchParams(batchVectorSearch.options(), 
scope.effectiveK));
 
         // result i corresponds to vectors[i], matching input order.
         List<Optional<ScoredGlobalIndexResult>> results = new ArrayList<>(n);
@@ -181,8 +184,6 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
         validateSearchVector(vectorSearch.vector());
         float[] queryVector = vectorSearch.vector().clone();
         int limit = vectorSearch.limit();
-        int nprobe = nprobe(vectorSearch.options());
-        int efSearch = efSearch(vectorSearch.options());
         String metric = nativeMeta.metric();
 
         SearchScope scope = resolveScope(vectorSearch.includeRowIds(), limit);
@@ -192,8 +193,12 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
         VectorSearchResult result =
                 scope.filterBytes != null
                         ? vectorReader.search(
-                                queryVector, scope.effectiveK, nprobe, 
efSearch, scope.filterBytes)
-                        : vectorReader.search(queryVector, scope.effectiveK, 
nprobe, efSearch);
+                                queryVector,
+                                searchParams(vectorSearch.options(), 
scope.effectiveK),
+                                scope.filterBytes)
+                        : vectorReader.search(
+                                queryVector,
+                                searchParams(vectorSearch.options(), 
scope.effectiveK));
 
         return buildScoredResult(result.ids(), result.distances(), 
metric).orElse(null);
     }
@@ -279,18 +284,26 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
         throw new IllegalArgumentException("Unknown metric: " + metric);
     }
 
-    static int nprobe(Map<String, String> parameters) {
-        return intParameter(parameters, NPROBE_PARAMETER, DEFAULT_NPROBE);
-    }
-
-    static int efSearch(Map<String, String> parameters) {
-        return intParameter(parameters, EF_SEARCH_PARAMETER, 
DEFAULT_EF_SEARCH);
+    static VectorSearchParams searchParams(Map<String, String> parameters, int 
topK) {
+        Integer nprobe = intParameter(parameters, NPROBE_PARAMETER);
+        Integer lSearch = intParameter(parameters, L_SEARCH_PARAMETER);
+        if (nprobe != null && lSearch != null) {
+            throw new IllegalArgumentException(
+                    "Cannot set both '" + NPROBE_PARAMETER + "' and '" + 
L_SEARCH_PARAMETER + "'.");
+        }
+        if (nprobe != null) {
+            return VectorSearchParams.ivf(topK, nprobe);
+        }
+        if (lSearch != null) {
+            return VectorSearchParams.diskAnn(topK, lSearch);
+        }
+        return VectorSearchParams.automatic(topK);
     }
 
-    private static int intParameter(Map<String, String> parameters, String 
key, int defaultValue) {
+    private static Integer intParameter(Map<String, String> parameters, String 
key) {
         String value = parameters.get(key);
         if (value == null) {
-            return defaultValue;
+            return null;
         }
         try {
             return Integer.parseInt(value);
@@ -330,14 +343,21 @@ public class NativeVectorGlobalIndexReader implements 
GlobalIndexReader {
             synchronized (this) {
                 if (vectorReader == null) {
                     SeekableInputStream in = fileReader.getInputStream(ioMeta);
+                    VectorIndexReader reader = null;
                     try {
-                        NativeVectorIndexLoader.loadJni();
-                        VectorIndexReader reader =
-                                new VectorIndexReader(new 
SeekableStreamVectorIndexInput(in));
+                        reader = new VectorIndexReader(new 
SeekableStreamVectorIndexInput(in));
                         nativeMeta = reader.metadata();
+                        reader.optimizeForSearch();
                         vectorReader = reader;
                         openStream = in;
                     } catch (Exception e) {
+                        if (reader != null) {
+                            try {
+                                reader.close();
+                            } catch (Exception closeException) {
+                                e.addSuppressed(closeException);
+                            }
+                        }
                         IOUtils.closeQuietly(in);
                         throw e;
                     }
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexWriter.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexWriter.java
index 6ff6843f00..d0f52288de 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexWriter.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexWriter.java
@@ -44,6 +44,7 @@ import java.nio.ByteOrder;
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -252,7 +253,6 @@ public class NativeVectorGlobalIndexWriter implements 
GlobalIndexSingleColumnWri
         LOG.info("{} vector index build started: {} vectors, dim={}", 
identifier, count, dim);
         long buildStart = System.currentTimeMillis();
 
-        NativeVectorIndexLoader.loadJni();
         // Phase 1: Train
         long phaseStart = System.currentTimeMillis();
         LOG.info("{} train phase started", identifier);
@@ -305,7 +305,7 @@ public class NativeVectorGlobalIndexWriter implements 
GlobalIndexSingleColumnWri
         float[] batchVectors = new float[trainBatchSize * dim];
         logTrainingMemoryEstimate(trainCount);
 
-        try (VectorIndexTrainer trainer = 
VectorIndexTrainer.create(nativeOptions);
+        try (VectorIndexTrainer trainer = 
VectorIndexTrainer.create(trainingOptions());
                 RandomAccessFile raf = new RandomAccessFile(tempVectorFile, 
"r");
                 FileChannel channel = raf.getChannel()) {
             ByteBuffer readBuf = ByteBuffer.allocateDirect(IO_BUFFER_SIZE);
@@ -355,6 +355,19 @@ public class NativeVectorGlobalIndexWriter implements 
GlobalIndexSingleColumnWri
         }
     }
 
+    private Map<String, String> trainingOptions() {
+        Map<String, String> options = new LinkedHashMap<>(nativeOptions);
+        String indexType = options.get("index.type");
+        String nlist = options.get("nlist");
+        if (indexType != null
+                && indexType.startsWith("ivf_")
+                && (nlist == null || "auto".equals(nlist))
+                && !options.containsKey("expected-vector-count")) {
+            options.put("expected-vector-count", Long.toString(count));
+        }
+        return options;
+    }
+
     private void addVectorsFromTempFile(VectorIndexWriter writer) throws 
IOException {
         int addBatchSize = vectorBatchSize(ADD_BATCH_SIZE, dim);
         long[] batchIds = new long[addBatchSize];
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexer.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexer.java
index 3293d1620e..37e20e0af5 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexer.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexer.java
@@ -34,7 +34,7 @@ import java.util.concurrent.ExecutorService;
 /** Native vector global indexer backed by paimon-vector-index-java. */
 public class NativeVectorGlobalIndexer implements VectorGlobalIndexer {
 
-    private static final String DEFAULT_METRIC = "inner_product";
+    static final String DEFAULT_METRIC = "inner_product";
 
     private final DataType fieldType;
     private final Map<String, String> options;
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
index c351697518..0f28479fa0 100644
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
+++ 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
@@ -78,6 +78,7 @@ public abstract class NativeVectorGlobalIndexerFactory 
implements GlobalIndexerF
         nativeOptions.put("index.type", identifier.replace('-', '_'));
         nativeOptions.put(
                 "dimension", String.valueOf(dimension(fieldType, 
nativeOptions, identifier)));
+        nativeOptions.putIfAbsent("metric", 
NativeVectorGlobalIndexer.DEFAULT_METRIC);
         return nativeOptions;
     }
 
@@ -146,14 +147,45 @@ public abstract class NativeVectorGlobalIndexerFactory 
implements GlobalIndexerF
             case "metric":
                 return "metric";
             case "nlist":
+            case "expected-vector-count":
             case "pq.m":
-            case "hnsw.m":
-            case "hnsw.ef-construction":
-            case "hnsw.max-level":
+            case "pq.code-ratio":
+            case "pq.bits":
+            case "rq.bits":
+            case "target-recall":
+            case "max-bytes-per-vector":
+            case "deployment-profile":
                 return optionKey;
             case "pq.use-opq":
             case "use-opq":
                 return "use-opq";
+            case "build-preset":
+            case "diskann.build-preset":
+                return "diskann.build-preset";
+            case "max-degree":
+            case "diskann.max-degree":
+                return "diskann.max-degree";
+            case "build-search-list-size":
+            case "diskann.build-search-list-size":
+                return "diskann.build-search-list-size";
+            case "alpha":
+            case "diskann.alpha":
+                return "diskann.alpha";
+            case "seed":
+            case "diskann.seed":
+                return "diskann.seed";
+            case "memory-budget-bytes":
+            case "diskann.memory-budget-bytes":
+                return "diskann.memory-budget-bytes";
+            case "storage-layout":
+            case "diskann.storage-layout":
+                return "diskann.storage-layout";
+            case "raw-vector-encoding":
+            case "diskann.raw-vector-encoding":
+                return "diskann.raw-vector-encoding";
+            case "build-distance":
+            case "diskann.build-distance":
+                return "diskann.build-distance";
             default:
                 return null;
         }
diff --git 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorIndexLoader.java
 
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorIndexLoader.java
deleted file mode 100644
index 3c55fda25f..0000000000
--- 
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorIndexLoader.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.paimon.vector.index;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Locale;
-
-/** Loads native libraries packaged by paimon-vector-index-java. */
-final class NativeVectorIndexLoader {
-
-    private static volatile boolean loaded;
-
-    private NativeVectorIndexLoader() {}
-
-    static void loadJni() {
-        if (loaded) {
-            return;
-        }
-        synchronized (NativeVectorIndexLoader.class) {
-            if (loaded) {
-                return;
-            }
-            loadFromResource();
-            loaded = true;
-        }
-    }
-
-    private static void loadFromResource() {
-        String libraryPath = nativeLibraryPath();
-        try (InputStream input = 
NativeVectorIndexLoader.class.getResourceAsStream(libraryPath)) {
-            if (input == null) {
-                throw new UnsupportedOperationException(
-                        "Vector index native library resource not found: " + 
libraryPath);
-            }
-
-            File temp = File.createTempFile("paimon-vector-", librarySuffix());
-            temp.deleteOnExit();
-            try (FileOutputStream output = new FileOutputStream(temp)) {
-                byte[] buffer = new byte[8192];
-                int len;
-                while ((len = input.read(buffer)) >= 0) {
-                    output.write(buffer, 0, len);
-                }
-            }
-            System.load(temp.getAbsolutePath());
-        } catch (IOException e) {
-            throw new RuntimeException(
-                    "Failed to load vector index native library: " + 
libraryPath, e);
-        }
-    }
-
-    private static String nativeLibraryPath() {
-        return "/native/" + os() + "/" + arch() + "/" + libraryName();
-    }
-
-    private static String os() {
-        String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
-        if (os.contains("mac")) {
-            return "macos";
-        } else if (os.contains("linux")) {
-            return "linux";
-        } else if (os.contains("windows")) {
-            return "windows";
-        }
-        throw new UnsupportedOperationException("Unsupported vector index 
native OS: " + os);
-    }
-
-    private static String arch() {
-        String arch = System.getProperty("os.arch").toLowerCase(Locale.ROOT);
-        if ("amd64".equals(arch) || "x86_64".equals(arch)) {
-            return "x86_64";
-        } else if ("aarch64".equals(arch) || "arm64".equals(arch)) {
-            return "aarch64";
-        }
-        throw new UnsupportedOperationException("Unsupported vector index 
native arch: " + arch);
-    }
-
-    private static String libraryName() {
-        if ("windows".equals(os())) {
-            return "paimon_vindex_jni.dll";
-        }
-        return "libpaimon_vindex_jni" + librarySuffix();
-    }
-
-    private static String librarySuffix() {
-        if ("macos".equals(os())) {
-            return ".dylib";
-        } else if ("windows".equals(os())) {
-            return ".dll";
-        }
-        return ".so";
-    }
-}
diff --git 
a/paimon-vector/src/main/resources/META-INF/services/org.apache.paimon.globalindex.GlobalIndexerFactory
 
b/paimon-vector/src/main/resources/META-INF/services/org.apache.paimon.globalindex.GlobalIndexerFactory
index a11570704a..28a1439e57 100644
--- 
a/paimon-vector/src/main/resources/META-INF/services/org.apache.paimon.globalindex.GlobalIndexerFactory
+++ 
b/paimon-vector/src/main/resources/META-INF/services/org.apache.paimon.globalindex.GlobalIndexerFactory
@@ -15,5 +15,6 @@
 
 org.apache.paimon.vector.index.IvfFlatVectorGlobalIndexerFactory
 org.apache.paimon.vector.index.IvfPqAlgorithmVectorGlobalIndexerFactory
-org.apache.paimon.vector.index.IvfHnswFlatVectorGlobalIndexerFactory
-org.apache.paimon.vector.index.IvfHnswSqVectorGlobalIndexerFactory
+org.apache.paimon.vector.index.IvfSqVectorGlobalIndexerFactory
+org.apache.paimon.vector.index.IvfRqVectorGlobalIndexerFactory
+org.apache.paimon.vector.index.DiskAnnVectorGlobalIndexerFactory
diff --git 
a/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexTest.java
 
b/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexTest.java
index 82b345b799..4995f3e886 100644
--- 
a/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexTest.java
+++ 
b/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexTest.java
@@ -27,6 +27,7 @@ import org.apache.paimon.globalindex.ResultEntry;
 import org.apache.paimon.globalindex.ScoredGlobalIndexResult;
 import org.apache.paimon.globalindex.io.GlobalIndexFileReader;
 import org.apache.paimon.globalindex.io.GlobalIndexFileWriter;
+import org.apache.paimon.index.vector.VectorSearchParams;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.predicate.BatchVectorSearch;
 import org.apache.paimon.predicate.VectorSearch;
@@ -72,7 +73,6 @@ public class NativeVectorGlobalIndexTest {
 
     private static boolean isNativeAvailable() {
         try {
-            NativeVectorIndexLoader.loadJni();
             Options options = new Options();
             options.setInteger("ivf-flat.dimension", 2);
             options.setString("ivf-flat.metric", "l2");
@@ -240,25 +240,38 @@ public class NativeVectorGlobalIndexTest {
     public void testVectorSearchParameterParsing() {
         Map<String, String> parameters = new HashMap<>();
         parameters.put("ivf.nprobe", "24");
-        parameters.put("hnsw.ef_search", "80");
         parameters.put("ignored", "bad");
 
-        
assertThat(NativeVectorGlobalIndexReader.nprobe(parameters)).isEqualTo(24);
-        
assertThat(NativeVectorGlobalIndexReader.efSearch(parameters)).isEqualTo(80);
-        
assertThat(NativeVectorGlobalIndexReader.nprobe(Collections.emptyMap())).isEqualTo(16);
-        
assertThat(NativeVectorGlobalIndexReader.efSearch(Collections.emptyMap())).isEqualTo(0);
+        VectorSearchParams ivfParams = 
NativeVectorGlobalIndexReader.searchParams(parameters, 10);
+        assertThat(ivfParams.topK()).isEqualTo(10);
+
+        VectorSearchParams automaticParams =
+                
NativeVectorGlobalIndexReader.searchParams(Collections.emptyMap(), 5);
+        assertThat(automaticParams.topK()).isEqualTo(5);
+
+        VectorSearchParams diskAnnParams =
+                NativeVectorGlobalIndexReader.searchParams(
+                        Collections.singletonMap("diskann.l_search", "80"), 
10);
+        assertThat(diskAnnParams.topK()).isEqualTo(10);
     }
 
     @Test
     public void testVectorSearchParameterRangeValidationDelegatedToNative() {
         assertThat(
-                        NativeVectorGlobalIndexReader.nprobe(
-                                Collections.singletonMap("ivf.nprobe", "0")))
-                .isEqualTo(0);
+                        NativeVectorGlobalIndexReader.searchParams(
+                                Collections.singletonMap("ivf.nprobe", "0"), 
10))
+                .isNotNull();
         assertThat(
-                        NativeVectorGlobalIndexReader.efSearch(
-                                Collections.singletonMap("hnsw.ef_search", 
"-1")))
-                .isEqualTo(-1);
+                        NativeVectorGlobalIndexReader.searchParams(
+                                Collections.singletonMap("diskann.l_search", 
"-1"), 10))
+                .isNotNull();
+
+        Map<String, String> conflicting = new HashMap<>();
+        conflicting.put("ivf.nprobe", "16");
+        conflicting.put("diskann.l_search", "100");
+        assertThatThrownBy(() -> 
NativeVectorGlobalIndexReader.searchParams(conflicting, 10))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Cannot set both");
     }
 
     @Test
@@ -320,6 +333,57 @@ public class NativeVectorGlobalIndexTest {
         }
     }
 
+    @Test
+    public void testNewIndexTypesEndToEnd() throws IOException {
+        Assumptions.assumeTrue(isNativeAvailable(), "Vector index native 
library not available");
+
+        float[][] vectors = new float[32][2];
+        for (int i = 0; i < vectors.length; i++) {
+            double angle = 2 * Math.PI * i / vectors.length;
+            vectors[i][0] = (float) Math.cos(angle);
+            vectors[i][1] = (float) Math.sin(angle);
+        }
+
+        for (String identifier :
+                new String[] {
+                    IvfSqVectorGlobalIndexerFactory.IDENTIFIER,
+                    IvfRqVectorGlobalIndexerFactory.IDENTIFIER,
+                    DiskAnnVectorGlobalIndexerFactory.IDENTIFIER
+                }) {
+            Options options = new Options();
+            options.setInteger(identifier + ".dimension", 2);
+            options.setString(identifier + ".metric", "l2");
+            if (IvfRqVectorGlobalIndexerFactory.IDENTIFIER.equals(identifier)) 
{
+                options.setInteger(identifier + ".nlist", 1);
+            } else {
+                if 
(DiskAnnVectorGlobalIndexerFactory.IDENTIFIER.equals(identifier)) {
+                    options.setInteger("diskann.max-degree", 4);
+                    options.setInteger("diskann.build-search-list-size", 8);
+                }
+            }
+
+            NativeVectorGlobalIndexWriter writer =
+                    new NativeVectorGlobalIndexWriter(
+                            createFileWriter(indexPath),
+                            vectorType,
+                            NativeVectorGlobalIndexerFactory.nativeOptions(
+                                    vectorType, options, identifier, 
fieldName),
+                            identifier);
+            writeVectors(writer, vectors);
+            List<GlobalIndexIOMeta> metas = toIOMetas(writer.finish(), 
indexPath);
+
+            try (NativeVectorGlobalIndexReader reader =
+                    new NativeVectorGlobalIndexReader(
+                            createFileReader(indexPath), metas, vectorType, 
executor)) {
+                ScoredGlobalIndexResult result =
+                        reader.visitVectorSearch(new VectorSearch(vectors[0], 
2, fieldName))
+                                .join()
+                                .get();
+                
assertThat(result.results().contains(0L)).as(identifier).isTrue();
+            }
+        }
+    }
+
     @Test
     public void testSearchWithRoaringFilter() throws IOException {
         Assumptions.assumeTrue(isNativeAvailable(), "Vector index native 
library not available");
diff --git 
a/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactoryTest.java
 
b/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactoryTest.java
index 856a38185f..6c30913491 100644
--- 
a/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactoryTest.java
+++ 
b/paimon-vector/src/test/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactoryTest.java
@@ -38,9 +38,9 @@ public class NativeVectorGlobalIndexerFactoryTest {
     public void testIdentifier() {
         assertThat(new 
IvfFlatVectorGlobalIndexerFactory().identifier()).isEqualTo("ivf-flat");
         assertThat(new 
IvfPqAlgorithmVectorGlobalIndexerFactory().identifier()).isEqualTo("ivf-pq");
-        assertThat(new IvfHnswFlatVectorGlobalIndexerFactory().identifier())
-                .isEqualTo("ivf-hnsw-flat");
-        assertThat(new 
IvfHnswSqVectorGlobalIndexerFactory().identifier()).isEqualTo("ivf-hnsw-sq");
+        assertThat(new 
IvfSqVectorGlobalIndexerFactory().identifier()).isEqualTo("ivf-sq");
+        assertThat(new 
IvfRqVectorGlobalIndexerFactory().identifier()).isEqualTo("ivf-rq");
+        assertThat(new 
DiskAnnVectorGlobalIndexerFactory().identifier()).isEqualTo("diskann");
     }
 
     @Test
@@ -49,10 +49,12 @@ public class NativeVectorGlobalIndexerFactoryTest {
                 .isExactlyInstanceOf(IvfFlatVectorGlobalIndexerFactory.class);
         assertThat(GlobalIndexerFactoryUtils.load("ivf-pq"))
                 
.isExactlyInstanceOf(IvfPqAlgorithmVectorGlobalIndexerFactory.class);
-        assertThat(GlobalIndexerFactoryUtils.load("ivf-hnsw-flat"))
-                
.isExactlyInstanceOf(IvfHnswFlatVectorGlobalIndexerFactory.class);
-        assertThat(GlobalIndexerFactoryUtils.load("ivf-hnsw-sq"))
-                
.isExactlyInstanceOf(IvfHnswSqVectorGlobalIndexerFactory.class);
+        assertThat(GlobalIndexerFactoryUtils.load("ivf-sq"))
+                .isExactlyInstanceOf(IvfSqVectorGlobalIndexerFactory.class);
+        assertThat(GlobalIndexerFactoryUtils.load("ivf-rq"))
+                .isExactlyInstanceOf(IvfRqVectorGlobalIndexerFactory.class);
+        assertThat(GlobalIndexerFactoryUtils.load("diskann"))
+                .isExactlyInstanceOf(DiskAnnVectorGlobalIndexerFactory.class);
     }
 
     @Test
@@ -84,6 +86,51 @@ public class NativeVectorGlobalIndexerFactoryTest {
                 .doesNotContainKey("vector.file.format");
     }
 
+    @Test
+    public void testNativeOptionsUseDefaultMetric() {
+        Map<String, String> nativeOptions =
+                NativeVectorGlobalIndexerFactory.nativeOptions(
+                        new ArrayType(new FloatType()),
+                        new Options(),
+                        IvfFlatVectorGlobalIndexerFactory.IDENTIFIER,
+                        "vec");
+
+        assertThat(nativeOptions).containsEntry("metric", "inner_product");
+    }
+
+    @Test
+    public void testNewVectorIndexOptions() {
+        Options options = new Options();
+        options.setString("ivf-rq.rq.bits", "5");
+        options.setString("ivf-rq.max-bytes-per-vector", "96");
+        options.setString("diskann.build-preset", "balanced");
+        options.setString("diskann.pq.code-ratio", "0.0625");
+        options.setString("diskann.raw-vector-encoding", "f16");
+
+        Map<String, String> rqOptions =
+                NativeVectorGlobalIndexerFactory.nativeOptions(
+                        new ArrayType(new FloatType()),
+                        options,
+                        IvfRqVectorGlobalIndexerFactory.IDENTIFIER,
+                        "vec");
+        assertThat(rqOptions)
+                .containsEntry("index.type", "ivf_rq")
+                .containsEntry("rq.bits", "5")
+                .containsEntry("max-bytes-per-vector", "96");
+
+        Map<String, String> diskAnnOptions =
+                NativeVectorGlobalIndexerFactory.nativeOptions(
+                        new ArrayType(new FloatType()),
+                        options,
+                        DiskAnnVectorGlobalIndexerFactory.IDENTIFIER,
+                        "vec");
+        assertThat(diskAnnOptions)
+                .containsEntry("index.type", "diskann")
+                .containsEntry("diskann.build-preset", "balanced")
+                .containsEntry("pq.code-ratio", "0.0625")
+                .containsEntry("diskann.raw-vector-encoding", "f16");
+    }
+
     @Test
     public void testNativeOptionsUsesVectorTypeDimension() {
         Options options = new Options();

Reply via email to