typekro 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/alchemy/clickhouse-schema/executor.d.ts +25 -0
  3. package/dist/alchemy/clickhouse-schema/executor.d.ts.map +1 -0
  4. package/dist/alchemy/clickhouse-schema/executor.js +144 -0
  5. package/dist/alchemy/clickhouse-schema/executor.js.map +1 -0
  6. package/dist/alchemy/clickhouse-schema/index.d.ts +12 -0
  7. package/dist/alchemy/clickhouse-schema/index.d.ts.map +1 -0
  8. package/dist/alchemy/clickhouse-schema/index.js +11 -0
  9. package/dist/alchemy/clickhouse-schema/index.js.map +1 -0
  10. package/dist/alchemy/clickhouse-schema/resource.d.ts +54 -0
  11. package/dist/alchemy/clickhouse-schema/resource.d.ts.map +1 -0
  12. package/dist/alchemy/clickhouse-schema/resource.js +158 -0
  13. package/dist/alchemy/clickhouse-schema/resource.js.map +1 -0
  14. package/dist/alchemy/clickhouse-schema/runner.d.ts +154 -0
  15. package/dist/alchemy/clickhouse-schema/runner.d.ts.map +1 -0
  16. package/dist/alchemy/clickhouse-schema/runner.js +613 -0
  17. package/dist/alchemy/clickhouse-schema/runner.js.map +1 -0
  18. package/dist/alchemy/clickhouse-schema/sql.d.ts +109 -0
  19. package/dist/alchemy/clickhouse-schema/sql.d.ts.map +1 -0
  20. package/dist/alchemy/clickhouse-schema/sql.js +368 -0
  21. package/dist/alchemy/clickhouse-schema/sql.js.map +1 -0
  22. package/dist/alchemy/clickhouse-schema/types.d.ts +394 -0
  23. package/dist/alchemy/clickhouse-schema/types.d.ts.map +1 -0
  24. package/dist/alchemy/clickhouse-schema/types.js +370 -0
  25. package/dist/alchemy/clickhouse-schema/types.js.map +1 -0
  26. package/dist/alchemy/index.d.ts +1 -0
  27. package/dist/alchemy/index.d.ts.map +1 -1
  28. package/dist/alchemy/index.js +2 -0
  29. package/dist/alchemy/index.js.map +1 -1
  30. package/package.json +1 -1
