Overlap depth before and after a blind compaction
Simulate a modelNeeds Python with NumPy
Problem
Overlap depth is the number of files whose key range contains a given key: the number of files a point lookup must open. Small files that each hold a narrow range of keys have low overlap even if they arrived in no particular order. Compaction can make it worse: merging files that arrived together (time-adjacent) combines unrelated key ranges into one wide file, and every lookup then has to open it. Compacting by key range merges files that cover the same keys, and overlap falls.
Generate small files whose customer_id ranges are narrow but written in arrival order. Compute overlap depth at 1,000 random points. Then compact (a) the oldest files together (time-adjacent) and (b) files grouped by key range. Recompute the depth.
The question. What does each compaction do to the number of files a point lookup must open?
Predict
Expect (a) to raise it and (b) to lower it. This reproduces the "compaction made queries slower" effect.
Run it
1Install uv once. It fetches Python and the lab's packages for you.
Terminal curl -LsSf https://astral.sh/uv/install.sh | sh2Get the script. It is the whole lab: read it, then download it.
View the codeHide the code
overlap-depth-before-and-after-a.py # /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """Overlap depth before and after a compaction. 200 small files written in arrival order, each holding a narrow range of customer_id that wanders over time. Overlap depth (files whose [min, max] contains a point) at 1,000 random points, then after compacting (a) the oldest files together in time-adjacent groups of 10 and (b) files grouped by key range into the same number of outputs. """ import argparse, json import numpy as np p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--files", type=int, default=200, help="Small files.") p.add_argument("--group", type=int, default=10, help="Files merged per compaction output.") p.add_argument("--seed", type=int, default=0, help="Random seed.") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() rng = np.random.default_rng(args.seed) centers = rng.uniform(0, 1_000_000, args.files) # each file's customers cluster somewhere: arrival order is not key order files = [np.clip(rng.normal(c, 5_000, 1000), 0, 1_000_000) for c in centers] def depth(fs): mn = np.array([f.min() for f in fs]); mx = np.array([f.max() for f in fs]); pts = rng.uniform(0, 1_000_000, 1000) d = np.array([((mn <= x) & (x <= mx)).sum() for x in pts]); return float(d.mean()), int(d.max()), len(fs) by_time = [np.concatenate(files[i:i + args.group]) for i in range(0, args.files, args.group)] allv = np.sort(np.concatenate(files)); k = len(by_time) by_key = np.array_split(allv, k) rows = [] for name, fs in (("before: small files in arrival order", files), ("(a) compacted by time (oldest together)", by_time), ("(b) compacted by key range", by_key)): mean, mx, n = depth(fs); rows.append({"layout": name, "files": n, "mean_depth": mean, "max_depth": mx}) for r in rows: print(f"{r['layout']:<42} {r['files']:>4} files overlap depth mean {r['mean_depth']:6.2f}, max {r['max_depth']}") if args.json: with open(args.json, "w") as f: json.dump({"tables": {"layouts": rows}}, f)3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.
Terminal uv run overlap-depth-before-and-after-a.py uv run overlap-depth-before-and-after-a.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --files200 | Small files. |
| --group10 | Files merged per compaction output. |
| --seed0 | Random seed. |
Our run
Overlap depth at 1,000 random keys
| Layout | Files | Mean depth | Max depth |
|---|---|---|---|
| before: small files in arrival order | 200 | 6.41 | 13 |
| (a) compacted by time (oldest together) | 20 | 17.1 | 20 |
| (b) compacted by key range | 20 | 0.999 | 1 |
before: small files in arrival order 200 files overlap depth mean 6.41, max 13
(a) compacted by time (oldest together) 20 files overlap depth mean 17.07, max 20
(b) compacted by key range 20 files overlap depth mean 1.00, max 1What we found
Before compaction, a lookup opened 6.41 files on average across 200. Compacting by time into 20 files raised that to 17.1: almost every merged file now spans the whole key range. Compacting by key range into the same number of files lowered it to 0.999.
Read it
Fewer files is not the goal of compaction; fewer files per query is. Merging by arrival time reduces the file count and can multiply the work of every lookup. Compaction should sort or cluster by the keys queries filter on.