Free audit

View Audit Scope

Full-Featured PostgreSQL Proxy

pgPool-II: When You Need Load Balancing, Read/Write Splitting, and HA — Not Just Connection Pooling

Executive Direct Answer · pgPool-II Architecture & Operations

pgPool-II is an open-source, Layer 7 middleware that sits between applications and PostgreSQL clusters. It inspects SQL statements to provide automatic read/write query splitting, connection pooling, and load balancing across replicas. Its Watchdog mechanism delivers high availability with virtual IP failover, while online recovery re-attaches failed standbys without application downtime.

Architecture: L7 Protocol-Aware Middleware·Pooling: Pre-Forked Child Cache·HA: Watchdog Virtual IP (delegate_ip)·Traffic: Automated Read/Write Splitting·Response Targets: Per Agreement

pgPool-II sits between your application and PostgreSQL to provide connection pooling, automatic read/write query routing to standbys, load balancing across replicas, and watchdog high availability — all in a single component. It is the right choice when PgBouncer alone is insufficient.

What pgPool-II Does That PgBouncer Cannot

pgPool-II parses every SQL query to determine if it is a write (routed to primary) or a read (routed to a standby replica). This query-level intelligence is what sets it apart from PgBouncer's transport-level pooling.

Automatic Read/Write Splitting

pgPool-II inspects each SQL statement. SELECT queries are load-balanced across standby replicas. INSERT/UPDATE/DELETE go to the primary. Your application uses a single connection string.

Load Balancing Across Replicas

Round-robin or weight-based load balancing distributes read queries across multiple standbys. Prevents any single replica from becoming a bottleneck in read-heavy workloads.

Watchdog HA for the Proxy

Watchdog runs multiple pgPool-II instances in active/standby mode with a virtual IP. If the active pgPool-II dies, standby takes over the VIP automatically — no SPOF.

Online Recovery

pgPool-II can automatically clone a failed standby back into the cluster using the online recovery feature — executing pg_basebackup in the background and promoting the node when ready.

Connection Pooling

Per-child connection reuse (connection_cache with max_pool cached connections per pre-forked child) reduces the number of PostgreSQL backend processes. Works alongside the load-balancing features simultaneously.

Query Cache (optional)

In-memory query result caching for SELECT queries — results are served from cache until the underlying table is modified. Reduces database load for repetitive read patterns.

pgPool-II Production Failure Modes

Critical proxy, routing, and Watchdog quorum failure scenarios analyzed and remediated by JusDB DBREs to prevent silent write routing to read-only standbys, split-brain VIP binding, and child process starvation.

Critical P1

Silent Standby Write Routing via Missing write_function_list Definitions

Custom PL/pgSQL functions, sequence increments (nextval), or write operations inside stored procedures not declared in write_function_list are misclassified as read-only queries by pgPool-II and routed to read-only standby nodes, triggering transaction abortion with 'cannot execute UPDATE in a read-only transaction'.

JusDB Engineering Mitigation

Perform complete AST schema auditing of all application stored procedures, functions, and ORM triggers, compiling exhaustive write_function_list and read_only_function_list declarations within pgpool.conf.

High P2

Watchdog Quorum Split-Brain and Duplicate VIP Takeover

Transient inter-proxy network latency or misconfigured trusted_servers IP addresses cause partitioned pgPool-II Watchdog nodes to independently elect themselves master, binding duplicate delegate_ip virtual interfaces and dropping client TCP packets.

JusDB Engineering Mitigation

Configure trusted_servers to probe dedicated upstream network gateways rather than database hosts, enforce odd-numbered Watchdog quorum nodes (minimum 3), and integrate STONITH fencing scripts.

Medium P3

Child Process Starvation and Backend Connection Cascades

Surging client connections exceeding num_init_children force new TCP requests into an unbounded backlog queue, causing client connection timeouts while idle child processes hold persistent PostgreSQL backend connections without multiplexing.

JusDB Engineering Mitigation

