How does Lucene's TieredMergePolicy decide which segments to merge?
answer
- Not adjacency-based, unlike the older policy
- Sort by size, then slide a window
- Similar sizes make a better merge
- There is a ceiling on merged size
- Tombstone percentage triggers work of its own
basics
~20 sTieredMergePolicy sorts segments by size, works out how many segments of each size tier the index is allowed, and while the index is over budget picks the best-scoring candidate group of similarly-sized segments to merge — favouring low size skew and reclaiming deleted documents.
solid answer
~50 s`TieredMergePolicy` is Lucene's default. Rather than merging adjacent segments in index order like the older log-based policies, it sorts all segments by size in bytes (discounting deleted documents), floors very small segments at `floorSegmentMB` so they are treated as one tier, and computes an allowed segment count from `segmentsPerTier`. While the index exceeds that budget it evaluates candidate merges of up to `maxMergeAtOnce` segments taken from a sliding window over the size-sorted list, **scores** each candidate — preferring low skew, meaning segments of similar size, and rewarding candidates that reclaim many deleted documents — and schedules the best one. `maxMergedSegmentBytes` (default 5 GB) caps the size of any merge output, so an index naturally settles into several large segments rather than one. Separately, if the index-wide deleted fraction exceeds `deletesPctAllowed` (default 20), the policy schedules merges specifically to bring it back down, including rewriting a single oversized segment on its own.
code
java · 7 linesTieredMergePolicy tmp = new TieredMergePolicy();
tmp.setSegmentsPerTier(10.0); // allowed segments per size tier
tmp.setMaxMergeAtOnce(10); // segments per merge
tmp.setMaxMergedSegmentBytes(5L * 1024 * 1024 * 1024); // 5 GB cap
tmp.setFloorSegmentMB(2.0); // treat smaller segments as this size
tmp.setDeletesPctAllowed(20.0); // index-wide tombstone budget
new IndexWriterConfig(analyzer).setMergePolicy(tmp);go deeper
Know that background merging combines smaller Lucene segments into larger ones and that this is what keeps segment counts and deleted documents under control.
Explain the shape of the algorithm: size-sorted segments, an allowed segment budget, candidates of similar-sized segments, and a cap on merged output size.
Demonstrate operational judgment — recognising merge starvation, indexing stalls from scheduler back-pressure, and the tradeoff that lower segment counts cost background I/O.
Own the capacity question: merge throughput is a first-class resource to provision alongside indexing and query capacity, and merge policy is where write amplification is budgeted.
## What a merge policy is for Because Lucene segments are immutable, every flush adds a segment and every delete leaves a tombstone. Without merging, an index drifts toward thousands of small segments full of dead documents, and query latency degrades because every query pays a fixed cost per segment. A **merge policy** is the component that looks at the current segment list and decides which groups of segments should be rewritten into one. It does not perform the merge — the `MergeScheduler` runs the selected merges on background threads. ## Why tiered, not logarithmic The older `LogByteSizeMergePolicy` merged runs of *adjacent* segments in index order, which keeps documents roughly in insertion order but constrains which merges are possible. `TieredMergePolicy`, the default since Lucene 3.x, drops the adjacency requirement: it may merge any set of segments. That freedom is what lets it pick well-balanced merges and target the segments carrying the most deleted documents. ## The selection algorithm Roughly, on each invocation: 1. **Measure.** Each segment's size is taken in bytes and reduced in proportion to its deleted documents, so a segment that is half tombstones counts as roughly half its on-disk size. 2. **Floor.** Segments smaller than `floorSegmentMB` are all treated as that size. Without this, the policy would chase an unbounded number of ever-tinier tiers; flooring lumps all the small fry together so they get merged as a group. 3. **Budget.** From the total index size and `segmentsPerTier`, the policy computes how many segments the index is allowed to have. `segmentsPerTier` is the knob that trades merge work against search cost: a lower value means fewer segments and faster searches, paid for with more merging. 4. **Enumerate candidates.** Segments are sorted largest-first, and the policy slides a window over that list, considering each group of up to `maxMergeAtOnce` segments as a candidate merge. 5. **Score.** Each candidate gets a score dominated by **skew** — the ratio of the largest segment in the candidate to the total candidate size. A merge of five similarly-sized segments scores far better than one that folds four tiny segments into a huge one, because the latter rewrites a lot of bytes for very little reduction in segment count. The score is then adjusted to favour candidates that reclaim more deleted documents and, mildly, smaller total size. 6. **Cap.** Any candidate whose merged output would exceed `maxMergedSegmentBytes` (default 5 GB) is rejected or trimmed. This is why a healthy large index ends up with a number of multi-gigabyte segments rather than one enormous one. 7. **Schedule.** The best-scoring candidate is returned and the loop repeats until the index is inside its budget or nothing worthwhile is left. ## The deletes path Selection above already discounts deleted documents, but there is also an explicit rule: `deletesPctAllowed` (default 20) bounds the fraction of the whole index that may be deleted documents. When the index exceeds it, the policy schedules merges aimed at reclaiming, and since Lucene 8 it may merge a single segment on its own — rewriting one oversized, heavily-deleted segment into a clean one — even though that segment is above the normal maximum merged size. This matters because it is the mechanism that eventually cleans up a segment produced by an earlier `forceMerge`. `forceMergeDeletes()` is the manual version, driven by `forceMergeDeletesPctAllowed`: rewrite any segment whose deleted percentage exceeds the threshold, leaving segment count otherwise alone. ## Running the merges `ConcurrentMergeScheduler` executes selected merges on background threads, with `setMaxMergesAndThreads(maxMergeCount, maxThreadCount)` controlling concurrency and an auto-I/O-throttle that slows merge threads when indexing is keeping up and releases the brakes when merges fall behind. If pending merges exceed `maxMergeCount`, the scheduler deliberately stalls the indexing threads. That back-pressure is a feature — it prevents an index from accumulating an unrecoverable merge backlog — but it presents as sudden, puzzling indexing slowdowns if you do not know to look for it. ## How to reason about the knobs The practical mental model is a single tradeoff dial. `segmentsPerTier` and `maxMergeAtOnce` set how aggressively the index is compacted: lower values buy faster queries with more background I/O and write amplification; higher values buy cheaper indexing with more segments to search. `maxMergedSegmentBytes` bounds the worst-case duration and disk headroom of any one merge — raising it means fewer, larger segments but longer, riskier merges. `floorSegmentMB` decides how small a segment has to be before it is treated as noise. In most systems the correct action is to leave these alone and instead give merges enough I/O; the common real failure is not a bad policy but starved merge threads on slow storage.
- Why does TieredMergePolicy penalise a candidate merge with high size skew?Skew measures how much one segment dominates the candidate. Folding a few tiny segments into a very large one rewrites the large segment's bytes almost entirely to remove only a couple of segments from the list, which is a terrible ratio of I/O to benefit. Preferring similar-sized inputs keeps the cost of each merge proportional to what it achieves.
- What happens if merges cannot keep up with the indexing rate?Segment count climbs, queries slow down, and ConcurrentMergeScheduler eventually stalls incoming indexing threads once pending merges exceed its configured limit. The back-pressure is intentional, but it surfaces as unexplained indexing latency. The usual cause is I/O-starved storage or too few merge threads rather than a misconfigured policy.
- Would raising maxMergedSegmentBytes improve search performance?It lets the index settle into fewer, larger segments, which does reduce per-query fixed costs somewhat. The price is that individual merges become much longer and need far more temporary disk and I/O, and a very large segment carrying deletes takes a correspondingly huge rewrite to clean. It is rarely the highest-leverage change.
saying these in an interview costs you the question
- Thinks Lucene merges only adjacent segments in index order
- Believes merging continues until one segment remains
- Says merge policy also executes the merges
- Ignores the deleted-document percentage as a merge trigger
- Treats merge tuning as free rather than trading I/O for query speed