ChistaDATA · Data Reliability Engineering for ClickHouse

Data SRE for ClickHouse: 7 Proven Reliability Practices We Run in Production

Data SRE is the discipline of running a database the way a site reliability team runs a service: with service level objectives, error budgets, alerting that pages only when users are affected, rehearsed recovery, and postmortems that change the system. ChistaDATA delivers Data SRE for ClickHouse estates as an engineering practice, not a monitoring subscription.

This page sets out the seven practices we apply, with the SQL, the alert rules and the runbook structure behind each one, so your team can judge the method before any conversation with us.

15 minSeverity 1 response under the enterprise SLA
24×7×365On-call ClickHouse engineering coverage
7Reliability practices, each with a written artefact
100%Open-source ClickHouse, zero vendor lock-in

Definition

What Data SRE means for a ClickHouse estate

Most ClickHouse clusters are monitored. Far fewer are operated against an explicit reliability target. The difference shows up during an incident: a monitored cluster produces forty alerts and no decision, while a cluster run under Data SRE produces one page that says which objective is at risk, how fast the error budget is burning, and which runbook applies.

Data SRE borrows its vocabulary from site reliability engineering and adapts it to stateful systems. A stateless service recovers by restarting. A database has to recover without losing acknowledged writes, without serving stale replicas as if they were current, and without a restore that has never been rehearsed. That is why the practice puts as much weight on replication state, ClickHouse Keeper quorum health and restore drills as it does on query latency.

The terms overlap, so it helps to fix them. Data reliability engineering is the broader discipline, ClickHouse SRE is what it looks like on one engine, and Data SRE is the name we use for the practice as a whole.

We treat three properties as the core of reliability for analytical databases:

  • Availability: the share of valid queries and inserts that succeed.
  • Latency: the share of queries that complete inside the threshold the product actually needs, measured at p95 and p99 rather than the average.
  • Freshness and durability: how far replicas and downstream materialized views lag behind ingestion, and whether a backup can be restored inside the recovery time objective.

Release coverage: ChistaDATA delivers consulting, 24×7 support, managed services and remote DBA on ClickHouse 26.8 LTS (current long-term-support release, August 2026, supported to August 2027) and 26.3 LTS (supported to March 2027), plus every supported stable release between them, with upgrade engineering for 24.x and 25.x estates. Last verified September 2026.

Method

The Data SRE operating loop

Every engagement runs the same loop. Indicators are computed from ClickHouse telemetry, objectives are agreed per workload tier, the error budget is derived from the objective, and alerting is tied to the rate at which the budget is being spent. When an alert fires it lands on a runbook, the incident is handled under a severity matrix, and the postmortem feeds a reliability backlog that changes the indicators on the next pass.

Data SRE operating loop for ClickHouse: service level indicators, objectives, error budget, burn-rate alerting, runbooks, incident response, postmortems and the reliability backlog
Figure 1. The Data SRE operating loop. The error budget policy at the bottom is what turns measurement into a decision about what ships next.

The loop matters more than any single tool in it. Teams that adopt dashboards without an error budget policy keep arguing about whether reliability work or feature work comes first. The policy settles that argument in advance, in writing, with the product owner in the room.

Practice 1

Compute service level indicators from system.query_log

ClickHouse already records what a Data SRE practice needs. system.query_log holds one row per query event with duration, exception code, memory and the initiating user. We compute availability and latency indicators directly from it, across every replica, rather than inferring them from host metrics.

-- Availability and latency SLIs per hour, last 24 hours, whole cluster.
-- Replace cluster_name with the name from system.clusters.
SELECT
    toStartOfHour(event_time) AS hour,
    countIf(type = 'QueryFinish') AS ok_queries,
    countIf(type = 'ExceptionWhileProcessing') AS failed_queries,
    round(ok_queries / (ok_queries + failed_queries), 5) AS availability_sli,
    quantileIf(0.95)(query_duration_ms, type = 'QueryFinish') AS p95_ms,
    quantileIf(0.99)(query_duration_ms, type = 'QueryFinish') AS p99_ms,
    round(countIf(type = 'QueryFinish' AND query_duration_ms <= 1000)
          / countIf(type = 'QueryFinish'), 5) AS latency_sli_1s
