Build1 distinct publisher3 min readUpdated
A dev.to walkthrough lays out four hand-written PostgreSQL index patterns that have existed since version 7.2. The patterns hold up. The arithmetic in the write-up does not.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A tutorial published on dev.to makes a narrow, useful argument: the index lists in struggling PostgreSQL deployments are usually full-column indexes generated by ActiveRecord, SQLAlchemy or Hibernate, covering every row including the 97 percent that queries never touch [s1c1]. The author's point is that the remedy is ordinary DDL, and PostgreSQL has supported it since version 7.2 [s1c2], which makes this a discipline problem rather than a capability problem.
The claim with teeth is that a full-column index on `deleted_at` or `status` across a 50M-row table can be worse than no index, because the planner may read a large index and still hand back millions of rows to filter [s1c3]. The soft-delete fix is one clause: the naive index covers all 50M rows in `users`, while `CREATE INDEX ... ON users(email) WHERE deleted_at IS NULL` covers roughly 1M active users [s1c4][s1c5], about 2 percent of the table [1].
Then the numbers. The "before" plan is a sequential scan at 2840.112 ms that removed 49,020,000 rows by filter [s1c6]; the "after" plan is an index scan at 0.091 ms returning one row [s1c7]. That is 50,000,000 rows examined [2] and a ratio near 31,000x [3]. It is also not the same query twice: the first plan reports 980,000 rows matching a filter that includes an equality predicate on `email`, and the second reports one row for the same predicate [4]. Treat the plans as illustrations of shape, not measurements.
The index size figure has the same problem in the other direction. The article reports 2.1 GB dropping to 42 MB and calls that "eighty percent smaller" [s1c8], while its own summary says 90 percent [s1c9]. The real reduction is about 98 percent [5]. The win is larger than advertised, which is the forgivable direction, but a write-up that cannot divide is not a write-up whose `EXPLAIN` output you copy without rerunning.
The patterns themselves are sound and the author is honest about the weakest one. The multi-tenant example, an index on `orders(created_at DESC)` restricted to `tenant_id = 42 AND status = 'pending'`, only works for a small number of high-volume tenants known at schema design time, and is explicitly not a general multi-tenancy strategy [s1c10]. It is a hot-partition tool with a maintenance cost every time the tenant list changes.
Expression indexes carry the sharper operational trap. `WHERE LOWER(email) = LOWER($1)` will never use a plain btree on `email` [s1c11]; you need an index on `LOWER(email)` [s1c12], and the query must spell the expression exactly, because the planner matches the expression rather than the column [s1c13]. Any call site that lowercases differently loses the index silently. Same mechanism for JSONB: without an index on `(payload->>'user_id')`, every JSONB predicate is a sequential scan carrying per-row extraction cost [s1c14], and the article puts that query at 3100 ms falling to 1.1 ms [s1c15], roughly 2,800x [6].
One step is easy to skip: run `ANALYZE` manually after creating a partial or expression index, before autovacuum gets there, because a fresh expression index with no statistics in `pg_statistic` forces the planner to guess, and the guess is often badly wrong [s1c16][s1c17].
What to watch is your write path. Every index adds cost to `INSERT`, `UPDATE` and `DELETE`, and that compounds on high-write tables such as event streams [s1c18], which are exactly the tables where the JSONB expression index looks most attractive. Build the index on a copy, run `ANALYZE`, then compare `EXPLAIN ANALYZE` against the query your application actually emits rather than the one in the tutorial.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The 'before' plan is a Seq Scan on users with cost 0.00..142000.00, actual time 0.042..2831.445, rows=980000, Filter ((deleted_at IS NULL) AND ((email)::text = $1)), Rows Removed by Filter: 49020000, Execution Time: 2840.112 ms.
The 'after' plan is an Index Scan using idx_users_email_active with cost 0.43..8.45, actual time 0.023..0.091, rows=1, Index Cond ((email)::text = $1), Execution Time: 0.091 ms.
With an expression index CREATE INDEX idx_events_user_id ON events((payload->>'user_id')), the query WHERE payload->>'user_id' = '10034' hits the index; without it, every JSONB predicate is a full sequential scan with per-row extraction cost.
For the JSONB pattern, the article reports query time dropping from 3100 ms to 1.1 ms.
The article states that index lists in struggling PostgreSQL deployments are almost always full-column indexes generated by ActiveRecord, SQLAlchemy or Hibernate, covering every row including the 97% that queries never touch.
PostgreSQL has supported partial and expression indexes since version 7.2; the tutorial recommends being on a supported version, PostgreSQL 12 or later.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Mechanisms documented, measurements unreproducible
The technique claims rest on long-standing, officially documented PostgreSQL behaviour that the article itself links (partial indexes, expression indexes, planner expression matching, pg_statistic/ANALYZE), which is why the qualitative claims read as supported. The quantitative spine is weak: no schema, dataset, row width, hardware or PostgreSQL version is given, the JSONB 3100 ms to 1.1 ms figure has no plan output at all, the stated size percentages contradict the stated sizes, and the two headline plans cannot be the same query. One source, one publisher, zero independent corroboration.
No adoption evidence supplied
The cluster contains a single tutorial. There is no release, deployment, benchmark run, usage disclosure or telemetry in the supplied material — only an unquantified authorial aside about production systems the writer has managed. Nothing here measures how widely these index patterns are used.
Framing and figures overshoot the substance
The title and description frame a documented feature set from PostgreSQL 7.2 as an optimisation the ORM 'is hiding from you', and headline a 90% index-size cut and sub-millisecond seeks. The techniques are real and the caveats are honest, but the dramatic evidence is unreproducible and internally broken: the before/after plans report incompatible row counts, and the size reduction is quoted at 80%, 90% and figures implying 98% within one article. The gap is in presentation, not in the mechanics, which is why it is moderate rather than extreme.
Agency content marketing under a developer byline
The front matter sets canonical_url to mvpfactory.co/blog and the byline is an agency account (software_mvp-factory), so the piece functions as lead-generation content for a development shop that sells exactly the audit-and-fix work it describes. Authority is asserted through unverifiable first-person claims ('a pattern I audit in every struggling PostgreSQL deployment', 'production systems I've managed'). No vendor product is being sold and no sponsorship is disclosed, so the incentive is promotional rather than a direct conflict over the technical content.
Confident on technique, weak on numbers
High confidence that the described index patterns and the ANALYZE and write-overhead caveats are accurate, since they restate documented PostgreSQL behaviour. Low confidence in anything quantitative: a single vendor-adjacent source, no corroborating publisher, no reproducible measurement, demonstrable arithmetic and plan inconsistencies, and no adoption data on which to triangulate.
build
Your "Index Only Scan" Did 2,847 Heap Fetches: Covering Indexes Are a Vacuum Problem1 distinct publisher
build
The optional EntityManager is the bug: moving the transaction boundary into AsyncLocalStorage1 distinct publisher
build
Three attackers hide behind one connect button, and encryption only stops one of them1 distinct publisher
build
Three services you can delete: queue, cache and search in one Postgres1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 20, 2026