Calibrate num_init_children against PostgreSQL max_connections, implement aggressive child_life_time recycling, and configure front-end load shedding thresholds to prevent queue saturation.

Production pgPool-II Diagnostic Runbooks

Non-blocking inspection commands executed via the PCP administrative interface and PostgreSQL query port to inspect backend node health, replication lag thresholds, and Watchdog cluster states.

Backend Node Status & Replication Delay
PCP Port 9898 · Live

Inspects live backend node states, replication lag byte delays, connection pool allocations, and primary/standby roles.

# Query backend node status, role, and replication delay via PCP
pcp_node_info -h 127.0.0.1 -p 9898 -U pgpool_admin -w -v 0
pcp_node_info -h 127.0.0.1 -p 9898 -U pgpool_admin -w -v 1

# Inspect active child processes and pooled connection allocation
pcp_pool_status -h 127.0.0.1 -p 9898 -U pgpool_admin -w | grep -E "(total_pools|num_init_children)"

# Inspect live cluster status directly via PostgreSQL query port
psql -h 127.0.0.1 -p 9999 -U postgres -d postgres -c "SHOW POOL_NODES;"
Watchdog Quorum & Virtual IP Introspection
Watchdog Port 9000

Audits Watchdog quorum state, heartbeat ping latencies, delegate IP ownership, and trusted server reachability.

# Query live Watchdog cluster membership and coordinator status
psql -h 127.0.0.1 -p 9999 -U postgres -d postgres -c "SHOW POOL_WATCHDOG;"

# Inspect network interface binding for delegate virtual IP (VIP)
ip addr show to 10.0.1.99/32

# Query Watchdog heartbeat ping latency and quorum health via PCP
pcp_watchdog_info -h 127.0.0.1 -p 9898 -U pgpool_admin -w -v

pgPool-II vs PgBouncer vs HAProxy vs Direct

Evaluate architectural trade-offs between intelligent Layer 7 SQL routing, lightweight transaction pooling, raw TCP streaming proxies, and direct application connections.

Evaluation VectorJusDB Pgpool-II DBREPgBouncer / HAProxyDirect / In-House
Query Inspection & Read/Write SplittingIntelligent SQL parsing routes SELECT to read replicas and INSERT/UPDATE/DELETE to primary without application code changes or dual connection poolsPgBouncer does no SQL parsing; HAProxy requires dual listening ports and application-level routing logic across distinct database endpointsManual dual-datasource configurations in application ORMs prone to replica write exceptions and stale connection pool exhaustion
Connection Pooling & Process MultiplexingPre-forked pool tuning (num_init_children, max_pool) with connection cache reuse, protecting PostgreSQL from backend fork stormsPgBouncer excels at lightweight sub-millisecond transaction pooling but lacks read balancing; HAProxy maintains raw TCP streams with zero poolingUnconstrained client connections spawning hundreds of heavy postgres OS backend processes, causing severe memory bloat and context switching
Watchdog HA & Quorum VIP ManagementMulti-node Watchdog clustering with virtual IP (delegate_ip) failover, trusted server ping health checks, and automated split-brain avoidanceRequires external Keepalived or cloud load balancers with separate health check daemons and uncoordinated failover logicSingle-node proxy instances creating single points of failure, or uncoordinated Watchdog nodes fighting over duplicate virtual IPs
Automated Standby Online RecoveryIntegrated recovery_1st_stage and 2nd_stage commands leveraging pg_basebackup to automatically reattach failed standbys with zero cluster downtimeNo built-in standby recovery; requires separate orchestration tooling (pgBackRest, Patroni, or manual shell scripts)Manual DBA intervention required to rebuild failed replicas, leading to extended periods of reduced read capacity during outages
In-Proxy Query Caching & ThrottlingSelective shared-memory query caching for high-frequency read statements with automatic table-invalidation triggers and delay threshold gatingNeither PgBouncer nor HAProxy supports query caching; requires separate Redis/Memcached layers with app cache invalidation complexityEvery repeated query directly hits PostgreSQL shared buffers, causing cache churn, lock contention, and high CPU usage under spikes
Monitoring & Incident ResponseContinuous pcp command audits, pool node health monitoring, Grafana dashboards, and senior DBREs on your incident bridge. Initial-response targets: 2 to 4 hours in covered business hours; 30 minutes for P1 incidents with 24x7 on-call. Targets, not resolution guarantees.Generic infrastructure tickets with multi-hour turnaround and no deep Pgpool-II protocol-level diagnostic expertiseEngineers struggling to decipher Watchdog consensus logs and write_function_list errors at 2 AM during customer-facing outages

