Problem 89 · Data Engineering & Streaming
External Merge Sort
01Problem
Sort a stream of integers too large for memory, given that at most chunk_size items fit in RAM: produce sorted runs on disk, then k-way merge them lazily.
02Key insight
Two phases: sort fixed-size chunks into spilled runs, then a heap-based k-way merge that streams with O(k) memory. With many runs, do multi-pass merges (fan-in limited by file handles/buffer size) - this is exactly how databases spill ORDER BY.
03Solution
Try it first.
TimeO(n log n) CPU, O(n) IO per pass
SpaceO(chunk_size + k) memory
tests.py · 5 passing
tests.pypython24 lines
import random
rnd = random.Random(1)
data = [rnd.randint(-1000, 1000) for _ in range(5003)]
assert list(external_sort(data, chunk_size=100)) == sorted(data)
assert list(external_sort([], chunk_size=10)) == []
assert list(external_sort(iter([3, 1, 2]), chunk_size=1)) == [1, 2, 3]
import builtins
live, peak = [0], [0]
class _Tracked: # counts input runs held open concurrently
def __init__(self, f, reading): self.f, self.reading = f, reading
def __enter__(self):
live[0] += self.reading; peak[0] = max(peak[0], live[0])
return self.f.__enter__()
def __exit__(self, *exc):
live[0] -= self.reading
return self.f.__exit__(*exc)
def open(path, mode="r", *a, **kw):
return _Tracked(builtins.open(path, mode, *a, **kw), "r" in mode)
big = [rnd.randint(-10**6, 10**6) for _ in range(1000)]
for fan_in in (64, 8, 2):
peak[0] = 0
assert list(external_sort(big, chunk_size=2, max_fan_in=fan_in)) == sorted(big) # 500 runs
assert 0 < peak[0] <= fan_in
del open04Staff-level follow-up
Data is 10 TB across a cluster - how do you sort it? Sample to pick range-partition boundaries, shuffle each record to its range's node, sort locally (external sort), and concatenate partitions in order (TeraSort); skew is handled by finer sampling.