8000 [Draft] CHK Raft safety planner: observation, admission, and disruption by alex-zaitsev · Pull Request #2114 · Altinity/clickhouse-operator · GitHub
[go: up one dir, main page]

Skip to content

[Draft] CHK Raft safety planner: observation, admission, and disruption - #2114

Draft
alex-zaitsev wants to merge 38 commits into
0.28.0from
refactor/chk-raftplan-shadow-20261022
Draft

alex-zaitsev wants to merge 38 commits into
0.28.0from
refactor/chk-raftplan-shadow-20261022

Conversation

@alex-zaitsev
@alex-zaitsev alex-zaitsev commented Oct 22, 2026 •
Copy link
Copy Markdown
Member

Summary

Incrementally introduce an Observe → Decide → Act Raft safety planner for CHK.

  • Extract pure raftplan.PlanAdmission and raftplan.CheckDisruption, with unit and parity tests.
  • Observe live Keeper StatefulSets and published common-ConfigMap raft_configuration before staged admissions.
  • Use the planner as the authoritative admission and quorum-disruption decision-maker by default; the normal e2e operator installation requires no environment variables.
  • Retain the existing ConfigMap/StatefulSet reconcilers, preliminary staging, quorum wait/retry budget, serialization, and settle delays. Remove the superseded pre-create membership rollback.
  • Treat unknown or inconsistent state conservatively (fail closed). Keep shadow comparison and the optional legacy decision predicate available for diagnostics/fallback.
  • Stage 1→N / 2→3 joiners with a host-local Raft bootstrap configuration; publish into the established members only after Pod Started, then verify live membership and current Kubernetes readiness before completing.
  • Integration from #2115 was merged into this branch (not into 0.28.0).

How to test

Build the operator from this PR's head branch refactor/chk-raftplan-shadow-20261022 and run the usual e2e suite; the planner is on by default. To run unit tests:

go test -mod=vendor -race -count=1 ./pkg/controller/chk/...

Prioritize staged 1→3 admission, intermediate unready joiner, interrupted Create/operator restart, 2→3, 3→5, degraded quorum during rollout, stop/resume, and scale-down.

The staged-join and readiness changes passed the owner's local suite, but CI for the later cleanup exposed a separate intermittent watcher test failure and a unit-test fixture panic. Commit a5b78bd corrects the fixture, tests the watcher using its recorded no-endpoint-change event and unchanged CHI ZooKeeper XML instead of an unrelated live session timestamp, and adds failure diagnostics for a distinct 0-2 scale-up stall. Re-run CI before marking the PR ready.

Not in this change

  • No elimination of existing blocking membership settle delays.
  • No CHK CRD or status-format change.

Review notes

The authoritative implementation was developed as stacked PR #2115 and merged into this branch for a single end-to-end review. This PR still targets 0.28.0; it has not been merged into the release branch.

Broken-image scale-up regression

The uncommitted test_020003_4 scenario found that a 1→3 scale-up with a broken image could remain indefinitely InProgress rather than reporting Aborted. Two startup paths were corrected:

  • PodHasAllContainersStarted now requires reported Started=true status for every declared container; empty/partial container statuses are not successful startup.
  • For an unchanged (ObjectStatusSame), unready staged joiner on a later reconciliation, the StatefulSet reconciler performs the normal bounded Started, not Ready, wait and uses CHK's existing ErrCRUDAbort fallback on timeout.

The regression motivated a two-phase join flow: boot with host-local membership, wait for Pod Started, publish the new peer to the leader, then verify the leader's live Raft membership and the joiner's fresh StatefulSet/Pod readiness. A failed startup may abort and resume after a spec edit. No existing Keeper needs to restart for admission.

Cross-reconcile stalled-joiner timeout

After the initial broken-image startup fix, e2e still remained InProgress: the next joiner was repeatedly returning a soft ErrCRUDDeferred for a previously published, unready member. The admission executor now inspects the blocking member's live Pod: if all containers never started and its Pod age exceeds reconcile.statefulSet.update.timeout, it returns hard ErrCRUDAbort instead of repeating the deferral. This deadline is based on Kubernetes timestamps and survives reconcile retries/operator restarts; Pods that have started but are not yet Keeper Ready continue to defer safely. A missing Pod uses the blocking StatefulSet's age. Failed API observations remain errors, not absence.