Only need connection pooling? PgBouncer is lighter and faster →

pgpool vs pgbouncer: Performance & Resource Profile

Pure pooling throughput numbers from the same hardware (16 vCPU, 32 GB RAM, PostgreSQL 15) running pgbench at 200 concurrent clients. pgbouncer wins on raw throughput; pgpool-II wins once you factor in features like read/write splitting that would otherwise force the app to implement routing itself.

Dimensionpgpool-IIpgbouncer
TPS (pgbench, pooling only)~38,000~52,000
Per-connection memory~5 MB (pre-forked)~2 KB
Total RAM (200 connections)~1 GB~20 MB
Median connection-acquisition latency~0.8 ms~0.1 ms
Cold-start to ready~5 s (forks num_init_children)<1 s
Read/write split (no app changes)✅ built-in❌ application or external proxy
Proxy HA✅ watchdog + VIP❌ external (HAProxy/keepalived)

Numbers are indicative — your workload (read ratio, query complexity, statement size) will shift these. pgpool-II's TPS overhead comes from SQL parsing per request; pgbouncer simply multiplexes connections.

pgpool-II Configuration Walkthrough

A minimal production pgpool.conf for read/write splitting with watchdog HA — the three settings that matter most arebackend_*,load_balance_mode, anduse_watchdog.

# Backend nodes — primary + 2 read replicas
backend_hostname0 = 'pg-primary.internal'
backend_port0     = 5432
backend_weight0   = 0          # do not load-balance reads to primary
backend_flag0     = 'ALLOW_TO_FAILOVER'

backend_hostname1 = 'pg-replica-1.internal'
backend_port1     = 5432
backend_weight1   = 1
backend_flag1     = 'ALLOW_TO_FAILOVER'

backend_hostname2 = 'pg-replica-2.internal'
backend_port2     = 5432
backend_weight2   = 1
backend_flag2     = 'ALLOW_TO_FAILOVER'

# Pooling
num_init_children   = 100      # pre-forked child processes
max_pool            = 4         # connections per child
connection_cache    = on
child_life_time     = 300

# Read/write splitting
load_balance_mode               = on
master_slave_mode               = on
master_slave_sub_mode           = 'stream'
sr_check_period                 = 10
delay_threshold                 = 1000000   # bytes — fall back to primary if replica lags
write_function_list             = 'nextval,setval,nextval_simple'

# Watchdog HA (active/standby + VIP)
use_watchdog                    = on
wd_hostname                     = 'pgpool-1.internal'
wd_port                         = 9000
delegate_ip                     = '10.0.1.99'
heartbeat_destination0          = 'pgpool-2.internal'
heartbeat_destination_port0     = 9694
trusted_servers                 = '10.0.1.1'

# Health check
health_check_period             = 10
health_check_timeout            = 5
health_check_user               = 'pgpool_health'

Note write_function_list — any stored procedure that writes but is not listed here will be classified as a read and routed to a replica. This is the single most common pgpool-II misconfiguration in production.

Common pgpool-II Misconfigurations (and How We Catch Them)

Writes routed to a standby

A custom function not declared in write_function_list is classified as a read. Symptom: 'cannot execute UPDATE in a read-only transaction'. Audit every PL/pgSQL function during setup.

Watchdog split-brain

Two pgpool-II nodes can both think they own the VIP if trusted_servers isn't reachable. Always pin trusted_servers to a dedicated network device, not the database host.

Excessive num_init_children

