Skip to content

fix(clustering): surface silent cache-transport failures and make rewire retryable (#36803) - #36864

Open
wezell wants to merge 1 commit into
mainfrom
issue-36803-silent-cache-transport-failures
Open

fix(clustering): surface silent cache-transport failures and make rewire retryable (#36803)#36864
wezell wants to merge 1 commit into
mainfrom
issue-36803-silent-cache-transport-failures

Conversation

@wezell

@wezell wezell commented Aug 3, 2026

Copy link
Copy Markdown
Member

Proposed Changes

Fixes #36803 — cluster cache invalidations were dropped silently when the pub/sub transport failed to initialize, and a persistently failing rewire was logged and forgotten (root cause analysis in #36544).

  • PubSubCacheTransport.send() no longer discards invalidations silently: increments a dropped-message counter and logs at WARN, rate-limited (CACHE_TRANSPORT_DROP_WARN_INTERVAL_MILLIS, default 30s) so a sustained outage cannot flood the log.
  • PubSubCacheTransport.init() is idempotent: a rewire on an already-healthy transport is a no-op instead of a tear-down/rebuild of the pub/sub listener — this removes the rebuild-per-rewire-pass churn measured in Spike: Cluster re-wire re-inits Postgres pub/sub transport and leaks DB connections → pool exhaustion + startup crash-loop #36544 (LISTEN cluster_actions ×3,687).
  • ClusterFactory.addMeToCacheIfNeeded() now reports success/failure instead of swallowing exceptions. KNOWN_SERVERS is only updated on success, so a failed transport init is retried on the next heartbeat instead of being forgotten until the server set changes. Consecutive failures are tracked and exposed via ClusterFactory.getRewireFailures() (reset on success).
  • New cache-transport health check (CacheTransportHealthCheck, registered in CoreHealthCheckProvider): reports unhealthy when the transport is uninitialized or rewires are persistently failing; exposes initialized / droppedInvalidations / rewireFailures as structured data. Single-node/community (NullTransport) always reports healthy.
  • Micrometer gauges in the existing CacheMetrics binder:
    • dotcms.cache.transport.invalidations.dropped
    • dotcms.cache.transport.initialized
    • dotcms.cache.transport.rewire.failures
  • CacheTransport gains a default long getDroppedMessages() (0 for all other transports).

Readiness decision (AC: "decide and document")

The health check defaults to MONITOR_MODE: it reports degradation but never fails readiness probes. A node that cannot send invalidations can still serve traffic, and gating readiness on the transport risks a cold-start deadlock (transport init happens during cluster wiring). Operators who prefer to drain such nodes can opt in with health.check.cache-transport.mode=PRODUCTION. It is never a liveness check — restarting pods on transport failure is what amplified the #36544 incident.

Testing

🤖 Generated with Claude Code

https://claude.ai/code/session_01XFuXwYdNsb7irpMEeSGTyj

Cache invalidations were dropped with no log, no metric and no health
signal when the pub/sub cache transport was not initialized, and a
failing cluster rewire was logged and forgotten while KNOWN_SERVERS was
updated as if it had succeeded (issue #36544 incident).

- PubSubCacheTransport.send(): count dropped invalidations and WARN,
  rate-limited (CACHE_TRANSPORT_DROP_WARN_INTERVAL_MILLIS, default 30s)
- PubSubCacheTransport.init(): idempotent - a rewire on a healthy
  transport no longer tears down/rebuilds the pub/sub listener
- ClusterFactory.addMeToCacheIfNeeded(): report success/failure; only
  update KNOWN_SERVERS on success so failed rewires are retried; track
  consecutive failures in a counter exposed via getRewireFailures()
- New cache-transport health check (MONITOR_MODE by default so it never
  fails readiness unless an operator opts in via
  health.check.cache-transport.mode=PRODUCTION - avoids cold-start
  deadlock)
- Micrometer gauges: dotcms.cache.transport.invalidations.dropped,
  dotcms.cache.transport.initialized,
  dotcms.cache.transport.rewire.failures

Fixes #36803

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFuXwYdNsb7irpMEeSGTyj
@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Claude Code is working…

I'll analyze this and get back to you.

View job run

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

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Cluster cache invalidations are dropped silently when the pub/sub transport fails to initialize

1 participant