Persisting Chat History in Redis and PostgreSQL
Store conversation history externally using RedisChatMessageHistory and PostgresChatMessageHistory so memory survives application restarts and scales across multiple instances.
Persisting Chat History in Redis and PostgreSQL is a free AI Engineering Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Engineering Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Persist Chat History Externally?
In-memory conversation history vanishes when your application restarts or scales to multiple instances. External persistence solves three problems: history survives crashes, multiple server instances share the same history, and you can load past sessions for returning users. Redis and PostgreSQL are the two most popular backends for this.
LangChain Chat Message History Interface
LangChain defines a BaseChatMessageHistory interface with two methods: add_message(message) and messages (property). Any storage backend that implements this interface can drop in as memory for any LangChain chain. Both Redis and PostgreSQL implementations follow this interface.
from langchain_community.chat_message_histories import RedisChatMessageHistory
# Each unique session_id gets its own history namespace
history = RedisChatMessageHistory(
session_id='user:alice:session:42',
url='redis://localhost:6379'
)
# Add messages
history.add_user_message('What is RAG?')
history.add_ai_message('RAG stands for Retrieval-Augmented Generation...')
# Retrieve stored messages
for msg in history.messages:
print(msg.type, ':', msg.content[:50])Connecting Redis History to a Chain
Wrap your chain with RunnableWithMessageHistory and provide a factory function that returns the right history object for each session. LangChain automatically loads history before each call and saves new messages after each response.
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
llm = ChatOpenAI(model='gpt-4o-mini')
prompt = ChatPromptTemplate.from_messages([
('system', 'You are a helpful assistant.'),
MessagesPlaceholder(variable_name='history'),
('human', '{input}')
])
chain = prompt | llm
chain_with_history = RunnableWithMessageHistory(
chain,
lambda session_id: RedisChatMessageHistory(
session_id=session_id,
url='redis://localhost:6379'
),
input_messages_key='input',
history_messages_key='history'
)Invoking the Chain with a Session ID
When you invoke a chain wrapped with message history, pass the session ID in the config dictionary under configurable. LangChain routes each call to the correct history store automatically, so Alice and Bob have completely separate conversation histories.
# First turn for session alice-001
response = chain_with_history.invoke(
{'input': 'My name is Alice.'},
config={'configurable': {'session_id': 'alice-001'}}
)
print(response.content)
# Second turn — the chain remembers Alice's name from Redis
response = chain_with_history.invoke(
{'input': 'What is my name?'},
config={'configurable': {'session_id': 'alice-001'}}
)
print(response.content) # Should say 'Your name is Alice.'Redis Key Design for Chat History
Redis stores each session's messages as a list under a key derived from the session ID. Use a structured key schema like chat:<app>:<user_id>:<session_id> to avoid key collisions between applications and to enable efficient scanning by user. Set a TTL on old session keys to prevent unbounded storage growth.
import redis
r = redis.Redis.from_url('redis://localhost:6379')
# List all chat sessions for user alice
keys = r.keys('message_store:alice-*')
print('Active sessions:', len(keys))
# Set a 30-day TTL on a session to auto-expire old history
for key in keys:
r.expire(key, 60 * 60 * 24 * 30)PostgreSQL Chat History Table Design
For SQL-based persistence, create a table with columns for session ID, message role, content, and timestamp. PostgreSQL is ideal when your chat history needs to be queried, audited, or joined with user account data. The langchain_postgres package provides a ready-made implementation.
-- SQL table for storing chat messages
CREATE TABLE chat_messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL, -- 'human' or 'ai'
content TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_chat_session ON chat_messages (session_id, created_at);PostgresChatMessageHistory in LangChain
The PostgresChatMessageHistory class from langchain_postgres works just like the Redis version but stores messages in your PostgreSQL database. Pass a connection string and session ID, and it handles table creation and message serialization automatically.
from langchain_postgres import PostgresChatMessageHistory
import psycopg
CONNECTION_STRING = 'postgresql://user:pass@localhost:5432/mydb'
# Create history object for a session
history = PostgresChatMessageHistory(
table_name='chat_messages',
session_id='alice-001',
connection=psycopg.connect(CONNECTION_STRING)
)
history.add_user_message('Tell me about embeddings.')
history.add_ai_message('Embeddings are dense vector representations...')
print(f'Stored {len(history.messages)} messages')Loading Previous Sessions for Returning Users
When a user returns to your app after closing it, you can restore their last session by loading the history from Redis or PostgreSQL. Present the user with a summary of where they left off, or seamlessly continue by injecting the stored history into the next prompt.
def get_or_create_history(user_id: str, session_id: str):
history = RedisChatMessageHistory(
session_id=f'{user_id}:{session_id}',
url='redis://localhost:6379'
)
if history.messages:
print(f'Resuming session with {len(history.messages)} messages.')
else:
print('Starting a new session.')
return historyTrimming History in the Database
Long-running sessions can accumulate thousands of messages. Implement a trim strategy that deletes messages older than a cutoff or keeps only the last N messages in the database. This keeps retrieval fast and prevents context windows from overflowing when you load history.
# Keep only the last 20 messages per session in PostgreSQL
CLEAN_SQL = '''
DELETE FROM chat_messages
WHERE session_id = %s
AND id NOT IN (
SELECT id FROM chat_messages
WHERE session_id = %s
ORDER BY created_at DESC
LIMIT 20
)
'''
def trim_session(conn, session_id: str, keep_last: int = 20):
with conn.cursor() as cur:
cur.execute(CLEAN_SQL, (session_id, session_id))
conn.commit()Choosing Between Redis and PostgreSQL
Both backends are production-ready but have different strengths. Redis offers sub-millisecond reads, making it ideal for real-time chat where latency matters most. PostgreSQL offers full SQL queryability, ACID transactions, and easy integration with your existing user database for analytics and auditing. Many production systems use Redis as the primary cache and PostgreSQL for durable archival.
Security: Isolating Multi-Tenant Histories
In multi-tenant applications, you must ensure users cannot access each other's histories. Always include the user ID in the session key, validate that the session ID belongs to the authenticated user before loading history, and never expose raw session keys in client-side URLs or cookies.
- Good key:
chat:app1:user:42:session:abc - Bad key:
session:abc(guessable, no user scope)
Quick Check
Test your understanding of persisting chat history externally.
Lesson Recap
In this lesson you learned: RedisChatMessageHistory stores chat history in Redis with per-session keys, PostgresChatMessageHistory enables SQL-queryable durable storage, and RunnableWithMessageHistory wires any backend to your chain via a session_id factory. Next up we dive into the ReAct framework for building reasoning and acting agents.
Frequently asked questions
Is the “Persisting Chat History in Redis and PostgreSQL” lesson free?
Yes — the full text of “Persisting Chat History in Redis and PostgreSQL” is free to read here on the web, and the AI Engineering Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Engineering Academy course, upgrade to CoddyKit PRO.
What will I learn in “Persisting Chat History in Redis and PostgreSQL”?
Store conversation history externally using RedisChatMessageHistory and PostgresChatMessageHistory so memory survives application restarts and scales across multiple instances. You practise AI Engineering Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Engineering Academy?
No prior experience is required. AI Engineering Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Persisting Chat History in Redis and PostgreSQL” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Engineering Academy lesson?
Yes. Every AI Engineering Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Why Stateless LLMs Need External Memory
- Buffer and Window Memory
- Summary Memory and Token-Aware Truncation
- Persisting Chat History in Redis and PostgreSQL