Regression tests cover pure deadline cases, abort propagation, resume, staged Raft publication after Pod Started, and the final committed-but-not-Ready join state. The owner previously confirmed the targeted join scenario passed. The broader e2e watch/scale-up scenario is not yet fully green on CI: two retries stalled creating the final Keeper Pod. The latest patch adds StatefulSet and Kubernetes event diagnostics to investigate any recurrence; no admission logic has been changed.

Cleanup after the working refactor

Removed the unused rollbackStagedRaftAdmit path and its obsolete tests, the empty includeAllHostsIntoCluster hook, the duplicate staged-join verification branch, and the unused boolean return from publishStagedAdmit. Replaced pre-create publication test coverage with a test of the current Started → publish ordering. Updated stale inline comments. Legacy decision fallback, shadow diagnostics, quorum safety, and settle delays remain intentionally intact.

alex-zaitsev added a commit that referenced this pull request Oct 23, 2026
[Draft] Enable authoritative CHK Raft planner for e2e
@alex-zaitsev alex-zaitsev changed the title [Draft] Extract CHK Raft safety policy and shadow-mode parity checks [Draft] CHK Raft safety planner: observation, admission, and disruption Oct 23, 2026
@alex-zaitsev
alex-zaitsev force-pushed the refactor/chk-raftplan-shadow-20261022 branch from a5b78bd to 106f7d6 Compare October 25, 2026 09:21
@alex-zaitsev
alex-zaitsev force-pushed the refactor/chk-raftplan-shadow-20261022 branch from 67eab8f to c5a71d4 Compare October 28, 2026 09:47
@Slach
Slach commented Nov 3, 2026
Copy link
Copy Markdown
Collaborator

@alex-zaitsev question about scope of this planner vs #2041

#2041 is not planned to merge (#2041 (comment)), and #2087 + this PR cover 1→N / 2→3: staged admit, publish after Pod Started, then check live membership via /keeper/config
this is the same observe-wait-confirm idea filimonov described in #2041 (comment) - don't touch the quorum again until the previous membership change is committed

but some parts of #2041 are not in 0.28.0 and not in this branch

  • 3→N still fan-out, raftplan/admission.go:63 "Fresh bootstrap and 3->N intentionally retain the existing fan-out policy", so 3→7 publishes 4 fresh voters at once, quorum(7)=4
  • <start_as_follower> for joiners of established ensemble, you suggested it as first step in Raft-safe ClickHouseKeeperInstallation rescale #2041 (comment) but it is not emitted anywhere
  • scale-down: no rqld leadership transfer from departing members and no barrier "member left committed membership" before STS/PVC purge, raftplan/admission.go:10 says scale-down needs a separate safety gate
  • follower sync barrier (mntr synced followers), now only comment "extension point" in worker-raft-safety.go
  • lease leader election for CHK controller manager

do you plan to cover these in this PR, or is it better to extract them from #2041 into separate small PRs on top of the planner?

@Slach
Slach commented Nov 3, 2026
Copy link
Copy Markdown
Collaborator

@alex-zaitsev one more point, about what readiness actually checks

Keeper /ready is ok when the node is leader, or is follower/observer and sees some leader
https://github.com/ClickHouse/ClickHouse/blob/v25.8.1.5101-lts/src/Server/KeeperReadinessHandler.cpp#L27-L33

auto is_leader = keeper_dispatcher->isLeader();
auto is_follower = keeper_dispatcher->isFollower() && keeper_dispatcher->hasLeader();
auto status = is_leader || is_follower || is_observer;

it is local view of one node, it doesn't say the node is in committed membership of original ensemble or caught up with the log
in the split-brain from #2041 (2nd and 3rd elect own leader without 1st) all 3 pods are Ready

so gating next joiner on previous joiner Ready (#2087, and still in this PR) can't catch it, only /keeper/config check after publish does, and only for 1→N / 2→3
can we gate next admission on committed membership from /keeper/config == expected + leader mntr zk_synced_followers, like #2041 does, instead of Ready?

# Conflicts:
#	pkg/controller/common/statefulset/statefulset-reconciler_test.go

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0