The file was already tight -- freelist 0 pages, and a plain VACUUM reclaimed
nothing -- so the saving had to come from schema rather than compaction.
Profiled with dbstat and measured every step on copies of the live file.
Three changes to Notes, applied 2026-08-07:
- Rebuild as WITHOUT ROWID (-32 MB). The 5-column composite primary key was
stored twice: once in the table, once in a 62 MB autoindex existing only to
map key -> rowid. Keying the table b-tree on the primary key itself drops the
second copy. ix_NoteBlogName01 grows 25 -> 58 MB in exchange, since a
secondary index on such a table carries the whole primary key instead of a
rowid; net -32 MB.
- Drop Notes_idx_06e01ae3 on TimeStamp DESC (-14 MB). Barely earned its keep as
a rowid index and would have cost 58 MB after the conversion, cancelling the
entire exercise. The crawler's only TimeStamp filter (>= 1535778000) excludes
786 of 1,182,333 rows; Rolodex's default Notes sort carries a three-column
tiebreaker forcing a full sort regardless; the reply-matching UPDATE uses
ABS(TimeStamp - ?) <= 5, which no index on the column can serve. Cost is one
path: Rolodex's Notes page with a date-range filter, 60 ms -> 164 ms.
- Null the DatetimeCrawled placeholder (-13 MB). 1,148,077 rows stored the
literal DDL default '2/12/26 12am', a backfill marker rather than a crawl
time. UI-neutral: Rolodex reads the column through DateSql.Sortable, whose
CASE matches neither format, so those rows already rendered as an em dash.
No application code changed. The schema keeps the same tables, columns, types
and constraints; WITHOUT ROWID is a storage-layout change behind the same SQL
surface, and no consumer referenced rowid on Notes.
Verified against all three consumers on the live file: integrity_check ok, row
counts unchanged (1182333 / 22468 / 188620), journal_mode still wal, crawler
INSERT OR IGNORE still dedupes, Rolodex's NoteBlogName filter still uses
ix_NoteBlogName01, and exactly as many rows read as null through Sortable after
the change as before it. TumblThree touches only Blogs, which is untouched.
Deliberately not done: nulling Notes.DateCreated (a further -12 MB). Unlike
DatetimeCrawled its value parses as a real date, so Rolodex displays and sorts
by it; nulling would turn visible dates into em dashes.
Note that DEFAULT '2/12/26 12am' remains on the column, so any writer inserting
a note without naming it reintroduces the placeholder. Consumer-side date
normalisation must stay.
Co-Authored-By: Claude Opus 5 <[email protected]>