Postgres 19: How Our Advice Has Changed Since We Wrote It

winslett1 pts0 comments

Postgres 19: How Our Advice Has Changed Since... | Crunchy Data Blog

Christopher Winslett

Aug 18, 2026·16 min read

More by this author

Latest Articles<br>Postgres 19: How Our Advice Has Changed Since We Wrote It<br>Hybrid Search Patterns with Postgres and pgvector<br>Postgres 19 Compression: from pglz to LZ4<br>British Columbia, Time Zones, and Postgres<br>Postgres Serials Should be BIGINT (and How to Migrate)

Production PostgresPostgres 19<br>Postgres 19: How Our Advice Has Changed Since We Wrote It

Christopher Winslett

Aug 18, 2026·16 min read·More by this author

Over the years we have written a lot about how data gets into Postgres, how it sits on disk, and how indexes help you find it again. Some of that advice was written against Postgres 10 or 11. A surprising amount of it is still exactly what we would tell you for the upcoming Postgres 19 release. Functionality described here is based on current betas; minor details may still change before GA.This post revisits Crunchy posts in the “load, storage, indexes, and partitioning” bucket: what we wrote, which version moved the needle, and what we would tell you to do now. Along the way: async I/O, more resilient COPY, LZ4 by default, richer BRIN shapes, skip scan, and smoother partition operations.Async I/O: faster scans and vacuum on modern storage<br>In 2019 we benchmarked a BRIN index against a B-tree and a parallel sequential scan on the same time-series table. Sometimes BRIN won. Sometimes the parallel seq scan won: four workers chewing through the heap beat a clever index. That was the right lesson for Postgres 11: indexes are a tradeoff against what the executor can already do in parallel.Postgres 18 made those heap reads substantially faster.Async I/O lets backends queue multiple disk reads instead of waiting on each one. Sequential scans, bitmap heap scans (the path BRIN and many bitmap index plans finish with), and vacuum all benefit. Community benchmarks have shown up to ~3× on cold, latency-bound storage, a big deal for cloud disks. Defaults matter here: io_method = worker is on out of the box; on Linux 5.1+ you can try io_method = io_uring. See Get Excited About Postgres 18 for the operator view.Postgres 19 builds on that: I/O workers can autoscale (io_min_workers / io_max_workers), read-ahead scheduling improved, and EXPLAIN (ANALYZE, IO) can show what the async subsystem is doing. Parallel query is still there; each worker can queue several reads and keep making progress while some of them are still in flight, so you get more useful work between waits. Parallel autovacuum workers also landed in 19 (autovacuum_max_parallel_workers and per-table autovacuum_parallel_workers), so maintenance can fan out, but the defaults are conservative: tune them when vacuum is falling behind on large tables.One related default flip: JIT is off by default in Postgres 19 (it had been on since 12). The old cost model was unreliable, so large analytical or parallel scans that used to compile at runtime no longer will unless you turn jit back on. If those workloads matter to you, re-enable explicitly and re-check plans after upgrade.Postgres 19 advice: Keep choosing indexes for selectivity, and re-test BRIN-vs-parallel plans on your storage with EXPLAIN (ANALYZE, BUFFERS, IO) after you upgrade. Tune effective_io_concurrency / maintenance_io_concurrency with the new defaults in mind (they rose to 16 in 18). Treat async I/O as more headroom for the heap paths, alongside good COPY, TOAST modeling, BRIN, and covering indexes.Loading data: COPY is still king, and now more resilient<br>What we wrote: In Fast CSV and JSON Ingestion in PostgreSQL with COPY (2018, Postgres 10), Jonathan Katz showed the classic pattern: generate CSV or newline-delimited JSON, pipe it into psql, and let COPY ... FROM STDIN do the heavy lifting. Prefer COPY over row-by-row INSERT. Store JSON as jsonb. Add a GIN index when you need containment queries.That advice was right then and is still right now. The load path itself did not need reinventing; it got more capable.What changed: VersionChange that matters for loads16 COPY FROM can map a sentinel string to a column DEFAULT17 ON_ERROR ignore skips bad type conversions and keeps loading; LOG_VERBOSITY reports what was skipped18 REJECT_LIMIT caps how many bad rows you will tolerate; LOG_VERBOSITY silent quiets the noise; CSV handling of \. is clearer19 Faster text/CSV parsing via SIMD; ON_ERROR SET_NULL turns invalid values into NULL; skip multiple header lines; COPY TO can emit JSON (and a single JSON array with FORCE_ARRAY) and can target partitioned tables directlyFREEZE on an initial load into a freshly created or truncated table is still the right performance trick when you want to skip a later freeze vacuum. That option predated these releases and remains useful.Postgres 19 advice: Keep using COPY for bulk ingest, including imperfect feeds. A practical 19-era load looks like this:COPY events (event_id, occurred_at, payload)<br>FROM STDIN<br>WITH (<br>FORMAT...

postgres copy advice still parallel brin

Related Articles