Skip to content

Sandbox lifecycle events lost on ungraceful orchestrator exit: in-memory queue has no durable/reconciled path #3776

Open

Description

@AdaAibaby

Summary

Sandbox lifecycle events reach ClickHouse through an in-memory, best-effort queue on each orchestrator. Even with the back-pressure + retry improvements in #3770 / #3774, events still sitting in that queue are lost on an ungraceful exit (SIGKILL, OOM, panic, or a graceful drain that exceeds the scheduler's kill_timeout). Because the terminal killed event is what billing/audit reconcile against, this is a durability gap, not just a throughput one: sandbox_events can silently under-count terminations, and nothing backfills the missing rows.

This issue tracks the durable/reconciled path that #3774 deliberately did not attempt (it's a mitigation, not a guarantee).

Why #3774 isn't enough

#3774 makes terminal-event delivery best-effort-harder (bounded-blocking push, bounded flush retry, bounded drain, per-type drop metric), but the event is still only in RAM until a batch flushes. The queue was recently widened in prod (queue-size 1000→20000, max-batch-size 1000→5000), which raises the number of events lost per ungraceful exit (up to ~20000 + an in-progress 5000 batch). Node drain/rebalance — exactly when hard-kills happen — is the same scenario that produced the original incident (#3770).

Concretely, loss still occurs when:

  • the process is SIGKILLed/OOM-killed/panics with a non-empty queue;
  • a graceful drain can't flush the backlog within kill_timeout (ClickHouse slow/down at shutdown);
  • ClickHouse is down long enough that bounded retries give up and the batch is dropped.

Options (seeking maintainer steer on direction)

1. Redis Streams as the source of truth + a ClickHouse consumer.
Orchestrators already publish lifecycle events to a Redis stream (the webhook path) in addition to the direct ClickHouse batcher. Make the stream authoritative: a dedicated consumer reads it and writes sandbox_events, with consumer-group acks so an unacked event is redelivered after a crash. Removes the parallel best-effort writer entirely and gives at-least-once delivery. Biggest change; cleanest end state.

2. Durable local WAL for terminal events.
Terminal events are appended to an on-disk log on the node before/alongside the batcher; a flusher drains the WAL to ClickHouse and truncates on ack. Survives process crash (not disk loss). Smaller blast radius than (1), but adds a disk format + recovery path per node.

3. Reconciliation / backfill job.
A periodic job compares an authoritative kill signal (orchestrator logs / Redis stream / routing-record teardown) against sandbox_events and backfills missing killed rows. Doesn't prevent loss but makes the system self-healing across all loss modes including crash — and is the cheapest to ship. Could run first as detection-only (emit a sandbox_events.reconciliation.missing metric) to size the real gap before writing backfill.

Suggested sequencing

Ship 3 in detect-only mode first — it quantifies the residual loss after #3774 across all modes (including crash), with no risk — then decide between the durable paths (1 preferred long-term) based on the measured gap.

Context / related

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions