Skip to content

ClickHouseCluster ​

Scope: namespaced · Backend: the official ClickHouse operator (type: clickhouse)

ClickHouse for the platform's analytical stores — Langfuse traces and Wäg gateway analytics. Follows the same type × mode shape as the other datastores, and like PostgresCluster it hosts per-consumer logical databases inside one cluster.

Spec ​

FieldTypeDescription
typeenum clickhouse (default)Backend: the official ClickHouse operator. altinity is still accepted for existing objects and behaves the same; it never meant the Altinity operator
modeenum managed | adopt | external (required)Provenance mode
managedobjectStorage, resources, replicas when mode: managed
adoptobjectReference to an existing in-cluster ClickHouse CR
externalobjectConnection secret for an external ClickHouse
databases[]listDatabases to ensure inside the cluster (one per consuming workload) — see databases[]

adopt ​

FieldDescription
installationRefThe existing ClickHouse installation. namespace is optional and may point at another namespace — the service host is built from it
credentialsSecretRefRequired in practice: the Secret holding username / password. namespace defaults to the installation's namespace; a Secret outside this CR's namespace is mirrored here as <name>-adopted-credentials

databases[] ​

Logical databases to ensure inside the cluster — one per consuming workload, the same idea PostgresCluster.spec.databases already covers for Postgres.

FieldDescription
nameDatabase name. Pattern ^[A-Za-z_][A-Za-z0-9_]*$, max 63 characters. ClickHouse identifiers are case-sensitive
yaml
spec:
  type: clickhouse
  mode: managed
  databases:
    - name: waeg

The operator ensures each entry with CREATE DATABASE IF NOT EXISTS over ClickHouse's HTTP interface and reports the result on status.databases as { name, ready }.

Why this exists ​

Neither Langfuse nor Wäg creates its own ClickHouse database — both create only their tables. Langfuse gets away with it because it lands in ClickHouse's built-in default, which always exists. Wäg targets a database named waeg and exits at start-up with:

Database waeg does not exist

So a Wäg gateway on a fresh managed ClickHouse could not start at all without someone running CREATE DATABASE by hand. Declaring the database here is what makes the cluster usable without that step.

Which modes it applies to ​

ModeApplied?Why
managedyesThe operator owns the cluster
adoptyesCREATE DATABASE IF NOT EXISTS is purely additive — it never touches an existing database or its data — and an adopted cluster otherwise cannot host a Wäg gateway
externalnoThe operator was handed a connection string, not ownership. It does not issue DDL against infrastructure it does not manage — create the database yourself

Databases are never dropped

Removing an entry from spec.databases, deleting the consuming workload, or deleting the ClickHouseCluster CR itself never drops a database — on any path. Analytics and trace history should outlive a mistaken delete. Drop one by hand when you really mean to.

Workloads that reference this cluster ​

With the auto-wiring licence, databases that referencing workloads use (an Observability's langfuse.clickhouse.databaseName, a Wäg gateway's database) are created without being declared, and status.databases[].claimedBy shows who uses each. See Databases for workloads that reference a datastore.

A Stack declares waeg for you ​

A Stack with a type: waeg gateway in the default split storage mode adds waeg to its managed ClickHouse automatically — you write nothing. With gateway.waeg.storageMode: single there is no ClickHouse at all, so nothing is declared.

Langfuse's database ​

A new Stack gives Langfuse its own langfuse database: it is declared here, and the Observability's clickhouse.databaseName points Langfuse at it (CLICKHOUSE_DB, via langfuse-operator 0.10.1 or later). Langfuse waits until this cluster reports the database present.

A Langfuse that already runs on ClickHouse's built-in default — every Stack created before this option existed — stays there. Moving it would start Langfuse on an empty database, and its existing traces would disappear from the UI. To move one deliberately, set clickhouse.databaseName on a standalone Observability and accept that it starts empty (earlier traces stay in default).

Selecting a database by hand ​

Wiring a consumer yourself? The database is selected with the ?database= query parameter on the ClickHouse HTTP URL — not a path:

http://forge-ch:8123/?database=waeg     # correct
http://forge-ch:8123/waeg               # silently wrong

ClickHouse's HTTP interface ignores an unknown path and serves its human-readable landing page, so the second form returns help text instead of an error — which the consumer then reports as an unparseable response, with nothing pointing at the URL.

Behavior by mode ​

  • managed — ensures the ClickHouse operator and creates its custom resource. A ClickHouseCluster requires a companion KeeperCluster, which the controller also emits.
  • adopt — references an existing in-cluster ClickHouse CR — possibly in another namespace (cross-namespace adoption) — and publishes its credentials for consumers; lifecycle is not managed. Without adopt.credentialsSecretRef nothing can connect, so the resource reports Ready=False naming that field instead of going Ready with no credentials.
  • external — wires a connection secret; no operator installed. databases is not applied here.

spec.databases is ensured on managed and adopt clusters on every reconcile, and never dropped — see databases[].

Langfuse can manage ClickHouse for you

The langfuse-operator can manage ClickHouse and Redis internally. Unless you reference an external datastore, the Observability controller defaults to operator-managed ClickHouse/Redis — so a dedicated ClickHouseCluster is optional for Langfuse alone. Use this resource when you want to manage or share ClickHouse explicitly — which is also what a type: waeg Gateway in split storage mode needs.

Example — managed ​

yaml
apiVersion: core.navique.com/v1alpha1
kind: ClickHouseCluster
metadata:
  name: forge-ch
  namespace: forge-data
spec:
  type: clickhouse
  mode: managed

Example — external ​

yaml
spec:
  type: clickhouse
  mode: external
  external:
    connectionSecretRef: { name: clickhouse-conn }

Backups & restore ​

Set managed.backup (managed mode only) to schedule backups to object storage. The block is the same provider-neutral shape as Postgres/Mongo — provider: s3 | azure | gcs, a destination, credentials, schedule, and retentionPolicy.

The operator emits an owned <name>-backup CronJob running clickhouse-backup in embedded mode (server-side SQL BACKUP), so it works over the network with the official ClickHouse operator — no sidecar or data-volume access. Setting backup.enabled: false (or removing the block) deletes the CronJob.

yaml
spec:
  type: clickhouse
  mode: managed
  managed:
    storageSize: 50Gi
    backup:
      enabled: true
      provider: s3
      s3:
        destinationPath: s3://my-bucket/clickhouse
        credentialsSecretRef: { name: ch-backup-creds }   # access-key-id / secret-access-key
      schedule: "0 0 3 * * *"     # 6-field cron; the CronJob uses the 5-field form
      retentionPolicy: "30d"      # mapped to BACKUPS_TO_KEEP_REMOTE

Restore is a manual runbook (run from a pod with the same backup env):

bash
clickhouse-backup list remote                       # find the backup name
clickhouse-backup restore_remote <backup-name>      # download + restore via SQL

See the Altinity restore guide.

Status ​

FieldDescription
host / portConnection coordinates for consumers
credentialsSecretThe Secret carrying username / password in this namespace
databases[]One { name, ready } entry per requested database
phase, conditions, observedGenerationThe standard status surface

Open core under AGPL-3.0. Enterprise components are proprietary and license-gated.