@@ -0,0 +1,613 @@
1
+ /**
2
+ * Convergence logic for the `ClickHouseSchema` alchemy resource: fingerprinting,
3
+ * `clickhouse-client` command rendering, Ready-pod selection and statement execution.
4
+ *
5
+ * Kept free of alchemy and of `@kubernetes/client-node` so it can be exercised whole
6
+ * against an injected {@link ClickHouseExecutor}.
7
+ */
8
+ import { createHash } from 'node:crypto';
9
+ import { getComponentLogger } from '../../core/logging/index.js';
10
+ import { ClickHouseSchemaError, parseClickHouseErrorCode, parseClickHouseExceptionName, redactClickHouseOutput, } from './types.js';
11
+ /** The Altinity CHI server container name (see `clickHouseInstallation`'s pod template). */
12
+ export const DEFAULT_CLICKHOUSE_CONTAINER = 'clickhouse';
13
+ /** ClickHouse's native protocol port, which `clickhouse-client` speaks. */
14
+ export const DEFAULT_CLICKHOUSE_PORT = 9000;
15
+ export const DEFAULT_CLICKHOUSE_USER = 'default';
16
+ export const DEFAULT_CLICKHOUSE_DATABASE = 'default';
17
+ /**
18
+ * Default environment variable read for the password INSIDE the container.
19
+ *
20
+ * This is the convention every in-repo ClickHouse workload already uses: the ClickStack
21
+ * retention CronJob reads `CLICKHOUSE_PASSWORD`, and `clickHouseS3BackupCronJob` sets
22
+ * that same variable from the credentials Secret. `clickhouse-client` also honours
23
+ * `CLICKHOUSE_PASSWORD` natively, but this resource does NOT depend on that: it passes
24
+ * `--password "${VAR:-}"` explicitly through `sh -c`, which behaves identically for a
25
+ * custom `passwordEnv` and on images whose client build predates the native support.
26
+ */
27
+ export const DEFAULT_CLICKHOUSE_PASSWORD_ENV = 'CLICKHOUSE_PASSWORD';
28
+ export const DEFAULT_WAIT_FOR_POD_TIMEOUT_MS = 120_000;
29
+ export const DEFAULT_STATEMENT_TIMEOUT_MS = 300_000;
30
+ export const DEFAULT_MAX_ATTEMPTS = 3;
31
+ export const DEFAULT_BACKOFF_MS = 1_000;
32
+ /**
33
+ * How many times a `fanout` apply re-lists and applies to newly appeared pods before it
34
+ * gives up and fails. Three covers the ordinary races — a scale-out or a replacement
35
+ * landing mid-apply — without letting a cluster that is genuinely churning stretch one
36
+ * converge indefinitely.
37
+ */
38
+ export const DEFAULT_MAX_RECONCILE_PASSES = 3;
39
+ const POD_POLL_INTERVAL_MS = 2_000;
40
+ export const defaultRuntimeDeps = {
41
+ now: () => Date.now(),
42
+ sleep: (ms) => new Promise((resolve) => {
43
+ setTimeout(resolve, ms);
44
+ }),
45
+ };
46
+ /** Single-quote a word for `sh -c`, closing and reopening around any embedded quote. */
47
+ function shellQuote(value) {
48
+ return `'${value.replaceAll("'", `'\\''`)}'`;
49
+ }
50
+ export function resolveDatabase(config) {
51
+ return config.client?.database ?? DEFAULT_CLICKHOUSE_DATABASE;
52
+ }
53
+ export function resolveContainer(config) {
54
+ return config.target.container ?? DEFAULT_CLICKHOUSE_CONTAINER;
55
+ }
56
+ /**
57
+ * The `sh -c` command run inside the server container.
58
+ *
59
+ * The password is expanded from the container's OWN environment (`"${VAR:-}"`, a POSIX
60
+ * default expansion so an unset variable means the empty password the stock Altinity
61
+ * `default` user has) and is therefore never in props, never in alchemy state, never in
62
+ * this process's memory, and never echoed. `exec` replaces the shell so the client is
63
+ * the container's direct child and receives the statement on stdin unbuffered.
64
+ *
65
+ * `--host 127.0.0.1`: the exec already landed inside a server pod, so the connection
66
+ * never leaves it. There is no port-forward and no network path from the runner.
67
+ */
68
+ export function renderClickHouseCommand(config) {
69
+ const user = config.client?.user ?? DEFAULT_CLICKHOUSE_USER;
70
+ const port = config.client?.port ?? DEFAULT_CLICKHOUSE_PORT;
71
+ const passwordEnv = config.client?.passwordEnv ?? DEFAULT_CLICKHOUSE_PASSWORD_ENV;
72
+ const settings = Object.entries(config.settings ?? {}).map(([name, value]) => `--${name}=${shellQuote(String(value))}`);
73
+ const client = [
74
+ 'exec clickhouse-client',
75
+ '--host 127.0.0.1',
76
+ `--port ${port}`,
77
+ `--user ${shellQuote(user)}`,
78
+ `--database ${shellQuote(resolveDatabase(config))}`,
79
+ // POSIX default-value expansion: an unset variable means the empty password.
80
+ `--password "\${${passwordEnv}:-}"`,
81
+ ...settings,
82
+ ].join(' ');
83
+ return ['sh', '-c', client];
84
+ }
85
+ /**
86
+ * sha256 over the ordered statements, the settings, the client configuration and the
87
+ * execution model.
88
+ *
89
+ * The TARGET is deliberately NOT part of the fingerprint — it describes where to reach
90
+ * the server, not what is applied — so {@link needsApply} compares it separately and a
91
+ * re-pointed resource re-applies even though its DDL is byte-identical.
92
+ *
93
+ * The EXECUTION MODEL is part of it, because switching from `onCluster` to `fanout`
94
+ * changes which servers the identical statements reached, and that is a change to what is
95
+ * applied even though the SQL is untouched.
96
+ */
97
+ export function computeFingerprint(config) {
98
+ const canonical = JSON.stringify({
99
+ statements: config.statements,
100
+ settings: Object.entries(config.settings ?? {})
101
+ .map(([name, value]) => [name, String(value)])
102
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)),
103
+ client: {
104
+ user: config.client?.user ?? DEFAULT_CLICKHOUSE_USER,
105
+ passwordEnv: config.client?.passwordEnv ?? DEFAULT_CLICKHOUSE_PASSWORD_ENV,
106
+ database: resolveDatabase(config),
107
+ port: config.client?.port ?? DEFAULT_CLICKHOUSE_PORT,
108
+ },
109
+ execution: config.execution,
110
+ });
111
+ return createHash('sha256').update(canonical).digest('hex');
112
+ }
113
+ function runtimeDeps(context) {
114
+ return context.deps ?? defaultRuntimeDeps;
115
+ }
116
+ /**
117
+ * Whether a converge must (re-)run the statements against this target.
118
+ *
119
+ * Three things are compared, and the third is the one that is easy to forget: the
120
+ * fingerprint (what is applied), the target strings (where, within a cluster), and the
121
+ * CLUSTER ITSELF. Namespace, selector and container are just strings — `telemetry` +
122
+ * `chi=orders` names a pod in staging exactly as well as it names one in production — so
123
+ * without the cluster identity, re-pointing a resource at a second cluster matches the
124
+ * recorded target, matches the fingerprint, and silently applies nothing there.
125
+ *
126
+ * The live pod SET is deliberately not compared here: it needs an API call, so
127
+ * {@link applyClickHouseSchema} checks it separately and only under `fanout`, by NAME AND
128
+ * UID ({@link samePodIdentitySet}) so a same-name replacement is not invisible.
129
+ */
130
+ export function needsApply(config, previous, clusterId) {
131
+ if (!previous)
132
+ return true;
133
+ if (previous.fingerprint !== computeFingerprint(config))
134
+ return true;
135
+ if (previous.clusterId !== clusterId)
136
+ return true;
137
+ return (JSON.stringify({
138
+ namespace: previous.target.namespace,
139
+ podSelector: previous.target.podSelector,
140
+ container: previous.target.container,
141
+ }) !==
142
+ JSON.stringify({
143
+ namespace: config.target.namespace,
144
+ podSelector: config.target.podSelector,
145
+ container: config.target.container,
146
+ }));
147
+ }
148
+ function selectorText(config) {
149
+ return Object.entries(config.target.podSelector)
150
+ .map(([key, value]) => `${key}=${value}`)
151
+ .join(',');
152
+ }
153
+ /**
154
+ * A pod whose container list is empty is a pod the transport could not describe, not a
155
+ * pod without containers; exec'ing into it and letting the API server object is a better
156
+ * failure than refusing it here on missing information.
157
+ */
158
+ function hasContainer(pod, container) {
159
+ return pod.containers.length === 0 || pod.containers.includes(container);
160
+ }
161
+ /** Phases a pod never leaves. It cannot become Ready, so waiting for one is waiting forever. */
162
+ const TERMINAL_PHASES = new Set(['Succeeded', 'Failed']);
163
+ /**
164
+ * Whether a matching pod belongs to the set this converge is responsible for.
165
+ *
166
+ * Excluded are pods on their way out (`metadata.deletionTimestamp` set) and pods that have
167
+ * already finished (`Succeeded`/`Failed`). Both are matched by the selector and neither
168
+ * will ever serve a statement, so counting them would make `fanout` hang until its budget
169
+ * expired and then fail on a pod nobody was waiting for. Everything else — Ready, Pending,
170
+ * Running-but-not-Ready, phase unknown — is in the set and must become Ready.
171
+ */
172
+ function isMatchingPod(pod) {
173
+ return pod.terminating !== true && !TERMINAL_PHASES.has(pod.phase ?? '');
174
+ }
175
+ /** `name: Ready` / `name: Pending, not Ready` — what a failure has to name to be actionable. */
176
+ function describePod(pod) {
177
+ if (pod.ready)
178
+ return `${pod.name}: Ready`;
179
+ return `${pod.name}: ${pod.phase ?? 'phase unknown'}, not Ready`;
180
+ }
181
+ function byName(left, right) {
182
+ return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
183
+ }
184
+ /**
185
+ * One `list pods` call, reduced to the matching set and sorted by name.
186
+ *
187
+ * Sorted so the recorded pod set, the execution order and every error message are stable
188
+ * across converges.
189
+ */
190
+ async function listMatchingPods(context) {
191
+ const pods = await context.executor.listPods(context.config.target.namespace, context.config.target.podSelector, context.abortSignal);
192
+ return [...pods].filter(isMatchingPod).sort(byName);
193
+ }
194
+ /**
195
+ * The mid-run empty set, in the words a reader needs to recognise the race.
196
+ *
197
+ * A set that goes empty after this run has already applied to pods is not convergence and
198
+ * is not "nothing matched": it is the window in which a pod has been deleted and its
199
+ * replacement has not yet appeared. Naming the count that WAS applied to, and the
200
+ * successor that would otherwise be missed, is what tells the reader which of the two
201
+ * empty-set situations they are looking at.
202
+ */
203
+ function fanoutSetBecameEmptyClause(appliedSoFar) {
204
+ return (`the matching set became empty after ${appliedSoFar} pod(s) were applied; a same-named ` +
205
+ `successor would be unapplied`);
206
+ }
207
+ /**
208
+ * `fanout`: EVERY matching pod, or none at all.
209
+ *
210
+ * The rule this enforces is that a `fanout` converge is never partial. Taking whichever
211
+ * pods happen to be Ready is what makes a StatefulSet mid-rollout — one Ready replica and
212
+ * two Pending ones — record a successful, fingerprinted, cluster-wide apply after updating
213
+ * a single server; the remaining replicas then come up with no schema and the fingerprint
214
+ * says the work is done. So the whole matching set is enumerated first and each pod must
215
+ * become Ready within `waitForPod.timeoutMs`:
216
+ *
217
+ * - a pod that never becomes Ready fails the converge, naming the pods and their phases;
218
+ * - a pod without the requested container fails IMMEDIATELY rather than after the budget,
219
+ * because no amount of waiting adds a container to a running pod.
220
+ *
221
+ * Failing is the conservative outcome: alchemy retries a failed converge, and the
222
+ * fingerprint is recorded only on success, so the next converge applies the full set. A
223
+ * recorded partial apply would never be retried at all.
224
+ *
225
+ * `deadline` lets {@link applyFanoutUntilCovered} spend ONE `waitForPod` budget across all
226
+ * of its reconcile passes rather than handing each pass a fresh one, so a set that keeps
227
+ * churning cannot stretch the converge without limit.
228
+ *
229
+ * `appliedSoFar` distinguishes the two ways the matching set can be empty. Empty from the
230
+ * start is "no pod matched the selector" — a selector or a namespace to fix. Empty AFTER
231
+ * this run applied to some pods is the replacement race {@link fanoutSetBecameEmptyClause}
232
+ * names, and it gets the same budget: a single-replica StatefulSet being replaced shows an
233
+ * empty set between the old pod disappearing and its same-named successor appearing, and
234
+ * the successor must be waited for and applied to rather than declared covered.
235
+ */
236
+ async function selectFanoutPods(context, budgetDeadline, appliedSoFar = 0) {
237
+ const { config, resourceId } = context;
238
+ const deps = runtimeDeps(context);
239
+ const timeoutMs = config.waitForPod?.timeoutMs ?? DEFAULT_WAIT_FOR_POD_TIMEOUT_MS;
240
+ const container = resolveContainer(config);
241
+ const deadline = budgetDeadline ?? deps.now() + timeoutMs;
242
+ for (;;) {
243
+ const matching = await listMatchingPods(context);
244
+ const withoutContainer = matching.filter((pod) => !hasContainer(pod, container));
245
+ if (withoutContainer.length > 0) {
246
+ throw new ClickHouseSchemaError(`ClickHouseSchema '${resourceId}': execution.mode 'fanout' applies the statements to ` +
247
+ `EVERY pod matching ${selectorText(config)} in namespace ` +
248
+ `'${config.target.namespace}', and ${withoutContainer.length} of ${matching.length} ` +
249
+ `has no container '${container}' (` +
250
+ `${withoutContainer.map((pod) => `${pod.name}: ${pod.containers.join(', ')}`).join('; ')}` +
251
+ `). Set target.container, or narrow target.podSelector to the server pods.`, resourceId);
252
+ }
253
+ const notReady = matching.filter((pod) => !pod.ready);
254
+ if (matching.length > 0 && notReady.length === 0)
255
+ return matching;
256
+ if (deps.now() >= deadline) {
257
+ const state = matching.length === 0
258
+ ? appliedSoFar > 0
259
+ ? fanoutSetBecameEmptyClause(appliedSoFar)
260
+ : 'no pod matched the selector'
261
+ : `${notReady.length} of ${matching.length} pod(s) were still not Ready ` +
262
+ `(${notReady.map(describePod).join('; ')})`;
263
+ throw new ClickHouseSchemaError(`ClickHouseSchema '${resourceId}': execution.mode 'fanout' applies the statements to ` +
264
+ `EVERY pod matching ${selectorText(config)} in namespace ` +
265
+ `'${config.target.namespace}' and never to a subset, but after ${timeoutMs}ms ` +
266
+ `${state}. Let the rollout finish, raise waitForPod.timeoutMs, or switch to ` +
267
+ `execution.mode 'onCluster'.`, resourceId);
268
+ }
269
+ await deps.sleep(Math.min(POD_POLL_INTERVAL_MS, Math.max(0, deadline - deps.now())));
270
+ }
271
+ }
272
+ /**
273
+ * `onCluster`: the ONE pod that initiates the distributed DDL.
274
+ *
275
+ * The opposite trade-off to {@link selectFanoutPods}, and deliberately so: the statements
276
+ * distribute themselves through Keeper's DDL queue, so a pod that is still starting is not
277
+ * a pod this converge has to wait for — the server it eventually becomes picks the DDL up
278
+ * from the queue. Only one usable Ready pod is needed, which is what makes `onCluster` the
279
+ * right mode on a large cluster where some replica is almost always rolling.
280
+ *
281
+ * EVERY Ready pod is considered, not just the first: a rollout can leave a Ready pod whose
282
+ * container set does not match — a sidecar-injected replica, a pod from an older template —
283
+ * and judging by the first one throws away perfectly good initiators behind it.
284
+ */
285
+ async function selectInitiatorPod(context) {
286
+ const { config, resourceId } = context;
287
+ const deps = runtimeDeps(context);
288
+ const timeoutMs = config.waitForPod?.timeoutMs ?? DEFAULT_WAIT_FOR_POD_TIMEOUT_MS;
289
+ const container = resolveContainer(config);
290
+ const deadline = deps.now() + timeoutMs;
291
+ let lastSeen = 0;
292
+ for (;;) {
293
+ const matching = await listMatchingPods(context);
294
+ lastSeen = matching.length;
295
+ const ready = matching.filter((pod) => pod.ready);
296
+ const initiator = ready.find((pod) => hasContainer(pod, container));
297
+ if (initiator)
298
+ return [initiator];
299
+ if (ready.length > 0) {
300
+ throw new ClickHouseSchemaError(`ClickHouseSchema '${resourceId}': no Ready pod in namespace ` +
301
+ `'${config.target.namespace}' has a container '${container}' (` +
302
+ `${ready.map((pod) => `${pod.name}: ${pod.containers.join(', ')}`).join('; ')}). ` +
303
+ `Set target.container.`, resourceId);
304
+ }
305
+ if (deps.now() >= deadline) {
306
+ throw new ClickHouseSchemaError(`ClickHouseSchema '${resourceId}': no Ready pod matched ${selectorText(config)} in ` +
307
+ `namespace '${config.target.namespace}' within ${timeoutMs}ms (${lastSeen} pod(s) matched ` +
308
+ `the selector but none were Ready).`, resourceId);
309
+ }
310
+ await deps.sleep(Math.min(POD_POLL_INTERVAL_MS, Math.max(0, deadline - deps.now())));
311
+ }
312
+ }
313
+ /**
314
+ * Poll until the server pods the execution model needs are Ready, or the budget runs out.
315
+ *
316
+ * A ClickHouse server accepts connections only once it is Ready, and a CHI rollout has a
317
+ * window where pods exist but are still replaying logs — exec'ing then produces a
318
+ * connection-refused that looks like a SQL failure. Waiting for readiness first is what
319
+ * makes "ordered after the instance is ready" true in practice as well as in the
320
+ * dependency graph.
321
+ *
322
+ * WHICH pods have to be Ready is the execution model's whole difference: `fanout` reaches
323
+ * every server itself and so needs all of them ({@link selectFanoutPods}); `onCluster`
324
+ * hands the statements to Keeper and so needs exactly one ({@link selectInitiatorPod}).
325
+ */
326
+ export async function selectExecutionPods(context) {
327
+ return context.config.execution.mode === 'fanout'
328
+ ? await selectFanoutPods(context)
329
+ : await selectInitiatorPod(context);
330
+ }
331
+ /**
332
+ * Run one statement on one pod, retrying only TRANSIENT transport failures.
333
+ *
334
+ * The two failure modes are kept strictly apart (see {@link ClickHouseExecutor}): a
335
+ * rejected exec is the transport, a non-zero exit code is the server. Re-issuing a
336
+ * statement the server actively rejected cannot help and, for a statement that is not
337
+ * perfectly idempotent, can compound the damage.
338
+ */
339
+ async function runStatement(context, podName, statement, index) {
340
+ const { config, resourceId } = context;
341
+ const deps = runtimeDeps(context);
342
+ const maxAttempts = config.retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
343
+ const backoffMs = config.retry?.backoffMs ?? DEFAULT_BACKOFF_MS;
344
+ const command = {
345
+ namespace: config.target.namespace,
346
+ podName,
347
+ container: resolveContainer(config),
348
+ command: renderClickHouseCommand(config),
349
+ stdin: `${statement.trim()}\n`,
350
+ timeoutMs: config.statementTimeoutMs ?? DEFAULT_STATEMENT_TIMEOUT_MS,
351
+ };
352
+ let lastTransport;
353
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
354
+ let result;
355
+ try {
356
+ result = await context.executor.exec(command, context.abortSignal);
357
+ }
358
+ catch (error) {
359
+ lastTransport = error instanceof Error ? error : new Error(String(error));
360
+ if (attempt === maxAttempts)
361
+ break;
362
+ await deps.sleep(backoffMs * attempt);
363
+ continue;
364
+ }
365
+ if (result.exitCode === 0)
366
+ return;
367
+ // A server-side failure. The code and exception class are parsed from the RAW output
368
+ // — before redaction, which may well blank the very line that carries them — and the
369
+ // message is then redacted against the statement that was submitted.
370
+ const raw = `${result.stderr}\n${result.stdout}`.trim();
371
+ const code = parseClickHouseErrorCode(raw);
372
+ const exception = parseClickHouseExceptionName(raw);
373
+ throw new ClickHouseSchemaError(`ClickHouseSchema '${resourceId}': statement ${index} failed on pod ${podName}` +
374
+ `${code === undefined ? '' : ` with ClickHouse code ${code}`}` +
375
+ `${exception === undefined ? '' : ` (${exception})`} ` +
376
+ `(exit ${result.exitCode}).`, resourceId, index, code, redactClickHouseOutput(raw, statement), exception);
377
+ }
378
+ throw new ClickHouseSchemaError(`ClickHouseSchema '${resourceId}': exec transport failed for statement ${index} on pod ` +
379
+ `${podName} after ${maxAttempts} attempt(s): ` +
380
+ redactClickHouseOutput(lastTransport?.message ?? 'unknown error', statement), resourceId, index, undefined, undefined, undefined, lastTransport ? { cause: lastTransport } : undefined);
381
+ }
382
+ /**
383
+ * Run an ordered statement list against every selected pod, stopping at the first failure.
384
+ *
385
+ * Per POD, not per statement: each server receives the whole list in order, because the
386
+ * order is what makes the list meaningful (a table cannot be created before its database)
387
+ * and interleaving across pods would only make a partial failure harder to read.
388
+ */
389
+ export async function runStatements(context, statements) {
390
+ const pods = await selectExecutionPods(context);
391
+ for (const pod of pods) {
392
+ await runStatementsOnPod(context, pod, statements);
393
+ }
394
+ return { podNames: pods.map((pod) => pod.name), pods };
395
+ }
396
+ /** The whole ordered list against ONE pod, in statement order. */
397
+ async function runStatementsOnPod(context, pod, statements) {
398
+ for (const [index, statement] of statements.entries()) {
399
+ await runStatement(context, pod.name, statement, index);
400
+ }
401
+ }
402
+ /**
403
+ * The identity of one pod, for the set comparison: NAME AND UID.
404
+ *
405
+ * The name alone is not an identity — a StatefulSet replica that is deleted and recreated
406
+ * comes back as `chi-orders-0-0-0` with an empty disk and no schema — so a replacement is
407
+ * invisible to a set of names. `metadata.uid` is unique per pod object and never reused,
408
+ * which is exactly the distinction that was missing. The NUL separator keeps a name that
409
+ * happens to contain the delimiter from colliding with a UID.
410
+ */
411
+ function podKey(pod) {
412
+ return `${pod.name}\u0000${pod.uid ?? ''}`;
413
+ }
414
+ /** What goes into state for one applied pod. */
415
+ function appliedPod(pod) {
416
+ return { name: pod.name, ...(pod.uid !== undefined ? { uid: pod.uid } : {}) };
417
+ }
418
+ /** Set equality over two key lists, order-insensitively. */
419
+ function sameKeySet(left, right) {
420
+ if (left.length !== right.length)
421
+ return false;
422
+ const sortedLeft = [...left].sort();
423
+ const sortedRight = [...right].sort();
424
+ return sortedLeft.every((key, index) => key === sortedRight[index]);
425
+ }
426
+ /**
427
+ * Whether the live pod set is the one the recorded state covers, by NAME AND UID.
428
+ *
429
+ * State written before UIDs were recorded has only names, and comparing a name-only record
430
+ * against UID-bearing live pods would re-apply on every converge forever. Such state falls
431
+ * back to the name comparison; the apply it eventually does rewrites state in the new
432
+ * shape, and the UID guarantee starts from there.
433
+ */
434
+ function samePodIdentitySet(live, previous) {
435
+ if (previous.pods === undefined) {
436
+ return sameKeySet(live.map((pod) => pod.name), previous.podNames);
437
+ }
438
+ return sameKeySet(live.map(podKey), previous.pods.map(podKey));
439
+ }
440
+ /**
441
+ * THE EXIT PREDICATE of a `fanout` apply — the single condition under which a pod set may
442
+ * be recorded as successfully applied. All three parts are load-bearing:
443
+ *
444
+ * 1. `observed` is NON-EMPTY. An empty observation is never convergence: "no pod is
445
+ * uncovered" is vacuously true of the empty set, so a run that applied to a pod which
446
+ * then disappeared would otherwise commit `pods: []` as success. During a single-replica
447
+ * StatefulSet replacement that empty window sits exactly between the old pod going away
448
+ * and its SAME-NAMED successor arriving, and the successor — a new pod object with an
449
+ * empty disk — would be left unapplied behind a fingerprint that says the work is done.
450
+ * 2. every pod in `observed` is in `applied`, by NAME AND UID ({@link podKey}). Coverage is
451
+ * of pod OBJECTS; a replacement wearing the applied pod's name is not covered by it.
452
+ * 3. `observed` equals the observation BEFORE it. The two preceding parts describe a single
453
+ * snapshot, and a snapshot cannot distinguish a settled set from one that is still
454
+ * moving: a pod that vanished between the selection and the re-list leaves every
455
+ * remaining pod covered while the set itself is mid-change. Requiring two consecutive
456
+ * identical observations is what makes the set, and not merely the last glimpse of it,
457
+ * the thing that is recorded.
458
+ *
459
+ * The cost of part 3 on a settled cluster is nothing: the selection's list and the re-list
460
+ * are the two observations, so one pass still converges.
461
+ */
462
+ function fanoutCovered(observed, previous, applied) {
463
+ if (observed.length === 0)
464
+ return false;
465
+ if (!observed.every((pod) => applied.has(podKey(pod))))
466
+ return false;
467
+ return sameKeySet(observed.map(podKey), previous.map(podKey));
468
+ }
469
+ /** Which part of {@link fanoutCovered} an observation failed, in one readable clause. */
470
+ function unconvergedReason(observed, previous, uncovered, appliedTotal) {
471
+ if (observed.length === 0)
472
+ return fanoutSetBecameEmptyClause(appliedTotal);
473
+ if (uncovered.length > 0) {
474
+ return (`${uncovered.length} pod(s) had still not been applied to ` +
475
+ `(${uncovered.map((pod) => pod.name).join(', ')})`);
476
+ }
477
+ return (`the matching set was still moving between two consecutive observations ` +
478
+ `(${previous.map((pod) => pod.name).join(', ')} then ` +
479
+ `${observed.map((pod) => pod.name).join(', ')})`);
480
+ }
481
+ /**
482
+ * `fanout`: apply, re-list, apply again — until the LIVE pod set is covered, or fail.
483
+ *
484
+ * THE SCALE RACE. Selection sees one matching set; a replica can be added, replaced or
485
+ * removed while the statements are still running, so the set that is live when the run
486
+ * finishes is not necessarily the set that was applied to. Recording the live set instead
487
+ * would claim coverage of a pod nothing ran on — and that claim is never revisited,
488
+ * because it makes the two sets agree. Recording only what was reached and WARNING about
489
+ * the difference does not fix it either: alchemy commits that state without another
490
+ * reconcile, so the newly observed pod stays unapplied until some future deployment
491
+ * happens to change the fingerprint or the set. A converge that observed an uncovered pod
492
+ * and returned success is exactly the half-applied schema `fanout` exists to rule out.
493
+ *
494
+ * So the loop keeps going instead:
495
+ *
496
+ * 1. select the complete Ready set (the all-or-nothing rules of {@link selectFanoutPods});
497
+ * 2. apply the whole ordered list to every pod not yet applied to IN THIS RUN — a pod
498
+ * already covered by an earlier pass is not re-run, so a settled set costs one pass;
499
+ * 3. re-list. Pods that appeared are uncovered and get another pass; pods that DISAPPEARED
500
+ * are dropped from the recorded set, because state must describe coverage of pods that
501
+ * exist rather than of ones that are gone;
502
+ * 4. repeat until {@link fanoutCovered} — the ONE exit predicate — holds.
503
+ *
504
+ * Two bounds keep a genuinely churning cluster from looping forever: `maxReconcilePasses`
505
+ * and the overall `waitForPod` budget, which is spent ACROSS the passes rather than renewed
506
+ * by each one. Hitting either before the predicate holds FAILS the converge, naming what is
507
+ * wrong with the observation ({@link unconvergedReason}) — never returns a successful state
508
+ * — so alchemy does not commit a partial apply and the next converge starts over. Failing is
509
+ * the recoverable outcome; a recorded partial apply would never be retried at all.
510
+ */
511
+ async function applyFanoutUntilCovered(context, statements) {
512
+ const { config, resourceId } = context;
513
+ const deps = runtimeDeps(context);
514
+ const maxPasses = config.maxReconcilePasses ?? DEFAULT_MAX_RECONCILE_PASSES;
515
+ const timeoutMs = config.waitForPod?.timeoutMs ?? DEFAULT_WAIT_FOR_POD_TIMEOUT_MS;
516
+ const budgetDeadline = deps.now() + timeoutMs;
517
+ const logger = getComponentLogger('alchemy-clickhouse-schema');
518
+ /** Pods this run has applied to, by {@link podKey}; pruned to what is still live. */
519
+ const applied = new Map();
520
+ /** How many pods this run has applied to IN TOTAL, including ones since departed. */
521
+ let appliedTotal = 0;
522
+ for (let pass = 1;; pass += 1) {
523
+ // The selection's own final list is the FIRST of the two observations the exit
524
+ // predicate compares; the re-list below is the second.
525
+ const selected = await selectFanoutPods(context, budgetDeadline, appliedTotal);
526
+ for (const pod of selected) {
527
+ if (applied.has(podKey(pod)))
528
+ continue;
529
+ await runStatementsOnPod(context, pod, statements);
530
+ applied.set(podKey(pod), pod);
531
+ appliedTotal += 1;
532
+ }
533
+ const live = await listMatchingPods(context);
534
+ const liveKeys = new Set(live.map(podKey));
535
+ for (const key of [...applied.keys()]) {
536
+ if (!liveKeys.has(key))
537
+ applied.delete(key);
538
+ }
539
+ if (fanoutCovered(live, selected, applied))
540
+ return [...applied.values()].sort(byName);
541
+ const uncovered = live.filter((pod) => !applied.has(podKey(pod)));
542
+ if (pass >= maxPasses) {
543
+ throw new ClickHouseSchemaError(`ClickHouseSchema '${resourceId}': execution.mode 'fanout' applies the statements to ` +
544
+ `EVERY pod matching ${selectorText(config)} in namespace ` +
545
+ `'${config.target.namespace}', but the pod set kept changing: after ${maxPasses} ` +
546
+ `reconcile pass(es), ${unconvergedReason(live, selected, uncovered, appliedTotal)}. ` +
547
+ `Nothing is recorded, so the next converge re-applies the whole list. Let the ` +
548
+ `rollout settle, raise maxReconcilePasses, or switch to execution.mode 'onCluster'.`, resourceId);
549
+ }
550
+ logger.info('ClickHouse server pod set changed while the schema was being applied; reconciling again', {
551
+ resourceId,
552
+ pass,
553
+ maxPasses,
554
+ appliedTo: [...applied.values()].map((pod) => pod.name),
555
+ uncovered: uncovered.map((pod) => pod.name),
556
+ reason: unconvergedReason(live, selected, uncovered, appliedTotal),
557
+ });
558
+ }
559
+ }
560
+ /**
561
+ * Converge the schema: no-op when nothing changed, otherwise re-run EVERY statement.
562
+ *
563
+ * There is no partial application. A fingerprint change re-runs the whole ordered list,
564
+ * which is only correct because every statement is required to be idempotent — and it is
565
+ * also what makes a failed converge recoverable: the fingerprint is recorded only after
566
+ * the last statement succeeds, so a run that dies at statement 7 re-runs 0..6 next time.
567
+ *
568
+ * Under `fanout` the POD SET is part of what "nothing changed" means, and the set is
569
+ * compared by NAME AND UID. A statement list applied to two replicas is not applied to the
570
+ * third one that a scale-out added, nor to the replacement a drain put back under the same
571
+ * name with an empty disk, and the fingerprint cannot see either — so an otherwise-unchanged
572
+ * converge still lists pods and re-applies when the set moved. That listing is one API call
573
+ * and no exec, so an unchanged, unchanged-topology converge stays free. When it does apply,
574
+ * it applies until the live set is COVERED; see {@link applyFanoutUntilCovered}.
575
+ *
576
+ * `onCluster` has no coverage to reconcile: one execution hands the DDL to Keeper's queue,
577
+ * which reaches the pods this converge never looked at, including ones that appear later.
578
+ */
579
+ export async function applyClickHouseSchema(context, previous) {
580
+ const { config } = context;
581
+ const deps = runtimeDeps(context);
582
+ if (previous && !needsApply(config, previous, context.clusterId)) {
583
+ if (config.execution.mode !== 'fanout')
584
+ return previous;
585
+ const live = await selectExecutionPods(context);
586
+ if (samePodIdentitySet(live, previous))
587
+ return previous;
588
+ }
589
+ const pods = config.execution.mode === 'fanout'
590
+ ? await applyFanoutUntilCovered(context, config.statements)
591
+ : (await runStatements(context, config.statements)).pods;
592
+ return {
593
+ fingerprint: computeFingerprint(config),
594
+ appliedAt: new Date(deps.now()).toISOString(),
595
+ statementCount: config.statements.length,
596
+ database: resolveDatabase(config),
597
+ target: config.target,
598
+ podNames: pods.map((pod) => pod.name),
599
+ pods: pods.map(appliedPod),
600
+ ...(context.clusterId !== undefined ? { clusterId: context.clusterId } : {}),
601
+ };
602
+ }
603
+ /**
604
+ * Teardown. `retain` touches nothing at all — not even the cluster — so a destroyed
605
+ * stack leaves the data and the schema exactly as they were.
606
+ */
607
+ export async function deleteClickHouseSchema(context) {
608
+ const statements = context.config.deleteStatements ?? [];
609
+ if (context.config.onDelete !== 'run' || statements.length === 0)
610
+ return;
611
+ await runStatements(context, statements);
612
+ }
613
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/alchemy/clickhouse-schema/runner.ts"],"names":[],"mappings":"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"}