The 2026 Postgres discourse is mostly “yes, it scales.” Peter Kraft’s post at DBOS is the opposite: a feature that intuitively should scale, doesn’t, and the planner limitation underneath. They hit it tuning a partitioned-queue workload, where dequeueing starts by finding the “active” partitions — the ones with an ENQUEUED workflow.
The query was SELECT DISTINCT over an index on (queue name, workflow status, partition key). That column order is exactly right for the query, so they expected cost proportional to the number of active partitions, since Postgres only needs one row seek per distinct partition key.
That is not what happens:
SELECT DISTINCTscans every row matching its predicates, however the table is indexed and however few unique values it returns- Wide/shallow workloads (many partitions, few rows each) looked fine; narrow/deep ones took seconds where sub-milliseconds were expected
- Benchmark with partitions fixed at 10 and rows per partition scaled from 100 to 1M: latency rises linearly with rows per partition
- The plan is a full index scan — 1M rows walked to find 3 partition keys it could have sought directly
Why the planner can’t do better:
- Every index-scanning operator Postgres implements retrieves all indexed values matching its predicates, so there is no cheaper plan to choose
- MySQL has a loose index scan; Postgres doesn’t
- Postgres 18’s skip scan only skips rows on a non-leftmost index column, so it still scans everything matching the predicates and can’t accelerate this
- A serious loose-index-scan patch went to commitfest in 2018 and was abandoned after four years of effort and maintainer churn
The workaround is a recursive CTE that reads as a loop: first iteration finds the smallest partition key, each next iteration finds the next one after it. Each iteration is a SELECT min() on a sorted index — one value instead of a full scan — so total cost is O(partitions). Re-benchmarked with partitions fixed at 10 and 1K→1M rows per partition, median latency is flat.
It is “remarkably hard to read,” in Kraft’s words — imperative code wearing declarative SQL as a disguise. That’s the actual trade-off: the query has no readability or portability upside, only the plan Postgres won’t pick on its own.
The transferable part is the diagnostic. They isolated “scales with table size” from “scales with distinct values” by holding one fixed and varying the other — which is how you catch this without waiting for production symptoms. And a benchmark made only of wide/shallow partitions would have shipped this.