Bound Memory Usage During Manual Slot Sync Retries — May 2026 Summary
Problem & Resolution
The pg_sync_replication_slots() SQL function had an unbounded memory growth problem during its internal retry loop. Unlike the background slotsync worker (which gets natural transaction boundaries between retry cycles), the manual SQL function executes its entire retry loop within a single transaction, causing per-cycle allocations to accumulate indefinitely.
The existing cleanup (list_free_deep(remote_slots)) only freed List cells and top-level RemoteSlot structs, leaving behind strings (slot name, plugin name, database name), quote_literal_cstr() results, TextDatumGetCString() results, standalone TupleTableSlot objects, and list containers from get_local_synced_slots().
Measurements
With 100 failover logical slots:
- HEAD: ~192 KiB growth over a short test (~few MB/hour extrapolated)
- Manual string freeing: ~64 KiB growth
- Per-retry memory context: 0 growth (completely flat)
Committed Solution
A per-retry memory context (cycle_ctx) that wraps each retry iteration and is reset before each new cycle, deterministically reclaiming all per-cycle allocations. The v2 patch was committed by Amit Kapila.
Key design points of the final patch:
- Per-retry memory context over individual
pfree()calls — future-proof and self-documenting - Slot names needed across retries are explicitly copied into the parent context before cycle context reset
- Explicit
ExecDropSingleTupleTableSlot()for semantic clarity at ownership boundaries - No
list_free()indrop_local_obsolete_slots()— removed in v2 per community consensus to rely on memory context management rather than scattered retail freeing in helpers
Design Discussions Resolved
- Error path cleanup: The new memory context does NOT need explicit deletion in
slotsync_failure_callback()— it's a child of the transaction's memory context and is destroyed automatically on transaction abort. - Explicit freeing philosophy: Community consensus reaffirmed that memory context lifetime management is preferred over scattered
pfree()/list_free()calls in helper functions when a proper context boundary exists above. - Practical impact: Detailed benchmarking with
pg_log_backend_memory_contexts()confirmed growth is real but modest under backoff, and entirely eliminated by the memory context approach.