Directory implementations & storage
|
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. |
A Directory is Lucene’s abstraction over the place index files live — almost always a filesystem
directory. It exposes create/open/delete/rename plus a lock, and hides whether reads go through
mmap, positional read, or a byte array. Choosing the right implementation and understanding how
it uses memory is most of what storage tuning involves.
Directory implementations
Call FSDirectory.open(Path) and let Lucene pick: on a 64-bit JVM it returns MMapDirectory, which
is the right default on every supported platform. The concrete types:
| Implementation | When it is used |
|---|---|
|
Memory-maps each file into the process address space through the Java Platform’s foreign-memory (Panama) API, as |
|
Positional |
|
Holds the whole "index" in heap byte buffers. Not persistent — for tests and short-lived in-memory indexes. This is the modern replacement for the removed |
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/store/FSDirectory.html
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/store/MMapDirectory.html
import org.apache.lucene.store.*;
import java.nio.file.Path;
// Recommended: let FSDirectory choose (MMapDirectory on a 64-bit JVM).
Directory dir = FSDirectory.open(Path.of("/var/data/idx"));
// Force a specific implementation only for a concrete reason.
Directory nio = new NIOFSDirectory(Path.of("/var/data/idx"));
// In-memory, non-persistent -- tests and scratch indexes.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/store/ByteBuffersDirectory.html
Directory mem = new ByteBuffersDirectory();
MMapDirectory can be told which files to keep resident and which to leave on-demand; see its
Javadoc for setPreload and the group-by-extension preload predicate. For test code, the
test framework's newDirectory() returns a
randomly chosen, assertion-wrapped implementation so tests exercise all of them.
Locking and write.lock
At most one IndexWriter may hold an index open at a time. On open, the writer acquires
write.lock in the directory through its LockFactory; a second writer — in this JVM or another
process — fails with LockObtainFailedException. FSDirectory defaults to NativeFSLockFactory,
which uses an OS advisory file lock that the kernel releases even if the JVM is killed, so a stale
lock file left on disk is not by itself a problem.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/store/LockFactory.html
import org.apache.lucene.store.*;
import java.nio.file.Path;
// Default: native advisory lock, released by the OS on process death.
Directory dir = FSDirectory.open(Path.of("/var/data/idx")); // NativeFSLockFactory
// Explicit choice, e.g. a single-JVM in-process guard.
Directory jvm = FSDirectory.open(Path.of("/var/data/idx"), new SingleInstanceLockFactory());
try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(analyzer))) {
// ... only writer holding /var/data/idx ...
} // close() releases write.lock
// Recover from a crashed writer ONLY after proving no live writer exists.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/index/IndexWriter.html#isLocked(org.apache.lucene.store.Directory)
if (IndexWriter.isLocked(dir)) {
// investigate; do not blindly delete write.lock while a writer may be running
}
Never point two writers at one directory expecting them to cooperate — that is a job for a search server (Solr’s update concurrency, Elasticsearch bulk indexing) or for your own single-writer design.
Prefetch and ReadAdvice
Lucene 10 added I/O hints on the read path. IndexInput#prefetch(offset, length) asks the OS to
start pulling a byte range into the page cache (via madvise(MADV_WILLNEED) on Linux) before Lucene
actually reads it, overlapping I/O with computation — query execution uses this to warm postings and
doc-values blocks it is about to touch. The ReadAdvice enum (NORMAL, RANDOM, SEQUENTIAL,
RANDOM_PRELOAD) is set per IndexInput open via an IOContext so the OS can tune read-ahead: bulk
merge reads are SEQUENTIAL, term-dictionary lookups are RANDOM.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/store/IndexInput.html
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/store/ReadAdvice.html
import org.apache.lucene.store.*;
try (Directory dir = FSDirectory.open(java.nio.file.Path.of("/var/data/idx"))) {
IOContext ctx = IOContext.DEFAULT.withReadAdvice(ReadAdvice.RANDOM);
try (IndexInput in = dir.openInput("_0.dvd", ctx)) {
in.prefetch(0L, Math.min(in.length(), 1 << 20)); // hint: read the first 1 MiB soon
// ... seek + read; the page cache is already being warmed ...
}
}
Most code never calls these directly — the codecs do — but the ReadAdvice default is worth
knowing when profiling merge or cold-cache behaviour.
Storage uses the OS page cache, not the heap
MMapDirectory reads are served from mapped pages the operating system caches outside the JVM
heap — the class Javadoc,
MMapDirectory,
spells this out. The practical sizing rule is the inverse of most Java services: give the JVM only
the heap it needs (query structures, IndexWriter RAM buffer, doc-values accessors) and leave the
rest of physical memory free for the OS to cache index files. An oversized -Xmx starves the page
cache and makes search slower. Mapped memory also shows up as large virtual size (VIRT) and
shared resident pages — expected, not a leak.
// Nothing special to configure -- FSDirectory.open already returns MMapDirectory on a 64-bit JVM,
// and its reads come from OS-cached pages, not the heap. Size -Xmx small; leave RAM for the cache.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/store/MMapDirectory.html
Directory dir = FSDirectory.open(Path.of("/var/data/idx"));
System.out.println(dir.getClass().getSimpleName()); // MMapDirectory
// A 32 GiB host serving a 40 GiB index: -Xmx6g for the app leaves ~26 GiB of page cache for
// hot index files. Raising -Xmx to 24g would evict those pages and regress query latency.
Sizing the IndexWriter RAM buffer, warming, and merge-time I/O are covered in
Performance tuning.
Related pages
-
Architecture & data flow — the segment files that live inside a
Directory. -
Getting started —
FSDirectory.openin the first round-trip. -
Performance tuning — heap vs. page cache, RAM buffer, and warming in depth.
-
Testing tools & modules —
newDirectory()and the mock directory wrappers. -
org.apache.lucene.storeJavadoc — everyDirectory,LockFactory,IndexInput, andIOContexttype.