Skip to content

OAK-12089: Add Lucene 9 index provider (oak-search-lucene-ng) - #2817

Open
bhabegger wants to merge 42 commits into
apache:OAK-12089from
bhabegger:oak-12089-lucene9-core
Open

bhabegger wants to merge 42 commits into
apache:OAK-12089from
bhabegger:oak-12089-lucene9-core

Conversation

@bhabegger

@bhabegger bhabegger commented Mar 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds oak-search-lucene-ng, an experimental Lucene 9 index provider. Indexes opt in with
type=luceneNg; existing indexes are unaffected. The goal is an initial version that can be
tested in real deployments alongside oak-lucene, replicating its behaviour on the shared
oak-search framework (planner, tracker, editor, document maker and result cursor).

  • Index data is stored under the definition's hidden :luceneNg node, chunked by the
    definition's blobSize; readers and the async writer can cache segments on local disk
    (repository.home/indexNg by default).
  • Queries: property restrictions typed by the declared definition, path and node-type filters,
    relative properties, fulltext with excerpts, sorting and facets (insecure, statistical,
    secure). Results go through the shared fulltext cursor, so a query that outlives an index
    refresh restarts on the new version without duplicates.
  • Indexing runs only in async lanes; nrt/sync lanes are not supported.
  • Undeclared properties, and properties declared with incompatible types across rules, are
    indexed as strings: Lucene 9 requires one field shape across documents.

Known gaps (configurable analyzers, suggestions, spellcheck, similarity, binary text
extraction, NRT, copy-on-write, JMX) are listed in the module README.

The shared comparison fixture (AbstractIndexComparisonTest in the oak-search test-jar)
runs the same query scenarios against oak-lucene and LuceneNg.

@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from e4bc5ad to dbccf97 Compare March 26, 2026 15:11
@bhabegger
bhabegger marked this pull request as draft March 26, 2026 15:13
@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from 28c304c to 66fb544 Compare March 27, 2026 16:59
@bhabegger bhabegger changed the title feat: add lucene9 index provider as safe opt-in target feat: add Lucene 9 index provider (oak-search-luceneNg) Mar 27, 2026
@bhabegger
bhabegger marked this pull request as ready for review March 30, 2026 05:10
Comment thread oak-search-luceneNg/README.md Outdated
Comment thread oak-search-luceneNg/README.md Outdated

@thomasmueller thomasmueller 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.

Just some bikeshedding: I wonder if we should use the term "Lucene 9" at all.

Comment thread pom.xml Outdated
Comment thread oak-search-lucene-ng/README.md Outdated
@bhabegger bhabegger changed the title feat: add Lucene 9 index provider (oak-search-luceneNg) OAK-12089: Add Lucene 9 index provider (oak-search-luceneNg) Apr 10, 2026
@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from f0a7199 to de83bda Compare June 5, 2026 07:59
@bhabegger
bhabegger changed the base branch from OAK-12089 to trunk June 5, 2026 08:12
@bhabegger
bhabegger changed the base branch from trunk to OAK-12089 June 5, 2026 08:13
@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from 7cbee48 to 5b36b34 Compare August 10, 2026 09:24
@bhabegger bhabegger changed the title OAK-12089: Add Lucene 9 index provider (oak-search-luceneNg) OAK-12089: Add Lucene 9 index provider (oak-search-lucene-ng) Aug 10, 2026
@reschke

reschke commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-resolvable parent POM for org.apache.jackrabbit:oak-search-lucene-ng:1.93-SNAPSHOT: The following artifacts could not be resolved: org.apache.jackrabbit:oak-parent:pom:1.93-SNAPSHOT (absent): Could not find artifact org.apache.jackrabbit:oak-parent:pom:1.93-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 23, column 13

@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from 5b36b34 to c335365 Compare August 12, 2026 05:59

@amit-jain amit-jain left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think there are some architectural gaps which we should address, some are even mentioned in the readme as well and could be addressed with these changes.

Mainly we should reuse the FulltextIndexEditor and FulltextIndexTracker from the oak-search module which the elastic module also reuses and extends.

@amit-jain

Copy link
Copy Markdown
Contributor

Another important missing piece that is needed is the local fs index file support with the IndexCopier etc class which then get called with https://github.com/apache/jackrabbit-oak/blob/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java#L97

@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from 7c648cd to a82839a Compare August 26, 2026 07:14
@bhabegger

Copy link
Copy Markdown
Contributor Author

Another important missing piece that is needed is the local fs index file support with the IndexCopier etc class which then get called with https://github.com/apache/jackrabbit-oak/blob/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java#L97

Can you remind me what this is for ? Is this a must for this PR or can it be moved to later ?

@amit-jain

Copy link
Copy Markdown
Contributor

Another important missing piece that is needed is the local fs index file support with the IndexCopier etc class which then get called with https://github.com/apache/jackrabbit-oak/blob/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java#L97

Can you remind me what this is for ? Is this a must for this PR or can it be moved to later ?

https://jackrabbit.apache.org/oak/docs/query/lucene.html#copyonread

@bhabegger

Copy link
Copy Markdown
Contributor Author

Another important missing piece that is needed is the local fs index file support with the IndexCopier etc class which then get called with https://github.com/apache/jackrabbit-oak/blob/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java#L97

Can you remind me what this is for ? Is this a must for this PR or can it be moved to later ?

https://jackrabbit.apache.org/oak/docs/query/lucene.html#copyonread

Ok, so this is in fact a performance optimization in the case the NodeStore is remote (many cases I agree). This doesn't block functional testing though (unless performance is so degraded that this becomes just not usable at all even restricted to testing). I would accept this for now, and leave open to solve the performance issue in maybe some way different than how it was managed in the legacy.

Ok, with you to differ ?

