A tuple is just bytes
- A tuple is a sequence of bytes, usually preceded by a header with metadata about those bytes — not the schema itself, which lives in the catalog
52:48 - The DBMS's job is to impose structure on that byte array: types, sizes, nullability, values
53:27 - Example table
foo(id INT32, value INT64): header of a system-specific size, then the ID bits, then the value bits53:53 - Reading an attribute = tuple start + header size + column offset, then a
reinterpret_cast-style read into the target type; the header size is identical for every tuple, so it is pure arithmetic54:2454:46
The alignment problem
- Modern CPUs want accesses along 64-bit word boundaries (sometimes you care about the 64-byte cache line)
55:21 - Mixed-width attributes break that: a 32-bit
idfills half the first word, then a 64-bit timestamp straddles two words — 32 bits in the first, 32 in the second55:4356:21 - On modern CPUs (x86, ARM) the hardware hides it: it performs two memory / cache-line accesses and stitches the value back together
57:11 - On older CPUs you got an interrupt telling you not to do that
57:11 - Net effect: the misaligned lookup costs twice as much, so the database runs slower
57:42
Fix 1 — Padding (most common)
- Pad the 32-bit integer with 32 zero bits so every attribute starts on a word boundary; the system just has to know the padded offsets
57:57 - It wins because it is easy, and it is the approach most systems take
57:5759:33
Fix 2 — Attribute reordering
- Logical/physical data independence means the storage order need not match the declared column order;
SELECT *still returns id, date, color, zip58:3458:56 - Physically reshuffle attributes so they pack cleanly into words
59:20 - Some trailing padding still remains, because the next tuple must not start in the middle of a word; you may not save space, but you avoid cross-boundary accesses
59:33
Postgres demo: measuring padding
Using row(...) plus pg_column_size() 59:52 1:00:31:
| Experiment | Size |
|---|---|
row(1,2,3) as 32-bit ints | 36 bytes (includes header overhead) 1:00:31 |
Same values cast to 16-bit ints (::) | 30 bytes 1:01:10 |
| Mixed chars and integers, one ordering | 48 bytes 1:01:28 |
| Same columns, reordered | 44 bytes — less padding needed 1:01:45 |
Takeaway: column declaration order measurably changes physical tuple size in Postgres; one ordering is more optimal than another 1:01:45