FROM clusterAllReplicas('cluster_name', system.query_log)
WHERE event_time >= now() - INTERVAL 24 HOUR
  AND is_initial_query = 1
  AND query_kind = 'Select'
  AND type IN ('QueryFinish', 'ExceptionWhileProcessing')
GROUP BY hour
ORDER BY hour;

Two details decide whether this indicator is trustworthy. First, is_initial_query = 1 keeps distributed sub-queries from being counted as separate user requests. Second, not every exception is the database’s fault. A syntax error or an unknown table is a client defect, so we classify exception codes into “counts against the budget” and “does not” before the number is shown to anyone.

-- Which errors are actually spending the budget?
SELECT
    exception_code,
    errorCodeToName(exception_code) AS error_name,
    count() AS occurrences,
    uniqExact(user) AS affected_users,
    any(substring(exception, 1, 120)) AS sample
FROM clusterAllReplicas('cluster_name', system.query_log)
WHERE event_time >= now() - INTERVAL 24 HOUR
  AND type = 'ExceptionWhileProcessing'
  AND is_initial_query = 1
GROUP BY exception_code
ORDER BY occurrences DESC
LIMIT 20;

Codes such as MEMORY_LIMIT_EXCEEDED, TOO_MANY_SIMULTANEOUS_QUERIES, TIMEOUT_EXCEEDED and TABLE_IS_READ_ONLY are reliability signals. SYNTAX_ERROR and UNKNOWN_TABLE usually are not. Check the retention of system.query_log before relying on it: the table has a TTL and is flushed on an interval, so a long-window SLO needs the log copied into a retained telemetry table.

Practice 2

Set Data SRE objectives per workload tier and derive the error budget

One objective for the whole cluster is almost always wrong. A customer-facing dashboard API and a nightly backfill share hardware but not expectations. We tier workloads by user, by log_comment, or by a settings profile, and agree an objective for each tier with the person who owns the product outcome.

Workload tierIndicatorIllustrative objectiveBudget over 30 days
Customer-facing APIValid queries that succeed99.9%0.1% of requests, about 43 minutes of full outage
Customer-facing APIQueries completing within 1 second99%1% of requests may be slower
Internal BIValid queries that succeed99.5%0.5% of requests
IngestionReplica delay under 60 seconds99%1% of measured intervals
RecoveryRestore drill inside the RTOEvery drillNone: a failed drill is an incident
The objectives above are illustrative starting points, not recommendations for your estate. The right number is the lowest one the business can accept, because every additional nine is paid for in hardware, redundancy and engineering time.

The error budget is simply one minus the objective, applied to the request volume in the window. It is the most useful number in Data SRE because it converts reliability from an opinion into a quantity that can be spent. A schema migration that risks a short read-only period is acceptable when the budget is healthy and deferred when it is not.

-- Error budget consumed so far in the current 30-day window (99.9% objective).
WITH 0.999 AS slo
SELECT
    countIf(type = 'QueryFinish') AS ok_queries,
    countIf(type = 'ExceptionWhileProcessing'
            AND exception_code NOT IN (62, 60, 47)) AS budget_errors,
    ok_queries + budget_errors AS valid_requests,
    round((1 - slo) * valid_requests) AS budget_total,
    round(100 * budget_errors / greatest(budget_total, 1), 1) AS budget_consumed_pct
FROM clusterAllReplicas('cluster_name', telemetry.query_log_retained)
WHERE event_time >= now() - INTERVAL 30 DAY
  AND is_initial_query = 1
  AND user = 'api_reader';
-- 62 = SYNTAX_ERROR, 60 = UNKNOWN_TABLE, 47 = UNKNOWN_IDENTIFIER (client defects)

