/** * ClickHouse-native activity probe for the migrate ↔ maintenance mutex. * * The migration task (Compute stack) and the maintenance sidecars (Database * stack) cannot see each other through ECS — the Database stack is deployed * first and cannot reference the Compute stack — so the mutual exclusion is * observed through the server both already talk to: `system.processes` * (queries running right now) plus `system.query_log` recency (a query that * finished within the last `recencySeconds`). Each side asks about the OTHER * control-plane identity and defers while it is active: * * - the maintenance wrapper asks about `schemaAdmin.name` before OPTIMIZE / * BACKUP and exits `deferred` (exit 0, metric) when a migration is under way; * - the migration runner asks about `fjall_maintenance` before its ClickHouse * phase and waits (bounded, abort-aware) for the sidecar to go idle. * * The recency window closes the gap between one statement of a multi- * statement job finishing and the next one starting (there is no process * row in between). Both sides read the same builder so the two halves of * the mutex cannot drift in what "active" means. * * `system.query_log` is flushed on an interval (7500 ms in the constructs' * server config), so the recency signal lands up to one flush late; the * residual window is the sub-second gap between a process row vanishing and * its finish row landing, and the two identities carry separate concurrency * caps, so a probe that slips through it costs contention, never a 202. Two * server-side preconditions keep the recency half honest: the table only * exists after the first flush (a brand-new server answers `Code: 60` until * then — the probe's own queries create it inside one interval, which is why * `awaitControlPlaneIdle` keeps polling through non-denied errors), and * `log_queries_min_query_duration_ms` must stay 0 (see `clickhouseTuning`), * else short control-plane statements never reach the log at all. * * The caller's own identity is never in `users`, so no self-exclusion is * needed; the probe is a plain SELECT the gate/maintenance/admin identities * are all granted on `system.processes` and `system.query_log`. */ /** * How long after a control-plane query completes the identity is still * considered active. Long enough to bridge a sidecar's per-statement gaps * (the backup script runs DROP → BACKUP → RESTORE back-to-back), short * enough that a finished job releases the mutex well inside one deploy. */ export declare const CONTROL_PLANE_ACTIVITY_RECENCY_SECONDS: 120; export interface ControlPlaneActivityQueryOpts { /** Identities to test for activity — the OTHER side of the mutex. */ users: readonly string[]; /** Overrides `CONTROL_PLANE_ACTIVITY_RECENCY_SECONDS`. */ recencySeconds?: number; } /** * Builds a single-row `SELECT active` returning `1` when any of `users` has * a running query or finished one within the recency window, else `0`. * Names are validated against the managed-user pattern before interpolation * — the only quoting the SQL needs — so a name that could carry a quote * never reaches the string. */ export declare function buildControlPlaneActivityQuery(opts: ControlPlaneActivityQueryOpts): string;