Agno supports using ClickHouse as a database with the ClickhouseDb class.
ClickhouseDb is traces-only. It implements upsert_trace, create_spans, and the read paths for traces and spans. Sessions, memories, knowledge, evals, and component configs are not stored here. Pair it with a row-store (Postgres, MySQL, MongoDB) for that data.
Why ClickHouse only for traces
ClickHouse is an OLAP columnar engine. It is designed for the workload traces actually produce:
- Append-heavy ingest. Spans arrive continuously. ClickHouse inserts coalesce into large columnar parts.
- Time-bucketed aggregates. Trace dashboards group by minute, hour, day. Columnar storage scans only the columns the query touches.
- Low-cardinality filters. Filtering by
status or span_kind over billions of rows is what LowCardinality(String) and the merge tree index are built for. Filtering by agent_id / session_id rides the merge tree’s primary index.
- Cheap retention.
PARTITION BY toYYYYMM(start_time) lets you drop a month of traces with one ALTER TABLE.
What ClickHouse is not built for:
| Workload | Why it’s a poor fit |
|---|
| Row-level updates | Mutations are asynchronous and heavy. There is no OLTP UPDATE. |
| Multi-row transactions | No transaction boundaries across rows. |
| Single-row reads by primary key | The merge tree is optimized for range scans, not point lookups. |
Session and memory storage hit all three of those patterns, which is why Agno uses a row-store for them and reserves ClickHouse for tracing.
Usage
from agno.agent import Agent
from agno.db.clickhouse import ClickhouseDb
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS
from agno.tools.hackernews import HackerNewsTools
from agno.tracing import setup_tracing
# Row-store for sessions, memories, evals.
primary_db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")
# OLAP store dedicated to traces.
traces_db = ClickhouseDb(
host="localhost",
port=8123,
username="ai",
password="ai",
database="agno_traces",
)
# Batch processing is strongly recommended for ClickHouse.
setup_tracing(
db=traces_db,
batch_processing=True,
max_queue_size=2048,
max_export_batch_size=512,
schedule_delay_millis=5000,
)
agent = Agent(
name="HackerNews Agent",
model=OpenAIResponses(id="gpt-5.2"),
tools=[HackerNewsTools()],
instructions="You are a hacker news agent. Answer questions concisely.",
markdown=True,
db=primary_db,
)
agent_os = AgentOS(
agents=[agent],
db=traces_db,
)
app = agent_os.get_app()
Always enable batch_processing=True with ClickHouse. The default SimpleSpanProcessor issues one insert per span and will hit the server’s parts_to_throw_insert limit under load. ClickHouse strongly prefers a smaller number of larger inserts.
Run ClickHouse
Install docker desktop and run ClickHouse on port 8123 (HTTP) and 9000 (native) using:
docker run -d \
--name clickhouse \
-e CLICKHOUSE_DB=ai \
-e CLICKHOUSE_USER=ai \
-e CLICKHOUSE_PASSWORD=ai \
-p 8123:8123 \
-p 9000:9000 \
clickhouse/clickhouse-server
The command above is the minimum to get running. Traces don’t persist across container restarts. For a persistent local setup with mounted volumes, use the cookbook script cookbook/scripts/run_clickhouse.sh.
ClickHouse Cloud
traces_db = ClickhouseDb(
host="<your-host>.clickhouse.cloud",
port=8443,
username="default",
password="<password>",
database="agno_traces",
secure=True,
)
Params
| Parameter | Type | Default | Description |
|---|
host | str | "localhost" | ClickHouse server host. |
port | int | 8123 | HTTP port. Use 8443 for TLS / ClickHouse Cloud. |
username | str | "default" | ClickHouse username. |
password | str | "" | ClickHouse password. |
database | str | "agno" | ClickHouse database name. Created at startup if missing. |
secure | bool | False | Use HTTPS when True. |
client | Optional[Client] | - | Pre-built clickhouse_connect client. When provided, the connection arguments above are ignored. |
traces_table | Optional[str] | - | Override for the traces table name. |
spans_table | Optional[str] | - | Override for the spans table name. |
versions_table | Optional[str] | - | Override for the schema-versions table name. |
id | Optional[str] | - | Stable identifier for this DB instance. If omitted, derived deterministically from connection params. |
create_schema | bool | True | Create database and tables on startup if missing. |
Developer Resources