Practice 3

Alert on burn rate, not on thresholds

Threshold alerts (“CPU above 80%”) page people for conditions users never notice and stay silent during slow degradations. Burn-rate alerting asks a better question: at the current error rate, how quickly will the budget run out? We use the multi-window, multi-burn-rate pattern described in the Google SRE Workbook chapter on alerting on SLOs, fed by the Prometheus endpoint that ClickHouse exposes natively.

Data SRE burn-rate alerting: fast and slow burn windows against a 30-day ClickHouse error budget
Figure 2. Burn rate is how many times faster than planned the budget is being spent. Fast burn pages, slow burn opens a ticket.
<!-- /etc/clickhouse-server/config.d/prometheus.xml
     New listener: requires a server restart. Restrict port 9363 to the
     monitoring network before enabling. -->
<clickhouse>
    <prometheus>
        <endpoint>/metrics</endpoint>
        <port>9363</port>
        <metrics>true</metrics>
        <events>true</events>
        <asynchronous_metrics>true</asynchronous_metrics>
    </prometheus>
</clickhouse>
# prometheus/rules/clickhouse-slo.yml  (99.9% availability objective)
groups:
  - name: clickhouse-slo-burn
    rules:
      - record: clickhouse:query_error_ratio:rate5m
        expr: |
          sum(rate(ClickHouseProfileEvents_FailedQuery[5m]))
          / sum(rate(ClickHouseProfileEvents_Query[5m]))
      - record: clickhouse:query_error_ratio:rate1h
        expr: |
          sum(rate(ClickHouseProfileEvents_FailedQuery[1h]))
          / sum(rate(ClickHouseProfileEvents_Query[1h]))
      - record: clickhouse:query_error_ratio:rate6h
        expr: |
          sum(rate(ClickHouseProfileEvents_FailedQuery[6h]))
          / sum(rate(ClickHouseProfileEvents_Query[6h]))

      # Fast burn: 2% of a 30-day budget gone in one hour. Page.
      - alert: ClickHouseErrorBudgetFastBurn
        expr: |
          clickhouse:query_error_ratio:rate1h > (14.4 * 0.001)
          and clickhouse:query_error_ratio:rate5m > (14.4 * 0.001)
        for: 2m
        labels: {severity: page}
        annotations:
          summary: "ClickHouse availability budget burning at 14.4x"
          runbook: "https://runbooks.example.internal/clickhouse/error-budget-fast-burn"

      # Slow burn: 5% of the budget gone in six hours. Ticket.
      - alert: ClickHouseErrorBudgetSlowBurn
        expr: |
          clickhouse:query_error_ratio:rate6h > (6 * 0.001)
          and clickhouse:query_error_ratio:rate1h > (6 * 0.001)
        for: 15m
        labels: {severity: ticket}

ClickHouseProfileEvents_FailedQuery counts every failed query, including client mistakes, so the Prometheus rule is the fast detector and the classified system.query_log figure from Practice 2 remains the number of record. Every paging alert carries a runbook link. An alert without a runbook is removed or demoted during the first month of an engagement. A Data SRE rotation should be paged rarely, and always for a reason a user would recognise.

ClickHouse observability stack for Data SRE: system tables and the Prometheus endpoint feed collectors, SLO rules and Alertmanager, which route to dashboards, on-call paging, runbooks and postmortems
Figure 3. The open-source observability path we deploy for Data SRE, from ClickHouse system tables to a page that reaches an engineer.

Practice 4

Watch replication and ClickHouse Keeper as first-class signals

A ReplicatedMergeTree table can look healthy from the query side while one replica is read-only or an hour behind. In Data SRE the replication state is an indicator in its own right, because it decides whether a failover is safe and whether readers are seeing current data.

-- Replicas that need attention right now.
SELECT
    hostName() AS host,
    database,
    table,
    is_readonly,
    is_session_expired,
    absolute_delay,
    queue_size,
    inserts_in_queue,
    merges_in_queue,
    log_max_index - log_pointer AS log_entries_behind,
    active_replicas,
    total_replicas
