Learn SQL with PostgreSQL - Backup, Replication & PgBouncer
Episode 19 of 21

Learn SQL with PostgreSQL - Backup, Replication & PgBouncer

This episode covers production readiness: logical backup with pg_dump and pg_restore, physical backup and WAL for Point-In-Time Recovery, physical streaming replication and logical replication, and connection pooling with PgBouncer to prevent connection exhaustion.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

Welcome to episode 19 of the Learn SQL with PostgreSQL series! We've built a database that's fast, secure, and scalable. But there's one question that decides production's fate: what happens if the server dies, the disk fails, or someone accidentally drops a table? The answer to all of that is backup and replication — two pillars of production readiness that aren't negotiable.

Backup is the ultimate safety net: a copy of the data that can be restored whenever needed. Replication is the parallel standby system: a copy that keeps living and can take over if the primary falls. And behind both lies one silent enemy: connections exploding in number — which is where PgBouncer comes in as a connection pooler.

In this episode, we'll cover logical backup strategies with pg_dump, pg_dumpall, and pg_restore, physical backup with Write-Ahead Logging (WAL) for Point-In-Time Recovery (PITR), physical streaming replication and logical replication, then connection pooling with PgBouncer to prevent connection exhaustion.

Database Backup & Restore Strategies

There are two complementary backup approaches: logical and physical.

Logical Backup: pg_dump and pg_restore

pg_dump produces an SQL dump (or archive format) of a single database. It works at the logical level — structure and data are exported as SQL statements.

Backup a single database
pg_dump -U postgres -h localhost -d shop -Fc -f shop.dump

The -Fc flag produces the custom format (compressed, selectively restorable). To back up an entire cluster (including roles and database definitions), use pg_dumpall:

Backup the entire cluster
pg_dumpall -U postgres -h localhost -f all.sql

Restore with pg_restore for the custom format, or pipe straight into psql for plain SQL:

Restore from the custom format
pg_restore -U postgres -h localhost -d shop --clean --if-exists shop.dump
Restore from plain SQL
psql -U postgres -h localhost -d shop < all.sql

Tip

Logical backup is the right choice for cross-version migrations (e.g. PostgreSQL 16 to 17) and object-level backups. But it's slow for large databases — running pg_dump nightly on a hundreds-of-gigabytes table will burden the server. For large scale, combine it with physical backup.

Physical Backup & WAL: Point-In-Time Recovery

Physical backup copies the raw data files — far faster for large databases. The combination of pg_basebackup + WAL enables Point-In-Time Recovery (PITR): restoring the database to a specific moment, even after a deliberate DROP TABLE.

Physical base backup
pg_basebackup -U postgres -h primary-host -D /backup/base -P

Write-Ahead Logging (WAL) is the ledger of every change — continuously shipped to the WAL archive. With a base backup + complete WAL, you can "replay" the database to a specific second:

Point-In-Time Recovery flow
1. Base backup lengkap di waktu T0
2. Arsip WAL menerima semua perubahan setelah T0
3. Recovery: restore base backup, lalu replay WAL sampai target waktu
4. Database pulih ke momen yang diinginkan (misal 5 menit sebelum DROP TABLE)

This is the main difference between logical and physical: logical backup = a photograph; physical + WAL = a VCR that can be rewound.

Warning

A backup that has never been tested for restore is not a backup — it's only hope. The industry rule of thumb: test restores periodically (e.g. monthly) to a separate instance, and verify that the data can be read and queries run. Many companies only discover their backup is corrupt precisely when the fire happens.

PostgreSQL High Availability & Replication

Replication creates a database copy that stays updated automatically — the foundation of high availability.

Physical Streaming Replication

Physical streaming replication copies the WAL in real-time from the primary (read-write) to a standby replica (read-only). The standby is byte-for-byte identical to the primary.

Physical replication topology
Primary (read-write)  ---WAL stream--->  Standby (read-only)

