What Is a Postings List in Elasticsearch?

What Is a Postings List in Elasticsearch?

A complete, plain-English guide to the humble list that makes full-text search fast: what is stored in it, how it is built and squeezed onto disk, how searches walk through it, and why its size and shape decide the speed and cost of your whole cluster.

Open a thick textbook and turn to the index at the very back. Next to the word “volcano” you find a short list of page numbers: 45, 78, 132. You do not read the whole book to find where volcanoes are discussed. You jump straight to those three pages. Now imagine a book with a hundred million pages, and an index that lists, for every word ever printed, every page where it appears. That index is what makes it possible to search the book in a blink.

Elasticsearch works exactly like that book. Behind every full-text search sits an index, and inside that index, for every word, sits a list of the documents that contain it. That list is called a postings list. It is one of the oldest and most important ideas in search, and it is the reason Elasticsearch can find a word among billions of documents in milliseconds. This guide starts from zero, opens the list to see what is inside, follows it onto the disk, watches searches walk through it, and ends with the numbers, the trade-offs, and the habits that keep it small and fast in production.

1What a Postings List Actually Is

Three plain ideas, put together: a term, a document, and a list. Once each is clear, the rest follows naturally.

Elasticsearch stores documents, such as product descriptions, news articles, or log lines. When a text document arrives, Elasticsearch runs it through an analyzer, which cuts the text into small pieces called terms. Roughly speaking, a term is a word that has been cleaned up: lowercased, stripped of punctuation, and sometimes reduced to its root form. The sentence “The Quick Brown Fox” might become the terms “the”, “quick”, “brown”, and “fox”.

For every distinct term, the index keeps a list of the documents in which that term occurs. That list is the postings list of the term. Each entry in the list is called a posting. At its simplest, a posting is just the number of a document. In richer forms, a posting also says how many times the term occurs in that document, and at which positions.

The collection of all terms, each with its postings list, is called an inverted index. It is “inverted” because a normal document is a list of words, while this structure turns things around: it is a list of words, each pointing back to documents. When you search for “fox”, Elasticsearch does not scan documents. It looks up the term “fox” in the sorted dictionary of terms, follows the pointer to its postings list, and reads off the documents.

Everyday Analogy

Think of the card catalog of an old library. There is a drawer for each subject word, and inside each drawer, cards pointing to the shelves where matching books can be found. Nobody walks past all the shelves looking for books about volcanoes. They open the “volcano” drawer and read the cards. The drawer is the term, and the stack of cards is the postings list.

One list
per term, per field, per segment
Sorted
document numbers are stored in increasing order
Immutable
once written into a segment, a list is never edited
A postings list does not hold your documents. It holds the answer to the question: which documents contain this word?

What it is not

A postings list is not the document itself. The original text lives in a different structure, the stored fields, from which Elasticsearch builds the source you see in results. It is not the structure used for sorting and grouping either, which is a column-style structure called doc values. And it is not the structure used for numbers and dates, which are stored in a special tree for fast range checks. The postings list has one job: to answer, quickly, which documents contain a given term. Chapter 8 shows how these structures sit side by side.

i
Where the Idea Comes From

Elasticsearch does not build postings lists by itself. It relies on Lucene, the open-source search library that powers every shard. Each shard is a Lucene index, and the postings lists described in this guide are Lucene’s. That is why the names, the file formats, and the details come from Lucene, and why they change when Lucene changes.

The one-sentence version

If you remember one line from this chapter, make it this: a postings list is the sorted list of documents, inside one segment, that contain a particular term, optionally carrying how often and where the term appears, and it is the data structure that makes full-text search fast.

2Why It Exists: Finding Words Without Reading Everything

Every data structure is born from a painful problem. The problem here is that reading everything is far too slow.

Suppose you keep a hundred million documents, and a user searches for the word “volcano”. The simplest approach is to open every document and check whether it contains that word. This is called a full scan. If each document takes a microsecond to check, the scan takes a hundred seconds. And that is for one word, for one user, on one machine. With thousands of users searching at once, the approach collapses.

The inverted index changes the arithmetic. Instead of checking every document, it goes straight to the word. Finding the word in the sorted dictionary takes a few steps, because the dictionary is organized so that a lookup skips most of it. Reading the postings list costs time only in proportion to the number of documents that match, not the number that exist. If only a few thousand documents mention “volcano”, then only a few thousand entries are read. The cost is tied to the answer, not to the size of the collection.

Everyday Analogy

Finding a name in a phone book by reading every entry would take all day. Finding it by jumping to the right letter, then the right page, takes seconds. The postings list is the extra step that says, once you have found the word, exactly which pages to visit, and no others.

A short history

1

The classic idea

Inverted indexes are decades older than Elasticsearch. They were the core of early information retrieval systems, and the idea of a dictionary of terms pointing to lists of documents has barely changed.

2

Lucene brings it to open source

Lucene made a fast, compact inverted index available to everyone, with sorted document numbers and compression, and it became the engine underneath many search products, including Elasticsearch.

3

Block-based compression

Later versions of Lucene moved to storing postings in fixed-size blocks, packed tightly with bit-level compression, which shrank indexes and sped up reading.

4

Better scoring and skipping

The default scoring formula was changed to a more modern one, and, around Elasticsearch 7, a technique that skips whole blocks of postings that cannot reach the top results made large searches much cheaper.

5

Today: a tuned, mature structure

The postings format has been refined in many steps. Modern Elasticsearch users mostly tune it by choosing what to index and how, not by touching the format.

The exact release for each step is worth double-checking in the release notes of your version. The direction of travel is what matters: the idea stayed the same, while the encoding became smaller and the reading became smarter.

Two ways to organize the same information

To see why the inversion matters, compare two views of the same three short documents. In the forward view, each document lists its words. In the inverted view, each word lists its documents. If you want to know “which documents contain brown?”, the forward view forces you to read every document. The inverted view gives you the list at once. The forward view is good for showing a whole document, and the inverted view is good for finding documents. Search needs the second, and Elasticsearch stores both, each in its own structure.

QuestionForward view (document to words)Inverted view (word to documents)
Which documents contain “fox”?Read every document and checkRead one list
What words are in document 2?Read document 2Check every list for document 2
How many documents contain “brown”?Read every documentRead the size of one list
Which documents contain both “quick” and “fox”?Read every documentCompare two short lists

Why not a database index on the text?

An ordinary database index on a text column stores the whole value in a sorted tree. It answers “find rows where the column equals this exact value” very well, but it cannot answer “find rows where the text contains this word” without scanning. Full-text search needs an index built from the words inside the text, and that is exactly what postings lists provide.

3Core Concepts You Need First

A dozen words explain nearly everything about postings lists. Learn them here, and later chapters will read like common sense.

Text

Token and term

Analysis cuts text into tokens. A token, once cleaned and stored in the index, is called a term.

Text

Analyzer

The machine that turns raw text into terms: it splits, lowercases, removes noise, and may reduce words to roots.

Structure

Term dictionary

The sorted collection of all distinct terms of a field in a segment, each pointing to its postings list.

Structure

Postings list

For one term, the sorted list of documents that contain it, with optional extra details.

Numbers

Document ID

A small whole number that names a document inside one segment. It is not the same as the document’s _id.

Numbers

Term frequency

How many times a term occurs in one document. It appears in the postings and feeds into scoring.

Numbers

Document frequency

How many documents contain the term. It is the length of the postings list and feeds into scoring.

Detail

Position

The place of a term within a document’s text, counted in terms. Positions make phrase searches possible.

Detail

Offsets and payloads

Optional extras: start and end character positions, used for highlighting, and small custom data attached to a position.

Scoring

Norms

A compact per-document number that records field length, so that matches in short fields can score higher.

Reading

Postings iterator

The object a search uses to walk a postings list, one document at a time, or to jump ahead to a target.

Reading

Skip data

Extra markers stored with a long list so that a reader can jump over stretches of it without reading them.

The unit of storage: the segment

Every shard is made of segments, small sealed sets of files that are never changed after they are written. Postings lists live inside a segment. This means that a term such as “fox” has not one postings list per shard but one per segment, each listing only the documents of that segment. When new documents arrive, they land in a new segment with its own fresh lists. When segments are merged, their lists are combined into new ones. The whole life of a postings list is tied to the life of its segment.

Everyday Analogy

An encyclopedia published in several volumes has an index at the end of each volume. To find “volcano,” you look in every volume’s index, and each one lists only the pages of its own volume. When the publisher reprints the volumes as one big volume, a new combined index is made.

Document IDs are local and small

Inside a segment, documents are numbered from zero upward, in the order they were added. These numbers are what postings lists contain. They are small whole numbers, because a segment holds at most a couple of billion documents, and typically far fewer. They are local: document number 7 in one segment has nothing to do with document number 7 in another. They are also temporary in a sense: when segments are merged, the surviving documents are renumbered. The identifier you see in results, the _id, is a different, permanent thing, stored separately.

Frequency versus position

These two details are often confused. Term frequency answers “how many times does the term appear in this document?” Position answers “where, exactly, does it appear?” Frequency is a single small number per document. Positions are a list of numbers per document, and therefore cost far more space. Frequency helps scoring. Positions help phrase and proximity searches. Whether they are stored is a choice you make in the mapping.