FROM clusterAllReplicas('cluster_name', system.replicas)
WHERE is_readonly
   OR is_session_expired
   OR absolute_delay > 60
   OR queue_size > 100
   OR active_replicas < total_replicas
ORDER BY absolute_delay DESC;

When the queue is not draining, system.replication_queue says why. The columns that matter are num_tries, last_exception and postpone_reason.

SELECT
    database,
    table,
    type,
    create_time,
    num_tries,
    substring(last_exception, 1, 160) AS last_exception,
    postpone_reason
FROM system.replication_queue
WHERE num_tries > 10
ORDER BY create_time
LIMIT 50;

ClickHouse Keeper (or ZooKeeper on older estates) sits underneath all of this. A slow or partitioned quorum turns into read-only replicas within seconds. We scrape the four-letter-word interface and alert on latency, outstanding requests and follower sync.

# Keeper health, run against every quorum member (default client port 9181).
echo mntr | nc keeper-1.internal 9181 | egrep \
  'zk_server_state|zk_avg_latency|zk_max_latency|zk_outstanding_requests|zk_synced_followers|zk_znode_count'

# From ClickHouse: which Keeper node is this server connected to?
clickhouse-client --query "
SELECT name, host, port, connected_time, is_expired
FROM system.zookeeper_connection"

The thresholds we start from are conservative: page when any replica is read-only for more than two minutes, when zk_outstanding_requests stays above zero for a sustained period, or when the number of synced followers drops below quorum minus one. They are tuned per estate during the first weeks, from measured baselines rather than copied defaults.

Practice 5

Keep merge and mutation pressure inside safe limits

The most common ClickHouse outage we are called into is not a crash. It is ingestion rejecting inserts with TOO_MANY_PARTS because parts are being created faster than merges can consolidate them. The cause is nearly always upstream: inserts that are too small, a partition key that is too fine, or a mutation that has monopolised the merge pool. Data SRE catches it while it is still a trend.

-- Active parts per partition against the server's own limits.
WITH
    (SELECT toUInt64(value) FROM system.merge_tree_settings
      WHERE name = 'parts_to_delay_insert') AS delay_limit,
    (SELECT toUInt64(value) FROM system.merge_tree_settings
      WHERE name = 'parts_to_throw_insert') AS throw_limit
SELECT
    database,
    table,
    partition,
    count() AS active_parts,
    delay_limit,
    throw_limit,
    round(100 * active_parts / throw_limit, 1) AS pct_of_throw_limit,
    formatReadableSize(sum(bytes_on_disk)) AS size_on_disk
FROM system.parts
WHERE active
GROUP BY database, table, partition
HAVING active_parts > delay_limit * 0.5
ORDER BY active_parts DESC
LIMIT 25;

Reading the limits from system.merge_tree_settings matters because the defaults have changed between releases and many estates override them per table. The same view of the world applies to mutations. An ALTER TABLE ... UPDATE or DELETE that cannot complete will retry indefinitely and hold merge capacity while it does.

-- Mutations that are stuck or failing.
SELECT
    database,
    table,
    mutation_id,
    substring(command, 1, 100) AS command,
    create_time,
    parts_to_do,
    latest_fail_time,
    substring(latest_fail_reason, 1, 160) AS latest_fail_reason
FROM clusterAllReplicas('cluster_name', system.mutations)
WHERE NOT is_done
  AND create_time < now() - INTERVAL 30 MINUTE
ORDER BY create_time;
Killing a mutation with KILL MUTATION is sometimes the right call and is never the first one. Our runbook requires the failure reason to be read and recorded, the affected parts to be listed, and a second engineer to confirm before the command is run on production. Test the procedure on a staging copy first.

The durable fix is an engineering change: larger insert batches or asynchronous inserts, a coarser partition key, or replacing row-level mutations with a ReplacingMergeTree or lightweight-delete design. Those changes come out of the reliability backlog, and the MergeTree storage engine guide explains the mechanics behind them.

