Skip to content

Commit 2e28e6c

Browse files
committed
feat: add replicated host sync gate
Signed-off-by: ku524 <yeonjuyeong@gmail.com>
1 parent 4ca7416 commit 2e28e6c

35 files changed

Lines changed: 1597 additions & 17 deletions

config/config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,16 @@ reconcile:
500500
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
501501
# is within this specified delay (in seconds)
502502
delay: 10
503+
# Optional replicated-host catch-up gate before advancing to the next host.
504+
# Disabled by default to preserve existing reconcile behavior.
505+
sync:
506+
enabled: "false"
507+
mode: "lightweight"
508+
timeout: 0
509+
onTimeout: "abort"
510+
health:
511+
pollInterval: 10
512+
successThreshold: 6
503513
probes:
504514
# Whether the operator during host launch procedure should wait for startup probe to succeed.
505515
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/builder/templates-config/config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,16 @@ reconcile:
494494
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
495495
# is within this specified delay (in seconds)
496496
delay: 10
497+
# Optional replicated-host catch-up gate before advancing to the next host.
498+
# Disabled by default to preserve existing reconcile behavior.
499+
sync:
500+
enabled: "false"
501+
mode: "lightweight"
502+
timeout: 0
503+
onTimeout: "abort"
504+
health:
505+
pollInterval: 10
506+
successThreshold: 6
497507
probes:
498508
# Whether the operator during host launch procedure should wait for startup probe to succeed.
499509
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/helm/clickhouse-operator/values.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,16 @@ configs:
775775
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
776776
# is within this specified delay (in seconds)
777777
delay: 10
778+
# Optional replicated-host catch-up gate before advancing to the next host.
779+
# Disabled by default to preserve existing reconcile behavior.
780+
sync:
781+
enabled: "false"
782+
mode: "lightweight"
783+
timeout: 0
784+
onTimeout: "abort"
785+
health:
786+
pollInterval: 10
787+
successThreshold: 6
778788
probes:
779789
# Whether the operator during host launch procedure should wait for startup probe to succeed.
780790
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/operator/clickhouse-operator-install-ansible.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6186,6 +6186,16 @@ data:
61866186
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
61876187
# is within this specified delay (in seconds)
61886188
delay: 10
6189+
# Optional replicated-host catch-up gate before advancing to the next host.
6190+
# Disabled by default to preserve existing reconcile behavior.
6191+
sync:
6192+
enabled: "false"
6193+
mode: "lightweight"
6194+
timeout: 0
6195+
onTimeout: "abort"
6196+
health:
6197+
pollInterval: 10
6198+
successThreshold: 6
61896199
probes:
61906200
# Whether the operator during host launch procedure should wait for startup probe to succeed.
61916201
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/operator/clickhouse-operator-install-bundle-v1beta1.yaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5360,7 +5360,6 @@ metadata:
53605360
namespace: kube-system
53615361
labels:
53625362
clickhouse.altinity.com/chop: 0.27.2
5363-
53645363
# Template Parameters:
53655364
#
53665365
# NAMESPACE=kube-system
@@ -5613,7 +5612,6 @@ subjects:
56135612
- kind: ServiceAccount
56145613
name: clickhouse-operator
56155614
namespace: kube-system
5616-
56175615
# Template Parameters:
56185616
#
56195617
# NAMESPACE=kube-system
@@ -6385,6 +6383,16 @@ data:
63856383
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
63866384
# is within this specified delay (in seconds)
63876385
delay: 10
6386+
# Optional replicated-host catch-up gate before advancing to the next host.
6387+
# Disabled by default to preserve existing reconcile behavior.
6388+
sync:
6389+
enabled: "false"
6390+
mode: "lightweight"
6391+
timeout: 0
6392+
onTimeout: "abort"
6393+
health:
6394+
pollInterval: 10
6395+
successThreshold: 6
63886396
probes:
63896397
# Whether the operator during host launch procedure should wait for startup probe to succeed.
63906398
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/operator/clickhouse-operator-install-bundle.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6445,6 +6445,16 @@ data:
64456445
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
64466446
# is within this specified delay (in seconds)
64476447
delay: 10
6448+
# Optional replicated-host catch-up gate before advancing to the next host.
6449+
# Disabled by default to preserve existing reconcile behavior.
6450+
sync:
6451+
enabled: "false"
6452+
mode: "lightweight"
6453+
timeout: 0
6454+
onTimeout: "abort"
6455+
health:
6456+
pollInterval: 10
6457+
successThreshold: 6
64486458
probes:
64496459
# Whether the operator during host launch procedure should wait for startup probe to succeed.
64506460
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/operator/clickhouse-operator-install-template-v1beta1.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5360,7 +5360,6 @@ metadata:
53605360
namespace: ${OPERATOR_NAMESPACE}
53615361
labels:
53625362
clickhouse.altinity.com/chop: 0.27.2
5363-
53645363
# Template Parameters:
53655364
#
53665365
# NAMESPACE=${OPERATOR_NAMESPACE}
@@ -6132,6 +6131,16 @@ data:
61326131
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
61336132
# is within this specified delay (in seconds)
61346133
delay: 10
6134+
# Optional replicated-host catch-up gate before advancing to the next host.
6135+
# Disabled by default to preserve existing reconcile behavior.
6136+
sync:
6137+
enabled: "false"
6138+
mode: "lightweight"
6139+
timeout: 0
6140+
onTimeout: "abort"
6141+
health:
6142+
pollInterval: 10
6143+
successThreshold: 6
61356144
probes:
61366145
# Whether the operator during host launch procedure should wait for startup probe to succeed.
61376146
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/operator/clickhouse-operator-install-template.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6179,6 +6179,16 @@ data:
61796179
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
61806180
# is within this specified delay (in seconds)
61816181
delay: 10
6182+
# Optional replicated-host catch-up gate before advancing to the next host.
6183+
# Disabled by default to preserve existing reconcile behavior.
6184+
sync:
6185+
enabled: "false"
6186+
mode: "lightweight"
6187+
timeout: 0
6188+
onTimeout: "abort"
6189+
health:
6190+
pollInterval: 10
6191+
successThreshold: 6
61826192
probes:
61836193
# Whether the operator during host launch procedure should wait for startup probe to succeed.
61846194
# In case probe is unspecified wait is assumed to be completed successfully.