!
Common Trap

The document number inside a postings list is not the value of the _id field. Do not expect the two to match, and do not expect them to stay the same after merges. The internal numbers are an implementation detail that Elasticsearch never asks you to use.

Quick glossary

WordPlain meaning
Inverted indexThe whole structure: terms, each pointing to its postings list.
PostingOne entry of a postings list, describing one document for one term.
FieldA named part of a document, such as title or body. Each field has its own terms and lists.
CardinalityThe number of distinct values in a field. High cardinality means many terms.
Stop wordA very common word, such as “the,” sometimes removed by an analyzer.
StemmingReducing words to a root, so that “running” and “runs” become the same term.
Analysis chainThe steps applied to text, in order, to produce terms.
FSTA compact in-memory structure used to find terms in the dictionary quickly.

4Architecture and Components

Where do postings lists come from, where do they live, and which parts of Elasticsearch and Lucene work together to build and use them?

A document that arrives at Elasticsearch travels through several stages before its words end up in postings lists. The analyzer cuts its text into terms. The indexing machinery of Lucene collects those terms in memory, building lists as it goes. When it is time to write, the collected data is sorted and saved as a new segment. From then on, the segment holds a term dictionary, the postings lists, and several neighbor structures, all sealed and read-only.

flowchart TB
    Doc["Document arrives"] --> An["Analyzer: splits text into tokens and cleans them"]
    An --> IC["Indexing chain: builds postings in memory"]
    IC --> Ref["Refresh or flush writes a new segment"]
    subgraph Seg["One segment on disk"]
        TD["Term dictionary: sorted terms with statistics"]
        PL["Postings lists: doc ids, frequencies, positions"]
        NR["Norms: field length information"]
        DV["Doc values and stored fields: other structures"]
    end
    Ref --> Seg
    TD -->|points to| PL
    
Figure 1: From a document to the postings lists inside a segment.

Read the picture from top to bottom. The analyzer is the front door: it decides what the terms are, and therefore what postings lists will exist. The indexing chain gathers terms and grows lists in memory. A refresh, or a flush when the memory buffer is full, turns the gathered data into a segment. Inside the segment, the term dictionary is a sorted directory of terms, and each entry points to a postings list. Norms, doc values, and stored fields sit beside them, serving other purposes.

The main components

Input

Analyzer

Turns text into terms. Its choices, such as lowercasing, stemming, and stop words, decide the vocabulary of the index.

Builder

Indexing chain

Gathers terms in memory for each field and builds the lists before they are written.

Storage

Term dictionary

A sorted directory of all terms in a field, kept in blocks, with a compact index into it held in memory.

Storage

Postings files

Files holding the lists themselves: document numbers with frequencies, positions, and optional offsets.

Scoring

Norms

A small number per document and field, recording length information used in scoring.

Reading

Postings iterators

The readers that walk lists during a search, with the ability to jump ahead.

Combining

Query and scorer classes

The machinery that combines several lists, such as requiring several terms at once, and assigns scores.

Upkeep

Merge policy

Decides when to combine segments, which rewrites and combines their postings lists.

One set of lists per field

Terms are kept separately for each field. The word “apple” in a title field and the word “apple” in a description field are two different entries in two different dictionaries, each with its own postings list. This is why a query names a field, and why adding more fields to your mapping increases the number of lists, and the size of the index.

Everyday Analogy

Imagine a newspaper with separate indexes for headlines and for the body text. The word “election” in the headline index points to the front pages that carry it in a headline, while the same word in the body index points to every article mentioning it. Two indexes, two lists, two purposes.

Walking one search through the machine

1

The query text is analyzed

The user’s words go through an analyzer, usually the same kind used at indexing time, to produce the terms to look for.

2

Each shard searches its segments

Every segment is searched separately, because each has its own dictionary and lists.

3

The term is looked up in the dictionary

The in-memory index leads to the right block of the dictionary, where the term and its statistics are found.

4

A postings iterator is opened

The pointer stored with the term tells the reader where the list begins on disk.

5

Lists are combined

If several terms are involved, their lists are intersected or merged according to the query.

6

Matches are scored and collected

The best documents are kept, and results from all segments and shards are merged into a final list.

i
Why This Matters for Tuning

Because a search visits every segment, the number of segments, the size of the lists in each, and the number of terms a query expands to all multiply the work. Chapters 11 and 13 return to this.

5Anatomy of a Postings List

Now we open the list and look at each part. A small example makes it concrete.

Take three tiny documents. Document 0 says “quick brown fox”. Document 1 says “lazy brown dog”. Document 2 says “quick brown cat”. After analysis, the index holds the terms shown in the picture below, each pointing to its list of documents.

flowchart LR
    T1["Term: brown"] --> P1["Postings: doc 0, doc 1, doc 2"]
    T2["Term: quick"] --> P2["Postings: doc 0, doc 2"]
    T3["Term: fox"] --> P3["Postings: doc 0"]
    T4["Term: dog"] --> P4["Postings: doc 1"]
    T5["Term: lazy"] --> P5["Postings: doc 1"]
    T6["Term: cat"] --> P6["Postings: doc 2"]
    
Figure 2: A tiny inverted index over three documents. Each term points to its sorted list of documents.

The term dictionary keeps terms in alphabetical order: brown, cat, dog, fox, lazy, quick. The list for “brown” has three postings, because the word appears in all three documents. The list for “fox” has one. The number of postings in a list is the term’s document frequency. A search for “quick brown” needs the lists for “quick” and “brown”, and asks which documents appear in both: documents 0 and 2.

What one posting can contain

In its simplest form, a posting is only a document number. Depending on how the field is mapped, it can carry more.

LevelEach posting holdsEnablesCosts
Documents onlyThe document numberFinding which documents contain the termSmallest
Plus frequenciesDocument number and how many times the term occursScoring that rewards repetitionSmall
Plus positionsDocument number, frequency, and each positionPhrase searches and proximity searchesMuch larger
Plus offsetsPositions and the start and end characters of each occurrenceFast highlighting from the indexLargest
Everyday Analogy

A book index can list only page numbers, or it can say how many times the word appears on each page, or it can point to the exact line. The more detail it gives, the longer the index, and the more useful it becomes for certain questions.

A richer example with positions

Now consider a document that says “the quick brown fox jumps over the lazy dog”. Counting from zero, the term “the” is at positions 0 and 6, “quick” at position 1, “brown” at position 2, “fox” at position 3, and so on. If the field stores positions, the posting for “the” in this document says: term frequency two, positions 0 and 6. A phrase search for “quick brown” finds documents that contain both terms and then checks that the position of “brown” is exactly one more than the position of “quick”. Here, positions turn a simple “both words appear” into “the words appear next to each other, in order.”

Statistics kept with each term

Besides the list, the dictionary entry for each term stores a few numbers. The document frequency is the number of documents in the list. The total term frequency is the sum of the term frequencies across all documents, meaning how many times the term occurs in the whole segment. These numbers are cheap to read, and they feed scoring: a term that appears in nearly every document says little about relevance, while a rare term says a lot.

The shape of real lists

In real text, the sizes of lists are very unequal. A tiny number of words, such as “the” and “and”, occur in nearly every document, so their lists are enormous. A great many words occur in only one or two documents, so their lists are tiny. This pattern, sometimes called Zipf’s law, is one of the most important facts about postings lists. It explains why some queries are cheap and others are costly, and why removing or handling very common words matters.

Long

Common terms

Lists of millions of postings. Reading them fully is expensive, so searches try to avoid it.

Short

Rare terms

Lists of a few postings. Cheap to read, and very informative for scoring.

Single

One-document terms

Very numerous. Lucene stores the single document number directly with the term, without a separate list.

Sorted order is the secret

The document numbers in each list are always in increasing order. This is not an accident. It makes two very important things possible. First, numbers can be stored as small differences from one to the next, which compresses well, as Chapter 7 shows. Second, two lists can be combined by walking them side by side, like zipping two sorted rows, instead of comparing every pair. Nearly every clever thing that Lucene does with postings depends on this one rule.

!
A Note on Format

The exact layout of postings on disk is an internal detail of Lucene and changes between versions. This guide describes the ideas, which have stayed stable for many years, and names the details only in general terms. Do not build tools that depend on the internal file layout.

6How Postings Lists Are Built

Postings lists are not typed by anyone. They grow, in memory, as documents flow through the analyzer. Here is how.

flowchart LR
    A["Tokens from analysis"] --> B["In-memory table of terms with growing lists"]
    B --> C{"Buffer full or refresh due?"}
    C -- No --> B
    C -- Yes --> D["Sort the terms alphabetically"]
    D --> E["Write term dictionary and postings files"]
    E --> F["New immutable segment, ready to search"]
    
Figure 3: Building postings in memory and writing them out as a new segment.

Step one: analysis

The journey starts with the analyzer. It cuts the text into tokens, and each token receives a position, a counter that goes up by one for each token. Filters then transform the tokens: they lowercase them, remove stop words, reduce words to stems, or add synonyms. Whatever comes out at the end of this chain is the set of terms that will get postings. The analyzer is therefore the most powerful control you have over the shape of the index, because it decides what the vocabulary is.

Everyday Analogy

