| <!-- |
| 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. |
| --> |
| |
| # paimon-eslib: ES Index (Multi-Column Global Index) |
| |
| `paimon-eslib` provides the `es-index` global index: a **Lucene-backed, multi-column, multi-modal** |
| index. A single `es-index` file indexes a **primary column** (typically a vector) together with |
| optional **companion columns** — full-text, keyword, scalar, and date — so vector search and |
| full-text search can be served from the same index, and companion columns stay available for scoring |
| and filtering inside one index definition. |
| |
| Unlike the single-purpose vector and full-text global indexes — each of which indexes one column with |
| one dedicated engine — `es-index` groups a primary column plus optional companion columns into one |
| index. The primary column determines the main modality (usually a vector); companion columns are |
| indexed according to their data type and per-field options. |
| |
| > **Plugin required.** `es-index` is provided by this `paimon-eslib` module. Make sure `paimon-eslib` |
| > is on the classpath of your Spark or Flink job (and of any reader) before building or querying an |
| > `es-index`. The module and its ESLib/Lucene dependencies require Java 11 or newer. It is excluded |
| > from the default Maven reactor; build it explicitly with `-Ppaimon-eslib` on JDK 11 or newer. |
| |
| `paimon-eslib` is distributed as a thin JAR and does not embed ESLib or Lucene classes. Maven and |
| Gradle resolve these dependencies transitively. When installing JARs manually, place |
| `eslib-core-lucene9` and `eslib-simdvec-lucene9` 1.0.7, together with their Lucene 9.12 |
| dependencies, in the same runtime classloader as `paimon-eslib`. |
| |
| ## ESLib source and artifacts |
| |
| - ESLib source code: [CrownChu/es-paimon-lib](https://github.com/CrownChu/es-paimon-lib) |
| - Published ESLib JARs: |
| [CrownChu/es-paimon-lib-releases](https://github.com/CrownChu/es-paimon-lib-releases) |
| |
| The ESLib source and binary repositories are maintained independently of the Apache Software |
| Foundation. The exact ESLib artifact versions used by this module are declared in |
| [`pom.xml`](pom.xml). |
| |
| See the general [Global Index](../docs/docs/multimodal-table/global-index.mdx) documentation for the |
| required Data Evolution table properties, coverage/freshness behavior, and shared build options. |
| |
| ## How columns map to index types |
| |
| For each indexed column, `es-index` decides the sub-index type from the column data type, unless you |
| override it with `fields.<field>.type`: |
| |
| | Column data type | Default sub-index | Override with `type` | |
| |---|---|---| |
| | `VECTOR<FLOAT>` / `ARRAY<FLOAT>` | Vector (ANN) | `vector` | |
| | `STRING` | Full-text primary + keyword sub-field | `fulltext` or `keyword` | |
| | `TIMESTAMP` / `DATE` | Long scalar | `date` | |
| | supported numeric scalars | Scalar | — | |
| |
| Text columns always provide both capabilities. A `fulltext` primary field gets a `<column>.keyword` |
| exact-match sub-field; a `keyword` primary field gets a `<column>.fulltext` analyzed sub-field. |
| |
| ## Prerequisites |
| |
| Create a Data Evolution table with the required properties: |
| |
| ```sql |
| CREATE TABLE my_table ( |
| id INT, |
| embedding ARRAY<FLOAT>, |
| content STRING, |
| category STRING, |
| price INT |
| ) TBLPROPERTIES ( |
| 'bucket' = '-1', |
| 'row-tracking.enabled' = 'true', |
| 'data-evolution.enabled' = 'true', |
| 'global-index.enabled' = 'true' |
| ); |
| ``` |
| |
| ## Build ES Index |
| |
| Build the index with `sys.create_global_index` and `index_type => 'es-index'`. To index several |
| columns in one `es-index`, pass a **comma-separated** `index_column` list: the **first** column is |
| the primary column, the rest are companion columns. Per-column behavior is configured through options |
| under the `global-index.es-index.` prefix. |
| |
| ### Spark SQL |
| |
| ```sql |
| -- Single vector column with an HNSW index |
| CALL sys.create_global_index( |
| table => 'db.my_table', |
| index_column => 'embedding', |
| index_type => 'es-index', |
| options => 'global-index.es-index.fields.embedding.algorithm=hnsw,global-index.es-index.fields.embedding.dimension=768,global-index.es-index.fields.embedding.metric=cosine' |
| ); |
| |
| -- Multi-column: vector (primary) + full-text + keyword + scalar in one index. |
| -- 'content' becomes full-text because an analyzer is set; 'category' stays keyword; 'price' scalar. |
| CALL sys.create_global_index( |
| table => 'db.my_table', |
| index_column => 'embedding,content,category,price', |
| index_type => 'es-index', |
| options => 'global-index.es-index.fields.embedding.algorithm=diskbbq,global-index.es-index.fields.embedding.dimension=768,global-index.es-index.fields.embedding.metric=cosine,global-index.es-index.fields.content.analyzer=standard,global-index.es-index.fields.category.type=keyword' |
| ); |
| ``` |
| |
| ### Flink SQL |
| |
| ```sql |
| -- Single vector column with an HNSW index |
| CALL sys.create_global_index( |
| `table` => 'db.my_table', |
| index_column => 'embedding', |
| index_type => 'es-index', |
| options => 'global-index.es-index.fields.embedding.algorithm=hnsw,global-index.es-index.fields.embedding.dimension=768,global-index.es-index.fields.embedding.metric=cosine' |
| ); |
| |
| -- Multi-column: vector (primary) + full-text + keyword + scalar in one index. |
| CALL sys.create_global_index( |
| `table` => 'db.my_table', |
| index_column => 'embedding,content,category,price', |
| index_type => 'es-index', |
| options => 'global-index.es-index.fields.embedding.algorithm=diskbbq,global-index.es-index.fields.embedding.dimension=768,global-index.es-index.fields.embedding.metric=cosine,global-index.es-index.fields.content.analyzer=standard,global-index.es-index.fields.category.type=keyword' |
| ); |
| ``` |
| |
| You can also set the same options in `TBLPROPERTIES` at table-creation time instead of passing them |
| per build. Options are resolved with field-level keys taking precedence over index-type-level keys: |
| `global-index.es-index.fields.<field>.<key>` overrides `global-index.es-index.<key>`. |
| |
| ## Options |
| |
| Index-type-level keys use the `global-index.es-index.<key>` prefix and apply to every column of that |
| kind; field-level keys use `global-index.es-index.fields.<field>.<key>` and override them for one |
| column. |
| |
| | Key | Applies to | Default | Description | |
| |---|---|---|---| |
| | `type` | any column | inferred from data type | Force the sub-index type: `vector`, `fulltext`, `keyword`, `date`. | |
| | `algorithm` | vector | `hnsw` | Vector algorithm: `hnsw`, `int8_hnsw`, or `diskbbq`. `native` is not supported by paimon-eslib. | |
| | `dimension` | vector | inferred for `VECTOR<FLOAT>` | Vector dimension (1–4096). Required for `ARRAY<FLOAT>`; taken from the type for `VECTOR<FLOAT>`. | |
| | `metric` | vector | `cosine` | Distance metric (for example `cosine`, `l2`, `dot_product`). Use the same metric at build and query time. | |
| | `m` | vector (`hnsw`, `int8_hnsw`) | engine default | HNSW graph out-degree; valid range: 1–512. | |
| | `ef_construction` | vector (`hnsw`, `int8_hnsw`) | engine default | HNSW construction search width; valid range: 1–3200. | |
| | `vectors_per_cluster` | vector (`diskbbq`) | engine default | Target vectors per cluster for DiskBBQ; valid range: 64–65536. | |
| | `centroids_per_parent_cluster` | vector (`diskbbq`) | engine default | Number of child centroids per parent cluster for DiskBBQ; valid range: 2–384. | |
| | `analyzer` | string | `standard` | Text analyzer used by the full-text primary field or `.fulltext` sub-field: `standard`, `whitespace`, `simple`, or `keyword`. | |
| |
| ## Query |
| |
| `es-index` implements the same search API as the other global indexes, so querying does not depend on |
| which engine built the index — only the build step differs. |
| |
| ### Spark SQL |
| |
| ```sql |
| -- Vector search: top-5 nearest neighbors on the primary vector column |
| SELECT * FROM vector_search('my_table', 'embedding', array(1.0f, 2.0f, 3.0f), 5); |
| |
| -- Full-text search on a companion full-text column, using the JSON query DSL |
| SELECT * FROM full_text_search( |
| 'my_table', |
| 'content', |
| '{"match":{"query":"paimon lake format"}}', |
| 10 |
| ); |
| ``` |
| |
| Vector routes and full-text routes over an `es-index` can also be combined with the |
| `hybrid_search(...)` table-valued function. |
| |
| ### Flink SQL |
| |
| Flink exposes vector search as a `CALL` procedure. The procedure returns JSON-serialized rows as |
| strings. |
| |
| ```sql |
| -- Vector search: top-5 nearest neighbors on the primary vector column |
| CALL sys.vector_search( |
| `table` => 'db.my_table', |
| vector_column => 'embedding', |
| query_vector => '1.0,2.0,3.0', |
| top_k => 5 |
| ); |
| |
| -- Optionally restrict the returned columns |
| CALL sys.vector_search( |
| `table` => 'db.my_table', |
| vector_column => 'embedding', |
| query_vector => '1.0,2.0,3.0', |
| top_k => 5, |
| projection => 'id,content' |
| ); |
| ``` |
| |
| > **Note.** Flink SQL currently exposes vector search only. For full-text search and hybrid search |
| > over an `es-index`, use Spark SQL (the `full_text_search` / `hybrid_search` table-valued functions) |
| > or the Java API (`Table.newFullTextSearchBuilder()` / `Table.newHybridSearchBuilder()`). |
| |
| ## Drop ES Index |
| |
| ### Spark SQL |
| |
| ```sql |
| CALL sys.drop_global_index( |
| table => 'db.my_table', |
| index_column => 'embedding', |
| index_type => 'es-index' |
| ); |
| ``` |
| |
| ### Flink SQL |
| |
| ```sql |
| CALL sys.drop_global_index( |
| `table` => 'db.my_table', |
| index_column => 'embedding', |
| index_type => 'es-index' |
| ); |
| ``` |