@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch 2 times, most recently from 402d45c to 605a3a6 Compare August 28, 2026 10:41
Comment thread oak-search-lucene-ng/README.md Outdated
@bhabegger

Copy link
Copy Markdown
Contributor Author

Another important missing piece that is needed is the local fs index file support with the IndexCopier etc class which then get called with https://github.com/apache/jackrabbit-oak/blob/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/reader/DefaultIndexReaderFactory.java#L97

FYI, last commit adds CopyOnRead

@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from 819a148 to aa4ba72 Compare October 9, 2026 14:57
@bhabegger

Copy link
Copy Markdown
Contributor Author

Tested locally :

image

bhabegger and others added 29 commits October 20, 2026 11:53
Migrate the standalone LuceneNg tests to JUnit 5. Tests that extend JUnit 4 base classes from other modules stay on JUnit 4; both run in the same build. Temporary directories that were created under target keep that location.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Undeclared and String properties index every non-binary value as a string term, including Date, Path, Reference, WeakReference, URI and Decimal values. Boolean, Name, Path, Reference/WeakReference and URI definitions index only matching values and strings. Decimal definitions index and query Double points. A declared Binary type produces no property field. The accepted value types are documented in the README and covered case by case in PropertyTypeIndexingTest.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ed type

Long, Date, Double and Decimal properties sort numerically; all other declared types sort by the indexed string form. Single- and multi-valued properties now write the same doc-values type (SORTED_NUMERIC or SORTED_SET), so documents mixing both are no longer rejected by Lucene and dropped, and a multi-valued property sorts by its smallest value. Ordered Decimal, Name, Path, Reference, WeakReference and URI properties can now be sorted. Existing indexes with ordered properties need reindexing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merges write segment files through the index definition's NodeBuilder, which is not
thread-safe; as in oak-lucene for repository-backed directories, use SerialMergeScheduler.
… not queries

Query planning no longer refreshes the tracker with the querying session's root, which
serialized all queries and could reopen older index versions. As in oak-lucene, the
provider observes repository changes through a BackgroundObserver.
Index files were split into hard-coded 32 KB blobs, 32 times more than oak-lucene's
default for repository-backed directories. Files now use the definition's blobSize
(about 1 MB by default) and must record it; there is no implicit fallback size.
Query translation moves from LuceneNgIndex to LuceneNgQueryBuilder and requires the
planner's result: only planner-validated restrictions become clauses, typed by their
declared definition. Exclusive bounds beyond Long.MIN/MAX_VALUE now match nothing.
Query behaviour is covered end to end by the shared comparison fixture, translation
edge cases by unit tests.
As in oak-lucene, a query that outlives an index refresh restarts on the new index
version and the shared FulltextPathCursor skips rows already returned, instead of
continuing from a position in the previous version. Read limits, the size estimate,
excerpts and facets follow the shared cursor's behaviour.
… in LuceneNg

When the shared planner evaluates a relative property such as jcr:content/keyword on
descendant documents of an nt:base rule, LuceneNg now queries the mapped field, adjusts
path restrictions and returns the transformed ancestor paths, as oak-lucene does, instead
of returning no results.
…is requested

Fulltext queries highlighted every result row whether or not the query selected an
excerpt. As in oak-lucene, excerpts are generated only for requested rep:excerpt columns.
…tor term

Documents index their ancestor paths, as in oak-lucene, so a descendant restriction
is a single term lookup instead of a prefix query walking every path below it.
Existing LuceneNg indexes need reindexing.
…ync lane

LuceneNg has no near-real-time index, so writing during user commits put a second
writer on the repository-backed index. Definitions with an async lane are now indexed
only by the async indexer; Oak reports the missing nrt/sync provider.
Lucene rejects empty facet labels, which made the whole document fail and left the node
unindexed or stale. As in oak-lucene, empty facet values are skipped.
…as String

Lucene 9 rejects a field whose shape differs between documents, so a property declared
with incompatible types in two indexing rules (or explicitly and through a regex
definition) dropped the nodes of one rule. Such properties are now indexed and queried
as String in all rules, with a warning naming the property and rules.
The writer no longer downloads the whole index before an incremental indexing run.
Files already on local disk are read locally; others are read from the repository and
copied in the background, as the reader cache does.
… tracking

As in oak-lucene, only readers record the current local folder of an index. The writer
registering its folder could make the cache delete a folder that readers of another
index version still used.
…down

Once the copier is closed, or its executor no longer accepts tasks, files are read
remotely without scheduling a copy and directories are closed directly, instead of
failing with RejectedExecutionException and leaving the local directory open.
LuceneNg now defaults to repository.home/indexNg. Its startup cleanup assumes no
folder under its root is in use, which is only true for its own folders; it also no
longer deletes nrt-* folders, which only oak-lucene creates.
Creating an existing file now fails with FileAlreadyExistsException instead of silently
replacing it, files are listed in sorted order, and temporary files use Lucene's
temp file names.
As in oak-search-elastic, the exported package is marked @internal and carries an OSGi
package version.
…E queries

As in oak-lucene and for property LIKE restrictions, the pattern is converted with
QueryUtils.sqlLikeToLuceneWildcardQuery, so \_ and \% stay literal.
…vation step fails

Each deactivation step logs its failure instead of aborting, so the open indexes, the
local cache copier and the executor are always released.
…roperties by depth

Documents index their depth, as in oak-lucene, so a direct-children restriction evaluated
on descendant documents matches the ancestor and the depth in Lucene instead of returning
the whole subtree for post-filtering. Existing LuceneNg indexes need reindexing.
@bhabegger
bhabegger force-pushed the oak-12089-lucene9-core branch from ff387fb to 2bcbbf2 Compare October 20, 2026 09:58
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.

4 participants