Think of a librarian who prepares to index a book. She decides that “Volcano,” “volcanoes,” and “volcanic” should all be filed under one heading, that tiny words like “the” are not worth listing, and that capital letters do not matter. These decisions happen before a single page number is written down, and they decide what the final index looks like.

Step two: growing lists in memory

As tokens arrive, Lucene keeps, for each field, an in-memory table from terms to growing lists. When a term is seen for the first time in a document, a new entry is created, and the document number is added to its list. When the same term appears again in the same document, its frequency is increased and the new position is added. When the next document arrives, and contains the same term, its number is appended. Because documents are numbered in the order they arrive, each list is naturally in increasing order without any extra sorting.

Step three: writing a segment

When the memory buffer fills up, or when a refresh happens, Lucene writes the collected data to disk as a new segment. At this point, it sorts the terms alphabetically, writes the term dictionary in sorted order, and writes each term’s list, in compressed form, to the postings files. The segment is then sealed. From this moment, its lists are read-only.

1

A document arrives

It receives the next document number in the current in-memory segment.

2

Each field is analyzed

Text fields produce many terms. Keyword fields usually produce a single term.

3

Lists are extended

Each term’s list in memory gets one more posting, with its frequency and, if stored, positions.

4

The buffer fills or a refresh is due

The shard decides that it is time to write out the collected data.

5

Terms are sorted and written

The dictionary and the lists are compressed and saved.

6

The segment becomes searchable

A new, small, sealed segment joins the shard, with its own complete inverted index.

How positions are counted

Positions are more subtle than they look. Each token normally advances the position by one. But some filters change this. If a stop word is removed, the analyzer still leaves a gap where it was, so that the positions of the remaining words stay true to the original text. Synonyms are placed at the same position as the word they stand for, so that a phrase search works with either word. And when a field holds several values, such as an array of strings, Elasticsearch inserts a gap of a hundred positions between the values by default, so that a phrase search does not accidentally match words from the end of one value and the start of the next.

Filter

Lowercasing

Merges “Fox” and “fox” into one term, making lists longer and the dictionary smaller.

Filter

Stop words

Removes very common words. This deletes the longest lists, but breaks searches that need those words.

Filter

Stemming

Reduces related words to one root, shrinking the dictionary and merging their lists.

Filter

Synonyms

Adds extra terms at the same position. At indexing time, this enlarges the index, and it makes changes hard.

Filter

N-grams

Cuts each word into many overlapping pieces. It supports partial matching, but multiplies the number of terms and postings.

Choice

Whole-value terms

Keyword fields keep the whole value as one term, with a tiny list per distinct value.

The cost of building

Building postings is real work. Analysis uses processor time, growing lists use memory, sorting and compressing use more processor time, and writing uses disk. This is why heavy text fields make indexing slower than light ones, and why choices such as storing positions, or applying n-grams, show up directly in indexing throughput and in the size of the index.

See what the analyzer produces

Elasticsearch has an analysis endpoint that shows the terms and positions that a piece of text would produce under a given analyzer. It is the best way to understand why a document does or does not match a query, because the match happens between the terms of the query and the terms in the postings, not between the raw words.

i
A Debugging Habit

When a search does not find a document you expect, do not stare at the document. Analyze the text of the document and the text of the query with the same analyzer, and compare the terms. Most surprises turn out to be a mismatch between what was indexed and what was searched.

7How Postings Are Stored on Disk

A postings list can have millions of entries. Storing it compactly, and reading it quickly, is where much of Lucene’s cleverness lies.

flowchart TD
    Q["Look up a term"] --> FST["Term index in memory: finds the right block of the term dictionary"]
    FST --> TB["Term dictionary block on disk: finds the term and its statistics"]
    TB --> PP["Pointers to postings, positions, and skip data"]
    PP --> DB["Document number blocks of 128, packed with few bits each"]
    PP --> SK["Skip data: jump ahead over blocks"]
    PP --> PS["Positions read only if the query needs them"]
    
Figure 4: The path from a term to the bytes of its postings list.

Finding the term

The term dictionary of a segment is stored in sorted order, in blocks of terms that share a common beginning. A small index on top of the blocks, held in a very compact structure called a finite state transducer, lets Lucene find the right block for a given term in a handful of steps, without reading the whole dictionary. Once the block is read from disk, the term itself is found, and with it, its statistics and its pointers into the postings files. This design keeps the part that must be in memory small, even when the number of terms is huge.

Everyday Analogy

Finding a word in a big dictionary is done in two steps. First you use the thumb tabs on the side to jump to the right letter section, which is the small in-memory index. Then you scan a page or two to find the exact word, which is the block on disk. You never read the whole dictionary.

Squeezing the document numbers

The first trick is delta encoding. Because the numbers in a list are sorted, each can be stored as the difference from the previous one, instead of as a whole number. A list of document numbers 3, 7, 8, 15, 20 becomes the differences 3, 4, 1, 7, 5. The differences are small numbers, and small numbers need few bits.

Everyday Analogy

Instead of writing down the full house numbers of a long street, 1203, 1207, 1208, 1215, 1220, you could write the first one and then the gaps: 4, 1, 7, 5. The gaps are shorter to write and easier to remember.

Blocks and bit-packing

The second trick is to store the list in blocks of a fixed size, 128 postings in current formats. For each block, Lucene finds the largest difference in the block and works out how many bits are needed to write the biggest one. Then every difference in that block is written using exactly that many bits, packed tightly next to each other. If the biggest gap in a block is 30, five bits are enough for each entry, so 128 entries take 80 bytes, instead of hundreds of bytes in plain form. Fixed-size, bit-packed blocks are also very fast to decode with modern processors. Frequencies are packed in a similar way.

TechniqueWhat it doesWhy it helps
Delta encodingStores differences between neighboring document numbersTurns big numbers into small ones
Fixed blocks of 128Groups postings into equal chunksAllows fast, uniform decoding
Bit-packingUses only as many bits per entry as the largest gap of the block needsShrinks each block
Variable-length tailWrites the leftover postings, fewer than a block, with a simple variable-size codeAvoids wasting space on partial blocks
Single-document shortcutStores the lone document number inside the dictionary entrySaves a separate list for rare terms
Skip dataStores markers every block, and higher-level markers over groups of blocksLets readers jump over stretches of a list
Separate position dataKeeps positions in their own file, read only when neededQueries that do not need positions never touch them

A numbers walkthrough

Let us use invented numbers. Suppose a common term appears in one million documents of a segment with ten million documents. On average, the gap between consecutive documents in its list is about ten. A gap of ten fits in four bits, though a few larger gaps make some blocks need five or six bits. Plain, each posting would need four bytes, so the list would take four megabytes. With delta encoding and bit-packing at about five bits per posting, the same list takes roughly 600 kilobytes. That is a saving of nearly seven times, and the saving is even bigger for very dense lists, where the gaps are tiny.

Skip data

When a search needs to jump, for example to the first document at or after number 5,000,000, reading every posting from the start would be wasteful. So along with the list, Lucene stores skip data: for each block, the last document number in it and where the block starts on disk, with higher levels that summarize groups of blocks. A reader looks at the skip data first, jumps to the right block, and decodes only that block. This is like the tabs on the side of a directory, and it makes the “advance to document X” operation cheap even on lists with millions of entries.

The files

File

Term dictionary and its index

Hold the sorted terms, their statistics, and pointers. The small index helps find the right block.

File

Documents and frequencies

Hold the blocks of document numbers and frequencies, with skip data.

File

Positions

Hold, for each posting, the positions of the term inside the document.

File

Offsets and payloads

Hold offsets and custom data, if the field is configured to store them.

File

Norms

Hold the compact per-document length information used in scoring.

Note

Names and layout change

The exact names and formats vary between Lucene versions. Treat them as internal.

Memory and the operating system

Postings files are read through the operating system’s file cache, by mapping them into memory. That is why Elasticsearch performs best when a good part of the machine’s memory is left to the operating system rather than given entirely to the Java heap. Frequently read lists stay in the cache, and the disk is touched only for the cold parts. The small in-memory index over the term dictionary has historically lived in memory too, and newer versions may read it through memory mapping instead, so check the notes for your version.

!
Do Not Give the Heap Everything

A very large heap leaves little memory for the file cache, and postings reads then go to disk. Keep the heap moderate, and let the operating system keep the hot postings in memory.

8Index Options and Field Types: Which Fields Get Postings

Not every field has a postings list, and not every postings list stores the same details. The mapping decides.

The choices for text fields

For fields that hold text, a mapping option chooses how much detail the postings store. The choice is a trade between features and size.

OptionStored in each postingSupportsTypical use
Documents onlyDocument numberMatching, filteringFields used only to include or exclude documents
FrequenciesDocument number, frequencyMatching and relevance scoringFields that need scoring but not phrases
Positions (default for text)Number, frequency, positionsPhrases and proximityMost full-text fields
OffsetsPositions plus character offsetsFaster highlighting from the indexFields that are heavily highlighted

Different field types, different structures

A common confusion is to assume that every field is stored as a postings list. It is not. Elasticsearch uses several structures, and each field type picks the ones it needs.

