Solving the Silent Postgres Disconnect Problem in Agentic Systems
How I Fixed Idle Connection Failures in Production
Solving the Silent Postgres Disconnect Problem in Agentic Systems
How I Fixed Idle Connection Failures in Production
Long-running agent workflows are one of LangGraph’s biggest strengths. But they also expose a subtle production issue: PostgreSQL quietly kills idle connections, and your persistence layer isn’t always ready for it.
Photo by Kerin Gedge on Unsplash
If your app holds a connection for too long and Postgres restarts, times it out, or drops it, you won’t find out until the next query suddenly fails. For agent systems that depend on continuous checkpointing, that failure can stall runs or even require a server restart.
I ran into this exact issue. Here’s what was happening and the solution that made my persistence layer production-proof.
The Problem: Idle Connections Die in Production
In development, your database rarely disconnects. But in real deployments, several things can kill idle sessions:
- Postgres maintenance restarts
- idle_session_timeout or PgBouncer timeouts
- Short network blips or failovers
When your persistence layer tries to reuse one of these dead connections, the driver throws an error like:
connection not open
server closed the connection unexpectedly
Without explicit recovery, that error bubbles up and breaks checkpointing logic. Some setups even end up needing a full restart to reset connections.
That’s unacceptable for agentic systems running for hours — or days.
What Reliability Should Look Like
A resilient persistence layer should:
- Detect retry-able connection errors
- Reconnect automatically
- Retry the failed operation
- Do all of this transparently, without changing the rest of your code
Surprisingly, this behavior isn’t built into most savers by default.
The Fix:
ResilientPostgresSaver
To solve this, I built a wrapper: ResilientPostgresSaver.
It sits around the existing Postgres saver and adds reconnection + retry logic in a clean, centralized way.
Key Features
1. Automatic Connection Error Detection
The saver intercepts errors associated with terminated or invalid connections. Instead of failing fast, it attempts controlled recovery.
2. Retry Logic with Backoff
You can set:
- max_retries
- retry_delay
This makes the retry flow predictable and production-safe.
3. Works With Connection Pools
Instead of destroying the pool, it simply acquires a fresh connection.
This avoids double-closing and keeps pool state healthy.
4. Drop-In Replacement
You don’t have to touch anything else in your LangGraph code.
5. Survives Forced Termination (pg_backend_terminate)
Even if a backend connection is explicitly terminated (for example, using pg_backend_terminate), the saver detects the failure, acquires a fresh connection, and retries, so workflows keep running uninterrupted.
Usage Example
conn_pool = get_connection_pool(database_url)
checkpointer = ResilientPostgresSaver(
conn_pool,
max_retries=3,
retry_delay=2.0
)
checkpointer.setup()
If the connection was idle and Postgres restarts, the saver:
- Catches the failure
- Reconnects
- Retries the operation
Your agent continues running without interruptions.
Impact
After integrating this, I saw immediate improvements:
- Zero crashes during brief database restarts
- Stable agent runs even with long idle periods
- No more manual process restarts after connection resets
It turned out to be one of those small architectural upgrades that significantly improves reliability.
Final Thoughts
Postgres is reliable, but production environments aren’t perfect. Idle connections can and will be dropped. If you’re running LangGraph with Postgres, your persistence layer needs to handle that reality.
ResilientPostgresSaver ensures that transient database hiccups don’t break your workflows.
I have used this in my agentic repo template : https://github.com/samirpatil2000/agentic-template/blob/main/agents/resilient_postgres_saver.py
Code Snippet
import time
from typing import Optional
import logging
from langgraph.checkpoint.postgres import Conn, PostgresSaver
from langgraph.checkpoint.serde.base import SerializerProtocol
from psycopg import Connection, Pipeline
from psycopg.errors import OperationalError
from psycopg.rows import dict_row
from psycopg_pool import ConnectionPool
# Configure logger
logger = logging.getLogger(__name__)
class ResilientPostgresSaver(PostgresSaver):
def __init__(
self,
conn: Conn,
pipe: Optional[Pipeline] = None,
serde: Optional[SerializerProtocol] = None,
max_retries: int = 3,
retry_delay: float = 2.0, # seconds
) -> None:
super().__init__(conn, pipe, serde)
self.max_retries = max_retries
self.retry_delay = retry_delay
def _execute_with_retries(self, query_func, *args, **kwargs):
retries = 0
while retries < self.max_retries:
try:
return query_func(*args, **kwargs)
except (OperationalError, ConnectionError) as e:
logging.error(
f"Database operation failed: {e}, retrying...", exc_info=True
)
retries += 1
if retries >= self.max_retries:
raise
time.sleep(self.retry_delay)
self._reconnect()
def _reconnect(self):
try:
self.conn.close()
except Exception as e:
logging.exception(f"Error closing connection {e}", exc_info=True)
if isinstance(self.conn, ConnectionPool):
logging.info(f"Reinitializing connection pool for {self.conn.conninfo}")
try:
self.conn = get_connection_pool(self.conn.conninfo)
except Exception as e:
logging.exception(
f"Error reinitializing connection pool {e}", exc_info=True
)
else:
try:
self.conn = Connection.connect(
self.conn.conninfo,
autocommit=True,
prepare_threshold=0,
row_factory=dict_row,
)
except Exception as e:
logging.exception(f"Error reconnecting {e}", exc_info=True)
def setup(self) -> None:
self._execute_with_retries(super().setup)
def list(self, *args, **kwargs):
return self._execute_with_retries(super().list, *args, **kwargs)
def get_tuple(self, *args, **kwargs):
return self._execute_with_retries(super().get_tuple, *args, **kwargs)
def put(self, *args, **kwargs):
return self._execute_with_retries(super().put, *args, **kwargs)
def put_writes(self, *args, **kwargs):
return self._execute_with_retries(super().put_writes, *args, **kwargs)
def get_connection_pool(db_url: str):
connection_kwargs = {
"autocommit": True,
"prepare_threshold": 0,
"application_name": "cosmos",
}
conn_pool = ConnectionPool(
conninfo=db_url,
kwargs=connection_kwargs,
min_size=1,
max_size=4,
max_idle=60 * 2,
)
return conn_pool 메타데이터
- post_id
- 33dc85b3f4b1
- slug
- production-proof-persistence-auto-reconnect-postgres-for-langgraph-33dc85b3f4b1
- url
- https://medium.com/@samir00/production-proof-persistence-auto-reconnect-postgres-for-langgraph-33dc85b3f4b1
- canonical_url
- https://medium.com/@samir00/production-proof-persistence-auto-reconnect-postgres-for-langgraph-33dc85b3f4b1
- author_url
- https://medium.com/@samir00
- status
- ok
- fetched_at
- 2026-06-15 20:49:13