Skip to content

Commit ba6d890

Browse files
committed
first commit
0 parents  commit ba6d890

1 file changed

Lines changed: 138 additions & 0 deletions

File tree

PLAN.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
Name: pg_column_tetris — a pure SQL/PL/pgSQL extension that enforces optimal column alignment via event triggers.
2+
3+
Why it matters: Wasted alignment padding doesn't just cost disk space — more importantly, it costs memory. Padding bytes are loaded as-is into shared_buffers and OS page cache. More bytes per row means fewer rows per 8KB page, which means more pages resident in memory for the same dataset. This increases cache pressure, causes more evictions, and leads to more disk I/O. For high-row-count tables the memory impact far outweighs the storage cost.
4+
5+
Core components:
6+
7+
1. Alignment calculator function
8+
pg_column_tetris.compute_layout(oid) → TABLE(attname, typname, typalign, typlen, current_position, optimal_position, padding_bytes)
9+
This is the brain. For a given relation OID it:
10+
11+
Fetches all non-dropped columns from pg_attribute joined with pg_type
12+
Walks the column list in attnum order, simulating the heap tuple layout byte by byte (23-byte header → null bitmap if any nullable columns → MAXALIGN padding → then each column with its alignment requirement)
13+
Computes actual padding per column
14+
Computes the optimal order for fixed-width columns: 8-byte aligned first (bigint, timestamptz, float8), then 4-byte (int, float4, date, oid), then 2-byte (smallint), then 1-byte (boolean, char(1)), then all varlena last (text, varchar, numeric, jsonb, bytea) — varlena are 4-byte aligned (typalign='i') but go last because their variable in-row size creates unpredictable padding for any column that follows them. Within each alignment group, NOT NULL columns are preferred earlier (minor CPU optimization — cheaper to deform, though not a storage benefit).
15+
Padding is only computed between fixed-width columns (deterministic). Varlena columns are reported as "variable — placed last" rather than claiming exact byte savings.
16+
Returns both layouts with total fixed-width waste
17+
18+
2. Validation function
19+
pg_column_tetris.validate(oid) → void
20+
Calls compute_layout, compares current vs optimal total row size. If delta > 0, raises EXCEPTION with the suggested CREATE TABLE column order in the HINT. If equal, no-op.
21+
22+
3. Event trigger function
23+
pg_column_tetris.ddl_check() → event_trigger
24+
Fires on ddl_command_end. Loops over pg_event_trigger_ddl_commands(), filters for CREATE TABLE only. ALTER TABLE is deliberately skipped — users cannot affect column order on existing tables (new columns always get the highest attnum), so blocking or warning on ALTER TABLE would be noise with no actionable fix. For each affected relation OID, calls validate(). If validation raises, the whole DDL statement rolls back.
25+
Also skips: temp tables, tables in pg_catalog/information_schema.
26+
27+
4. Mode control
28+
A pg_column_tetris.config table with a single mode column: strict (block), warn (NOTICE only), off (skip). The event trigger reads this before doing anything. Defaults to warn on install so it doesn't break anything out of the box.
29+
30+
5. Escape hatch
31+
A companion table pg_column_tetris.exclusions(relname text) — if you have a legitimate reason to skip a table (e.g., you're matching an external schema), you add it here and the trigger skips validation for that table.
32+
Packaging:
33+
Standard extension structure — pg_column_tetris.control, pg_column_tetris--0.1.0.sql. Pure SQL install, no compilation, works on any managed Postgres (RDS, Cloud SQL, Supabase, Neon) since event triggers are supported everywhere that allows them.
34+
File structure:
35+
pg_column_tetris/
36+
├── pg_column_tetris.control
37+
├── pg_column_tetris--0.1.0.sql (schema, functions, event trigger, config)
38+
├── test/
39+
│ └── sql/
40+
│ ├── 01_strict_blocks.sql
41+
│ ├── 02_warn_allows.sql
42+
│ ├── 03_optimal_passes.sql
43+
│ ├── 04_exclusions.sql
44+
│ └── 05_edge_cases.sql (all-nullable, single column, varlena-only)
45+
├── README.md
46+
└── LICENSE
47+
Edge cases to handle:
48+
49+
Tables with only varlena columns (no fixed-width padding possible, always pass)
50+
Single-column tables (always optimal)
51+
All-nullable tables (null bitmap changes MAXALIGN boundary after header)
52+
Partitioned tables (check parent definition only)
53+
Temp tables (skip — ephemeral, not worth blocking)
54+
Tables in pg_catalog/information_schema (skip — system schemas)
55+
56+
Rollout order:
57+
58+
compute_layout function + manual SELECT usage — useful standalone
59+
validate + event trigger in warn mode
60+
Test suite
61+
README + examples
62+
63+
64+
CREATE EXTENSION pg_column_tetris;
65+
-- installs in warn mode by default
66+
67+
68+
Daily workflow — the event trigger does the work invisibly:
69+
Developer writes a migration:
70+
71+
72+
CREATE TABLE orders (
73+
is_shipped boolean,
74+
order_total numeric,
75+
user_id bigint,
76+
item_ct smallint,
77+
order_dt timestamptz,
78+
status smallint,
79+
ship_dt timestamptz
80+
);
81+
82+
83+
In strict mode, it rolls back and they see:
84+
85+
86+
ERROR: suboptimal column alignment — 19 bytes of fixed-width padding wasted per row
87+
DETAIL: Current fixed-width layout wastes 19 bytes per row in alignment padding; optimal order wastes 0.
88+
HINT: Suggested order:
89+
CREATE TABLE orders (
90+
user_id bigint, -- 8-byte aligned
91+
order_dt timestamptz, -- 8-byte aligned
92+
ship_dt timestamptz, -- 8-byte aligned
93+
item_ct smallint, -- 2-byte aligned
94+
status smallint, -- 2-byte aligned
95+
is_shipped boolean, -- 1-byte aligned
96+
order_total numeric -- varlena (last)
97+
);
98+
99+
In warn mode, same message but as NOTICE — DDL goes through.
100+
101+
Auditing existing tables:
102+
103+
-- single table report
104+
SELECT * FROM pg_column_tetris.check('orders');
105+
106+
-- returns:
107+
-- attname | alignment | current_pos | optimal_pos | padding
108+
-- ------------+-----------+-------------+-------------+--------
109+
-- is_shipped | c (1) | 1 | 7 | 7
110+
-- order_total| i (varlena)| 2 | 5 | (variable)
111+
-- user_id | d (8) | 3 | 1 | 0
112+
-- ...
113+
-- fixed_waste_bytes_per_row: 19
114+
-- Note: padding between fixed-width columns only; varlena waste is variable
115+
116+
117+
-- generate the fix DDL for a specific table (rewrite script)
118+
SELECT pg_column_tetris.suggest_rewrite('orders');
119+
120+
-- returns the full migration:
121+
-- ALTER TABLE orders RENAME TO orders_old;
122+
-- CREATE TABLE orders ( ... optimal order ... );
123+
-- INSERT INTO orders SELECT user_id, order_dt, ... FROM orders_old;
124+
-- DROP TABLE orders_old;
125+
126+
127+
Configuration:
128+
129+
SELECT pg_column_tetris.set_mode('strict'); -- block
130+
SELECT pg_column_tetris.set_mode('warn'); -- notice only
131+
SELECT pg_column_tetris.set_mode('off'); -- disable
132+
133+
-- exclude a table
134+
SELECT pg_column_tetris.exclude('legacy_imports');
135+
136+
-- check current mode
137+
SELECT pg_column_tetris.mode();
138+

0 commit comments

Comments
 (0)