Field typePostings list?Other structuresGood for
TextYes, with positions by defaultNorms for length informationFull-text search and phrases
KeywordYes, documents only, one term per valueDoc values for sorting and groupingExact matching, filtering, grouping
Numbers, datesNo, uses a tree built for rangesDoc valuesRange checks, sorting, statistics
Geographic pointsNo, uses a treeDoc valuesLocation filters
Fields marked not indexedNoPossibly doc values or the sourceData that is only stored and returned
Everyday Analogy

A library has more than one kind of catalog. A subject-word card catalog helps find books about a topic, a shelf-order list helps sort books by number, and a map of the building helps find rooms by location. Each catalog fits a different question, and no library builds all of them for every kind of item.

Why numbers do not use postings

A postings list is excellent for finding documents with a specific term. But a question such as “price between 20 and 50” would need to look at every distinct price value between the two and combine all their lists, which is slow. For numbers and dates, Lucene therefore uses a different structure, a tree built to answer range questions quickly. A search for one exact number also uses that tree. So do not expect to see a postings list for an integer field, and do not choose an integer type for identifiers that you only ever match exactly. A keyword field, which does use a postings list, is usually the faster and more natural choice for such values.

Keyword fields: one term per value

A keyword field does not split its value. The whole value, such as a country code or a status, is one term. The postings list of that term names all documents with that value. For low-cardinality fields, such as status, the lists are long and few. For high-cardinality fields, such as user identifiers, the lists are short and many, and the dictionary becomes large. Both work, but the shape differs, and so does the cost.

Norms: length information beside the lists

Norms are not part of the postings, but they travel with them. For each document and each scored field, the index stores one small number that encodes the field length. It lets scoring treat a match in a short title as more significant than the same match inside a long body. Norms take about one byte per document per field. For fields that are never scored, such as filters, turning norms off saves space.

Options that add extra lists

Speed

Phrase indexing

An option can also index pairs of neighboring words as extra terms, so that phrase searches are answered from short lists instead of by checking positions. It uses more space.

Speed

Prefix indexing

An option can index the beginnings of terms, so that prefix searches read one list instead of many. It uses more space.

Space

Space-saving text variants

Newer versions offer variants that skip positions and scoring data, and verify phrases against the source, trading speed for size.

Substring

Wildcard-friendly field type

A special field type indexes small pieces of the value to make substring and wildcard searches fast, with a verification step.

How to choose

1

List how each field will be used

Searched by words, matched exactly, filtered, sorted, grouped, or only returned.

2

Pick the type that matches

Text for words, keyword for exact values, numbers and dates for ranges.

3

Choose the least detail that works

Positions only where phrases are needed, frequencies only where scoring matters.

4

Turn off what you do not use

Norms for filter fields, indexing for fields never searched.

5

Measure the size

Look at the index size by field, before and after a change.

9How Searches Read Postings Lists

A search is, at heart, a small program that walks and combines lists. Understanding the walk explains why some queries are fast and others slow.

The iterator

To read a list, Lucene creates an iterator. It has two main abilities. It can move to the next document in the list. And it can advance to the first document at or after a given number, using the skip data to jump over stretches it does not need. These two abilities are enough to build every kind of query.

Requiring several terms: the leapfrog

Suppose a query requires documents that contain both “quick” and “brown”. Both lists are sorted. Instead of comparing every pair, Lucene picks the list that is expected to be shortest as the lead. It takes the lead’s first document, and asks the other list to advance to that document. If the other list lands exactly on it, the document is a match. If the other list lands on a later document, that later number becomes the new target, and the lead advances to it. The iterators leapfrog over each other until one is exhausted.

flowchart TD
    A["Start with the rarest term as the lead iterator"] --> B["Take its next document"]
    B --> C["Ask the other terms to advance to that document"]
    C --> D{"Do all terms land on the same document?"}
    D -- Yes --> E["Match: record the document"]
    D -- No --> F["Use the largest document seen as the new target"]
    E --> B
    F --> C
    
Figure 5: Requiring several terms is done by leapfrogging sorted lists, guided by the rarest term.
Everyday Analogy

Two friends each hold a sorted list of house numbers they have visited, and they want to find the houses both have visited. The friend with the shorter list reads a number out loud. The other friend jumps ahead in her list to that number or the next larger one. If it matches, they celebrate. If not, she reads her larger number, and the first friend jumps to that. Neither needs to read every entry.

Why the rarest term leads

The lead iterator walks its entire list. Every other iterator only needs to jump. So choosing the shortest list as the lead minimizes the number of steps. A query for a rare word and a common word is therefore cheap: it walks the short list and jumps through the long one. A query for two common words is costly, because even the shortest list is long. This is one reason that combining a rare term with a common one is much cheaper than combining two common ones.

Alternatives and exclusions

When a query asks for documents that contain either of several terms, Lucene walks all the lists at once, always moving on the list whose current document is smallest, so that each matching document is visited once. When a query excludes documents that contain a term, the list of that term is used as a filter that rejects matches. Both operations rely on the sorted order of the lists.

One document at a time

Lucene evaluates queries by going through documents in increasing order, and for each candidate document, finding out which terms match and computing its score, before moving to the next. This document-at-a-time style works well with sorted lists, keeps memory use small, and lets the search stop early when it has enough results. The alternative, term-at-a-time, would process one whole list before the next and needs a table of partial scores for all documents, which is heavy for large indexes.

Phrase searches use positions

A phrase search, such as “quick brown”, first finds documents where both terms appear, using the leapfrog. Only for those candidate documents does it read the positions data, and check whether the positions line up. This two-step approach keeps position reading, which is expensive, to a minimum. It also explains why phrase searches are slower than plain term searches, and why fields that never need phrase matching should not store positions.

Query kindHow lists are usedRelative cost
One termWalk one listProportional to the list length
All of several termsLeapfrog, led by the shortest listProportional to the shortest list, plus jumps
Any of several termsWalk all lists togetherProportional to the sum of lengths, unless skipping applies
Term but not anotherWalk one list, reject using the otherProportional to the first list
PhraseLeapfrog, then check positionsHigher, because of position reading
Filter reused oftenStored bitset from the query cacheVery low after the first use

Filters and caching

Filters, meaning yes-or-no conditions that do not need scores, are handled like any other list, but with a difference: the result can be remembered. Elasticsearch has a query cache that stores the set of matching documents of costly, repeated filters per segment, so that the postings lists behind them do not need to be walked again. This connects the two subjects: postings lists are the raw material, and the query cache is a shortcut for the lists that are read again and again.

Every segment, every time

A search must visit each segment of each shard it covers, since each has its own lists. A shard with many small segments needs many dictionary lookups and many small list reads. A shard with a few large segments needs fewer, larger reads. This is a large part of why heavy write activity, which creates many small segments, can slow searches until merging tidies up.

i
See It With the Profile Option

Elasticsearch can report, for a search, how long each part of each query took, including counts of how many times iterators moved to the next document or advanced. It is the best way to see whether a slow query is dominated by walking long lists.

10Scoring and Skipping: What Postings Give the Ranking

Finding matching documents is only half of search. The other half is ranking them, and postings supply most of the numbers for that.

The ingredients of the score

The default ranking formula in Elasticsearch is called BM25. It combines a few quantities, and most of them come straight from postings and their neighbors.

From the posting

Term frequency

How often the term occurs in the document. More is better, but with diminishing returns, so that a term repeated a hundred times is not a hundred times better.

From the dictionary

Document frequency

How many documents contain the term. Rare terms carry more weight, because matching them says more.

From the norms

Field length

A match in a short field counts for more than the same match in a long one.

From the statistics

Average field length

The typical length in the collection, used to judge whether a field is short or long.

Everyday Analogy

Imagine judging how well a book answers a question about volcanoes. A book that mentions volcanoes many times is probably about them, though the hundredth mention adds little. A mention of a rare, specific word counts for more than a mention of a common one. And a mention in a short leaflet means more than one in a thousand-page encyclopedia.

Statistics are per shard

By default, the document frequency and other statistics used in scoring are taken from the shard that holds the document, not from the whole index. For big indexes with evenly spread data, the numbers are close enough and nobody notices. For small indexes, or unevenly spread data, the numbers may differ noticeably between shards, and the same document may score differently depending on where it lives. A search mode exists that first collects statistics from all shards and then scores, at the price of an extra round trip.

The cost of scoring long lists

Consider a search for “the quick brown fox”. The word “the” has a list of millions of entries, and it contributes almost nothing to relevance. A naive engine would still score every document in that list. This is wasteful, because most of those documents will never reach the top ten. The cleverness of modern Lucene lies in avoiding it.

Block-max skipping

For each block of 128 postings, Lucene stores a summary of the best score that any document in that block could achieve for the term, based on the largest frequencies and shortest lengths inside it. While collecting the top results, the search keeps track of the lowest score among the current best ones. Before scoring a block, it asks: even if a document in this block had the highest possible score for each of its terms, could it beat the current lowest? If not, the whole block is skipped without decoding or scoring a single document.

flowchart TD
    S["Keep the top k documents so far and their lowest score"] --> B{"Can the next block of postings beat the lowest score?"}
    B -- No --> K["Skip the whole block without scoring"]
    B -- Yes --> C["Score the documents in the block"]
    C --> U["Update the top k"]
    K --> N["Move to the next block"]
    U --> N
    N --> S
    
Figure 6: Block-max skipping avoids scoring blocks that cannot reach the top results.
Everyday Analogy

