Skip to content

feat(realtime): add in-memory predicate indexes - #504

Open
lxy-9602 wants to merge 3 commits into
apache:mainfrom
lxy-9602:rt-mem-index
Open

lxy-9602 wants to merge 3 commits into
apache:mainfrom
lxy-9602:rt-mem-index

Conversation

@lxy-9602

@lxy-9602 lxy-9602 commented Nov 3, 2026

Copy link
Copy Markdown
Member

Purpose

Linked issue: #158

Add predicate indexes for realtime append-only and deduplicate reads. Queries can use indexed key and value fields to select candidate offsets before reading and filtering rows.

  • Introduce pluggable realtime index interfaces and per-field configuration through realtime.index.<plugin>.columns.
  • Provide an append-only hash index supporting equality and IN predicates, with concurrent readers sharing postings without copying index data.
  • Restrict index queries to [disk snapshot committed end, pinned memory end) and combine candidates with the pinned deletion vector.
  • Preserve candidate bitmaps through batch slicing and projection, skip non-matching batches, and evaluate residual predicates only on selected rows.
  • Keep memory indexes independent of persisted file indexes. File indexes are built from actual output rows after deduplication, preserving correct file row positions.

Indexes remain in memory when segment data spills and are retained by pinned read views until released.

Tests

Added or extended coverage for:

  • Hash postings, offset bounds, concurrent reads, plugin configuration, and conservative predicate fallback.
  • Store reads across segment rotation, spill, projection, bitmap slicing, and pinned DV snapshots.
  • Predicate evaluation on full, sparse, empty, and nullable selections.
  • Realtime integration scenarios covering index pruning without statistics or residual filtering, file-index row remapping after deduplication, snapshot boundaries, pinned plans across updates/deletes and refresh, and residual predicate composition.

API and Format

  • Add public RealtimeIndex, RealtimeIndexReader, and RealtimeIndexFactory interfaces.
  • Add a required offset_range to RealtimeQueryContext; callers must supply the bounds associated with the pinned memory view and selected disk snapshot.
  • No changes to persisted storage formats or protocols.

Documentation

Introduce the optional configuration:

realtime.index.hash.columns=id,payload

Generative AI tooling

Generated-by: OpenAI Codex (GPT-6)

@SteNicholas SteNicholas left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lxy-9602, I left comments concerning fragmented-selection performance and the documentation of the built-in hash index.

Comment on lines +139 to +141
for (const std::shared_ptr<arrow::Array>& slice : slices) {
PAIMON_ASSIGN_OR_RAISE(std::vector<char> result,
predicate_filter_->Test(*slice, arrow_pool_.get()));

@SteNicholas SteNicholas Nov 3, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preserve batched predicate evaluation for fragmented selections: An alternating selection such as {0, 2, 4, ...} produces one slice per selected row, so this loop calls PredicateFilter::Test() thousands of times for a single input batch. MultiLiteralsLeafFunction::Test() rebuilds the IN value set and invokes Arrow IsIn for each call, repeatedly initializing the lookup state. The work therefore grows with the number of selected runs multiplied by the IN-list size. Since this is the common reader, existing file-index and deletion-vector reads are affected too.

An isolated C++ experiment with the locally available Arrow 19.0.1, 8,192 input rows, an alternating selection, and 1,024 IN values took about 0.035 ms for one whole-batch call versus 40 ms for the single-row calls, with identical selected results. This is a kernel-level reproduction, not a benchmark of Paimon's Arrow 17 build; it also reused the value-set array, whereas this path rebuilds it.

Could we gather fragmented candidates once, evaluate the predicate in a batch, and map the results back, or reuse the initialized predicate lookup state across slices? Please add performance coverage for a sparse selection combined with a large IN list.

Comment on lines +418 to +421
Replace ``<index_name>`` with the registered index plugin name. Use a
comma-separated list such as ``id,payload`` to index multiple fields. Supported
field types and predicates depend on the plugin; indexing ``_REALTIME_OFFSET``
is unsupported.

@SteNicholas SteNicholas Nov 3, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document the built-in hash plugin and its supported operations: This asks users to substitute a registered plugin name, but the guide never identifies the built-in hash plugin introduced by this PR or describes its capabilities. Please provide a usable example such as realtime.index.hash.columns=id,payload, list equality/IN support and the accepted field types, and explicitly mention the current DECIMAL, TIMESTAMP, and nested-type restrictions.

It would also help to state that the hash index returns candidates rather than exact matches: postings are keyed only by Literal::HashCode(), so hash collisions can retain nonmatching rows. Residual filtering is needed for exact results even when batch statistics are disabled. The explanation below currently motivates filtering mainly in terms of mixed batches and statistics.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants