Lucene File Formats Cheat-Sheet

This is a consolidated quick-reference for every per-segment file a Lucene index writes — extension, what it holds, the format class that defines it, and the chapter that takes it apart. It is the companion lookup table to the Lucene data-structures masterclass and the storage-engine masterclass: those teach you why each structure exists; this page is what you keep open while you find/xxd a real shard.

A Lucene index is a set of segments plus one segments_N commit file. Each segment _N is a bundle of files sharing the prefix _N, one per data structure, each written by a sub-format of the active Codec. Start from Segments and Codecs for the immutability and commit-point model; this page assumes it.

Warning: The default codec is versioned (LuceneNNNCodec, e.g. Lucene101Codec); NNN bumps whenever an on-disk format changes, which is exactly what a Lucene upgrade inside OpenSearch is. Do not hard-code the number — grep for it. The extensions below are stable across many versions; the format class names carry the version and move.


Master table: every segment file extension

Extension(s)StructureHoldsFormat class (sub-format)Chapter
segments_NSegmentInfos (commit point)the list of live segments, generation N, per-segment codec + deletes/field-infos genSegmentInfosFormat (read/written by SegmentInfos)segments-and-codecs.md
.siSegmentInfoper-segment metadata: doc count, codec name, file set, sort, diagnostics, versionSegmentInfoFormat (Lucene*SegmentInfoFormat)segments-and-codecs.md
.cfs / .cfeCompound file + entriesall of a segment's other files packed into one blob + its directory (offsets/lengths)CompoundFormat (Lucene*CompoundFormat)segments-and-codecs.md
.fnmField infosthe field list: name, number, index options, doc-values type, points dims, vector dims/similarityFieldInfosFormat (Lucene*FieldInfosFormat)inverted-index-and-postings.md
.fdt / .fdx / .fdmStored fields data / index / metathe original _source-style stored values, block-compressed (LZ4 or DEFLATE)StoredFieldsFormat (Lucene*StoredFieldsFormat)docvalues-columnar.md, storage-engine/index.md
.tim / .tip / .tmdTerms dictionary / index / metathe BlockTree terms dictionary, its FST term index, and the terms metadataPostingsFormat (Lucene*PostingsFormat)inverted-index-and-postings.md, lucene-data-structures/lab-01-fst-terms-dict.md
.doc / .pos / .payPostings: docs / positions / payloads+offsetsthe inverted lists: doc ids + freqs, term positions, payloads & offsetsPostingsFormat (Lucene*PostingsFormat)inverted-index-and-postings.md, lucene-data-structures/lab-02-postings-and-bkd.md
.dvd / .dvmDocValues data / metacolumnar per-doc values (NUMERIC/BINARY/SORTED/SORTED_SET/SORTED_NUMERIC) for sort/aggs/scriptsDocValuesFormat (Lucene*DocValuesFormat)docvalues-columnar.md, lucene-data-structures/lab-04-docvalues-encoding.md
.nvd / .nvmNorms data / metaper-field length norms (the `d`/avgdl term in BM25)
.kdd / .kdi / .kdmPoints data / index / metathe BKD tree for numeric, date, IP, and geo points (range/geo queries)PointsFormat (Lucene*PointsFormat)points-and-bkd-trees.md, lucene-data-structures/lab-02-postings-and-bkd.md
.vec / .vex / .vemVector data / graph / metaHNSW float/byte vectors (.vec), the graph connections (.vex), and metadata (.vem)KnnVectorsFormat (Lucene*HnswVectorsFormat)hnsw-vector-search.md, lucene-data-structures/lab-03-hnsw-vector-files.md
.livLive docsthe deletion bitset: which docs in this segment are still aliveLiveDocsFormat (Lucene*LiveDocsFormat)indexwriter-and-merges.md
.dii / .dim (older)Points (pre-BKD-split layout)older points layout; superseded by .kdd/.kdi/.kdmPointsFormat (legacy)points-and-bkd-trees.md
write.lock(not a segment file)the IndexWriter lock — one writer per directoryn/a (Directory lock)indexwriter-and-merges.md

Note: The k-NN plugin's faiss / nmslib engines do not use .vec/.vex/.vem. Those native engines write their own files (e.g. .faiss, historically .hnsw) through the plugin's own KnnVectorsFormat, and the graph lives in off-heap native memory, not the JVM heap — see native-jni-and-memory.md and knn/engines.md. The .vec/.vex/.vem triple is specifically Lucene's built-in HNSW engine.


Grouped by codec sub-format

The Codec is a bundle of sub-format factories. Map each one to the files it owns — this is the table from segments-and-codecs.md, reorganized as a lookup:

Sub-formatFiles it ownsOne-line job
PostingsFormat.tim .tip .tmd .doc .pos .payterms dictionary + inverted lists
DocValuesFormat.dvd .dvmcolumnar per-doc values
StoredFieldsFormat.fdt .fdx .fdmthe stored _source
PointsFormat.kdd .kdi .kdmBKD trees for numeric/geo
KnnVectorsFormat.vec .vex .vemHNSW vectors + graph
NormsFormat.nvd .nvmscoring norms
FieldInfosFormat.fnmfield metadata
SegmentInfoFormat.siper-segment metadata
LiveDocsFormat.livdeletes bitset
CompoundFormat.cfs .cfepack everything into one file

Each sub-format is independently SPI-pluggable and versioned, which is why per-field formats exist (PerFieldPostingsFormat, PerFieldDocValuesFormat, PerFieldKnnVectorsFormat): the codec can pick a different format per field, and the chosen format name is recorded in .fnm so the reader re-instantiates the right one. This is exactly how the k-NN plugin attaches its vector format to knn_vector fields only.

# See the default codec and its sub-formats in your Lucene checkout:
ls lucene/core/src/java/org/apache/lucene/codecs/lucene*/
grep -rln "extends Codec\b\|extends FilterCodec" lucene/core/src/java/org/apache/lucene/codecs/

# The SPI service file that registers the default codec:
find lucene -path "*META-INF/services/org.apache.lucene.codecs.Codec"

Compound vs non-compound segments

A small segment is usually written compound: every file above (except .si, .liv, and .fnm-gen-bumped files) is packed into a single .cfs blob with a .cfe table of contents, so the segment costs ~3 file handles instead of ~12. Large merged segments are typically non-compound (per-file overhead amortizes away).

You see (per segment)Means
_5.si _5.cfs _5.cfe onlycompound segment — the real files are inside .cfs
_5.si _5.fnm _5.tim _5.doc _5.dvd ... (many)non-compound — files on disk directly
# Per-segment file count tells you which is which:
find "$SHARD/index" -maxdepth 1 -type f | sed -E 's/.*\/_[0-9a-z]+//' | sort | uniq -c

To look inside a .cfs you need Luke or CheckIndex (next section) — .cfs is opaque to xxd beyond its header/footer.


Every Lucene file written through the codec framework is wrapped by CodecUtil with a fixed header and footer. This is what makes a file self-describing and corruption-detectable.

PartWritten byContents
HeaderCodecUtil.writeIndexHeader / writeHeadermagic int 0x3fd76c17, codec name (string), format version (int), optionally a 16-byte segment id + suffix
Bodythe sub-formatthe actual data
FooterCodecUtil.writeFootermagic int 0xc02893e8 (footer magic), algorithm id, and a CRC32 checksum of everything before it

On open, Lucene calls CodecUtil.checkHeader (name + version range) and, when verifying, CodecUtil.checksumEntireFile / checkFooter to compare the stored CRC32 against a recomputed one. A mismatch is a CorruptIndexException.

grep -rn "CODEC_MAGIC\|FOOTER_MAGIC\|writeHeader\|checkHeader\|writeFooter\|checkFooter\|checksumEntireFile" \
  lucene/core/src/java/org/apache/lucene/codecs/CodecUtil.java
# First 4 bytes of any codec file are the magic 0x3fd76c17:
xxd -l 16 "$SHARD/index/_5.si"
# 00000000: 3fd7 6c17 0014 4c75 6365 6e65 ...   .l..Lucene...
#           ^^^^^^^^^ CODEC_MAGIC   ^^ length+"Lucene..." codec name

Note: This header/footer is why a truncated or bit-rotted segment file is caught at open time rather than returning wrong results: the CRC32 footer won't match. It is also why you can identify a stray file's format by xxd-ing the first ~32 bytes — the codec name is right there in plaintext after the magic.


Inspecting an index: the toolbox

ToolWhat it doesWhen
find + dulist files / sizes per extension"what's eating disk in this shard?"
xxd / oddump bytes; read the CodecUtil magic + codec nameidentify/confirm a single file's format
CheckIndexwalk every segment, verify checksums + structure, report per-segment health; -exorcise drops broken segmentscorruption suspected; CI of a custom codec
LukeGUI: browse terms, postings, doc-values, points, vectors, stored fields per segmentlearning the structures visually
_cat/segments, _segmentsOpenSearch's view of the same segments (docs, size, codec, committed/searchable)from a running cluster
# 1. Files by extension and size in a real shard:
SHARD=/path/to/<data>/nodes/0/indices/<uuid>/0
find "$SHARD/index" -maxdepth 1 -type f | sed 's/.*\.//' | sort | uniq -c
du -ah "$SHARD/index" | sort -rh | head

# 2. CheckIndex (needs the lucene-core + codecs jars on the classpath; OpenSearch
#    ships them under lib/ and modules/lang-painless etc. — point -cp at them):
java -cp 'lib/*' org.apache.lucene.index.CheckIndex "$SHARD/index"
#   -> "No problems were detected with this index." or a per-segment failure

# 3. Read a file's codec name straight from its header:
for f in "$SHARD"/index/_*.si "$SHARD"/index/_*.fnm; do
  printf '%s -> ' "$(basename "$f")"; xxd -s 4 -l 24 "$f" | head -1
done

# 4. The cluster's own view:
curl -s 'localhost:9200/my-index/_segments?pretty' | head -60
curl -s 'localhost:9200/_cat/segments/my-index?v'

CheckIndex is the ground truth: it re-reads every sub-format and re-verifies every CRC32. If you write a custom codec (Lab L2), running CheckIndex over its output is your acceptance test.


hybridfs: how those files get mapped into memory

OpenSearch's default store type is hybridfs (index.store.type), which chooses, per file extension, whether to mmap the file (MMapDirectory → page cache, off-heap) or read it through NIO (NIOFSDirectory → heap buffers). The rule of thumb: random-access structures that benefit from the OS page cache are mmapped; large sequential-scan files are read with NIO so they don't blow out the virtual address space and mmap count.

File groupTypically mapped viaWhy
.tip (terms index / FST), .dvm/.nvm/.fdm/.kdm/.vem (meta), .tmd, .si, .cfe, .fnm, segments_Nmmapsmall, hot, random-access; page cache is ideal
.kdd/.kdi (points/BKD), .dvd (doc-values), .tim (terms dict), .vec/.vex (vectors)mmap (page-cache-resident hot data)random access; benefit from staying in page cache
.fdt (stored fields), .pos/.pay and large postingsNIO (or mmap, version-dependent)large, more sequential; avoid exhausting mmaps

Warning: The exact per-extension split is decided in code and shifts between versions — don't memorize the rows above as gospel, grep them. mmap requires a high vm.max_map_count (OpenSearch's docs require 262144); too low and the node fails to open shards with "max virtual memory areas vm.max_map_count too low." Store types are taken apart in storage-engine/lab-03-store-types-mmap.md.

# Where the hybridfs per-extension routing lives (names vary by version):
grep -rn "hybridfs\|HybridDirectory\|HybridFSDirectory\|FsDirectoryFactory\|nioExtensions\|mmapExtensions" \
  server/src/main/java/org/opensearch/index/store/

# Confirm the store type on a running index:
curl -s 'localhost:9200/my-index/_settings?filter_path=**.store' | python3 -m json.tool

The k-NN native engines (faiss/nmslib) sidestep this entirely: their graphs are loaded into native memory by JNI, not through the Directory at all — which is why they need explicit warmup.


Quick decoding drills

Given a find listing, name every file. Try it before reading the answer.

_0.cfs  _0.cfe  _0.si
_1.fnm  _1.tim  _1.tip  _1.tmd  _1.doc  _1.pos  _1.dvd  _1.dvm  _1.kdd  _1.kdi  _1.kdm  _1.vec  _1.vex  _1.vem  _1.si  _1.liv
segments_3   write.lock
File(s)What it is
_0.cfs/.cfe/.sisegment _0, compound (real files inside .cfs) + its metadata
_1.fnmsegment _1 field infos
_1.tim/.tip/.tmd + _1.doc/.posterms dict + index + meta, and postings (docs + positions)
_1.dvd/.dvmdoc-values (sort/aggs source)
_1.kdd/.kdi/.kdmBKD points (a numeric/geo field exists)
_1.vec/.vex/.vemLucene HNSW vectors + graph (a knn_vector on the Lucene engine)
_1.livsegment _1 has deletes (a live-docs bitset)
segments_3the commit point, generation 3
write.lockthe IndexWriter lock — not a segment file

If you can do that cold, you can read any shard directory in the wild.