A teacher marking hundreds of exam papers only wants the top ten. Each pile comes with a note saying “the best possible mark in this pile is 40 out of 100.” If she already has ten papers marked above 60, she can put that whole pile aside without opening it.

The price: approximate counts

Skipping means that the search does not look at every matching document. As a result, it cannot say exactly how many documents matched. By default, Elasticsearch counts accurately only up to a threshold, ten thousand hits, and after that reports a lower bound with a note that the total is “at least” that number. If you need the exact total, you can ask for it, but then the skipping must be turned off, and the search reads far more of the lists. For most user-facing search, the lower bound is fine, and the speed gain is large.

SituationEffect of skipping
Ranked search, top ten, default settingsSkipping active, large savings on long lists
Exact total count requestedSkipping disabled, all matches read
Sorting by a field instead of relevanceScores not needed, other shortcuts may apply
Filter-only queryNo scoring, lists read through as filters, or served from the cache
Query with several optional termsSkipping is most effective here

A numbers walkthrough

Suppose a search has one common term with a list of five million documents and one rare term with a list of two thousand. Without skipping, a “match any” search would score up to five million documents. With block-max skipping, once the top ten documents have been found among those that contain the rare term, the lowest score among them is high. Blocks of the common term’s list that cannot reach that score are skipped entirely. In practice, only a small fraction of the long list is actually decoded. The figures are invented, but the effect is real, and it is why modern Elasticsearch handles common words far better than older versions did.

!
Do Not Ask for Exact Totals Casually

Requesting the exact number of hits on every search silently disables the skipping and can turn a fast query into a slow one. Ask for exact counts only where the number itself is needed, and let ordinary result pages use the default.

11Segments, Merges, Deletes, and Updates: The Life of a List

Postings lists are sealed and never edited. Yet documents change all the time. Here is how the two facts live together.

flowchart LR
    W["New documents"] --> R["Refresh: new small segment with its own postings"]
    R --> M["Merge: postings of several segments combined and document numbers renumbered"]
    M --> B["Larger segment with fewer, longer lists"]
    D["Deleted or updated documents"] --> L["Marked in a live-documents bitmap"]
    L --> M
    
Figure 7: New documents create new lists, deletes are only marked, and merges rewrite lists into larger ones.

New documents: new lists

Each refresh writes the documents indexed since the last refresh into a new segment, with a complete little inverted index of its own. Existing segments are untouched. That is why new documents can become searchable quickly and cheaply: nothing has to be edited, only added. The price is that the shard now has one more segment, with its own lists, that every search must visit.

Deletes and updates: marking, not editing

Because lists cannot be edited, deleting a document does not remove its number from any list. Instead, the segment keeps a separate bitmap of live documents, and the deleted one is marked as gone. At search time, matches are checked against this bitmap and skipped if deleted. An update is treated as a delete of the old version and an add of the new one, in a newer segment. The old version lingers in the old segment’s lists until a merge removes it.

Everyday Analogy

A printed book index cannot be corrected with an eraser. When a page is removed from the book, the publisher does not reprint the index at once. Readers are told to ignore references to that page, and the index is fixed when the next edition is printed. The deletion list is the “ignore these pages” note, and the merge is the next edition.

Merging: rewriting lists into bigger ones

In the background, Elasticsearch merges segments. The merge policy chooses several segments of similar size, and a merge process reads their term dictionaries together, in sorted order. For each term, it takes the lists from each input segment and combines them into one new, longer list. Along the way, it drops the postings of deleted documents and renumbers the surviving documents, so that the numbers in the new segment run from zero without gaps. Then it writes the new segment and, when it is ready, replaces the old ones.

1

The policy picks segments

Usually a group of similar size. Large segments are merged rarely, because it is costly.

2

Terms are walked in order

The dictionaries of the inputs are read together, so that the same term in each segment is handled at the same time.

3

Lists are combined

Each input’s list, with its documents shifted by an offset, is appended to the output list in order.

4

Deleted documents are dropped

Postings of documents marked as deleted are left out, and the remaining numbers are compacted.

5

The new segment is written

With fresh compression, fresh skip data, and a new term dictionary.

6

Old segments are removed

Once the new one is safely in place, the inputs are deleted from disk.

What merging changes

Faster

Fewer segments to visit

A search does fewer dictionary lookups and fewer small reads.

Smaller

Dead entries removed

Deleted documents no longer occupy space in the lists.

Tighter

Better compression

Longer lists share overhead, and skip data works better on big lists.

Costly

Heavy on disk and processor

Merging reads and rewrites data. It competes with indexing and searching.

Cache

Resets cached results

Cached filter results belong to old segments and are lost when those disappear.

Statistics

Cleaner numbers

Document frequencies stop counting deleted documents once they are merged away.

Deleted documents still count, for a while

A subtle consequence is that the statistics stored with each term, such as its document frequency, keep counting deleted documents until a merge. This slightly distorts scoring in indexes with many recent deletes or updates. It usually goes unnoticed, but it can explain small scoring differences between a freshly merged index and a busy one.

Force merge for finished data

For an index that will not receive writes anymore, such as last month’s logs, you can ask for a force merge down to a small number of segments. This produces the most compact lists and the fastest searches. It is expensive, and it should only be done on data that is truly finished, because merging an index that still receives writes creates very large segments that are costly to keep tidy.

Index sorting and list compression

By default, documents are numbered in the order they arrive. An optional index setting can sort the documents inside each segment by a chosen field. Documents with similar values then get neighboring numbers, and their postings lists contain runs of close numbers, which compress better and allow some searches to stop early. The cost is slower indexing and merging, so use it only when a specific, frequent search pattern benefits.

i
Why Update-Heavy Indexes Are Costly

Every update leaves a dead entry in old lists and adds a new one in a new segment. Indexes with constant updates therefore generate more merges, carry more dead postings, and cache less effectively than append-only ones. If you can model your data as append-only, the postings will stay smaller and calmer.

12Queries That Touch Many Terms: Wildcards, Prefixes, and Fuzzy Search

A plain term query reads one list. Some queries read hundreds or thousands. Knowing how they work helps you avoid nasty surprises.

Expanding a pattern into terms

A query such as “words starting with quick” does not correspond to one term. It corresponds to every term in the dictionary that starts with those letters: quick, quickly, quicker, quicksand, and possibly hundreds more. Lucene first walks the sorted term dictionary to find all matching terms. Then it combines the postings lists of all of them, as an “any of these” query. The work is therefore the cost of finding the terms plus the cost of reading every one of their lists.

Everyday Analogy

Asking a librarian for “all books about anything starting with vol” means she must first find every subject heading that starts with those letters: volcano, volume, volunteer, and more. Then she must gather the cards from every one of those drawers. It takes far longer than opening a single drawer.

How each kind of pattern behaves

Query kindHow terms are foundCost
PrefixJump to the first term with that beginning and read forwardModerate, depends on how many terms share the prefix
Wildcard with fixed beginningUse the beginning to narrow the dictionary, then test each candidateModerate to high
Wildcard with leading starTest every term in the dictionaryVery high on big dictionaries
Regular expressionCompile to an automaton and intersect with the dictionaryDepends on the pattern, can be very high
Fuzzy searchAutomaton for words within a few edits, intersected with the dictionaryModerate, controlled by edit distance and required prefix
Range over keywordsWalk the sorted dictionary between two termsHigh if many terms fall in the range
Terms query with a listLook up each listed termProportional to the number of terms and their list sizes

Why leading wildcards hurt

The dictionary is sorted by the beginning of terms. A pattern that starts with a fixed piece can use this order to jump close to the right region. A pattern that starts with a star, such as “anything followed by fox”, cannot jump anywhere, because matching terms may be scattered throughout. Lucene must test every single term. On a dictionary with tens of millions of terms, this can take a long time. This is the most common way people accidentally build slow queries.

Combining many lists

Once the matching terms are known, Lucene has to combine their lists. If there are only a few terms, it treats the query as a scored “any of these.” If there are many, it takes a shortcut: it reads all their lists once, builds a single set of matching documents, and uses that set as a constant-score filter. This avoids enormous scoring work, at the price of giving all matches the same score. The switch happens automatically according to the number of terms.

Limits to protect the cluster

Guard

Maximum expansions

Some queries cap how many terms they expand to, so that a short prefix cannot pull in the whole dictionary.

Guard

Limit on the terms list

A terms query accepts only up to a set number of terms, by default tens of thousands.

Guard

Limit on clauses

The number of clauses in a combined query is capped to protect memory.

Guard

Timeouts

A search can be given a time limit so that a runaway query returns partial results instead of hanging.

Making these searches cheap: pay at indexing time

The remedy for slow pattern searches is usually to do the work when documents are indexed, not when queries run. Options include indexing the beginnings of terms for prefix search, indexing small overlapping pieces of words for partial matching, or using a special field type built for substring searches. Each of these multiplies the number of terms and postings, so the search becomes fast at the cost of a larger index and slower indexing. Choose them only for fields that truly need such searches.

!
Do Not Expose Raw Patterns to Users

If a search box passes user input straight into a wildcard or regular expression query, one user typing a leading star can slow the cluster for everyone. Sanitize input, forbid leading wildcards, set limits, and use purpose-built search-as-you-type fields.

