← Back to list

Solving the Silent Postgres Disconnect Problem in Agentic Systems

How I Fixed Idle Connection Failures in Production

Samir Patil · 2025-11-15 17:58 · 0 claps · 3.1 min read
#postgresql #langchain #langgraph
Open on Medium ↗
Wiki topics: AGT · AI Agents

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

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:

  1. Catches the failure
  2. Reconnects
  3. 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