<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Joe Bylund</title><link>http://jbylund.github.io/posts/</link><description>Recent content in Posts on Joe Bylund</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>© 2026 Joe Bylund</copyright><lastBuildDate>Mon, 07 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://jbylund.github.io/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>The ILIKE Trap: 40ms of Planning for 3ms of Work</title><link>http://jbylund.github.io/posts/00384_ilike-trap-postgres-planner/</link><pubDate>Mon, 07 Sep 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00384_ilike-trap-postgres-planner/</guid><description>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.</description></item><item><title>Mana Symbol Rendering: Regex + Map for 47× Speedup</title><link>http://jbylund.github.io/posts/00352_mana-symbol-rendering-regex/</link><pubDate>Wed, 02 Sep 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00352_mana-symbol-rendering-regex/</guid><description>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.</description></item><item><title>Why Mana Symbols Are a Custom Webfont, Not SVG Sprites</title><link>http://jbylund.github.io/posts/00320_fonts-and-mana-symbols/</link><pubDate>Fri, 28 Aug 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00320_fonts-and-mana-symbols/</guid><description>Rendering Magic&amp;rsquo;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.</description></item><item><title>Scryfall Sorts Alphabetically. I Sort by CubeCobra Popularity.</title><link>http://jbylund.github.io/posts/00288_card-preference-scoring-function/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00288_card-preference-scoring-function/</guid><description>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.</description></item><item><title>Autocomplete Without a Round-Trip: Suggestions from a Frozen In-Memory Type List</title><link>http://jbylund.github.io/posts/00256_autocomplete-card-types/</link><pubDate>Tue, 18 Aug 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00256_autocomplete-card-types/</guid><description>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.</description></item><item><title>22 Indexes on One Table: How Card Search Uses Every Index Type PostgreSQL Has</title><link>http://jbylund.github.io/posts/00224_postgres-index-strategies/</link><pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00224_postgres-index-strategies/</guid><description>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.</description></item><item><title>Progressive Enhancement: Two Card Renderers for the Price of One</title><link>http://jbylund.github.io/posts/00192_progressive-enhancement-no-js/</link><pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00192_progressive-enhancement-no-js/</guid><description>/?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.</description></item><item><title>PostgreSQL COPY Loading: Faster Bulk Import</title><link>http://jbylund.github.io/posts/00160_postgres-copy-loading/</link><pubDate>Tue, 28 Jul 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00160_postgres-copy-loading/</guid><description>Switching from row-by-row inserts to PostgreSQL&amp;rsquo;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.</description></item><item><title>One Query, Two Answers: How NOT MATERIALIZED Lets Each Branch Pick Its Own Index</title><link>http://jbylund.github.io/posts/00144_results-and-count-single-query/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00144_results-and-count-single-query/</guid><description>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.</description></item><item><title>Compiling a Query AST to Parameterized SQL</title><link>http://jbylund.github.io/posts/00128_compiling-ast-to-sql/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00128_compiling-ast-to-sql/</guid><description>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.</description></item><item><title>Whitespace as an Operator: Parsing Scryfall's Implicit AND</title><link>http://jbylund.github.io/posts/00112_implicit-and-query-balancing/</link><pubDate>Tue, 07 Jul 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00112_implicit-and-query-balancing/</guid><description>The Scryfall query language implicitly ANDs adjacent terms — pyparsing does not. This post covers the preprocessing layer built on top: implicit AND injection, query balancing in two languages, and the edge cases that required fixes.</description></item><item><title>Choosing a Parser for a Query DSL: Regex, ANTLR, Lark, and pyparsing</title><link>http://jbylund.github.io/posts/00096_query-dsl-with-pyparsing/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00096_query-dsl-with-pyparsing/</guid><description>How I evaluated parsing approaches for Scryfall&amp;rsquo;s query language: why regex and ANTLR were ruled out, how Lark compares to pyparsing, and why pyparsing won.</description></item><item><title>13× the Throughput of FastAPI: Why I Use Falcon + Bjoern</title><link>http://jbylund.github.io/posts/00064_falcon-bjoern-web-framework/</link><pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00064_falcon-bjoern-web-framework/</guid><description>Why Sylvan Librarian uses Falcon and Bjoern instead of the FastAPI + uvicorn default: a preference for explicit, close-to-vanilla Python over framework magic.</description></item><item><title>The Query Scryfall Can't Answer: `power+toughness&gt;cmc+cmc`</title><link>http://jbylund.github.io/posts/00032_why-build-a-self-hosted-scryfall/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>http://jbylund.github.io/posts/00032_why-build-a-self-hosted-scryfall/</guid><description>Motivation for building Sylvan Librarian: owning the query language, and the killer feature Scryfall can&amp;rsquo;t do — arithmetic comparisons across card attributes.</description></item></channel></rss>