ILIKE on a trigram-indexed column was spending ~40ms in the query planner for every ~3ms of execution. The fix was two lines of SQL and one line of Python — but finding it required understanding what the planner actually does with ILIKE.
Replacing a per-symbol replaceAll loop with a single regex pass and a Map lookup cut mana symbol conversion time by 47×. The mechanism, the benchmark, and a gotcha with stateful regex.
Rendering Magic’s mana symbols as a custom webfont: why a font beats SVG sprites or images, how subsetting drops a 200–300 KB file to under 40 KB, and the tradeoffs around font loading that the obvious approach misses.
Search results need two distinct scoring concerns: which printing of a card to surface, and which cards to rank first. Both are numeric ORDER BY expressions baked into the card rows.
When you type t:cre, the search box completes it to t:Creature before any network request leaves your browser. The type list lives in a frozen in-memory index, populated once at page load from the Rust engine.
The magic.cards table carries indexes of every type PostgreSQL offers. A tour of trigram GIN for substring search, GIN for JSONB containment, B-tree for numerics, hash for exact-match fields, and an expression index that unlocks a color identity query that otherwise requires a full table scan.
/?q=fire always returns fully rendered HTML — no JavaScript required. JS layers typeahead and live updates on top. The cost: two card renderers that have to stay in sync.
Switching from row-by-row inserts to PostgreSQL’s COPY protocol meaningfully cut import time. Why COPY is fast, how to stream data into it from Python, and two approaches to expanding a JSON blob into a typed row.
A search endpoint needs paginated results and a total count. A single CTE with UNION ALL answers both — and for deduplicated searches it runs in roughly half the time. For non-deduplicated searches it matches two separate queries while using one round trip.
Each AST node emits a SQL fragment and bound parameters. How the node hierarchy works, how different field types generate different SQL, and why user input never touches the query string. Covers text/regex/arithmetic; JSONB operators and the count+results CTE are in separate posts.