13Size, Performance, and Tuning

The postings lists of a text-heavy index are often its biggest part. Shaping them well saves disk, memory, and time.

What determines the size

Volume

Number of tokens

More text means more postings. Long fields and many fields multiply the size.

Detail

Index options

Positions and offsets can be larger than the rest of the postings put together.

Vocabulary

Number of distinct terms

Many distinct terms enlarge the dictionary. Identifiers and n-grams are the usual causes.

Analysis

Analyzer choices

Stemming shrinks the vocabulary. Synonyms and n-grams enlarge it.

Layout

Segment structure

Many small segments compress worse than a few large ones.

Hygiene

Dead entries

Deleted documents keep their postings until merges remove them.

A numbers walkthrough

Let us use invented numbers. Suppose an index holds 50 million documents, each with about 100 tokens in its main text field, so 5 billion token occurrences in total. Suppose, after removing repeats within a document, there are about 3 billion pairs of a term and a document. With compression, storing only document numbers and frequencies might cost about 1 byte per pair, so around 3 gigabytes. Storing positions costs roughly another byte per occurrence, adding about 5 gigabytes. In this example, positions are more than half of the postings size. If phrase searches are not needed on that field, dropping positions cuts the postings by well over half.

Now imagine the same field also gets edge n-grams from 2 to 15 characters. A ten-letter word now yields nine or more extra terms, so token occurrences might grow eight to ten times, and the index may grow by a similar factor. It is easy to see why applying n-grams to long text is dangerous, and why they belong on short fields such as names and titles.

Everyday Analogy

An index at the back of a book that lists only page numbers is thin. One that also lists the exact line of every mention is thicker. One that lists every partial spelling of every word, including “vo,” “vol,” “volc,” and so on, is thicker than the book itself.

Measuring where the space goes

Elasticsearch has an analysis endpoint for index disk usage that breaks an index down by field and by structure: how many bytes go to the inverted index, to norms, to stored fields, to doc values, and to the number trees. It is an expensive operation, so run it on a test copy or in a quiet period. It answers the most useful question in sizing: which fields are the big ones, and which structure inside them.

Tuning levers

LeverEffect on sizeEffect on speedTrade-off
Do not index fields you never searchRemoves their lists entirelyFaster indexingField can no longer be searched
Drop positions where phrases are unneededLarge reductionFaster indexing, smaller readsNo phrase or proximity search
Turn off norms on filter-only fieldsSaves about a byte per documentSlightly fasterNo length-based scoring
Use keyword for exact valuesAvoids position data and analysisFaster matchingNo word-level matching
Limit the length of keyword valuesAvoids huge, useless termsAvoids indexing errorsLong values are not searchable
Avoid n-grams on long textAvoids huge growthFaster indexingNo partial matching on that field
Force merge finished indexesSomewhat smaller, dead entries removedFaster searchesCostly operation, only for read-only data
Index sortingCan compress lists betterSome searches stop early, indexing slowerSlower writes and merges
Extra indexing options for phrases or prefixesLargerFaster targeted searchesMore space and slower indexing

Queries that cost a lot

  • Two common terms required together. Even the shorter list is long. Use filters, or rank on rarer terms where possible.
  • Leading wildcards and heavy regular expressions. They test every term.
  • Very large terms lists. Each term costs a lookup and a list read.
  • Exact counts on every request. They disable skipping.
  • Phrase searches on very common words. Many candidates, each needing a position check.
  • Searching across very many segments. Each adds a lookup.

Memory: leave it for the file cache

Postings are read from files through the operating system’s cache. The more of the hot lists fit in that cache, the fewer disk reads a search needs. That is why guidance always says to keep the heap moderate and leave a generous share of the machine’s memory to the operating system. If searches are slow and disk reads are high, more memory for the cache, faster disks, or smaller lists will help more than any query trick.

i
Change One Thing and Measure

Because so many levers interact, change one at a time, and measure index size, indexing speed, and search latency on realistic data and queries before and after. The best setting depends on your text, your queries, and your hardware.

14Advantages, Disadvantages, and Trade-offs

Every design carries a price. Here is the honest balance sheet of the postings list.

Advantages

  • Search cost follows the size of the answer, not the size of the data.
  • Sorted lists combine quickly using leapfrogging and skipping.
  • Compression makes lists small, so more fit in the file cache.
  • Frequencies, positions, and statistics enable scoring and phrase search.
  • Immutable segments are safe to read from many threads and easy to cache.
  • Block-level summaries let searches skip work on long lists.
  • A mature, well-understood structure with decades of refinement.

Disadvantages

  • Lists cannot be edited, so updates and deletes leave dead entries.
  • Merges are needed, and they consume disk and processor time.
  • Positions and n-grams can make the index very large.
  • Pattern queries may need to touch a great many terms.
  • Every segment has its own lists, so searches multiply by segment count.
  • Fast searches by skipping give only approximate hit counts by default.
  • Choices made at indexing time, such as analyzers, are hard to change later.
Trade-offLeaning toward speed of searchingLeaning toward cost of indexing and storage
Index detailPositions and phrase-friendly options: fast phrase searches, bigger indexDocuments only: smaller and faster to index, fewer features
Partial matchingN-grams at indexing time: fast searches, large indexWildcards at query time: small index, slow searches
Segment countFew large segments: fast searches, costly mergesMany small segments: cheap writes, slower searches
Refresh frequencyShort: new data visible sooner, more small segmentsLong: fewer segments, data visible later
Hit countingApproximate: skipping saves workExact: full reading of lists
Stop wordsRemoved: smaller lists, some searches impossibleKept: fully faithful, much longer lists
SynonymsAt indexing time: fast searches, index bloat, hard to changeAt search time: flexible, slightly slower queries
Everyday Analogy

A more detailed index makes a book easier to search but harder and longer to prepare, and bigger to print. A publisher decides how detailed the index should be by thinking about how readers will look things up.

How rich should the postings be?

SituationRecommended stance
Article or document search with phrase queriesPositions on the main text, norms on, moderate analyzers
Product catalog with filters and a search boxText with positions on title and description, keywords for filters, no norms on filter fields
Logs searched by exact values and timeKeywords and dates, minimal text fields, positions only where message search is needed
Autocomplete on short namesN-grams or prefix indexing on the short field only
Fields returned but never searchedNot indexed at all

15High Availability and Reliability

Postings lists live in files on disk. Their reliability comes from checks, copies, and careful upgrades.

Every copy builds its own lists

In the standard model, each replica applies the same operations as the primary, so each builds its own postings lists in its own segments. The segments on two copies of a shard therefore differ in layout and timing, although they describe the same documents. A search may be served by any copy, and returns the same matches, except for small differences caused by refresh timing and by statistics that depend on segment layout.

Everyday Analogy

Two librarians in different buildings each receive the same new books in the same order and each prepares her own card catalog. The catalogs list the same books, but the cards may be filed in different drawers and reprinted at different times.

Checksums guard every file

Lucene writes a checksum at the end of every file it creates, including postings files. The checksum is verified in several situations: when files are copied to another node during recovery, when segments are merged, and, if requested, when a shard opens. If a file has been damaged by a disk fault, the check finds it, rather than letting a search read garbage or spread the damage into a merge.

When a segment is corrupted

If a shard copy discovers corruption, it is marked as failed, and the cluster rebuilds it from a healthy copy, in the same way as after any other failure. This is one of the practical reasons to keep at least one replica for important data. Without a replica, a corrupted shard can only be repaired from a snapshot, or, in the worst case, may lose the affected documents.

SituationWhat happens to the postings
A replica is rebuilt by file-based recoverySegment files, including their postings, are copied from the primary and verified
A replica catches up by operation replayThe missed operations are re-indexed, producing new postings in new segments
A node restartsSealed segments are reopened as they are, and the recent translog is replayed into new lists
A snapshot is takenSegment files, with their postings, are copied to the repository
A shard is found corruptedThe copy is failed and rebuilt from a healthy copy or a snapshot
A merge runsInput files are read and verified, and a new segment is written

Upgrades and the file format

The format of postings files changes when Lucene moves forward. Elasticsearch can read segments written by the previous major version of Lucene, and new segments and merged ones are written in the newer format, so old data is rewritten gradually. But it cannot read segments that are more than one major generation old. For very old indexes, upgrading requires reindexing the data into a new index, or using the special handling that the release notes describe. Plan large upgrades with this in mind, and check the compatibility rules of your target version early.

Habits that keep postings safe

1

Keep at least one replica for data you care about

A healthy copy is the fastest repair for a damaged one.

2

Use reliable disks and watch for errors

Disk faults are the usual source of corrupted segments.

3

Take and test snapshots

They protect against corruption on every copy, and against mistakes.

4

Plan upgrades around format compatibility

Reindex very old indexes before a major upgrade.

5

Leave headroom for merges

Merges temporarily need extra disk space. A full disk can stall them and cause trouble.

!
Merges Need Free Space

A merge writes a new segment before removing the old ones, so it temporarily needs room for both. On a nearly full disk, merges may fail or stall, and the index slowly accumulates small segments. Keep comfortable free space, especially before a force merge.

16Security Considerations

The words of your documents live in the dictionary and the lists. That deserves the same care as the documents themselves.

The index contains your text

