Skip to content
About
Main content

Block size, ratio and point-lookup cost

Measure your machineNeeds Python with zstandard

Problem

A compressed file is split into blocks so that a reader can decode part of it. The block size trades two things. Larger blocks give the compressor more context, so the ratio rises; but a must decode the whole block that holds its row, so its cost grows with the block. The ratio stops rising once the block holds enough repetitions for the compressor to learn the data's patterns.

Measure the three effects of block size with a real codec.

The question. How do the and the cost of one point lookup change with the block size?


Predict

Going from 64 KB blocks to 1 MB blocks, what happens to the compression ratio?

Run it

  1. 1Install uv once. It fetches Python and the lab's packages for you.

    Terminal
    curl -LsSf https://astral.sh/uv/install.sh | sh
  2. 2Get the script. It is the whole lab: read it, then download it.

    block-size-ratio-and-point-lookup.py39 linesDownload
    View the code
    block-size-ratio-and-point-lookup.py
    # /// script
    # requires-python = ">=3.10"
    # dependencies = ["zstandard"]
    # ///
    # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT
    # https://thedataengineering.com/lab-code/LICENSE.txt
    """Block size: compression ratio against point-lookup cost.
    
    200,000 JSON rows compressed with Zstd in blocks of 1 KB to 1 MB: the ratio,
    and the time to decode the one block a point lookup needs.
    """
    import argparse, json, random, time
    import zstandard as zstd
    
    p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    p.add_argument("--rows", type=int, default=200_000, help="Rows.")
    p.add_argument("--blocks", type=lambda s: [int(v) for v in s.split(",")], default=[1024, 4096, 16384, 65536, 262144, 1048576], help="Block sizes, bytes.")
    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()
    random.seed(args.seed)
    rows_ = [json.dumps({"parcel": f"P{random.randint(0, 10**6)}", "depot": random.choice(["RTM", "LYN", "HAM"]),
                         "status": random.choice(["SCANNED", "LOADED", "DELIVERED"]), "t": 1700000000 + i}).encode() for i in range(args.rows)]
    data = b"\n".join(rows_)
    c, d = zstd.ZstdCompressor(level=3), zstd.ZstdDecompressor()
    rows = []
    for block in args.blocks:
        comp = [c.compress(data[i:i + block]) for i in range(0, len(data), block)]
        ratio = len(data) / sum(len(x) for x in comp)
        t0 = time.perf_counter()
        for _ in range(200): d.decompress(comp[len(comp) // 2])
        us = (time.perf_counter() - t0) / 200 * 1e6
        rows.append({"block": block, "ratio": ratio, "lookup_us": us})
        print(f"block {block:>8} B  ratio {ratio:5.2f}  one lookup decodes {block} B in {us:7.1f} us")
    
    if args.json:
        with open(args.json, "w") as f:
            json.dump({"tables": {"blocks": rows}}, f)
    
  3. 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 block-size-ratio-and-point-lookup.py
    uv run block-size-ratio-and-point-lookup.py --help     # every knob
Knobs
Knob · defaultWhat it changes
--rows200,000 Rows.
--blocks1024, 4096, 16,384, 65,536, 262,144, 1,048,576 Block sizes, bytes.
--seed0 Random seed.

Our run

Compression ratio by block sizeApple M2 · 8 cores · 8 GB
Data table
Compression ratio by block size
Block size (bytes)Ratio
1,0204.25
4,1007.04
16.4K8.38
65.5K8.71
262K8.14
1.05M8.53
macOS 26.6.2 · Python 3.13.3 · zstandard 0.25.0 · run 2026-10-20
Decode time of one block (a point lookup)Apple M2 · 8 cores · 8 GB
Data table
Decode time of one block (a point lookup)
Block size (bytes)µs per lookup
1,0204.18
4,1003.36
16.4K8.29
65.5K28.3
262K127
1.05M497
macOS 26.6.2 · Python 3.13.3 · zstandard 0.25.0 · run 2026-10-20
Output · uv run block-size-ratio-and-point-lookup.py
block     1024 B  ratio  4.25  one lookup decodes 1024 B in     4.2 us
block     4096 B  ratio  7.04  one lookup decodes 4096 B in     3.4 us
block    16384 B  ratio  8.38  one lookup decodes 16384 B in     8.3 us
block    65536 B  ratio  8.71  one lookup decodes 65536 B in    28.3 us
block   262144 B  ratio  8.14  one lookup decodes 262144 B in   126.8 us
block  1048576 B  ratio  8.53  one lookup decodes 1048576 B in   497.0 us

What we found

The ratio rose from 4.25 at 1 KB to 8.38 at 16 KB and 8.71 at 64 KB, then stayed flat. A lookup cost about the same at 1 KB and 4 KB (4.18 and 3.36 µs, where the fixed cost of starting a decode dominates) and then grew in proportion to the block, to 497 µs at 1 MB.


Read it

Choose the block size where the ratio curve flattens for scans, and a small block for lookups. Past the knee a larger block buys nothing in size and costs every point read linearly. That is why row stores use small compressed pages and column stores large ones: the workload, not the codec, picks the block size.