Practice 6

Prove recovery with scheduled restore drills

A backup that has never been restored is a hope, not a control. Under Data SRE the recovery time objective and recovery point objective are tested on a calendar, the result is recorded, and a failed drill is handled as an incident. Replication is not a backup: a dropped table or a bad mutation replicates faithfully to every replica.

-- 1. Take a backup to object storage. Credentials come from the
--    environment or a named collection, never from the statement text.
BACKUP DATABASE analytics
TO S3('https://${BACKUP_BUCKET}.s3.amazonaws.com/clickhouse/2026-09-20/',
      '${AWS_ACCESS_KEY_ID}', '${AWS_SECRET_ACCESS_KEY}');

-- 2. Confirm it finished and note the size and duration.
SELECT name, status, error, start_time, end_time,
       formatReadableSize(total_size) AS total_size, num_files
FROM system.backups
ORDER BY start_time DESC
LIMIT 5;

-- 3. Restore one table into a scratch database and time it.
CREATE DATABASE IF NOT EXISTS restore_drill;

RESTORE TABLE analytics.events AS restore_drill.events
FROM S3('https://${BACKUP_BUCKET}.s3.amazonaws.com/clickhouse/2026-09-20/',
        '${AWS_ACCESS_KEY_ID}', '${AWS_SECRET_ACCESS_KEY}');

-- 4. Validate: row counts and a checksum over a stable key range.
SELECT 'source' AS side, count() AS row_count, sum(cityHash64(event_id)) AS checksum
FROM analytics.events WHERE event_date < today()
UNION ALL
SELECT 'restored', count(), sum(cityHash64(event_id))
FROM restore_drill.events WHERE event_date < today();

The drill report records four numbers: backup duration, restore duration, data volume and the validation result. Those numbers are what make an RTO credible. The scratch database is removed afterwards through your normal change process, with the object name confirmed by a second person before anything is dropped. For estates on older releases or with very large tables we use clickhouse-backup or filesystem snapshots instead, and the drill discipline is the same.

Practice 7

Plan capacity from measured growth and make changes reversible

Capacity planning in Data SRE starts from telemetry rather than from a vendor sizing sheet. Disk growth per table, peak memory per query class and concurrent query counts are trended, and the forecast is stated with the date on which a limit will be reached if nothing changes.

-- New data written per table per day, last 30 days (inserts only, merges excluded).
-- Requires system.part_log, which is enabled in the default packaged configuration.
SELECT
    database,
    table,
    event_date,
    formatReadableSize(sum(size_in_bytes)) AS written,
    sum(rows) AS rows_written
FROM system.part_log
WHERE event_type = 'NewPart'
  AND event_date >= today() - 30
GROUP BY database, table, event_date
ORDER BY database, table, event_date;

-- Free space per disk, to set against the growth rate.
SELECT name, path,
       formatReadableSize(free_space) AS free,
       formatReadableSize(total_space) AS total,
       round(100 * free_space / total_space, 1) AS free_pct
FROM system.disks;

Change safety is the other half. Most reliability loss in a mature estate is self-inflicted: an upgrade, a schema change, a settings change. Every change we make follows the same shape, and the same shape is written into every runbook we hand over:

  1. State the blast radius and the rollback path before the change is approved.
  2. Run a verification query that captures the state before the change.
  3. Apply the change to one replica or one shard first where the topology allows it.
  4. Run a validation query and compare it with the verification result.
  5. Hold for an agreed soak period while watching the burn rate, then proceed or roll back.
Data SRE safe change procedure for ClickHouse: plan, verify, apply to one replica, validate, soak, then proceed or roll back
Figure 4. Every production change follows the same reversible shape, whether it is an upgrade, a schema change or a settings change.

Upgrades follow the ClickHouse LTS calendar, one replica at a time, with the compatibility setting pinned until the whole cluster is on the new release. Schema changes on large tables are rehearsed on a restored copy from the most recent drill, which is one more reason the drills in Practice 6 pay for themselves.

