This sample demonstrates how to configure DatabaseSessionService to use PostgreSQL for persisting sessions, events, and state.
ADK's DatabaseSessionService supports multiple database backends through SQLAlchemy. This guide shows how to:
- Set up PostgreSQL as the session storage backend
- Configure async connections with
asyncpg - Understand the auto-generated schema
- Run the sample agent with persistent sessions
- PostgreSQL Database: A running PostgreSQL instance (local or cloud)
- asyncpg: Async PostgreSQL driver for Python
Install the required Python packages:
pip install google-adk asyncpg greenletDatabaseSessionService automatically creates the following tables on first use:
| Column | Type | Description |
|---|---|---|
| app_name | VARCHAR(128) | Application identifier (PK) |
| user_id | VARCHAR(128) | User identifier (PK) |
| id | VARCHAR(128) | Session UUID (PK) |
| state | JSON | Session state as JSON |
| create_time | TIMESTAMP | Creation timestamp |
| update_time | TIMESTAMP | Last update timestamp |
| Column | Type | Description |
|---|---|---|
| id | VARCHAR(128) | Event UUID (PK) |
| app_name | VARCHAR(128) | Application identifier (PK) |
| user_id | VARCHAR(128) | User identifier (PK) |
| session_id | VARCHAR(128) | Session reference (PK, FK) |
| invocation_id | VARCHAR(256) | Invocation identifier |
| timestamp | TIMESTAMP | Event timestamp |
| event_data | JSON | Event content as JSON |
| Column | Type | Description |
|---|---|---|
| app_name | VARCHAR(128) | Application identifier (PK) |
| state | JSON | Application-level state |
| update_time | TIMESTAMP | Last update timestamp |
| Column | Type | Description |
|---|---|---|
| app_name | VARCHAR(128) | Application identifier (PK) |
| user_id | VARCHAR(128) | User identifier (PK) |
| state | JSON | User-level state |
| update_time | TIMESTAMP | Last update timestamp |
| Column | Type | Description |
|---|---|---|
| key | VARCHAR(128) | Metadata key |
| value | VARCHAR(256) | Metadata value |
postgresql+asyncpg://username:password@host:port/database
from google.adk.sessions.database_session_service import DatabaseSessionService
from google.adk.runners import Runner
# Initialize with PostgreSQL URL
session_service = DatabaseSessionService(
"postgresql+asyncpg://postgres:postgres@localhost:5432/adk_sessions"
)
# Use with Runner
runner = Runner(
app_name="my_app",
agent=my_agent,
session_service=session_service,
)Pass additional SQLAlchemy engine options:
session_service = DatabaseSessionService(
"postgresql+asyncpg://postgres:postgres@localhost:5432/adk_sessions",
pool_size=10,
max_overflow=20,
pool_timeout=30,
pool_recycle=1800,
)Using Docker:
docker compose up -dOr use an existing PostgreSQL instance.
Create a .env file:
POSTGRES_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/adk_sessions
GOOGLE_CLOUD_PROJECT=<your-gcp-project-id>
GOOGLE_CLOUD_LOCATION=us-central1
GOOGLE_GENAI_USE_ENTERPRISE=trueOr run export command.
export POSTGRES_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/adk_sessions
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
export GOOGLE_CLOUD_LOCATION=us-central1
export GOOGLE_GENAI_USE_ENTERPRISE=truepython main.pyOr use the ADK:
adk run .Sessions and events are persisted across application restarts:
# First run - creates a new session
session = await session_service.create_session(
app_name="my_app",
user_id="user1",
session_id="persistent-session-123",
)
# Later run - retrieves the existing session
session = await session_service.get_session(
app_name="my_app",
user_id="user1",
session_id="persistent-session-123",
)PostgreSQL's JSON type stores state data as written:
- Session state: Stored in
sessions.state - User state: Stored in
user_states.state - App state: Stored in
app_states.state
Tables created by older ADK versions keep their jsonb columns, which still
reject NUL (\u0000) in strings. To switch an existing database to json:
ALTER TABLE sessions ALTER COLUMN state TYPE json USING state::json;
ALTER TABLE events ALTER COLUMN event_data TYPE json USING event_data::json;
ALTER TABLE app_states ALTER COLUMN state TYPE json USING state::json;
ALTER TABLE user_states ALTER COLUMN state TYPE json USING state::json;Each statement rewrites the table under an ACCESS EXCLUSIVE lock, blocking
reads and writes until it finishes, so run it during a maintenance window.
Values already stored keep their jsonb-normalized form.
- Connection Pooling: Use
pool_sizeandmax_overflowfor high-traffic applications - SSL/TLS: Always use encrypted connections in production
- Backups: Implement regular backup strategies for session data
- Indexing: The default schema includes primary key indexes; add additional indexes based on query patterns
- Monitoring: Monitor connection pool usage and query performance