A postings index is not an opaque blob. The term dictionary holds the words of your documents, and the lists, together with positions, can be used to reconstruct large parts of the original text. Anyone who can read the files of a shard can learn a great deal about the content. Protect the disks with encryption at rest, restrict who can log in to the machines, and treat snapshots, which contain the same files, with the same care.

Everyday Analogy

A book’s index, with every word and the pages it appears on, tells a curious reader a lot about the book even without the book itself. The index must be locked away with the same care as the book.

Some APIs reveal indexed terms

API

Term listing endpoints

Some endpoints list terms in a field, with their frequencies. They can reveal the vocabulary of an index.

API

Term vectors

For a document, they return its terms, positions, and statistics.

API

Grouping by terms

Grouping on a field returns its most common values, which may be sensitive.

API

Explain and profile

They expose term statistics and internal timings for a search.

Restrict who can call these on sensitive indexes. Use the security features of Elasticsearch to limit users to the indexes and fields they need, and where your subscription supports it, use field-level and document-level restrictions, which work by combining the user’s filters with the lists behind each query.

Deleted does not mean gone yet

When a document is deleted, its terms and postings remain in the sealed segment until a merge removes them, and older history may be kept for recovery for a while longer. For strict privacy rules, such as erasing personal data on request, this means that the data may remain on disk for some time after deletion. Design your deletion process with this in mind: know how long merges and history retention take in your setup, consider forcing merges on affected indexes where appropriate, and remember that snapshots hold copies until they expire.

Sensitive terms and the dictionary

Even after all documents that contained a certain word are deleted and merged away, ordinary practice is fine, but before that happens the word remains in the term dictionary. If a word itself is sensitive, such as a person’s name, treat the index as containing it until the data is fully purged.

Query abuse

As Chapter 12 explained, some queries are very expensive. A user, or an attacker, who can send arbitrary wildcard, regular expression, or huge terms queries can slow the cluster for everyone. Limit what queries applications can construct, set timeouts and limits, and put rate limits in front of public search endpoints.

Timing and inference

The time a query takes depends on the length of the lists it reads. In theory, an observer who can run many queries and measure the timings may infer how common a certain word is. For public search boxes over sensitive data, this is a reason to limit result detail, throttle requests, and avoid exposing raw timings.

i
Do Not Skip the Basics

Turn on authentication and encryption, even for clusters that seem internal. Most exposures of indexed data start with a cluster that was described as “only internal” and had no protection at all.

17Monitoring, Logging, and Metrics

You cannot tune what you cannot see. These are the windows onto your postings lists and the signals worth watching.

WindowWhat it showsUse it to
Index disk usage analysisBytes per field for the inverted index, norms, stored fields, doc values, and number treesFind which fields and structures use the space
Segment listingEach segment’s size, number of documents, and deleted documentsSpot too many small segments or many deletes
Index and node statisticsNumber of segments, merge activity, search and indexing timesTrack trends in segment count, merges, and query time
Analysis endpointThe terms and positions a text would produceDebug why a search does or does not match
ExplainHow a document’s score was computed, with term statisticsUnderstand ranking
Profile option on a searchTimings and counters for each part of a querySee where a slow query spends its time
Term vectorsTerms, frequencies, and positions of one documentInspect what was indexed for a document
Slow logsSearches and indexing operations slower than a thresholdFind expensive queries and heavy documents

Metrics to chart

MetricWhat it tells youWarning sign
Segments per shardHow many sets of lists each search must visitSteady growth without merges catching up
Share of deleted documentsDead entries in listsA large share for long periods
Index size per documentCost of your mapping choicesSudden growth after a mapping change
Merge time and throttlingWhether merging keeps up with writesMerges constantly running, indexing throttled
Search latency percentilesHow long queries take, including the slow tailRising slow tail
Disk reads versus cache hitsWhether hot lists fit in the file cacheHigh disk reads during searches
Slow query counts by typeWhich query shapes cost the mostWildcards, regular expressions, or huge terms lists appearing often
Free disk spaceRoom for mergesBelow a comfortable margin

Reading the patterns

Healthy

Few segments, calm merges

Searches read a few large lists, latency is steady, disk reads are low.

Fragmented

Many small segments

Frequent refreshes and heavy writes create many lists to visit. Consider a longer refresh interval and check merge settings.

Bloated

Index far larger than expected

Check positions, n-grams, dynamic fields, and long keyword values with the disk usage analysis.

Costly queries

A few queries dominate time

Look for leading wildcards, heavy patterns, and exact counts on every request.

Cache-starved

High disk reads during searches

The hot lists do not fit in the file cache. Free memory for it or shrink the lists.

Dead weight

Many deleted documents

Update-heavy indexes leave dead entries. Consider the data model, and let merges tidy up.

A troubleshooting playbook

1

Find the slow query

Use slow logs to identify the exact query and the index it targets.

2

Profile it

See which clause takes the time and how many iterator steps it needs.

3

Check what terms it touches

Does it expand to many terms? Does it include a very common term?

4

Look at the segments

How many are there, and how many deleted documents do they carry?

5

Check the mapping

Are there positions, n-grams, or norms that this field does not need?

6

Check the hardware

Disk reads, file cache, and free space.

7

Fix the cause, then re-measure

Change the query or the mapping, and compare before and after.

i
Alerts Worth Having

Alert on a rising slow tail of search latency, on segment counts that keep growing, on merges that are always running, on free disk space getting low, on sudden jumps in index size per document, and on the appearance of expensive query types in slow logs.

18Design Patterns and Anti-patterns

Good habits keep postings lists small and searches fast. Bad habits are behind most bloated indexes and slow queries.

Patterns that work

1

One field, one job

Decide for each field whether it is for words, for exact values, for ranges, or only for display, and give it the type that fits.

2

Multiple views of one value, only where needed

Use a text field for word search and a keyword sub-field for exact matching and grouping, but only for fields that truly need both.

3

Explicit mappings

Define types and index options up front, instead of letting dynamic mapping create two lists for every string.

4

Filters in filter context

Put yes-or-no conditions where they can be cached and where no scores are computed.

5

Time-based indexes for append-only data

New indexes for new periods, older ones read-only and force-merged, keeping lists compact.

6

Purpose-built fields for autocomplete

Use fields designed for search-as-you-type on short values, not wildcards on long text.

7

Test analysis before loading

Use the analysis endpoint on real samples, and compare index-time and search-time terms.

Everyday Analogy

A good publisher decides, before printing, which words deserve an index entry, how detailed it should be, and how to spell the headings. A publisher who indexes every word, in every possible spelling, with the exact line number, produces an index thicker than the book, and nobody can find anything in it.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What people do

They leave dynamic mapping on, so every string field is indexed both as full text and as a keyword, whether or not it is ever searched in both ways.

Why it hurts

Each string gets two dictionaries and two sets of lists, plus norms and doc values. The index is far larger than needed, and indexing is slower.

Better approach

Define mappings. Use text for word search, keyword for exact values, and skip indexing for fields that are never searched.

ANTI-PATTERN 02Avoid
What people do

They allow wildcard queries that start with a star, often built from user input, on large text fields.

Why it hurts

Every term of the dictionary must be tested, and thousands of lists may be read. A few such queries can slow the whole cluster.

Better approach

Forbid leading wildcards, use fields designed for substring or prefix search, and set limits and timeouts.

ANTI-PATTERN 03Avoid
What people do

They apply n-gram analysis to long text fields to support partial matching.

Why it hurts

The number of terms and postings multiplies many times over, and indexing and storage costs explode.

Better approach

Apply n-grams only to short fields such as names, and use a separate, lighter field for full text.

ANTI-PATTERN 04Avoid
What people do

They ask for the exact total number of hits on every search, including simple result pages.

Why it hurts

Exact counting disables block skipping, so searches read far more of the long lists than necessary.

Better approach

Use the default approximate count, and request exact numbers only where the exact number is truly needed.

ANTI-PATTERN 05Avoid
What people do

They index very long values, such as entire documents or encoded blobs, into keyword fields.

Why it hurts

Each such value becomes a giant term in the dictionary. Terms beyond a size limit cause indexing errors, and the rest bloat the dictionary for no searching benefit.

Better approach

Set a length limit on keyword values, and do not index data that will never be matched exactly.

ANTI-PATTERN 06Avoid
What people do

They update the same documents constantly, for example to increment counters, inside a search index.

Why it hurts

Every update leaves a dead entry in an old list and adds a new document in a new segment. Merges work harder, and dead entries pile up.

Better approach

Keep fast-changing counters in a system built for them, or model changes as appended events, and keep the search index for mostly stable content.

ANTI-PATTERN 07Avoid
What people do

They add synonyms at indexing time, and later discover that the synonym list must change.

Why it hurts

The extra terms are baked into the lists of every document. Changing the list requires reindexing the whole index.

Better approach

Prefer synonyms at search time, which can be updated without rewriting the index, unless a measured need justifies indexing them.

ANTI-PATTERN 08Avoid
What people do

They use different analyzers at indexing time and at search time by accident.

Why it hurts

The terms produced from the query do not match the terms stored in the lists, and documents that seem to match are not found.

Better approach

Use the same analysis on both sides unless there is a deliberate reason, and verify with the analysis endpoint.

19Best Practices and Common Mistakes

