Find your own break-even point D/B
Measure your machineNeeds Python with zstandard
Problem
Compression trades CPU for bytes. Reading a compressed file is faster only if decoding it is faster than reading the bytes it saved: with a ratio r, a decoder producing D bytes per second and storage delivering B bytes per second, compression wins only if D / B > r / (r − 1). The right side is close to 1 for any useful ratio, so the rule is roughly "the decoder must outrun the storage". A fast local SSD can outrun a single-core decoder; a network link to object storage almost never can. The compression level changes the write cost a great deal and the decode speed very little.
Check the rule compression wins only if D / B > r / (r − 1) on your machine. Use any 200–500 MB file with typical data (a CSV or JSON log is fine). The script generates a 300 MB JSON log, measures B for the local disk by reading the file with the page cache bypassed, and uses 0.08 GB/s for an object-store connection.
The question. At each zstd level, is single-core decoding faster than reading the saved bytes from a local disk, and from an object store?
Predict
The decode time (and so D) is almost the same for all levels, while the write time grows by one to two orders of magnitude at level 19. On a fast local disk (B of 3 GB/s or more) the single-core decode is likely to lose or be close. With disk_gbps = 0.08 (an object-store connection) all levels should win by a large margin.
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
find-your-own-break-even-point.py # /// script # requires-python = ">=3.10" # dependencies = ["zstandard"] # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """Find your own break-even point for compression. Compression wins on a read only if D / B > r / (r - 1): decode speed D over storage bandwidth B must beat a factor set by the ratio r. Zstd levels 1 to 19 on a JSON log, against the measured local disk and a slow object-store link. """ import argparse, json, os, platform, random, tempfile, time import zstandard as zstd p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--mb", type=int, default=300, help="Size of the generated JSON log, MB.") p.add_argument("--levels", type=lambda s: [int(v) for v in s.split(",")], default=[1, 3, 10, 19], help="Zstd levels.") p.add_argument("--remote-gbps", type=float, default=0.08, help="An object-store connection, GB/s.") p.add_argument("--path", default=os.path.join(tempfile.gettempdir(), "breakeven.jsonl"), help="The sample file.") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() import atexit, shutil atexit.register(lambda: shutil.rmtree(args.path, ignore_errors=True) if os.path.isdir(args.path) else os.path.exists(args.path) and os.remove(args.path)) # leave nothing behind random.seed(0) if not os.path.exists(args.path) or os.path.getsize(args.path) < args.mb * 1e6 * 0.99: with open(args.path, "w") as f: i = 0 while f.tell() < args.mb * 1e6: f.write(json.dumps({"ts": 1_700_000_000 + i, "user": f"u{random.randint(1, 50000)}", "event": random.choice(["view", "click", "buy"]), "path": f"/p/{random.randint(1, 3000)}", "ms": random.randint(1, 900)}) + "\n"); i += 1 data = open(args.path, "rb").read() # B: read the file with the page cache bypassed fd = os.open(args.path, os.O_RDONLY) if platform.system() == "Darwin": import fcntl; fcntl.fcntl(fd, 48, 1) # F_NOCACHE else: os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_DONTNEED) t = time.perf_counter(); n = 0 while (chunk := os.read(fd, 8 << 20)): n += len(chunk) disk = n / (time.perf_counter() - t) / 1e9; os.close(fd) print(f"local disk read, page cache bypassed: B = {disk:.2f} GB/s; object store: B = {args.remote_gbps} GB/s") rows = [] for level in args.levels: t0 = time.time(); comp = zstd.ZstdCompressor(level=level).compress(data); tc = time.time() - t0 t0 = time.time(); zstd.ZstdDecompressor().decompress(comp); td = time.time() - t0 r = len(data) / len(comp); D = len(data) / td / 1e9; need = r / (r - 1) rows.append({"level": level, "ratio": r, "write_s": tc, "decode_gbps": D, "need": need, "local": "wins" if D / disk > need else "loses", "remote": "wins" if D / args.remote_gbps > need else "loses", "d_over_b_local": D / disk}) print(f"level {level:2d} ratio {r:4.1f} write {tc:6.2f}s decode {D:5.2f} GB/s D/B local {D / disk:5.2f} " f"need > {need:4.2f} -> local {rows[-1]['local']}, object store {rows[-1]['remote']}") if args.json: with open(args.json, "w") as f: json.dump({"tables": {"levels": rows, "summary": [{"disk_gbps": disk}]}}, 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 find-your-own-break-even-point.py uv run find-your-own-break-even-point.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --mb300 | Size of the generated JSON log, MB. |
| --levels1, 3, 10, 19 | Zstd levels. |
| --remote-gbps0.08 | An object-store connection, GB/s. |
| --pathos.path.join(tempfile.gettempdir(), 'breakeven.jsonl') | The sample file. |
Our run
Data table
| zstd level | Compress time (s) |
|---|---|
| 1 | 0.513 |
| 3 | 0.521 |
| 10 | 4.34 |
| 19 | 137 |
The break-even rule at four levels
| Level | Ratio | Write | Decode (GB/s) | Needed D/B | D/B local | Local disk | Object store |
|---|---|---|---|---|---|---|---|
| 1 | 5.85× | 513 ms | 1.42 | 1.21 | 0.182 | loses | wins |
| 3 | 6.04× | 521 ms | 1.74 | 1.2 | 0.223 | loses | wins |
| 10 | 7.3× | 4.34 s | 1.94 | 1.16 | 0.249 | loses | wins |
| 19 | 8.85× | 137 s | 1.76 | 1.13 | 0.225 | loses | wins |
local disk read, page cache bypassed: B = 7.81 GB/s; object store: B = 0.08 GB/s
level 1 ratio 5.8 write 0.51s decode 1.42 GB/s D/B local 0.18 need > 1.21 -> local loses, object store wins
level 3 ratio 6.0 write 0.52s decode 1.74 GB/s D/B local 0.22 need > 1.20 -> local loses, object store wins
level 10 ratio 7.3 write 4.34s decode 1.94 GB/s D/B local 0.25 need > 1.16 -> local loses, object store wins
level 19 ratio 8.8 write 136.94s decode 1.76 GB/s D/B local 0.22 need > 1.13 -> local loses, object store winsWhat we found
Decode speed barely moved with the level, from 1.42 GB/s at level 1 to 1.76 GB/s at level 19, while the write took 513 ms at level 1 and 137 s at level 19. The local disk read at 7.81 GB/s, so D/B was about 0.223 against a needed 1.2: every level lost locally, and every level won against the object store. The local figure is higher than an SSD's rated speed, which suggests the file, written moments before, was still partly served from a cache below the page cache; a slower measured B would only narrow the local loss, not reverse it.
Read it
Compression is a decision about where the bottleneck is. On local NVMe the CPU is the bottleneck and compression slows reads unless decoding runs on many cores; on a network or object store the link is the bottleneck and any reasonable codec pays for itself. Higher levels buy ratio with write time, which is worth it for data written once and read many times, and not for hot write paths.