danny0405 commented on code in PR #19613: URL: https://github.com/apache/hudi/pull/19613#discussion_r3789250031
########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | Review Comment: Addressed. An index segment is explicitly not a Hudi or MDT file group. It can cover file slices from many data table file groups, and stores a file_slice_ordinal mapping to partition path, file ID, base instant, and exact identity. Each file group has its own COVERAGE record. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | Review Comment: Addressed. The word payload has been removed from the RFC and replaced with the precise term index data file or MDT metadata record as appropriate. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | +| Source slice | A Hudi base file and its ordered log files at a snapshot. | +| Coverage | Proof that a segment represents a particular source-slice fingerprint. | Review Comment: Addressed. Source fingerprint was replaced with a defined exact file-slice identity containing the table UUID, partition, file ID, base file, ordered log files, schema, and merger state. Analyzer fingerprint remains as a separate, defined compatibility identifier. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | +| Source slice | A Hudi base file and its ordered log files at a snapshot. | Review Comment: Addressed. Source slice has been replaced with data table file slice throughout the RFC. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | +| Source slice | A Hudi base file and its ordered log files at a snapshot. | +| Coverage | Proof that a segment represents a particular source-slice fingerprint. | +| Raw tail | Eligible source slices not covered by compatible visible segments. | Review Comment: Addressed. The terminology table was rewritten around Hudi concepts and now defines the text index definition, MDT partition, data table file group and file slice, index segment and shard, exact coverage, fallback scanning, indexed records, terms, and posting lists. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | +| Source slice | A Hudi base file and its ordered log files at a snapshot. | +| Coverage | Proof that a segment represents a particular source-slice fingerprint. | +| Raw tail | Eligible source slices not covered by compatible visible segments. | +| Document | One Hudi record, identified by record key and source information. | + +### SQL interface + +Creation follows Hudi's secondary-index syntax: + +```sql +CREATE INDEX article_body_fts +ON articles +USING text_index (body) +OPTIONS ( + 'base_tokenizer' = 'simple', + 'lower_case' = 'true', + 'language' = 'und', + 'with_position' = 'true', + 'posting_block_size' = '128' +); +``` + +The existing `HoodieIndexDefinition` is populated as follows: Review Comment: Addressed. The index-definition example now contains version V1. The Compatibility section distinguishes HoodieIndexDefinition.version, MDT schema, index-file format, analyzer, and API versions, and states when an incompatible Hudi definition or MDT interpretation requires a HoodieIndexVersion bump. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | +| Source slice | A Hudi base file and its ordered log files at a snapshot. | +| Coverage | Proof that a segment represents a particular source-slice fingerprint. | +| Raw tail | Eligible source slices not covered by compatible visible segments. | +| Document | One Hudi record, identified by record key and source information. | + +### SQL interface + +Creation follows Hudi's secondary-index syntax: + +```sql +CREATE INDEX article_body_fts +ON articles +USING text_index (body) +OPTIONS ( + 'base_tokenizer' = 'simple', + 'lower_case' = 'true', + 'language' = 'und', + 'with_position' = 'true', + 'posting_block_size' = '128' +); +``` + +The existing `HoodieIndexDefinition` is populated as follows: + +```json +{ + "indexName": "article_body_fts", + "indexType": "text_index", + "sourceFields": ["body"], + "indexFunction": "tokenize", + "indexOptions": { + "base_tokenizer": "simple", + "lower_case": "true", + "language": "und", + "with_position": "true", + "posting_block_size": "128" + } +} +``` + +#### Spark SQL predicates + +The primary Spark interface follows the conventional index experience: a +boolean predicate in `WHERE`. Whether the optimizer uses the text index is not +observable in query results. + +```sql +SELECT _hoodie_record_key, title +FROM articles +WHERE hudi_match(body, 'lakehouse indexing', 'operator=AND'); +``` + +Phrase and multi-column queries use companion predicates: + +```sql Review Comment: Addressed. The RFC states that hudi_match is modeled after Elasticsearch SQL and OpenSearch SQL MATCH, with phrase and multi-field behavior following match_phrase and multi_match concepts. It also states that this is Hudi-specific rather than ANSI SQL and links both systems. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | +| Source slice | A Hudi base file and its ordered log files at a snapshot. | +| Coverage | Proof that a segment represents a particular source-slice fingerprint. | +| Raw tail | Eligible source slices not covered by compatible visible segments. | +| Document | One Hudi record, identified by record key and source information. | + +### SQL interface + +Creation follows Hudi's secondary-index syntax: + +```sql +CREATE INDEX article_body_fts +ON articles +USING text_index (body) +OPTIONS ( + 'base_tokenizer' = 'simple', + 'lower_case' = 'true', + 'language' = 'und', + 'with_position' = 'true', + 'posting_block_size' = '128' +); +``` + +The existing `HoodieIndexDefinition` is populated as follows: + +```json +{ + "indexName": "article_body_fts", + "indexType": "text_index", + "sourceFields": ["body"], + "indexFunction": "tokenize", + "indexOptions": { + "base_tokenizer": "simple", + "lower_case": "true", + "language": "und", + "with_position": "true", + "posting_block_size": "128" + } +} +``` + +#### Spark SQL predicates + +The primary Spark interface follows the conventional index experience: a +boolean predicate in `WHERE`. Whether the optimizer uses the text index is not +observable in query results. + +```sql +SELECT _hoodie_record_key, title +FROM articles +WHERE hudi_match(body, 'lakehouse indexing', 'operator=AND'); +``` + +Phrase and multi-column queries use companion predicates: + +```sql +SELECT _hoodie_record_key, title +FROM articles +WHERE hudi_match_phrase(body, 'metadata table', 1) + AND category = 'engineering'; + +SELECT _hoodie_record_key, title +FROM articles +WHERE hudi_multi_match('lakehouse indexing', title, body); +``` + +The v1 signatures are: + +```text +hudi_match(column, query [, 'key=value,...']) -> boolean +hudi_match_phrase(column, query [, slop]) -> boolean +hudi_multi_match(query [, 'operator=AND|OR'], column, ...) -> boolean +``` + +`hudi_match` options include `operator`, `fuzziness`, `prefix_length`, and +`max_expansions`. Unknown options are rejected. The functions compose with +normal SQL predicates. Conjunctive text predicates can be pushed into index +planning; expressions whose `OR` semantics cannot be preserved are evaluated +by Spark without index pushdown. + +SQL predicate evaluation is always complete. Compatible segments accelerate +covered source slices, while uncovered or incompatible slices are evaluated by +the normal Hudi scan. There is no `_score` column and `LIMIT` does not imply +relevance order. Ranked top-k is a separate direct-search contract. + +#### Hudi RS Python table API + +Python users should not need to construct Spark SQL strings. Hudi RS extends +its existing `HudiTableBuilder` and `read_snapshot` API with the same structured +query model used by the index. The proposed predicate-style API is: + +```python +import pyarrow as pa + +from hudi import HudiTableBuilder +from hudi.search import FullTextOperator, MatchQuery, PhraseQuery + +table = ( + HudiTableBuilder + .from_base_uri("s3://warehouse/articles") + .build() +) + +query = MatchQuery( + "lakehouse indexing", + column="body", + operator=FullTextOperator.AND, +) + +batches = table.read_snapshot( + columns=["_hoodie_record_key", "title", "body"], + filters=[("category", "=", "engineering")], + full_text_query=query, +) +articles = pa.Table.from_batches(batches) +``` + +Structured queries compose without inventing a second query language: + +```python +query = ( + MatchQuery("metadata table", column="body") + & PhraseQuery("incremental indexing", column="body", slop=1) +) + +batches = table.read_snapshot(full_text_query=query) +``` + +For search applications, a Lance-style fluent API exposes ranked top-k and an Review Comment: Thanks. The Lance-style Python examples remain part of the direct Hudi RS API proposal and now use a concrete support-ticket workload. ########## rfc/rfc-full-text-search/rfc-full-text-search.md: ########## @@ -0,0 +1,891 @@ +<!-- + 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. +--> + +# RFC: Hudi Full-Text Search Index + +## Proposers + +- @danny0405 + +## Approvers + +- TBD + +## Status + +Issue: TBD + +> This proposal intentionally does not claim an RFC number. The number and +> catalog entry will be reserved through the separate RFC-number process. + +## Abstract + +This RFC proposes a full-text index for Apache Hudi. It supports token, phrase, +and multi-column search over string columns, exposes standard predicates through +Spark SQL, and offers a Lance-style direct table API through Hudi RS Python. +Indexes are built and maintained through the Hudi metadata table (MDT) +indexing lifecycle. + +The search engine follows Lance's useful architectural choices without making +Lance a storage dependency: immutable segments, a compact term dictionary, +compressed posting lists, document-length statistics, BM25 ranking, positions, +and block-max WAND. Hudi owns the analyzer contract and on-disk format. Spark +integration remains in the main Hudi repository; Rust and Python implementation +work belongs in Hudi RS and consumes the same versioned format. + +The MDT remains authoritative for index definitions, visibility, coverage, +rollbacks, and cleaning. Large immutable posting payloads are sidecar files in +an auxiliary directory owned by the MDT rather than values embedded in HFiles. +An MDT commit atomically publishes descriptors for already durable payloads. + +Queries are snapshot-safe. SQL predicates always combine index results with a +raw scan of source file slices not covered by a compatible segment, so using an +index never changes SQL results. The direct search API also defaults to complete +results and may offer an explicitly incomplete low-latency mode. + +## Background + +Hudi indexes currently answer questions such as which files might contain a +record key or a value range. Full-text search has a different contract: analyze +free text into terms, locate matching documents, optionally verify positions, +and, for direct search APIs, rank the best documents. Sending this workload to +Elasticsearch or OpenSearch is effective, but creates a second ingestion +pipeline and a second source of snapshot and retention truth. + +This proposal builds on: + +- [RFC-45](../rfc-45/rfc-45.md), which introduced asynchronous MDT indexing; +- [RFC-77](../rfc-77/rfc-77.md), which established dynamically named secondary + index partitions and index definitions; +- the standard Spark SQL predicate model, which keeps index acceleration + transparent to relational queries; and +- RFC-109, the native vector-index proposal listed in the RFC catalog. Text and + vector search may share Hudi RS storage adapters and top-k utilities, but + their persistent formats remain independent. + +### Design principles + +1. The Hudi timeline is the source of snapshot truth. +2. Index creation, visibility, rollback, and cleaning use MDT components. +3. Immutable payloads support object-store range reads and safe caching. +4. Index use never changes the result of a Spark SQL predicate. +5. Ranking is independent of how the index is physically partitioned. +6. JVM and Hudi RS clients share query semantics and format versions. + +### Goals + +- Match token, boolean, prefix, fuzzy, phrase, and multi-column queries. +- Support copy-on-write (COW) and merge-on-read (MOR) tables. +- Build asynchronously and incrementally using the MDT indexer lifecycle. +- Guarantee snapshot-correct results for SQL and the default direct API mode. +- Provide an object-store-friendly text-index format with bounded memory usage. +- Provide a direct Hudi RS Python API alongside Spark SQL. + +### Non-goals + +- Elasticsearch API, aggregation, highlighting, or percolator compatibility. +- Highlighting and custom relevance models in the first format version. +- Updating posting lists in place. +- Replacing SQL predicate indexes or the record index. +- Adding Rust code or native build integration to the main Hudi repository. + +### Alternatives considered + +**External Elasticsearch/OpenSearch.** This remains a valid integration, but it +requires change-data-capture coordination, separate retention, and explicit +mapping between external documents and a Hudi snapshot. + +**Embedding Tantivy.** Tantivy is mature and Rust-native. Its archive and +directory abstractions, however, become a second persistent compatibility +contract. A smaller Hudi-owned format gives the project control over source +file-slice identity, range-read layout, and MDT publication semantics. + +**Posting lists as MDT record values.** This would make MDT storage atomic, but +multi-gigabyte postings, merges, and random term reads fight the metadata +table's record-oriented HFile/MOR strengths. Small authoritative descriptors in +the MDT plus immutable sidecars preserve the lifecycle benefits without that +cost. + +## Implementation + +### Terminology + +| Term | Meaning | +| --- | --- | +| Logical index | A named SQL index and its immutable analyzer configuration. | +| MDT index partition | Dynamic `text_index_<name>` metadata partition containing authoritative control records. | +| Segment | An immutable set of documents and term postings built together. | +| Payload partition | A shard within a segment whose local document identifiers are `u32`. | +| Source slice | A Hudi base file and its ordered log files at a snapshot. | +| Coverage | Proof that a segment represents a particular source-slice fingerprint. | +| Raw tail | Eligible source slices not covered by compatible visible segments. | +| Document | One Hudi record, identified by record key and source information. | + +### SQL interface + +Creation follows Hudi's secondary-index syntax: + +```sql +CREATE INDEX article_body_fts +ON articles +USING text_index (body) +OPTIONS ( + 'base_tokenizer' = 'simple', + 'lower_case' = 'true', + 'language' = 'und', + 'with_position' = 'true', + 'posting_block_size' = '128' +); +``` + +The existing `HoodieIndexDefinition` is populated as follows: + +```json +{ + "indexName": "article_body_fts", + "indexType": "text_index", + "sourceFields": ["body"], + "indexFunction": "tokenize", + "indexOptions": { + "base_tokenizer": "simple", + "lower_case": "true", + "language": "und", + "with_position": "true", + "posting_block_size": "128" + } +} +``` + +#### Spark SQL predicates + +The primary Spark interface follows the conventional index experience: a +boolean predicate in `WHERE`. Whether the optimizer uses the text index is not +observable in query results. + +```sql +SELECT _hoodie_record_key, title +FROM articles +WHERE hudi_match(body, 'lakehouse indexing', 'operator=AND'); +``` + +Phrase and multi-column queries use companion predicates: + +```sql +SELECT _hoodie_record_key, title +FROM articles +WHERE hudi_match_phrase(body, 'metadata table', 1) + AND category = 'engineering'; + +SELECT _hoodie_record_key, title +FROM articles +WHERE hudi_multi_match('lakehouse indexing', title, body); +``` + +The v1 signatures are: + +```text +hudi_match(column, query [, 'key=value,...']) -> boolean +hudi_match_phrase(column, query [, slop]) -> boolean +hudi_multi_match(query [, 'operator=AND|OR'], column, ...) -> boolean +``` + +`hudi_match` options include `operator`, `fuzziness`, `prefix_length`, and +`max_expansions`. Unknown options are rejected. The functions compose with +normal SQL predicates. Conjunctive text predicates can be pushed into index +planning; expressions whose `OR` semantics cannot be preserved are evaluated +by Spark without index pushdown. + +SQL predicate evaluation is always complete. Compatible segments accelerate +covered source slices, while uncovered or incompatible slices are evaluated by +the normal Hudi scan. There is no `_score` column and `LIMIT` does not imply +relevance order. Ranked top-k is a separate direct-search contract. + +#### Hudi RS Python table API + +Python users should not need to construct Spark SQL strings. Hudi RS extends +its existing `HudiTableBuilder` and `read_snapshot` API with the same structured +query model used by the index. The proposed predicate-style API is: + +```python +import pyarrow as pa + +from hudi import HudiTableBuilder +from hudi.search import FullTextOperator, MatchQuery, PhraseQuery + +table = ( + HudiTableBuilder + .from_base_uri("s3://warehouse/articles") + .build() +) + +query = MatchQuery( + "lakehouse indexing", + column="body", + operator=FullTextOperator.AND, +) + +batches = table.read_snapshot( + columns=["_hoodie_record_key", "title", "body"], + filters=[("category", "=", "engineering")], + full_text_query=query, +) +articles = pa.Table.from_batches(batches) +``` + +Structured queries compose without inventing a second query language: + +```python +query = ( + MatchQuery("metadata table", column="body") + & PhraseQuery("incremental indexing", column="body", slop=1) +) + +batches = table.read_snapshot(full_text_query=query) +``` + +For search applications, a Lance-style fluent API exposes ranked top-k and an +explicit score: + +```python +results = ( + table.search_text( + MatchQuery("lakehouse indexing", column="body"), + ) + .where([("category", "=", "engineering")]) + .select(["_hoodie_record_key", "title", "body"]) + .limit(20) + .to_arrow() +) + +# Ranked by BM25 descending; `_score` is included in `results`. +``` + +`read_snapshot(full_text_query=...)` has predicate semantics and returns every +match. `search_text(...).limit(k)` has ranked-search semantics and returns +`_score`. Both pin one Hudi snapshot, use identical analyzer/query objects, and +raw-scan uncovered source slices by default. The fluent API may expose +`allow_incomplete_index=True`, but it must mark the result metadata as +incomplete rather than silently changing defaults. + +Index creation remains an MDT table-service operation in the initial release, +invoked through Spark SQL or the Java API. A future Hudi RS writer API may add +`create_text_index` only after it can publish the corresponding MDT timeline +changes safely. + +The three query entry points share one query model and coverage planner, but +their result contracts differ: + +```mermaid +flowchart LR + SQL["Spark SQL<br/>hudi_match(...)"] + SNAPSHOT["Hudi RS Python<br/>read_snapshot(full_text_query=...)"] + SEARCH["Hudi RS Python<br/>search_text(...).limit(k)"] + MODEL["Shared query objects,<br/>analyzer contract, and snapshot pinning"] + MATCHES["Complete unordered<br/>match set"] + RANKED["BM25-ranked top-k<br/>with _score"] + + SQL --> MODEL + SNAPSHOT --> MODEL + SEARCH --> MODEL + MODEL -->|"predicate semantics"| MATCHES + MODEL -->|"ranked-search semantics"| RANKED +``` + +### Architecture + +```mermaid +flowchart TD Review Comment: Addressed. The architecture diagram was replaced with a simpler left-to-right build and query flow showing data table file slices, the Java indexer, immutable index files, MDT visibility, exact coverage planning, index reads, normal Hudi fallback scans, and the final union. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
