Twelve parameters share the debug_ prefix, and the prefix is load-bearing: these are PostgreSQL’s own development and QA apparatus, exposed as runtime settings so the buildfarm and the core developers can exercise code paths without recompiling. Pavlo Golub, writing about one of them, summed up the correct posture: “I’ll never-ever touch a runtime option with a ‘debug’ prefix on my production clusters.” Mostly right. Let’s sort the dozen into what they actually give you, because one or two are quietly useful and the rest you should admire from a safe distance.
The readout
debug_assertions is read-only. It tells you whether the server was built with --enable-cassert, which compiles in thousands of internal consistency checks. Assertion builds are for development and the buildfarm — they’re slow, and a tripped assertion PANICs the backend deliberately. If SHOW debug_assertions says on, you’re on a debug build and should not be in production. That’s the entire utility: a label confirming what kind of binary you’re running.
Show me the trees
Four parameters dump the parser’s and planner’s internal node trees to the server log — not to the client, and not in the cleaned-up form EXPLAIN gives you. debug_print_parse emits the raw parse tree, debug_print_rewritten the parse tree after the rewriter has applied rules and expanded views, and debug_print_plan the finished plan tree, which is voluminous enough to bury a busy log in minutes. debug_pretty_print, on by default, indents all three so they’re legible rather than a single catastrophic line.
These are the only members of the family a curious non-hacker might genuinely reach for, and they work in any build. EXPLAIN and the auto_explain extension have superseded them for almost everything — those show you the plan in a form built for humans — but the debug_print_* family shows the literal node structures, Vars and RangeTblEntrys and all, which is occasionally what you want when you’re learning how the planner represents a query or chasing why a rewrite did something surprising. Set them per-session, read the log, turn them back off.
PostgreSQL’s torture tests
Four more exist purely to shake out bugs in core infrastructure by forcing expensive code paths to run constantly, and most require a build compiled with the corresponding support flag. debug_copy_parse_plan_trees forces every parse and plan tree through a copy-and-compare cycle to test the node copy functions. debug_write_read_parse_plan_trees round-trips them through serialize-then-deserialize to test the out/read functions. debug_raw_expression_coverage_test walks raw expression trees to exercise the node-walker machinery for coverage. And debug_discard_caches — the old CLOBBER_CACHE_ALWAYS — invalidates every system catalog cache entry at the first possible moment, set to 1; higher values recurse and are slower still. It makes the server run at a crawl and exists to surface hard-to-reproduce cache-invalidation bugs involving concurrent catalog changes.
These are how the buildfarm catches node-handling and cache bugs so that ordinary release builds don’t carry the cost. The “what you get out of them” in production is: a database that runs orders of magnitude slower while testing something that has nothing to do with your workload. Never.
Force a behavior, for testing
The last three force a specific behavior on so its machinery can be exercised. debug_parallel_query is the famous one — it was called force_parallel_mode until PostgreSQL 16, renamed because, as the commit put it, users kept assuming the GUC forced the planner into “usefully parallelizing queries” when in fact it forces parallel plans regardless of cost, to test the parallel infrastructure, and generally makes queries slower. The old name still works for now. Its values are off, on, and regress (the last suppresses the parallel-worker noise that would otherwise break regression-test output). If you turned this on hoping for a speedup, you wanted max_parallel_workers_per_gather, which gets its own post.
debug_logical_replication_streaming (buffered by default, or immediate) forces logical replication to stream each in-progress change immediately rather than buffering, so the streaming code path gets tested even on small transactions. debug_io_direct is the one worth actually knowing about: it enables direct I/O — O_DIRECT and its per-platform equivalents — bypassing the OS page cache for data, wal, or wal_init (any comma-separated combination), set only at server start. This is the leading edge of the direct-I/O work that, as covered in data_sync_retry, is the agreed-upon eventual escape from the fsync-error trap: own the writeback, own the error handling, stop trusting the kernel’s page cache. As of today the docs are blunt that it “reduces performance, and is intended for developer testing only,” because the asynchronous I/O infrastructure that would make direct I/O fast is still landing. Watch this one over the next few releases; it’s the future wearing a debug_ prefix.
So: debug_assertions to confirm your build, the debug_print_* family when you genuinely want to see raw node trees, and debug_io_direct to keep an eye on where durability is headed. The other eight are PostgreSQL testing itself, and Golub’s instinct is exactly right for every one of them — admire the engineering, leave them off.