8000 Protect last healthy shard replica during interrupted rolls (#1704) by alex-zaitsev · Pull Request #2046 · Altinity/clickhouse-operator · GitHub
[go: up one dir, main page]

Skip to content

Protect last healthy shard replica during interrupted rolls (#1704) - #2046

Merged
sunsingerus merged 2 commits into
0.27.3from
fix/1704-shard-safety-reconcile
Aug 3, 2026
Merged

sunsingerus merged 2 commits into
0.27.3from
fix/1704-shard-safety-reconcile

Conversation

@alex-zaitsev
Copy link
Copy Markdown
Member

Reconcile unhealthy replicas in the shard prior to healthy ones. Solves #1704. Replaces #1967

Summary

  • Recovery-first shard reconcile with peer-health gate before disruptive updates
  • E2E test_010083 for interrupted broken-image roll + operator restart

Test plan

  • test_010083
  • smoke: test_010008*, test_0100022, test_0100028

@alex-zaitsev
Copy link
Copy Markdown
Member Author

Final review — #2046

Commits: 93eaa5488 (shard safety) + a1be5bb63 (operator IP restore). Branch matches remote.

Verdict: approve

Addresses #1704 without dropping the operator-IP reconcile needed for clickhouse-operator user networks.

Behavior (correct)

Case Outcome
Generation same + healthy + IP same Skip
Generation same + healthy + IP changed Continue (networks refresh)
Unhealthy hosts Continue (recovery)
Disruptive roll with no healthy peer Defer host + ErrCRUDAbort
Unhealthy peer in shard Recovery first; do not roll last healthy

prevCHOpIP before buildCR is the right way to detect IP change.

Residual nits (non-blocking)

  1. Force-restart “skip” in reconcileHostStatefulSet only skips software restart; STS reconcile still runs. Shard-level deferral is the real gate.
  2. Unused down_start_time in test_010083.
  3. hasUnhealthyHosts does not short-circuit the host walk.

No further blockers from this pass.

@alex-zaitsev alex-zaitsev mentioned this pull request Jul 24, 2026
3 tasks done
@sunsingerus sunsingerus added the planned for review This feature is planned for review label Jul 31, 2026
@sunsingerus sunsingerus self-assigned this Jul 31, 2026
sunsingerus added a commit that referenced this pull request Aug 3, 2026
Reworks PR #2046 (issue #1704) on three counts found by review:

- A deferred host returned ErrCRUDAbort, which the shard[0] early-return turned
  into 'skip every sibling shard', and also skipped clean(), monitoring and the
  ancestor update. Deferral now has its own sentinel, is accumulated across shards
  and clusters, and is surfaced once at the end of the pass. It deliberately still
  reaches the top as an error: a pass that swallowed it would advance the ancestor
  past a host that was never rolled and let clean() purge that host.

- hostMayRequireDisruption tested !Is(ObjectStatusSame), but at that point host
  statuses are only Requested/Modified/Found - Same is assigned later, inside
  ReconcileStatefulSet. Every converged host therefore looked disruptable, so a
  healthy replica in a degraded shard got no reconcile at all. Replaced by
  hostDisruptionWouldDegradeShard, evaluated in reconcileHostStatefulSet after
  PrepareHostStatefulSetWithStatus where the status is authoritative.

- runConcurrently was last-writer-wins, so a benign deferral could mask a real
  shard failure. Deferrals are now tracked apart from errors.

shouldExcludeHost keeps a shard-safety check, now for the opposite reason: since
the restart is deferred later in the same pass, draining the host here would strip
its ready label and drop it from the Service endpoints with nothing left to put it
back - emptying the entrypoint Service of a shard whose only other replica is down.

Deferral is surfaced via ReconcileAbortWithReason plus a dedicated event reason so
'Aborted' names its cause.
@sunsingerus
sunsingerus merged commit 95950fc into 0.27.3 Aug 3, 2026
2 checks passed
sunsingerus added a commit that referenced this pull request Aug 3, 2026
…tarving sibling shards

Brings in PR #2046 (issue #1704 - an interrupted roll could take down a shard's
last healthy replica) together with a rework of it.

The deferral now has its own sentinel and is accumulated across shards and
clusters, so one deferred host no longer skips every sibling shard. It still
reaches the top as an error on purpose: swallowing it would advance the ancestor
past a host that was never rolled and let clean() purge that host's StatefulSet.

The disruption gate moved into reconcileHostStatefulSet, after the desired
StatefulSet is known - the previous predicate tested a status that is only
assigned later, so every converged host looked disruptable and a healthy replica
in a degraded shard received no reconcile at all. shouldExcludeHost keeps its
shard-safety check so a host that is about to be deferred is not drained out of
the Service with nothing left in the pass to restore it.

Tests: unit coverage for the shard-safety predicate and the sentinel, plus e2e
test_010083_1, verified to fail against the unreworked PR on exactly the sibling
convergence assertion.
Sign up for free to join this conversation on GitHub. Already have an account? Si 66A2 gn in to comment

Labels

planned for review This feature is planned for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0