We arrive at the enable_* parameters, a family of two-dozen-odd planner toggles that all default to on and all share one crucial property: they are not tuning knobs. They are diagnostic instruments. Each one switches off the planner’s ability to consider a particular plan type, and the reason that capability exists is so that an engineer chasing a bad plan can force the planner to show its second choice — to ask “what would you have done if this node type weren’t available?” Turning one off in production to make a query faster is almost always a mistake; what you actually want is to understand why the planner preferred the plan you didn’t like, and these switches are how you interrogate it. Every post in this family will repeat some version of that warning, because every one of these parameters gets misused the same way.
With that established, enable_async_append is a good one to start on, because it gates a genuinely modern feature rather than a decades-old plan node. Default on, context user.
What async append is
An Append node is what the planner builds when a query draws rows from several sources that get concatenated — the partitions of a partitioned table, the branches of a UNION ALL. Ordinarily Append works through its children one at a time: scan the first to completion, then the second, and so on. That serial execution is fine when the children are local heaps the CPU rips through, and it is decidedly not fine when the children are foreign tables on remote servers, because then each child scan spends most of its life waiting on a network round-trip, and doing them one after another means your query’s total time is the sum of every remote server’s latency, paid in sequence.
Asynchronous append, added in PostgreSQL 14, fixes exactly that. When the children of an Append are foreign scans against different servers, an async-aware Append fires the remote queries concurrently and collects results as they arrive, so the query waits roughly as long as the slowest shard rather than the sum of all of them. In EXPLAIN, the node shows up as Async Foreign Scan, and the difference between summing ten shards’ latencies and waiting for one of them is the difference between an FDW-sharded analytics query being usable and being a punchline. This was a real milestone for PostgreSQL’s foreign-data-wrapper sharding story — the first time a query spanning shards could fan out in parallel instead of trudging server to server.
There is a requirement that trips people up: the feature only engages when the foreign tables are marked async_capable, an option you set on the foreign server (or per foreign table) that defaults to false. So async append has two switches — this GUC, on by default, and the async_capable server option, off by default — and the one you most likely need to flip is the second. The GUC governs whether the planner will consider async append at all; the server option governs whether your specific foreign tables opt into it. A foreign server left at the default async_capable = false gets serial foreign scans no matter how enable_async_append is set.
When you would touch the GUC
For diagnosis, almost exclusively. The symptom that should make you reach for it is specific: a query against a partitioned table whose partitions are foreign tables — or any UNION ALL across foreign servers — running noticeably slower than the slowest single shard should account for, and an EXPLAIN ANALYZE whose total time looks suspiciously like the sum of the per-Foreign Scan times rather than their maximum. If you see plain Foreign Scan nodes under an Append where you expected Async Foreign Scan, the planner either didn’t choose async append or wasn’t allowed to, and the question is which. Flipping this GUC is how you answer half of it: if SET enable_async_append = off changes nothing about a plan that was already serial, the planner wasn’t using async append in the first place, and your problem is upstream — most likely the async_capable server option, below. If the plan did have Async Foreign Scan nodes and you want to measure what they’re worth, switching the GUC off and re-running EXPLAIN (ANALYZE) gives you the serial baseline to compare against. That is the legitimate use: a controlled experiment in a session you’ll close.
The docs do note one genuine production case for disabling asynchronous execution, but it lives on the async_capable option rather than this GUC: because postgres_fdw uses a single connection per foreign server and runs that server’s queries sequentially over it, a plan that references the same foreign server in several places can see async execution add coordination overhead without buying real concurrency, and there turning async_capable off can be faster. That’s a per-server tuning decision about the FDW, not a reason to disable the planner’s async-append capability cluster-wide.
So leave enable_async_append at on, where it lets the planner reach for concurrency when a sharded query can use it. If your cross-shard queries are running foreign scans serially when you expected parallelism, the lever is almost certainly async_capable on the foreign server, not this parameter — and if you’re reaching for this parameter at all, you should be doing it in a diagnostic session with EXPLAIN, which is the right and only good reason to touch any member of the family we’ve just opened.