Columnar files and in-memory formats
Why is a columnar file readable only when complete, and what does zero-copy really decide?
Columnar files: Parquet and ORC
Column layout stores each column's values together. This makes scans over a few columns cheap, because a reader loads only the columns it needs and each 64-byte cache line is full of useful values. What is new at a boundary is that a columnar file is a different kind of object from a row file: it is a small database, and it must publish an interface to its own inside.
The causal chain: why there is a footer
- In column-major order, the values of one record are scattered across the file.
- A reader that wants 3 of 200 columns must jump directly to certain regions.
- Direct jumping needs a directory of offsets.
- A directory for the whole file can only be written when the writer knows all the offsets, which is only at the end.
- So the metadata goes in a footer (at the end of the file).
- So the file must be finished before it can be read.
- So columnar formats are structurally unable to be tailed or streamed.
This chain explains why nobody writes a live stream straight into a columnar file, and why "convert to columnar" is always a separate, batched step later.
What that directory buys a reader (projection, homogeneous encoding, skipping) is encoding's arithmetic; here the point is the price: an ending.
The footer is also a query interface
Because the writer summarizes what it wrote anyway, it can cheaply publish statistics (for example, the minimum and maximum value of each column in each block). Then an engine that never talked to the writer can skip whole blocks: if the query asks for weight > 900 and a block's maximum is 500, skip it.
So the file now exports a query interface, not only bytes. A file format has crossed the line from representation into protocol.
In one sentence: columnar files are readable only when complete, because the price of random access into a file is a footer, and a footer means an ending.
In-memory and IPC formats: Arrow and FlatBuffers
Push early binding to its limit and the parse tax falls to zero. Define the layout so that the bytes on the wire are the bytes in memory. Reading becomes pointer arithmetic: nothing is decoded; the buffer is mapped and used. (IPC means inter-process communication: moving data between programs.)
What it costs: total agreement. Both sides must accept an identical physical representation: integer width, alignment, how nulls are encoded, the order of buffers. There is no negotiation and no variation, no "my version encodes decimals differently". This is acceptable because the time gap is almost zero and both parties usually run at the same time. It is a poor bargain for a file that must be read in 2033, and these formats do not claim otherwise.
Two consequences:
- The fixed cost has moved here. When storage and network stop being the main cost, serialization becomes the bottleneck. This format removes it. This is the same pattern as in hardware: as one part gets faster, the fixed cost moves to the next one up, and a new mechanism is invented to attack it there. (What zero-copy saves in CPU is encoding's measure.)
- A shared representation across processes and languages turns an N-by-N conversion problem (every system converting to every other) into N adapters (each system converting to the one shared form). It is a coordination win of the same kind as in Principle B, not a speed win.
In one sentence: zero-copy is not a speed trick. It is the decision to pay the whole cost as agreement instead of paying it per read.