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 bits 53: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 arithmetic 54:24 54: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 id fills half the first word, then a 64-bit timestamp straddles two words — 32 bits in the first, 32 in the second 55:43 56: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:57 59: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, zip 58:34 58: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:

ExperimentSize
row(1,2,3) as 32-bit ints36 bytes (includes header overhead) 1:00:31
Same values cast to 16-bit ints (::)30 bytes 1:01:10
Mixed chars and integers, one ordering48 bytes 1:01:28
Same columns, reordered44 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