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 d82e90d6f7 [global-index-eslib] Isolate dependencies and support int8
HNSW (#8702)
d82e90d6f7 is described below
commit d82e90d6f768e4701132c7308a18247b45a15f52
Author: CrownChu <[email protected]>
AuthorDate: Sat Jul 18 12:05:51 2026 +0800
[global-index-eslib] Isolate dependencies and support int8 HNSW (#8702)
---
.github/workflows/utcase-eslib.yml | 73 ++++++++++++++++++++++
paimon-eslib/README.md | 26 +++++---
paimon-eslib/pom.xml | 12 ++--
.../apache/paimon/eslib/index/ESIndexOptions.java | 15 +++--
.../paimon/eslib/index/ESIndexOptionsTest.java | 20 ++++++
pom.xml | 11 +---
6 files changed, 130 insertions(+), 27 deletions(-)
diff --git a/.github/workflows/utcase-eslib.yml
b/.github/workflows/utcase-eslib.yml
new file mode 100644
index 0000000000..a5dc7d9d20
--- /dev/null
+++ b/.github/workflows/utcase-eslib.yml
@@ -0,0 +1,73 @@
+################################################################################
+# 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.
+################################################################################
+
+name: UTCase ESLib on JDK 11
+
+on:
+ push:
+ paths:
+ - 'paimon-api/**'
+ - 'paimon-common/**'
+ - 'paimon-eslib/**'
+ - 'paimon-test-utils/**'
+ - 'pom.xml'
+ - '.github/workflows/utcase-eslib.yml'
+ pull_request:
+ paths:
+ - 'paimon-api/**'
+ - 'paimon-common/**'
+ - 'paimon-eslib/**'
+ - 'paimon-test-utils/**'
+ - 'pom.xml'
+ - '.github/workflows/utcase-eslib.yml'
+
+permissions:
+ contents: read
+
+env:
+ JDK_VERSION: 11
+ MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ eslib_test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ persist-credentials: false
+
+ - name: Set up JDK ${{ env.JDK_VERSION }}
+ uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
+ with:
+ java-version: ${{ env.JDK_VERSION }}
+ distribution: 'temurin'
+
+ - name: Build ESLib dependencies
+ run: mvn -T 2C -B -ntp clean install -DskipTests -Ppaimon-eslib -pl
paimon-eslib -am
+
+ - name: Test ESLib
+ run: mvn -B -ntp verify -Ppaimon-eslib -pl paimon-eslib
+ env:
+ MAVEN_OPTS: -Xmx4096m
diff --git a/paimon-eslib/README.md b/paimon-eslib/README.md
index 94a860bc3e..95b4e93577 100644
--- a/paimon-eslib/README.md
+++ b/paimon-eslib/README.md
@@ -32,13 +32,23 @@ 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; the root Maven
-> build intentionally skips `paimon-eslib` when it runs on JDK 8.
+> `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`,
-`eslib-simdvec`, and their Lucene 9.12 dependencies in the same runtime
classloader as
-`paimon-eslib`.
+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.
@@ -138,11 +148,11 @@ 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` or `diskbbq`.
`native` is not supported by paimon-eslib. |
+| `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`) | engine default | HNSW graph out-degree; valid range:
1–512. |
-| `ef_construction` | vector (`hnsw`) | engine default | HNSW construction
search width; valid range: 1–3200. |
+| `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`. |
diff --git a/paimon-eslib/pom.xml b/paimon-eslib/pom.xml
index a5fa8f5ee3..a6ada97e5f 100644
--- a/paimon-eslib/pom.xml
+++ b/paimon-eslib/pom.xml
@@ -35,6 +35,7 @@ under the License.
<!-- eslib-api / eslib-core are compiled for Java 11 (Lucene 9.12);
compile this module
at Java 11 instead of the project default (1.8) so their class
files are readable. -->
<target.java.version>11</target.java.version>
+ <eslib.version>1.0.7</eslib.version>
</properties>
<dependencies>
@@ -49,9 +50,8 @@ under the License.
into eslib-core), so no separate eslib-api artifact is needed. -->
<dependency>
<groupId>io.github.paimon.eslib</groupId>
- <artifactId>eslib-core</artifactId>
- <version>1.0.6</version>
- <classifier>lucene9</classifier>
+ <artifactId>eslib-core-lucene9</artifactId>
+ <version>${eslib.version}</version>
<!-- Paimon supplies its own ArchiveDataProvider over FileIO; the
standalone
OSSRangeIndexInput and Aliyun SDK are not used in this
module. -->
<exclusions>
@@ -63,8 +63,8 @@ under the License.
</dependency>
<dependency>
<groupId>io.github.paimon.eslib</groupId>
- <artifactId>eslib-simdvec</artifactId>
- <version>1.0.6</version>
+ <artifactId>eslib-simdvec-lucene9</artifactId>
+ <version>${eslib.version}</version>
</dependency>
<!-- eslib-native (Havenask JNI) intentionally omitted: it is a JDK 21
module (Phase 3,
not implemented) and is not referenced by paimon-eslib. Re-add
when NATIVE algorithm
@@ -115,7 +115,7 @@ under the License.
<repositories>
<repository>
<id>eslib-github</id>
-
<url>https://raw.githubusercontent.com/CrownChu/es-paimon-lib-releases/main/repository</url>
+
<url>https://raw.githubusercontent.com/CrownChu/es-paimon-lib-releases/eslib-${eslib.version}/repository</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
diff --git
a/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexOptions.java
b/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexOptions.java
index d1936ec21c..60b8a704f7 100644
---
a/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexOptions.java
+++
b/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexOptions.java
@@ -307,7 +307,7 @@ public class ESIndexOptions {
if (vectorAlgorithm == VectorAlgorithm.NATIVE) {
throw new IllegalArgumentException(
"Vector algorithm 'native' is not supported by
paimon-eslib; "
- + "use 'hnsw' or 'diskbbq'");
+ + "use 'hnsw', 'int8_hnsw', or 'diskbbq'");
}
String dimStr = resolve(options, fieldName, "dimension", null);
@@ -352,15 +352,16 @@ public class ESIndexOptions {
// table can contain HNSW and DiskBBQ fields at the same time, so a
global HNSW parameter
// must not make a DiskBBQ field invalid (and vice versa). Field-level
parameters remain
// strict because they unambiguously target this field.
+ boolean hnswAlgorithm = isHnswAlgorithm(vectorAlgorithm);
String mStr =
- vectorAlgorithm == VectorAlgorithm.HNSW
+ hnswAlgorithm
? resolve(options, fieldName, "m", null)
: resolveField(options, fieldName, "m");
String efStr =
- vectorAlgorithm == VectorAlgorithm.HNSW
+ hnswAlgorithm
? resolve(options, fieldName, "ef_construction", null)
: resolveField(options, fieldName, "ef_construction");
- if (vectorAlgorithm == VectorAlgorithm.HNSW) {
+ if (hnswAlgorithm) {
putBoundedPositiveIntegerParameter(
params, fieldName, "m", mStr,
Lucene99HnswVectorsFormat.MAXIMUM_MAX_CONN);
putBoundedPositiveIntegerParameter(
@@ -375,7 +376,7 @@ public class ESIndexOptions {
+ fieldName
+ "' configures m/ef_construction but algorithm is
"
+ algorithm
- + "; these parameters require algorithm=hnsw.");
+ + "; these parameters require algorithm=hnsw or
int8_hnsw.");
}
String vpcStr =
vectorAlgorithm == VectorAlgorithm.DISKBBQ
@@ -421,6 +422,10 @@ public class ESIndexOptions {
.build();
}
+ private static boolean isHnswAlgorithm(VectorAlgorithm algorithm) {
+ return algorithm == VectorAlgorithm.HNSW || algorithm ==
VectorAlgorithm.INT8_HNSW;
+ }
+
private static BuiltinAnalyzer parseAnalyzer(String fieldName, String
analyzerName) {
BuiltinAnalyzer analyzer = BuiltinAnalyzer.fromName(analyzerName);
if (analyzer == BuiltinAnalyzer.IK_SMART || analyzer ==
BuiltinAnalyzer.IK_MAX_WORD) {
diff --git
a/paimon-eslib/src/test/java/org/apache/paimon/eslib/index/ESIndexOptionsTest.java
b/paimon-eslib/src/test/java/org/apache/paimon/eslib/index/ESIndexOptionsTest.java
index eb1690bb50..637e9f9872 100644
---
a/paimon-eslib/src/test/java/org/apache/paimon/eslib/index/ESIndexOptionsTest.java
+++
b/paimon-eslib/src/test/java/org/apache/paimon/eslib/index/ESIndexOptionsTest.java
@@ -25,6 +25,7 @@ import org.apache.paimon.types.DataTypes;
import org.apache.lucene.codecs.KnnVectorsFormat;
import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat;
import org.elasticsearch.eslib.adapter.PaimonHnswVectorsFormat;
+import org.elasticsearch.eslib.adapter.PaimonInt8HnswVectorsFormat;
import org.elasticsearch.eslib.adapter.lucene9.PaimonLucene9Codec;
import org.elasticsearch.eslib.api.model.FieldIndexConfig;
import org.elasticsearch.eslib.api.model.ScalarFieldType;
@@ -108,6 +109,25 @@ class ESIndexOptionsTest {
assertThat(format.toString()).contains("maxConn=30").contains("beamWidth=360");
}
+ @Test
+ void int8HnswConstructionParametersReachLuceneCodec() {
+ Map<String, String> m = new HashMap<>();
+ m.put("global-index.es-index.fields.embedding.algorithm", "int8_hnsw");
+ m.put("global-index.es-index.fields.embedding.dimension", "128");
+ m.put("global-index.es-index.fields.embedding.m", "24");
+ m.put("global-index.es-index.fields.embedding.ef_construction", "160");
+
+ ESIndexOptions options = new ESIndexOptions(FIELDS,
Options.fromMap(m));
+ FieldIndexConfig config = options.getConfig("embedding");
+ PaimonLucene9Codec codec = new
PaimonLucene9Codec(options.getFieldConfigs());
+ PerFieldKnnVectorsFormat perField = (PerFieldKnnVectorsFormat)
codec.knnVectorsFormat();
+ KnnVectorsFormat format =
perField.getKnnVectorsFormatForField("embedding");
+
+ assertThat(config.algorithm()).isEqualTo(VectorAlgorithm.INT8_HNSW);
+ assertThat(format).isInstanceOf(PaimonInt8HnswVectorsFormat.class);
+
assertThat(format.toString()).contains("maxConn=24").contains("beamWidth=160");
+ }
+
@Test
void invalidHnswConstructionParametersAreRejected() {
Map<String, String> nonPositive = new HashMap<>();
diff --git a/pom.xml b/pom.xml
index 03b4c4ee2f..9465363a39 100644
--- a/pom.xml
+++ b/pom.xml
@@ -521,18 +521,13 @@ under the License.
</profile>
<profile>
- <!-- paimon-eslib depends on Java-11 eslib-api/eslib-core (Lucene
9.12), so it must be
- built with JDK 11+. The [11,) range activates on JDK
11/17/21; a bare
- <jdk>11</jdk> is a prefix match that only fires on JDK 11,
silently skipping the
- module (no compile, no test) on 17/21. The default JDK-8
build still skips it, so
- the RequireJavaVersion enforcer rule is not tripped. -->
+ <!-- paimon-eslib and its external ESLib/Lucene dependencies
require Java 11+. Keep
+ this module opt-in so the default reactor is independent of
the external ESLib
+ repository. Build it explicitly with -Ppaimon-eslib on JDK
11+. -->
<id>paimon-eslib</id>
<modules>
<module>paimon-eslib</module>
</modules>
- <activation>
- <jdk>[11,)</jdk>
- </activation>
</profile>
<profile>