We open the B cluster with a parameter whose existence is a confession. PostgreSQL has a complicated relationship with the Linux page cache, and backend_flush_after is one of four GUCs that exist to mediate that relationship. The other three — bgwriter_flush_after, checkpoint_flush_after, and wal_writer_flush_after — will get their own posts in due course. They share a mechanism and a motivation, and the motivation is worth understanding before we dive into the specific parameter.

A short tour of writeback and why PostgreSQL cares

When PostgreSQL writes a dirty page to a file, the write doesn’t go to disk. It goes into the kernel’s page cache, where it sits as a dirty buffer until the kernel decides to flush it. The Linux kernel decides this based on its own dirty-data thresholds (vm.dirty_background_ratio, vm.dirty_ratio) and a periodic writeback timer. By default these thresholds are generous, often allowing gigabytes of dirty data to accumulate before the kernel starts writing it back in earnest.

This is normally fine, and then it isn’t. At checkpoint time, PostgreSQL calls fsync() on every file it has written to since the last checkpoint. If the kernel has been hoarding dirty pages, that fsync() now blocks while gigabytes of writeback drain to disk, all at once, contending with whatever foreground I/O your queries are trying to do. Latency spikes. Queries that should take milliseconds take seconds. You blame the checkpoint. You are not entirely wrong, but the underlying cause is the kernel’s writeback strategy, which PostgreSQL has limited ability to influence.

The *_flush_after parameters are PostgreSQL’s attempt to influence it. The mechanism is sync_file_range() on Linux (or the platform equivalent where it exists), which tells the kernel “please start writing these specific dirty pages back, but don’t block waiting for it.” It is a hint, not a guarantee, and on platforms that don’t implement the underlying syscall the parameter has no effect.

What this parameter does

backend_flush_after controls writeback initiation for regular backends — the processes that handle client queries — when they end up doing their own dirty-page writeback because the background writer can’t keep up. Default is 0 (disabled). Range is 0 to 2MB (in 8kB blocks if the value is given without units). Context is user, so it can be set per-session, though there is essentially no reason to do that.

The default of 0 is conspicuously different from its three siblings:

Parameter Linux default
backend_flush_after 0 (disabled)
bgwriter_flush_after 512kB
checkpoint_flush_after 256kB
wal_writer_flush_after 1MB

This is not an accident. The other three writers do bulk work on predictable cadences; forcing writeback for them is almost always a win. Backends are different — they’re handling user queries, which means triggering writeback in the middle of a query path adds latency to that query. The PostgreSQL community has been conservative about enabling it by default for exactly this reason. Benchmarks have been mixed; some workloads see latency improvements, some see regressions, and the difference depends heavily on storage characteristics and the OS dirty-ratio settings.

When to enable it

Two scenarios:

  • Heavy write workload with checkpoint-induced latency spikes. If pg_stat_bgwriter (or pg_stat_checkpointer on PG 17+) shows buffers_backend consistently high relative to buffers_clean and buffers_checkpoint, your backends are doing writeback work that should be the background writer’s job. Fixing the background writer first is the right move, but backend_flush_after = 256kB or 512kB as a stopgap can reduce the magnitude of checkpoint stalls while you sort out the deeper issue.
  • Managed cloud PostgreSQL where you can’t tune the kernel. RDS, Aurora, Cloud SQL, and friends do not let you touch vm.dirty_background_ratio. The *_flush_after parameters may be the only lever you have over kernel writeback timing. In that environment they take on greater importance than they have on a server you control end-to-end.

When to leave it alone

Everywhere else, basically. On a properly-tuned server with reasonable kernel dirty-ratio settings and an adequately-configured background writer, the default 0 is correct.

Recommendation: Leave at 0 unless you have specific evidence of backend-triggered writeback contributing to query latency, or you are on managed PostgreSQL with no kernel access. In the latter case, start at 512kB and benchmark. The other three *_flush_after parameters are higher-leverage and will be covered next.