When isolation is worth itIsolation is aimed at large deployments with continuous ingestion. Below roughly 100 TB/month of stored data, a single read-write service normally absorbs both workloads and a second service probably isn’t necessary. Use the sizing model to estimate your compressed volume per month.
Why isolate reads from writes
- Writes stop degrading reads. Continuous OpenTelemetry ingestion - the inserts themselves, plus the background merges that follow them - competes with dashboard and search queries for CPU and memory. Read latency can degrade noticeably while ingestion is running, and recovers once it stops.
- Reads stop disrupting writes. The contention runs both ways: a heavy ad-hoc query or an expensive dashboard render can exhaust memory on the service and fail inserts outright, not merely slow them down.
- Read-only compute is fully dedicated to queries. Read-only services perform no background merges outside of system tables. They also idle without delay, unlike read-write services, which merges can keep awake.
- Each side is sized on its own. The sizing model estimates ingest compute and query compute separately, and a warehouse lets you provision each as its own service. Above the model’s 1 QPS baseline query compute dominates - its worked example at 5 QPS arrives at 58 vCPUs for ingest against 290 for queries - so a small write service can feed a much larger read service.
- Idling and autoscaling are configured per service. Each service has its own replica count, autoscaling and auto-idling settings, so the write service can stay always-on for continuous ingestion while the read service idles outside working hours.
- Storage isn’t duplicated. Services in a warehouse share the same object storage folder and the same tables, and storage is billed only once.
- Access can be restricted per endpoint. IP access lists are applied per service, so the write endpoint can be reachable only from your collectors and the read endpoint only from your ClickStack deployment. See our guide on Network access control.
Architecture
The recommended topology is a warehouse containing one read-write service for ingestion and one read-only service for ClickStack:
Keep in mind when planning the topology:
- The first service in a warehouse is always read-write, and a service’s type is fixed at creation - to switch between read-only and read-write, create a new service in the warehouse.
- All services in a warehouse share the same cloud provider, region, ClickHouse version and Keeper, and the upgrade schedule of the primary service.
- Use one read-write service for ingestion. Merges are assigned across every read-write service sharing the storage, so a merge for an insert on one service can be executed by another. If that other service is also serving heavy queries, those queries compete with the merge for CPU and memory on the service running it - slowing merges for the first service’s inserts, and with them insert performance. Keep query workloads on the read-only service, and only add a second read-write service if you need to separate merges from ingestion.
Setting up an isolated deployment
1
Prepare the read-write service
Use your existing service - or the primary service of a new warehouse - for ingestion, sized for the ingest compute from the sizing model.Create the database and the dedicated ingestion user on this service. Because all services in a warehouse share access controls, users you create here are available on every service in the warehouse:Generate the password with a tool such as
openssl rand -base64 24 and store it in a secret manager rather than in a manifest or shell history. See our guide on Creating an ingestion user for further details.If this service is already part of a warehouse, note that database-level DDL can hang when another service in it is idled - see Administration and DDL.2
Add a read-only service to the warehouse
In the ClickHouse Cloud console, click the plus sign on the service you just prepared to create a second service sharing its data. Select read-only as the service type, and size it for the query compute from the sizing model.For the full walkthrough, see our guide on How to set up a warehouse.
3
Point ingestion at the read-write service
Configure your collector to export to the read-write service endpoint, authenticating as the ingestion user:See the collector configuration options for details, or the equivalent settings for Vector and other ingestion paths.Writes sent to the read-only endpoint are rejected, so the collector must always target the read-write service.
4
Point ClickStack at the read-only service
The ClickStack UI always connects to the ClickHouse service from which it is launched in the ClickHouse Cloud console. To run it on read-only compute:
- Select the read-only service in the ClickHouse Cloud console.
- Select ClickStack from the left navigation menu.
5
Verify the split
Run a search or open a dashboard in ClickStack, then check where the queries landed. An empty result here doesn’t on its own mean the queries went elsewhere: Two things to keep in mind with this query: services that have idled can’t contribute rows, so wake them first if you need complete results, and
system tables are written on the node that ran the query, so a service with more than one replica needs clusterAllReplicas with the default cluster name to cover all of them. On the read-only service, you should see the ClickStack queries:system.query_log is flushed periodically - every 7.5 seconds by default - so a query run immediately after a search may not see it yet. Wait a moment and run it again, or force the flush with SYSTEM FLUSH LOGS if you have the grant.Grouping by user and http_user_agent is what attributes the traffic: it distinguishes the UI from the SQL console and from anything else connecting to the endpoint, whatever tables your sources point at. Filtering on is_initial_query = 1 keeps one row per query as it was submitted - secondary queries from distributed execution, and the internal queries that evaluate materialized views, are logged separately with is_initial_query = 0.On the read-write service, the same query should show inserts from the ingestion user and no ClickStack query traffic.Running the query on each service in turn is the reliable check, because the default cluster contains only the replicas of the service you’re connected to. For an aggregate view across the warehouse, use the all_groups.default cluster name instead:hostName() identifies a replica rather than a service - to attribute activity to a specific service, query that service directly.Separating merges from ingestion
At very high sustained ingest rates, merges - rather than the inserts themselves - become the dominant cost on the ingest service. Because merges are assigned across all read-write services sharing the storage, they can also be pulled onto a service you intended for something else. For these deployments, merges can be moved off the ingest service entirely, giving a three-service topology:Requires a support requestDisabling merges on a read-write service isn’t configurable from the Cloud console. Contact support to apply it to a service.
- Don’t rely on auto-idling for either read-write service. A service with merges disabled still processes the part download and removal events generated by inserts elsewhere in the warehouse, and a high count of unmerged parts can block idling on its own. Plan for both read-write services to be continuously awake.
- Keep queries off both read-write services. Heavy
SELECTqueries on a read-write service compete with merge work for CPU and memory, which is the failure mode this topology exists to avoid. Point ClickStack at the read-only service as described above. - Mutations, where you have them, are tracked on the service that executes them. Mutations are rare in observability - ClickStack’s schema sets
ttl_only_drop_parts = 1, so ordinary retention drops whole expired parts during TTL merges rather than mutating rows away. If you do submit a mutation-producingALTERto the ingest service, it is carried out by the merge service, and its progress appears insystem.mutationsthere rather than on the ingest service.
Administration and DDL
All schema changes must be run against the read-write service, including:- Table creation - performed automatically by the ClickStack collector on first ingest
- Modifying TTL to change retention
- Creating materialized views for query acceleration
- Adding skip indexes, projections and other performance optimizations
Isolating agentic workloads
AI assistants connected through the ClickStack MCP server are read traffic like any dashboard, but their load pattern is different: an agent investigating an incident issues many exploratory queries in quick succession, over ranges nobody chose in advance. Sharing one read-only service between agents and the UI puts that burst in front of the dashboards an engineer is looking at during the same incident. The same warehouse pattern applies - give the agents their own read-only compute:1
Add a second read-only service
Create another read-only service in the warehouse, exactly as in the setup above. It reads the same tables as the service serving the UI, with no data to copy.Then launch ClickStack on it once from the Cloud console, as in pointing ClickStack at a read-only service. Cloud MCP needs a service with ClickStack enabled as well as MCP itself - see the MCP prerequisites.Size it for the query load you expect from agents rather than from the sizing model’s dashboard QPS, and leave auto-idling enabled: agentic use is typically intermittent, so the service can idle between investigations.
2
Enable MCP on that service
Open the read-only service in the ClickHouse Cloud console, click Connect, select Connect with MCP and toggle it on. See enabling the remote MCP server.
3
Point MCP clients at it
The Cloud MCP endpoint is the same for every service - requests are routed by the Any MCP client can carry the header - see targeting a specific service for the equivalent configuration in Cursor, VS Code and others.
x-service-id header, and without it they go to the first ClickStack service used by your account. Copy your existing MCP configuration and add the header with the ID of the new read-only service:Alerts
ClickStack evaluates an alert on the service the alert was created from, so alerts run on the same compute as the UI - the read-only service in this topology.Managed ClickStackTo enable alerts, at least one user with Service Admin permissions must sign in to ClickStack at least once. This provisions the dedicated database user which runs alert queries, and that user is shared across every service in the warehouse. See our guide on Granting access to Managed ClickStack.
Isolating alert evaluation
Alert load can’t be routed centrally, because alerts are created by users: whoever adds an alert in ClickStack adds it to the service they’re working in, and it evaluates on that service’s compute. There’s no setting that moves the alerts of a service elsewhere. What you can isolate is the alerts you own centrally - the ones a platform team maintains for the whole organization, which are usually also the ones evaluating most frequently. Give them their own read-only service in the warehouse, and create them from a ClickStack launched there:
The remaining trade-offs are the ones that follow from state being per service:
- The common alerts, and any dashboards that go with them, exist only on the alerting service and aren’t visible to users working on the query service. Notifications are delivered to the same destinations either way, so what users lose is sight of the definitions, not the alerting itself.
- Sources on the alerting service are separate objects. Those using the default OpenTelemetry schema are auto-detected, but custom sources have to be configured there too before an alert can reference them.