Pre-forking 1000 child processes consumes ~5 GB RAM and rarely improves throughput. Size to peak concurrent app connections + 20% headroom.

delay_threshold too high

Default is 0 (disabled). Without a threshold, pgpool-II load-balances reads to replicas regardless of replication lag. Set to ~1 MB for read-after-write consistency.

Sticky session breakage

pgpool-II's connection_cache reuses pre-forked backend connections per child — temp tables and prepared statements may not survive across requests. For apps that rely on session state, keep clients pinned to a child (avoid aggressive child_life_time/child_max_connections recycling) rather than assuming PgBouncer-style transaction pooling.

Health-check user without password

pgpool-II's health_check_user must authenticate via pg_hba.conf trust or explicit password (health_check_password). A missing password causes pgpool to mark all backends as down.

When pgPool-II Beats PgBouncer

Choose pgPool-II when…

  • • Read-heavy workload — you want SELECTs automatically routed to standbys
  • • Multiple read replicas and you want load balancing across them without app changes
  • • You need the proxy itself to be HA (watchdog active/standby)
  • • You want automatic standby recovery via pgPool-II's online recovery
  • • Read query caching is useful for your workload pattern
  • • You prefer a single component managing pooling + routing + HA

Use PgBouncer instead when…

  • • You only need connection pooling — no read/write split required
  • • Serverless or microservices with many short-lived connections
  • • Minimal resource overhead is critical
  • • You use Patroni + HAProxy for HA separately
  • • Application already handles read/write routing at the ORM level

JusDB pgPool-II Implementation

Architecture Design & Sizing

Determine backend node count, num_init_children, max_pool, and connection_life_time based on your workload and PostgreSQL configuration.

Read/Write Split Configuration

Configure load_balance_mode, write_function_list, and backend weights for intelligent query routing to primary vs standbys.

Watchdog HA Setup

Deploy two pgPool-II nodes in watchdog active/standby mode with a virtual IP (delegate_ip), configure heartbeat interfaces, and test automatic VIP failover.

Online Recovery Configuration

Set up recovery_1st_stage_command and recovery_2nd_stage_command scripts so pgPool-II can automatically re-attach and sync failed PostgreSQL standbys.

Monitoring & Metrics

Expose pgPool-II pool status via SHOW POOL_NODES. Integrate with Prometheus using pgpool2_exporter for real-time Grafana dashboards.

Performance Tuning

Tune connection_cache, num_init_children, and child_max_connections. Identify and resolve query-routing misclassification that sends writes to standbys.

FAQ

Yes — this is the most common pgPool-II misconfiguration risk. If a function or stored procedure is not listed in write_function_list, pgPool-II may classify it as a read and route it to a standby. JusDB audits your application's query patterns during setup to prevent this.

HAProxy operates at the TCP level — it routes connections based on health checks without parsing SQL. pgPool-II operates at the SQL level — it reads each query to decide primary vs replica routing. HAProxy is simpler and faster for failover; pgPool-II is needed for automatic read/write splitting without application changes. See our HAProxy for PostgreSQL services →

Yes. pgPool-II can use Patroni's health-check endpoints to determine which node is primary. We configure pgPool-II's health_check and failover_command to query Patroni's /master endpoint so connection routing stays correct during Patroni failovers. Explore our Patroni HA services →

pgPool-II uses a pre-forked process model — it forks num_init_children processes at startup. With 100 child processes, expect ~50–100MB RAM usage. This is significantly more than PgBouncer (~2MB), but acceptable for the features it provides.

It can be deployed in Kubernetes but requires careful configuration for the watchdog VIP in container networking. Most Kubernetes PostgreSQL operators (Zalando, Crunchy) use PgBouncer + Patroni rather than pgPool-II. We recommend pgPool-II primarily for VM/bare-metal deployments.

Need read/write splitting for PostgreSQL?

JusDB designs and deploys pgPool-II with watchdog HA, correct query routing, and full monitoring — avoiding the common misconfiguration pitfalls.