The key setup: the primary has wal_level = replica and max_wal_senders, then the standby is configured via primary_conninfo and bootstrapped from pg_basebackup. If the primary falls, the standby can be promoted to a new primary (failover) — downtime drops to seconds.

Logical Replication

Logical replication works at the logical level, per table, with a publish-subscribe model: the publisher publishes specific tables, the subscriber receives them. Its advantages:

  • Replication per table or per subset — not necessarily the whole database.
  • The primary and subscriber can run different PostgreSQL versions.
  • The subscriber can be written to (not necessarily read-only) — useful for multi-source aggregation.
Logical replication on the publisher
CREATE PUBLICATION shop_pub FOR TABLE orders, order_items;
Logical replication on the subscriber
CREATE SUBSCRIPTION shop_sub
CONNECTION 'host=primary-host dbname=shop user=replicator'
PUBLICATION shop_pub;

Logical replication becomes the choice when you want certain data to flow to another database — e.g. operational data to a data warehouse or analytics cluster.

Note

Summary: physical replication copies the entire database identically (best practice for failover/HA), while logical replication copies selected tables with cross-version flexibility (for analytics and integration). Physical is for availability, logical is for data distribution.

Connection Pooling with PgBouncer

PostgreSQL handles connections with one process per connection. Each process consumes a few megabytes of memory. When thousands of requests arrive simultaneously from the application, you'll run out of memory and connections — a phenomenon called connection exhaustion.

PgBouncer is a connection pooler that stands between the application and PostgreSQL. Thousands of application connections are pooled into dozens of real connections to the server:

PgBouncer architecture
App x5000  -->  PgBouncer  -->  PostgreSQL (50 koneksi)
Simple pgbouncer.ini
[databases]
shop = host=127.0.0.1 port=5432 dbname=shop
 
[pgbouncer]
listen_port = 6432
auth_type = scram-sha-256
pool_mode = transaction
max_client_conn = 2000
default_pool_size = 50

The application now points to port 6432 (PgBouncer) instead of 5432 (PostgreSQL). pool_mode = transaction means a server connection is borrowed only for a single transaction — the best fit for web applications.

Tip

Choose pool_mode = transaction for OLTP applications — each short transaction borrows a connection and returns it immediately, so 50 server connections can serve thousands of transactions. Use session mode only if the application needs long, consistent sessions (e.g. for a per-session current_setting('app.current_tenant') like in episode 16).

Why Pooling Matters?

  • Saves server memory: thousands of client connections become dozens of real connections.
  • Prevents connection exhaustion: PostgreSQL no longer receives a flood of direct connections.
  • Limits application connections: max_client_conn and default_pool_size provide measurable limits.

Common Mistakes

#MistakeSymptomSolution
1Backup without restore testingBackup turns out corrupt only in a crisisScheduled restore testing
2Only logical backup for a big DBSlow restore, high loadCombine with physical + WAL
3Application connecting directly to PostgreSQLConnection exhaustion when traffic spikesPut PgBouncer in front
4Forgetting to archive WALPITR can't rewind far backConfigure WAL archiving properly

Closing

In this episode 19, we've prepared production readiness: logical backup with pg_dump and pg_restore, physical backup and WAL for Point-In-Time Recovery, physical streaming replication and logical replication, and connection pooling with PgBouncer to prevent connection exhaustion.

Key takeaways:

  • pg_dump for per-database logical backups; pg_dumpall for the entire cluster.
  • Physical backup + WAL enables Point-In-Time Recovery — like a VCR that can be rewound.
  • A backup without restore testing isn't a backup — test periodically.
  • Physical replication for identical HA; logical replication for per-table data distribution.
  • PgBouncer turns thousands of client connections into dozens of server connections.

In the final episode, episode 20, we assemble all these skills into one: A Production-Grade E-Commerce Database Case Study — designing a complete schema for users with UUID and RLS, product catalog with JSONB and full-text search, inventory and order processing with locking and audit triggers, and analytics with materialized views.

Learn SQL with PostgreSQL - Backup, Replication & PgBouncer | Learn SQL with PostgreSQL