deploy/operator/clickhouse-operator-install-tf.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6186,6 +6186,16 @@ data:
61866186
# a.k.a replication lag - calculated as "MAX(absolute_delay) FROM system.replicas"
61876187
# is within this specified delay (in seconds)
61886188
delay: 10
6189+
# Optional replicated-host catch-up gate before advancing to the next host.
6190+
# Disabled by default to preserve existing reconcile behavior.
6191+
sync:
6192+
enabled: "false"
6193+
mode: "lightweight"
6194+
timeout: 0
6195+
onTimeout: "abort"
6196+
health:
6197+
pollInterval: 10
6198+
successThreshold: 6
61896199
probes:
61906200
# Whether the operator during host launch procedure should wait for startup probe to succeed.
61916201
# In case probe is unspecified wait is assumed to be completed successfully.

docs/operator_configuration.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,85 @@ spec:
222222

223223
See [Keeper Reference](keeper_reference.md) for details on how CHI references CHK resources.
224224

225+
### Replicated Host Sync Gate
226+
227+
The operator can optionally block a rolling host reconcile until a recreated replicated
228+
ClickHouse host catches up to a bounded replication baseline. This is an operator
229+
rolling gate, not a readiness probe. It is disabled by default.
230+
231+
This is especially useful for local or direct-attached storage deployments, including
232+
NVMe-backed Local PVs, where a recreated pod may start with an empty or replaced disk
233+
and must rebuild replicated data from peer replicas before the operator rolls the next
234+
host.
235+
236+
The existing caught-up marker path remains unchanged when this gate is disabled. That
237+
path only polls the local host's `MAX(absolute_delay)` from `system.replicas` before
238+
writing `status.hostsWithReplicaCaughtUp`, which is weak for recreated-host recovery
239+
because the metric is limited to replicated objects already loaded and visible on that
240+
local server. During recreated-host recovery, asynchronous database/table loading may
241+
not have exposed all replicated objects on the local host yet, and a local delay metric
242+
cannot discover replicated objects that exist on peers or issue a ClickHouse sync
243+
barrier for their known parts. The sync gate adds those checks before the operator
244+
advances to the next host.
245+
246+
```yaml
247+
spec:
248+
reconcile:
249+
host:
250+
wait:
251+
replicas:
252+
sync:
253+
enabled: "false"
254+
mode: "lightweight"
255+
timeout: 0
256+
onTimeout: "abort"
257+
health:
258+
pollInterval: 10
259+
successThreshold: 6
260+
```
261+
262+
| Setting | Default | Description |
263+
|---|---|---|
264+
| `enabled` | `"false"` | Enables the replicated-host sync gate. Existing replica-delay behavior is unchanged when disabled. |
265+
| `mode` | `"lightweight"` | Uses `SYSTEM SYNC REPLICA ... LIGHTWEIGHT`. No fallback to legacy `SYSTEM SYNC REPLICA` is performed. |
266+
| `timeout` | `0` | Whole-gate timeout in seconds. `0` means unbounded. |
267+
| `onTimeout` | `"abort"` | `abort` stops reconcile on the gate deadline. `proceed` advances without writing the caught-up marker, so a later reconcile can try again. |
268+
| `health.pollInterval` | `10` | Seconds between post-sync health checks. |
269+
| `health.successThreshold` | `6` | Consecutive healthy checks required after sync before the caught-up marker is written. |
270+
271+
When enabled, the gate waits for asynchronous database loading when ClickHouse exposes
272+
`system.asynchronous_loader`, discovers replicated objects from peer replicas, syncs
273+
`Replicated` databases with `SYSTEM SYNC DATABASE REPLICA`, syncs replicated tables
274+
with `SYSTEM SYNC REPLICA ... LIGHTWEIGHT`, and then requires a stable health window.
275+
Health is based on `system.replicas`: `is_readonly = 0`, `is_session_expired = 0`, and
276+
`absolute_delay <= reconcile.host.wait.replicas.delay`.
277+
278+
The `LIGHTWEIGHT` baseline is the time when the sync command runs. It waits for the
279+
relevant part-acquisition work known at that point; it does not require
280+
`system.replication_queue` to become empty and does not block forever on unrelated
281+
merges, mutations, or new ingest that arrives after the sync command. ClickHouse
282+
versions below `23.4` do not support `LIGHTWEIGHT`; enabling this gate on those
283+
versions fails explicitly instead of silently falling back.
284+
285+
Hard failures always abort regardless of `onTimeout`: query or connection failure,
286+
parent reconcile context cancellation, failed/canceled async load jobs, readonly
287+
replicas, and expired Keeper sessions. The caught-up marker is written only after real
288+
success or when peer discovery confirms that there are no replicated objects to sync.
289+
290+
Manual local-PV/data-loss validation:
291+
292+
1. Create a CHI with a replicated shard and `sync.enabled: "true"`.
293+
2. Wait for the current hosts to become caught up and confirm
294+
`status.hostsWithReplicaCaughtUp` contains the host FQDNs.
295+
3. Simulate storage loss for one host, for example by removing the local PV/PVC data
296+
in a test environment.
297+
4. Reconcile the CHI and confirm the operator removes the stale caught-up marker for
298+
the recreated host.
299+
5. Confirm the recreated host runs the sync gate and the next host in the shard does
300+
not advance while the recreated host is still behind.
301+
6. Allow replication to catch up and confirm the recreated host receives the
302+
caught-up marker again, then the next host proceeds.
303+
225304
## Security
226305

227306
The `security:` block at the chopconf top level (sibling of `clickhouse:`) holds operator-wide hardening defaults across three orthogonal axes: transport hardening (`security.policy`), FIPS cryptographic-module enforcement (`security.fips.enforced`), and workload supply-chain gating (`security.images.policy`). Per-component sub-blocks under it cover ClickHouse-client TLS, ZooKeeper-client TLS, Kubernetes-client TLS, and the operator↔metrics-exporter IPC channel.

0 commit comments

Comments
 (0)