Engagement

What a ChistaDATA Data SRE engagement delivers

Data SRE is delivered as part of ClickHouse managed services and 24×7 ClickHouse enterprise support, or as a fixed-scope reliability programme for teams that run their own on-call rotation. The engagement is led by a named principal engineer and every phase ends in a written deliverable your team keeps.

Weeks 1 to 2: reliability baseline

Telemetry review across system.query_log, system.replicas, system.parts and Keeper. Output: a baseline report with current indicators, the top reliability risks ranked by likelihood and impact, and proposed objectives per workload tier.

Weeks 3 to 6: instrumentation and runbooks

SLO recording rules, burn-rate alerts, Grafana views and a runbook for every paging alert, each with verification, action, validation and rollback steps. Output: the alert catalogue and the runbook set, in your repository.

Ongoing: operate and improve

On-call coverage under the published severity matrix, monthly error budget reviews, quarterly restore and failover drills, and blameless postmortems. Output: a reliability backlog that is reprioritised every month against budget impact.

Data SRE incident response under the enterprise SLA

SeverityDefinitionResponse target
Severity 1Production down or data at risk15 minutes
Severity 2Production degraded, workaround exists12 hours
Severity 3Non-production or minor production issue24 hours
Severity 4Question, guidance or planned work48 hours

Teams that need a point-in-time assessment before committing to a programme usually start with a ClickHouse performance audit, and architecture questions that come out of the baseline are handled through ClickHouse consulting. Data SRE is not the right first step for every team: a single-node cluster behind an internal dashboard rarely needs an error budget, and we will say so.

FAQ

Data SRE questions we are asked most often

How is Data SRE different from database monitoring?

Monitoring tells you what a system is doing. Data SRE defines what the system is supposed to do, measures the gap as an error budget, and uses that budget to decide when to page, when to freeze changes and what engineering work comes next. Monitoring is one input to the practice rather than the practice itself.

Does Data SRE replace our DBA or platform team?

No. Data SRE gives the team you already have a shared set of objectives, alerts that are worth waking up for, and rehearsed runbooks. Some customers hand on-call to us entirely, others keep first-line response and use our engineers as escalation. Both models work under the same objectives and runbooks.

Which ClickHouse deployments do you run Data SRE on?

Self-managed ClickHouse on bare metal, virtual machines and Kubernetes, and managed platforms including ClickHouse Cloud, Aiven and Altinity.Cloud. On managed platforms some signals, such as Keeper internals and host-level metrics, belong to the provider, so the indicator set is adapted to what the platform exposes.

What tooling do we need before an engagement starts?

Nothing proprietary. The reference stack is the ClickHouse Prometheus endpoint, Prometheus, Alertmanager and Grafana, with an optional retained telemetry database in ClickHouse for long-window objectives. If you already run Datadog, Grafana Cloud or another platform, the same rules and queries are implemented there.

How long before we see a measurable result?

The baseline report arrives in the first two weeks and usually surfaces risks that can be closed immediately, such as unmonitored read-only replicas or backups that have never been restored. Objectives and burn-rate alerting are typically live by week six. We do not quote improvement percentages in advance, because the honest answer depends on where your estate starts.

Can Data SRE work alongside our existing incident process?

Yes. We map our severity matrix to yours, join your incident channel and paging tool, and write postmortems in your template. The practice is deliberately tool-agnostic so that it strengthens the process you have instead of adding a second one.

Put your ClickHouse estate on a measured reliability footing

A fifteen-minute conversation with a ChistaDATA principal engineer is enough to establish whether Data SRE fits your estate and what the baseline would cover. Every recommendation we make should be tested in your own environment before it reaches production, and none of it removes the need for a rehearsed disaster recovery plan. Our ClickHouse SRE engineers and the wider data reliability engineering practice work as an extension of your team, not a replacement for it.