The lucene-monitor module
|
This section documents the current Apache Lucene 10.x line — Lucene 10 requires Java 21 — as published
at the Apache Lucene documentation and Javadoc, which is the reference these
pages are written and verified against. No specific patch version is pinned; examples target This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Lucene iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Normal search registers documents and runs a query against them. Reverse search inverts that: you
register the queries once and stream documents past them, asking "which of my stored queries does
this document match?". lucene-monitor (the module that was formerly the standalone Luwak
library) is Lucene’s implementation — the engine behind saved-search alerts, content-routing rules,
and real-time classification.
Registering queries in a Monitor
A Monitor holds a set of MonitorQuery records, each a stable id plus a parsed Query (and
optional original query string and metadata map). monitor.match takes one or more in-memory
documents and returns the ids of every registered query they satisfy.
// https://lucene.apache.org/core/10_0_0/monitor/org/apache/lucene/monitor/Monitor.html
import org.apache.lucene.monitor.Monitor;
import org.apache.lucene.monitor.MonitorQuery;
import org.apache.lucene.monitor.MatchingQueries;
import org.apache.lucene.monitor.QueryMatch;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.TextField;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.queryparser.classic.QueryParser;
import java.util.Map;
StandardAnalyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser("text", analyzer);
try (Monitor monitor = new Monitor(analyzer)) {
// register saved searches -- id, Query, original string, metadata
monitor.register(
new MonitorQuery("alert-1", new TermQuery(new Term("text", "lucene"))),
new MonitorQuery("alert-2", parser.parse("search AND (engine OR library)"),
"search AND (engine OR library)", Map.of("team", "platform")));
// an incoming document (never added to any index)
Document doc = new Document();
doc.add(new TextField("text", "the new lucene monitor module does reverse search",
Field.Store.NO));
MatchingQueries<QueryMatch> result = monitor.match(doc, QueryMatch.SIMPLE_MATCHER);
for (QueryMatch m : result.getMatches()) {
System.out.println("matched " + m.getQueryId()); // alert-1, alert-2
}
System.out.println("queries actually run: " + result.getQueriesRun());
}
register re-parses and stores the queries in an internal Lucene index; call it again with the same
id to update a query, and monitor.deleteById("alert-1") to remove one. monitor.match(Document[],
factory) matches a batch and returns MultiMatchingQueries.
How much detail: the matcher factory
The second argument to match is a MatcherFactory that builds a CandidateMatcher. It decides
what each match tells you — and how much work per candidate query:
| Factory | Each match yields |
|---|---|
|
Just the matching query id. Cheapest. |
|
The id plus the hit positions/offsets within the document. |
|
The id plus a Lucene |
|
The id plus a relevance score under the given |
// https://lucene.apache.org/core/10_0_0/monitor/org/apache/lucene/monitor/HighlightsMatch.html
import org.apache.lucene.monitor.HighlightsMatch;
import org.apache.lucene.monitor.MatchingQueries;
MatchingQueries<HighlightsMatch> hi = monitor.match(doc, HighlightsMatch.MATCHER);
for (HighlightsMatch m : hi.getMatches()) {
m.getHits("text").forEach(h -> System.out.println(h.startOffset() + ".." + h.endOffset()));
}
Scaling with a Presearcher
Running every registered query against every document does not scale past a few thousand queries. A
Presearcher builds a secondary query from the incoming document’s terms and runs it against the
query index first, so only queries that could match are executed. TermFilteredPresearcher (the
default) is term-based; MultipassTermFilteredPresearcher trades more index space for fewer false
candidates.
// https://lucene.apache.org/core/10_0_0/monitor/org/apache/lucene/monitor/TermFilteredPresearcher.html
import org.apache.lucene.monitor.MonitorConfiguration;
import org.apache.lucene.monitor.TermFilteredPresearcher;
import java.nio.file.Path;
MonitorConfiguration config = new MonitorConfiguration()
.setIndexPath(Path.of("/var/data/monitor-queries"), // persist the query set (see below)
new TermFilteredPresearcher());
try (Monitor monitor = new Monitor(analyzer, config)) {
// register 100k MonitorQuery objects ...
MatchingQueries<QueryMatch> result = monitor.match(doc, QueryMatch.SIMPLE_MATCHER);
// result.getQueriesRun() is now a small fraction of the registered total
}
Compare getQueriesRun() with the registered count to see the presearcher’s selectivity for your
workload.
Persisting the query set
By default the Monitor keeps its query index in memory and must be repopulated on restart. Give
MonitorConfiguration.setIndexPath a directory and the registered MonitorQuery records are stored
on disk and reloaded automatically — the original query strings and metadata are kept so they can be
re-parsed. The
lucene-monitor module overview documents
MonitorConfiguration, the QueryDecomposer that splits disjunctions for better presearcher
selectivity, and the QueryCache.
The Maven coordinate is org.apache.lucene:lucene-monitor:
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-monitor</artifactId>
<version>10.0.0</version>
</dependency>
Versus Elasticsearch’s percolator
Elasticsearch’s percolate query is the same reverse-search idea as a managed feature — stored
queries indexed in a percolator field type, matched against a document supplied at query time — and it is itself built on this Lucene machinery; see
Elasticsearch search extras.
Related pages
-
Query parsers — producing the
Queryobjects wrapped in eachMonitorQuery. -
Core queries — the
TermQuery/BooleanQuerybuilding blocks a saved search is made of. -
Highlighting, suggesters & more —
HighlightsMatchreuses the same hit-offset concepts as the highlighter. -
Elasticsearch search extras — the
percolatequery, the server-managed equivalent.