A short list you could hand to a new teammate on their first day.

Best practices

Types

Match the type to the use

Text for words, keyword for exact values, numbers and dates for ranges.

Detail

Store the least that works

Positions only for phrases, norms only for scored fields.

Mapping

Define, do not guess

Explicit mappings, controlled dynamic behavior, limits on fields.

Queries

Use filter context and rare terms

Cache-friendly filters, and rare terms to lead the leapfrog.

Patterns

Avoid leading wildcards

Use fields built for partial matching instead.

Segments

Let old data settle

Force merge finished indexes, and keep refresh intervals sensible.

Memory

Protect the file cache

Moderate heap, generous memory left for the operating system.

Measure

Check size by field

Use the disk usage analysis before and after every change.

Common mistakes

  • Thinking postings hold the documents. They hold only document numbers, frequencies, and positions. The text lives elsewhere.
  • Expecting the internal document number to be the _id. They are unrelated, and the internal number changes at merges.
  • Using numeric types for identifiers. Numbers use range trees, not postings. Exact-match identifiers usually belong in keyword fields.
  • Debugging documents instead of terms. Matching happens on analyzed terms. Compare the terms, not the raw text.
  • Forgetting that filters and scores differ. Conditions that need no score belong in filter context.
  • Ignoring segment counts. Many tiny segments slow every search.
  • Force merging live indexes. It creates very large segments that are hard to keep tidy.
  • Assuming a mapping can be changed in place. Changes to analyzers or index options usually require reindexing.

Myths versus facts

MythFact
Every field has a postings list.Numbers, dates, and locations use other structures, and unindexed fields have none.
A postings list contains the document text.It contains document numbers and small details. Text is kept in stored fields.
Search reads every document.Search reads only the lists of the terms in the query.
Deleting a document removes its postings.Postings stay until a merge, and the document is only marked as deleted.
More detail in the index is always better.Extra detail such as positions or n-grams can double or multiply the size.
Long lists are always slow.Skipping and caching often avoid reading most of a long list.
The exact hit count is free.Exact counts disable the skipping that makes ranked search fast.
The index format is stable across versions.It changes with Lucene, and only recent generations can be read.
!
A Golden Rule

Every term you index and every detail you attach becomes a list that must be written, merged, stored, cached, and read for as long as the index lives. Index what your users will actually search, at the level of detail they will actually need.

20Real-World and Industry Examples

Where do postings lists quietly decide success or trouble?

Many well-known organizations have said publicly that Elasticsearch is part of their systems, including Wikipedia’s search, Netflix, Uber, and Stack Overflow. They rarely publish how they shape their indexes, so the scenarios below are typical patterns for such workloads, not descriptions of anyone’s private configuration.

Online marketplace: words plus filters

A marketplace of the size of Amazon or eBay has a search box and a side menu of filters. Product titles and descriptions are text fields with positions, so that phrase queries like “wireless headphones” work. Brand, category, and color are keyword fields, whose short, dense lists are used as cached filters. Numbers such as price use range trees. Fields that are only displayed are not indexed at all.

Log search: exact values and a message field

A streaming or ride-hailing service searches logs by service name, level, and time, which are keyword and date fields, and only occasionally by words in the message. The message field is a text field, and the team measured that its positions took more than half of the index. Because only a small share of searches used phrases, they moved to a lighter variant for older data, and saved a large amount of disk.

News archive: phrases and long history

A publisher searches millions of articles, where readers often search for exact phrases, such as a quoted sentence. Positions are essential here. Older, read-only yearly indexes are force merged into a few large segments, which makes lists compact and searches fast, while the current year’s index stays lively and small.

Autocomplete on names

A travel site suggests city names as users type. A short name field uses edge n-grams, so that each typed prefix matches a small list. Because the field is short, the extra terms are affordable. The team never applied the same technique to long descriptions, after testing showed a many-fold growth in index size.

A support tool that broke on leading wildcards

An internal search page passed whatever users typed to a wildcard query. One user typed a leading star and a common word into a large index, and search latency spiked for everyone. The team banned leading wildcards, set limits and timeouts, and added a dedicated field for the substring searches that support agents truly needed.

A mapping that doubled the index

A pipeline let dynamic mapping index every string as both text and keyword, including long free-text notes. The index size analysis showed that keyword copies of long notes and their doc values took a third of the disk. The team defined explicit mappings, kept only the necessary keyword fields, and applied a length limit to the rest.

i
The Common Thread

In every example, the winning ideas are the same: the right structure for each field, the least detail that works, careful limits on expensive queries, tidy segments, and regular measurement of where the bytes and the milliseconds go.

21Frequently Asked Questions

Short answers to the questions that come up most often.

Q1What is a postings list in one sentence?
It is the sorted list of documents, inside one segment, that contain a particular term, optionally with how often and where the term occurs, and it is what makes full-text search fast.
Q2What is the difference between a postings list and an inverted index?
The inverted index is the whole structure: the dictionary of terms with all their lists. A postings list is one entry in it, belonging to one term.
Q3Is there one postings list per term in the whole index?
No. There is one per term, per field, per segment. A shard with many segments has many lists for the same term.
Q4What is stored in a posting?
At minimum, a document number. Depending on the mapping, also the term frequency, the positions where the term occurs, and the character offsets.
Q5Why are document numbers stored in sorted order?
Sorted order allows delta encoding, which makes numbers small and compressible, and allows fast combining of lists by leapfrogging and skipping.
Q6Do number and date fields have postings lists?
No. They use a tree structure built for range checks, plus doc values for sorting and grouping. Postings lists are used for text and keyword fields.
Q7What is term frequency, and what is document frequency?
Term frequency is how many times a term occurs in one document. Document frequency is how many documents contain the term, which is the length of its list. Both feed scoring.
Q8How do phrase searches work?
They first find documents that contain all the words, then read the positions in those documents to check that the words are next to each other in the right order. This needs positions to be stored.
Q9Why are positions so expensive?
Every occurrence of every term needs its own stored number, whereas frequencies need only one number per document. Positions can be larger than the rest of the postings put together.
Q10Why is a query with a rare word and a common word fast?
The search walks the short list of the rare word and jumps through the long list of the common one using skip data, so it never reads most of the long list.
Q11Why does Elasticsearch say “at least 10,000 hits”?
By default it counts exactly only up to that threshold, so that it can skip blocks of postings that cannot reach the top results. Exact counting is available but disables that saving.
Q12Why are leading wildcard queries slow?
The dictionary is sorted by the beginning of terms, so a pattern with a leading star cannot jump anywhere, and every term must be tested, with many lists then read.
Q13What happens to a postings list when I delete a document?
Nothing at first. The document is marked as deleted in a bitmap, and its entry stays in the lists until a merge rewrites the segment without it.
Q14What does a merge do to postings?
It combines the lists of several segments into new, longer lists, drops deleted documents, renumbers the survivors, and writes a new compressed segment.
Q15Can I change a field’s index options later?
Not in place. Changing what is stored in the postings generally requires creating a new index with the new mapping and reindexing the data.
Q16How can I see how big my postings are?
Use the index disk usage analysis, which reports bytes per field for the inverted index, norms, stored fields, doc values, and number trees. It is expensive, so run it carefully.
Q17How can I see what terms a document produced?
Use the analysis endpoint with the same analyzer to see the terms and positions of a text, or the term vectors endpoint to inspect an indexed document.
Q18Are postings the same on the primary and the replica?
They describe the same documents, but each copy builds its own segments, so the layout and refresh timing can differ. Matches are the same, apart from small timing differences.
Q19Do stop words matter for postings?
Yes. Very common words have huge lists. Removing them shrinks the index but makes some searches impossible. Modern skipping makes keeping them more affordable than it used to be.
Q20Do details change between versions?
The core ideas have been stable for many years, but block sizes, file formats, and defaults evolve with Lucene. Always confirm in the documentation for the exact version you run.

22Summary and Key Takeaways

If you skim only one part of this guide, make it this one.

The postings list is the quiet workhorse of Elasticsearch. For each word in a field, inside each segment, it keeps a sorted, compressed list of the documents that contain it, sometimes with how often and where. Searches read and combine these lists, ranking uses their numbers, merges rewrite them, and their size and shape decide much of the cost of your cluster.

Key Takeaways

  • What it is: a sorted list of document numbers for one term, in one field, in one segment, with optional frequencies, positions, and offsets.
  • Why it is fast: search cost follows the size of the answer, and sorted, block-compressed lists with skip data are combined by leapfrogging and skipping.
  • How it is built and stored: analysis creates terms, memory tables grow lists, a segment is written and sealed, and lists are stored as delta-encoded, bit-packed blocks of 128 with a term dictionary in front.
  • What it costs: positions, n-grams, dynamic string mappings, and long keyword values are the usual sources of oversized indexes, so index only what is searched, at the detail needed.
  • How searches use it: rare terms lead the leapfrog, phrases check positions, filters can be cached, and block-max skipping saves work at the price of approximate hit counts.
  • How it changes over time: lists are never edited, deletes only mark documents, updates add new entries, and merges rewrite lists into larger, cleaner segments.
  • How to run it well: match field types to uses, avoid leading wildcards and needless exact counts, keep segments tidy, protect the file cache, secure the files and the APIs that reveal terms, and measure size by field.