@fjall/components-infrastructure 16.0.1 → 18.0.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.
- package/dist/lib/config/aws/scpPreset.js +103 -17
- package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +103 -8
- package/dist/lib/patterns/aws/clickhouseDatabase.js +51 -12
- package/dist/lib/patterns/aws/database.d.ts +1 -1
- package/dist/lib/patterns/aws/database.js +1 -1
- package/dist/lib/patterns/aws/interfaces/domain.d.ts +1 -0
- package/dist/lib/patterns/aws/storage.d.ts +8 -0
- package/dist/lib/patterns/aws/storage.js +10 -3
- package/dist/lib/patterns/aws/targets/fjallTargets.d.ts +7 -5
- package/dist/lib/patterns/aws/targets/fjallTargets.js +9 -6
- package/dist/lib/patterns/aws/targets/targetResolution.d.ts +8 -5
- package/dist/lib/patterns/aws/targets/targetResolution.js +11 -8
- package/dist/lib/resources/aws/database/clickhouseBackupScript.d.ts +103 -0
- package/dist/lib/resources/aws/database/clickhouseBackupScript.js +123 -0
- package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +132 -20
- package/dist/lib/resources/aws/database/clickhouseConstants.js +126 -15
- package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +33 -1
- package/dist/lib/resources/aws/database/clickhouseSchemas.js +31 -0
- package/dist/lib/resources/aws/database/clickhouseStorage.d.ts +58 -0
- package/dist/lib/resources/aws/database/clickhouseStorage.js +87 -0
- package/dist/lib/resources/aws/database/clickhouseTuning.d.ts +5 -6
- package/dist/lib/resources/aws/database/clickhouseTuning.js +39 -16
- package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +21 -1
- package/dist/lib/resources/aws/database/clickhouseUserData.js +22 -6
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +22 -1
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +54 -5
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +106 -10
- package/dist/lib/resources/aws/storage/s3.d.ts +19 -7
- package/dist/lib/resources/aws/storage/s3.js +15 -3
- package/package.json +3 -3
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The backup task's shell body: take the backup, then prove it restores.
|
|
3
|
+
*
|
|
4
|
+
* `BACKUP DATABASE … TO S3(…)` returning `BACKUP_CREATED` means ClickHouse
|
|
5
|
+
* finished writing and the statement did not raise. It does NOT mean the
|
|
6
|
+
* bytes it wrote can be read back — and the gap between those two is the
|
|
7
|
+
* whole reason a backup exists. So the task restores what it just wrote,
|
|
8
|
+
* into a scratch database, and says so in its own log line.
|
|
9
|
+
*
|
|
10
|
+
* The restore is a FULL one, deliberately. `SETTINGS structure_only = 1`
|
|
11
|
+
* looks like the cheap version of this check and is not a version of it at
|
|
12
|
+
* all: on 26.3.17.56 a structure-only restore of a backup whose entire
|
|
13
|
+
* `data/` tree had been deleted returned `RESTORED` and exit 0, because it
|
|
14
|
+
* reads `metadata/` and never opens a part. A verification that passes on a
|
|
15
|
+
* backup with no data in it is worse than no verification — it converts an
|
|
16
|
+
* unrestorable backup into a green signal. The full restore fails that same
|
|
17
|
+
* case loudly (`STD_EXCEPTION`, missing `checksums.txt`), which is the
|
|
18
|
+
* behaviour the alarm is built on.
|
|
19
|
+
*/
|
|
20
|
+
export interface ClickHouseBackupScriptParams {
|
|
21
|
+
/** Fully-formed `clickhouse-client …` invocation, minus `--query`. */
|
|
22
|
+
readonly client: string;
|
|
23
|
+
/** Database the backup covers, and the restore compares against. */
|
|
24
|
+
readonly databaseName: string;
|
|
25
|
+
/** `https://<bucket>.s3.<region>.amazonaws.com/backup/` — trailing slash. */
|
|
26
|
+
readonly backupDestUrl: string;
|
|
27
|
+
/** Critical free-space floor in GiB; the verify gate keeps twice this
|
|
28
|
+
* clear so the check can never be the thing that pages. */
|
|
29
|
+
readonly diskFreeCriticalGib: number;
|
|
30
|
+
/** Declared data-volume size in GiB; bounds the verify restore's timeout
|
|
31
|
+
* via `clickHouseBackupVerifyTimeoutSeconds`. */
|
|
32
|
+
readonly storageGb: number;
|
|
33
|
+
/** When false, the script takes the backup and stops — no restore verify.
|
|
34
|
+
* The caller (`ClickHouseDatabase`) also withholds the verify alarms, so
|
|
35
|
+
* the two halves of the contract disable together. */
|
|
36
|
+
readonly verify: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Builds the backup + restore-verify script.
|
|
40
|
+
*
|
|
41
|
+
* Statement order carries decisions that are not obvious from reading it
|
|
42
|
+
* forward:
|
|
43
|
+
*
|
|
44
|
+
* 1. **The scratch database is dropped FIRST, not only last.** A restore
|
|
45
|
+
* onto an existing database of that name succeeds silently — verified on
|
|
46
|
+
* 26.3.17.56 — so a run that died between RESTORE and DROP would leave
|
|
47
|
+
* tables behind that satisfy the next run's table-set comparison no
|
|
48
|
+
* matter what the new backup contains. Dropping on the way in also makes
|
|
49
|
+
* the free-space reading below honest, since a leftover scratch copy is
|
|
50
|
+
* occupying the disk being measured.
|
|
51
|
+
*
|
|
52
|
+
* 2. **The live row count is sampled BEFORE the backup, and it arms the
|
|
53
|
+
* zero-rows check.** A restore of a genuinely empty database restores
|
|
54
|
+
* zero rows and is CORRECT; failing it would page FAILED on every fresh
|
|
55
|
+
* stack from night one. So `restored_rows=0` is a failure only when the
|
|
56
|
+
* source had rows at backup time. A failed source sample reads as
|
|
57
|
+
* `unknown`, which arms the check — an unreadable source is not evidence
|
|
58
|
+
* the database was empty (fail closed, per the read-failure posture).
|
|
59
|
+
*
|
|
60
|
+
* 3. **The restore is gated on free space, and the gate reads the same
|
|
61
|
+
* quantity the critical disk alarm guards.** A full restore writes a
|
|
62
|
+
* second copy of the database onto the volume it is verifying. That is
|
|
63
|
+
* affordable at today's size and stops being affordable at some larger
|
|
64
|
+
* one, and the failure mode if it is left ungated is the worst kind: the
|
|
65
|
+
* backup check fills the disk that the backups exist to protect. When
|
|
66
|
+
* the copy would not leave `diskFreeCriticalGib × margin` clear, the run
|
|
67
|
+
* skips the restore and SAYS SO with its own marker. Silent degradation
|
|
68
|
+
* is not available here — a skipped verify that logged nothing would be
|
|
69
|
+
* indistinguishable from a passing one. The same discipline covers the
|
|
70
|
+
* probe itself: a failed free-space read emits the SKIPPED marker (the
|
|
71
|
+
* verify did not run, which is exactly what that marker means) and exits
|
|
72
|
+
* non-zero, rather than being the one silent path out of the script.
|
|
73
|
+
*
|
|
74
|
+
* 4. **The restore is bounded by `timeout`, sized from the volume.** A hung
|
|
75
|
+
* restore is the one backup failure with no signal: `BACKUP_CREATED` is
|
|
76
|
+
* already emitted so the heartbeat is satisfied, and the verify alarms
|
|
77
|
+
* count markers rather than their absence. The bound converts that
|
|
78
|
+
* silence into exit 124, which takes the same branch as a raised
|
|
79
|
+
* restore. It scales with `storageGb` because the largest restore the
|
|
80
|
+
* free-space gate admits scales with the volume — a flat bound would
|
|
81
|
+
* misreport every healthy verify as FAILED past the size where the
|
|
82
|
+
* restore outgrows it.
|
|
83
|
+
*
|
|
84
|
+
* 5. **The comparison is on the table SET, never on row counts — and it
|
|
85
|
+
* ignores materialised-view inner tables.** The database keeps ingesting
|
|
86
|
+
* during and after the backup, so the restored copy is a point-in-time
|
|
87
|
+
* from somewhere inside the backup window and can never be expected to
|
|
88
|
+
* match live counts. Rows are checked only against zero (armed per 2).
|
|
89
|
+
* MV inner tables (`.inner_id.<uuid>` on Atomic databases) are excluded
|
|
90
|
+
* from both sides of the set comparison: `RESTORE … AS <scratch>` on the
|
|
91
|
+
* same server assigns NEW UUIDs, so the live database's inner names can
|
|
92
|
+
* never appear in the scratch database and a raw name comparison reports
|
|
93
|
+
* every implicit MV as missing — a FAILED page against a good backup.
|
|
94
|
+
* The MVs themselves (and their data, via the restored inner tables)
|
|
95
|
+
* remain fully compared.
|
|
96
|
+
*
|
|
97
|
+
* Every statement carries an explicit `|| …` failure branch rather than
|
|
98
|
+
* relying on `set -e`. The TLS preamble this script is concatenated onto
|
|
99
|
+
* sets `set -eu`, but ONLY when TLS is active — so a script depending on
|
|
100
|
+
* `set -e` would abort on the first failure with TLS on and blunder past it
|
|
101
|
+
* with TLS off. Explicit branches behave identically either way.
|
|
102
|
+
*/
|
|
103
|
+
export declare function buildClickHouseBackupScript(params: ClickHouseBackupScriptParams): string;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The backup task's shell body: take the backup, then prove it restores.
|
|
3
|
+
*
|
|
4
|
+
* `BACKUP DATABASE … TO S3(…)` returning `BACKUP_CREATED` means ClickHouse
|
|
5
|
+
* finished writing and the statement did not raise. It does NOT mean the
|
|
6
|
+
* bytes it wrote can be read back — and the gap between those two is the
|
|
7
|
+
* whole reason a backup exists. So the task restores what it just wrote,
|
|
8
|
+
* into a scratch database, and says so in its own log line.
|
|
9
|
+
*
|
|
10
|
+
* The restore is a FULL one, deliberately. `SETTINGS structure_only = 1`
|
|
11
|
+
* looks like the cheap version of this check and is not a version of it at
|
|
12
|
+
* all: on 26.3.17.56 a structure-only restore of a backup whose entire
|
|
13
|
+
* `data/` tree had been deleted returned `RESTORED` and exit 0, because it
|
|
14
|
+
* reads `metadata/` and never opens a part. A verification that passes on a
|
|
15
|
+
* backup with no data in it is worse than no verification — it converts an
|
|
16
|
+
* unrestorable backup into a green signal. The full restore fails that same
|
|
17
|
+
* case loudly (`STD_EXCEPTION`, missing `checksums.txt`), which is the
|
|
18
|
+
* behaviour the alarm is built on.
|
|
19
|
+
*/
|
|
20
|
+
import { CLICKHOUSE_BACKUP_SCRATCH_DATABASE, CLICKHOUSE_BACKUP_VERIFY_FREE_SPACE_MARGIN, CLICKHOUSE_BACKUP_VERIFY_FAILED_MARKER, CLICKHOUSE_BACKUP_VERIFY_OK_MARKER, CLICKHOUSE_BACKUP_VERIFY_SKIPPED_MARKER, clickHouseBackupVerifyTimeoutSeconds } from "./clickhouseConstants.js";
|
|
21
|
+
const BYTES_PER_GIB = 1024 ** 3;
|
|
22
|
+
/**
|
|
23
|
+
* Builds the backup + restore-verify script.
|
|
24
|
+
*
|
|
25
|
+
* Statement order carries decisions that are not obvious from reading it
|
|
26
|
+
* forward:
|
|
27
|
+
*
|
|
28
|
+
* 1. **The scratch database is dropped FIRST, not only last.** A restore
|
|
29
|
+
* onto an existing database of that name succeeds silently — verified on
|
|
30
|
+
* 26.3.17.56 — so a run that died between RESTORE and DROP would leave
|
|
31
|
+
* tables behind that satisfy the next run's table-set comparison no
|
|
32
|
+
* matter what the new backup contains. Dropping on the way in also makes
|
|
33
|
+
* the free-space reading below honest, since a leftover scratch copy is
|
|
34
|
+
* occupying the disk being measured.
|
|
35
|
+
*
|
|
36
|
+
* 2. **The live row count is sampled BEFORE the backup, and it arms the
|
|
37
|
+
* zero-rows check.** A restore of a genuinely empty database restores
|
|
38
|
+
* zero rows and is CORRECT; failing it would page FAILED on every fresh
|
|
39
|
+
* stack from night one. So `restored_rows=0` is a failure only when the
|
|
40
|
+
* source had rows at backup time. A failed source sample reads as
|
|
41
|
+
* `unknown`, which arms the check — an unreadable source is not evidence
|
|
42
|
+
* the database was empty (fail closed, per the read-failure posture).
|
|
43
|
+
*
|
|
44
|
+
* 3. **The restore is gated on free space, and the gate reads the same
|
|
45
|
+
* quantity the critical disk alarm guards.** A full restore writes a
|
|
46
|
+
* second copy of the database onto the volume it is verifying. That is
|
|
47
|
+
* affordable at today's size and stops being affordable at some larger
|
|
48
|
+
* one, and the failure mode if it is left ungated is the worst kind: the
|
|
49
|
+
* backup check fills the disk that the backups exist to protect. When
|
|
50
|
+
* the copy would not leave `diskFreeCriticalGib × margin` clear, the run
|
|
51
|
+
* skips the restore and SAYS SO with its own marker. Silent degradation
|
|
52
|
+
* is not available here — a skipped verify that logged nothing would be
|
|
53
|
+
* indistinguishable from a passing one. The same discipline covers the
|
|
54
|
+
* probe itself: a failed free-space read emits the SKIPPED marker (the
|
|
55
|
+
* verify did not run, which is exactly what that marker means) and exits
|
|
56
|
+
* non-zero, rather than being the one silent path out of the script.
|
|
57
|
+
*
|
|
58
|
+
* 4. **The restore is bounded by `timeout`, sized from the volume.** A hung
|
|
59
|
+
* restore is the one backup failure with no signal: `BACKUP_CREATED` is
|
|
60
|
+
* already emitted so the heartbeat is satisfied, and the verify alarms
|
|
61
|
+
* count markers rather than their absence. The bound converts that
|
|
62
|
+
* silence into exit 124, which takes the same branch as a raised
|
|
63
|
+
* restore. It scales with `storageGb` because the largest restore the
|
|
64
|
+
* free-space gate admits scales with the volume — a flat bound would
|
|
65
|
+
* misreport every healthy verify as FAILED past the size where the
|
|
66
|
+
* restore outgrows it.
|
|
67
|
+
*
|
|
68
|
+
* 5. **The comparison is on the table SET, never on row counts — and it
|
|
69
|
+
* ignores materialised-view inner tables.** The database keeps ingesting
|
|
70
|
+
* during and after the backup, so the restored copy is a point-in-time
|
|
71
|
+
* from somewhere inside the backup window and can never be expected to
|
|
72
|
+
* match live counts. Rows are checked only against zero (armed per 2).
|
|
73
|
+
* MV inner tables (`.inner_id.<uuid>` on Atomic databases) are excluded
|
|
74
|
+
* from both sides of the set comparison: `RESTORE … AS <scratch>` on the
|
|
75
|
+
* same server assigns NEW UUIDs, so the live database's inner names can
|
|
76
|
+
* never appear in the scratch database and a raw name comparison reports
|
|
77
|
+
* every implicit MV as missing — a FAILED page against a good backup.
|
|
78
|
+
* The MVs themselves (and their data, via the restored inner tables)
|
|
79
|
+
* remain fully compared.
|
|
80
|
+
*
|
|
81
|
+
* Every statement carries an explicit `|| …` failure branch rather than
|
|
82
|
+
* relying on `set -e`. The TLS preamble this script is concatenated onto
|
|
83
|
+
* sets `set -eu`, but ONLY when TLS is active — so a script depending on
|
|
84
|
+
* `set -e` would abort on the first failure with TLS on and blunder past it
|
|
85
|
+
* with TLS off. Explicit branches behave identically either way.
|
|
86
|
+
*/
|
|
87
|
+
export function buildClickHouseBackupScript(params) {
|
|
88
|
+
const { client, databaseName, backupDestUrl, diskFreeCriticalGib, storageGb, verify } = params;
|
|
89
|
+
const backupStatements = [
|
|
90
|
+
"STAMP=$(date +%Y%m%d-%H%M%S)",
|
|
91
|
+
`DEST="${backupDestUrl}backup-$STAMP/"`,
|
|
92
|
+
`${client} --query "BACKUP DATABASE ${databaseName} TO S3('$DEST')" || exit 1`
|
|
93
|
+
];
|
|
94
|
+
if (!verify) {
|
|
95
|
+
return backupStatements.join("; ");
|
|
96
|
+
}
|
|
97
|
+
const scratch = CLICKHOUSE_BACKUP_SCRATCH_DATABASE;
|
|
98
|
+
const requiredFreeBytes = Math.round(diskFreeCriticalGib *
|
|
99
|
+
BYTES_PER_GIB *
|
|
100
|
+
CLICKHOUSE_BACKUP_VERIFY_FREE_SPACE_MARGIN);
|
|
101
|
+
const verifyTimeoutSeconds = clickHouseBackupVerifyTimeoutSeconds(storageGb);
|
|
102
|
+
const dropScratch = `${client} --query "DROP DATABASE IF EXISTS ${scratch} SYNC"`;
|
|
103
|
+
const sourceRowsQuery = `SELECT sum(rows) FROM system.parts WHERE database = '${databaseName}' AND active`;
|
|
104
|
+
const fitVerdictQuery = `SELECT if((SELECT free_space FROM system.disks WHERE name = 'default')` +
|
|
105
|
+
` - (SELECT sum(bytes_on_disk) FROM system.parts WHERE database = '${databaseName}' AND active)` +
|
|
106
|
+
` >= ${requiredFreeBytes}, 'FITS', 'SKIP')`;
|
|
107
|
+
const missingTablesQuery = `SELECT count() FROM (SELECT name FROM system.tables WHERE database = '${databaseName}' AND name NOT LIKE '.inner%'` +
|
|
108
|
+
` EXCEPT SELECT name FROM system.tables WHERE database = '${scratch}' AND name NOT LIKE '.inner%')`;
|
|
109
|
+
const restoredRowsQuery = `SELECT sum(rows) FROM system.parts WHERE database = '${scratch}' AND active`;
|
|
110
|
+
return [
|
|
111
|
+
`${dropScratch} || exit 1`,
|
|
112
|
+
`SRCROWS=$(${client} --query "${sourceRowsQuery}") || SRCROWS=unknown`,
|
|
113
|
+
...backupStatements,
|
|
114
|
+
`FIT=$(${client} --query "${fitVerdictQuery}") || { echo "${CLICKHOUSE_BACKUP_VERIFY_SKIPPED_MARKER} $DEST free-space probe failed — verify did not run"; exit 1; }`,
|
|
115
|
+
`if [ "$FIT" != "FITS" ]; then echo "${CLICKHOUSE_BACKUP_VERIFY_SKIPPED_MARKER} $DEST restore would not leave ${requiredFreeBytes} bytes free"; exit 0; fi`,
|
|
116
|
+
`timeout ${verifyTimeoutSeconds} ${client} --query "RESTORE DATABASE ${databaseName} AS ${scratch} FROM S3('$DEST')" || { echo "${CLICKHOUSE_BACKUP_VERIFY_FAILED_MARKER} $DEST restore raised or exceeded ${verifyTimeoutSeconds}s"; ${dropScratch} || true; exit 1; }`,
|
|
117
|
+
`MISSING=$(${client} --query "${missingTablesQuery}") || MISSING=unknown`,
|
|
118
|
+
`ROWS=$(${client} --query "${restoredRowsQuery}") || ROWS=0`,
|
|
119
|
+
`${dropScratch} || true`,
|
|
120
|
+
`if [ "$MISSING" != "0" ] || { [ "$ROWS" = "0" ] && [ "$SRCROWS" != "0" ]; }; then echo "${CLICKHOUSE_BACKUP_VERIFY_FAILED_MARKER} $DEST missing_tables=$MISSING restored_rows=$ROWS source_rows=$SRCROWS"; exit 1; fi`,
|
|
121
|
+
`echo "${CLICKHOUSE_BACKUP_VERIFY_OK_MARKER} $DEST restored_rows=$ROWS source_rows=$SRCROWS"`
|
|
122
|
+
].join("; ");
|
|
123
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClickHouseInstanceType } from "@fjall/util/clickhouse";
|
|
1
2
|
/** Database name created at ClickHouse bootstrap; consumed by BACKUP DATABASE,
|
|
2
3
|
* OPTIMIZE TABLE, and the DatabaseName CfnOutput so all four sites share one source. */
|
|
3
4
|
export declare const CLICKHOUSE_DATABASE_NAME = "analytics";
|
|
@@ -42,28 +43,67 @@ export declare const DEFAULT_CLICKHOUSE_INSTANCE_TYPE = "m7g.medium";
|
|
|
42
43
|
* that pulls once per launch. Upstream CH CI runs its full perf + stress
|
|
43
44
|
* matrix on the Ubuntu build; Alpine is community-tier coverage. */
|
|
44
45
|
export declare const CLICKHOUSE_IMAGE = "docker.io/clickhouse/clickhouse-server:26.3.17.56";
|
|
46
|
+
/** Absolute floor of the disk-critical alarm, in GiB.
|
|
47
|
+
*
|
|
48
|
+
* A pure percentage cannot express "about to break": 85 % used is 12 GiB
|
|
49
|
+
* free on an 80 GiB volume and 600 GiB free on a 4 TiB one, and only one of
|
|
50
|
+
* those is an incident. But a pure constant cannot either, because the other
|
|
51
|
+
* half of the failure scales with the data — ClickHouse will not select a
|
|
52
|
+
* merge whose source parts need more than the free space allows, and parts
|
|
53
|
+
* grow with the table. So the default floor is the LARGER of this constant
|
|
54
|
+
* and `CLICKHOUSE_DISK_FREE_CRITICAL_SHARE` of the volume: the constant
|
|
55
|
+
* guards response time on a small disk, the share guards merge headroom on a
|
|
56
|
+
* large one. See `resolveClickHouseStorage`.
|
|
57
|
+
*
|
|
58
|
+
* 20 GiB is days of ingest headroom at the observed rate — enough lead time
|
|
59
|
+
* to grow the volume, which takes an instance restart and cannot be done in
|
|
60
|
+
* minutes. */
|
|
61
|
+
export declare const CLICKHOUSE_DISK_FREE_CRITICAL_GIB = 20;
|
|
62
|
+
/** Share of the volume the disk-critical floor rises to on larger disks.
|
|
63
|
+
* At 5 %, a 4 TiB volume pages with 200 GiB free — roughly twice a large
|
|
64
|
+
* part, so merges still have room to complete while the operator responds.
|
|
65
|
+
* The two halves cross over at 400 GiB, the default volume size. */
|
|
66
|
+
export declare const CLICKHOUSE_DISK_FREE_CRITICAL_SHARE = 0.05;
|
|
45
67
|
/** EBS volume configuration.
|
|
46
68
|
*
|
|
47
|
-
*
|
|
48
|
-
* not
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
69
|
+
* `CLICKHOUSE_EBS_VOLUME_SIZE_GB` is the DEFAULT for
|
|
70
|
+
* `ClickHouseDatabaseProps.storageGb`, not a fixed size. 400 GiB is sized
|
|
71
|
+
* for a mid-size multi-tenant analytics workload — order of tens of tenant
|
|
72
|
+
* organisations each carrying full `log_events` / `application_metrics`
|
|
73
|
+
* retention windows — with enough headroom that the first resize is a
|
|
74
|
+
* deliberate capacity decision rather than an early surprise. (The
|
|
75
|
+
* reference deployment's history is the evidence: 80 GiB bound at roughly
|
|
76
|
+
* two-thirds of that scale.) gp3 storage is cheap relative to an
|
|
77
|
+
* out-of-disk ClickHouse — merges stop, inserts start failing, and the
|
|
78
|
+
* recovery is a restore rather than a resize. Workloads outside that
|
|
79
|
+
* profile set `storageGb` directly, exactly as they would size an RDS
|
|
80
|
+
* volume.
|
|
81
|
+
*
|
|
82
|
+
* Raising the size alone is not enough — `buildClickHouseUserData` must grow
|
|
83
|
+
* the ext4 filesystem to match, because ModifyVolume resizes the block
|
|
84
|
+
* device and nothing else. The pair moves together: the declared size is
|
|
85
|
+
* embedded in the user data, so a `storageGb` change versions the launch
|
|
86
|
+
* template and triggers the instance refresh whose boot runs `resize2fs`.
|
|
87
|
+
* EBS permits one modification per volume per 6 hours.
|
|
88
|
+
*
|
|
89
|
+
* IOPS and throughput default to the gp3 baseline — the figures the volume
|
|
90
|
+
* price already includes — and are the construct's `iops` / `throughputMbps`
|
|
91
|
+
* props, the way RDS exposes provisioned IOPS and throughput on gp3. They are
|
|
92
|
+
* size-independent on gp3 and the default workload is merge- and scan-bound
|
|
93
|
+
* rather than IOPS-starved, so the baseline is the right default; a caller
|
|
94
|
+
* whose scans are bandwidth-bound raises them, within the bounds and ratios
|
|
95
|
+
* `resolveClickHouseVolumePerformance` enforces. */
|
|
64
96
|
export declare const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 400;
|
|
65
97
|
export declare const CLICKHOUSE_EBS_IOPS = 3000;
|
|
66
98
|
export declare const CLICKHOUSE_EBS_THROUGHPUT_MBPS = 125;
|
|
99
|
+
/** Volume and backup-retention bounds. Re-exported, NOT redeclared:
|
|
100
|
+
* `@fjall/generator` enforces the same numbers when it scaffolds or rewrites
|
|
101
|
+
* an `infrastructure.ts` declaration, and a second copy here is exactly the
|
|
102
|
+
* drift `backupRetentionDays` shipped for as long as it borrowed the RDS
|
|
103
|
+
* schema (generator 35 vs construct 3650). One source, two enforcement
|
|
104
|
+
* sites. */
|
|
105
|
+
export { CLICKHOUSE_MIN_STORAGE_GB, CLICKHOUSE_MAX_STORAGE_GB, CLICKHOUSE_MIN_IOPS, CLICKHOUSE_MAX_IOPS, CLICKHOUSE_MAX_IOPS_PER_GB, CLICKHOUSE_MIN_THROUGHPUT_MBPS, CLICKHOUSE_MAX_THROUGHPUT_MBPS, CLICKHOUSE_IOPS_PER_THROUGHPUT_MBPS, CLICKHOUSE_MIN_BACKUP_RETENTION_DAYS, CLICKHOUSE_MAX_BACKUP_RETENTION_DAYS, CLICKHOUSE_INSTANCE_TYPES } from "@fjall/util/clickhouse";
|
|
106
|
+
export type { ClickHouseInstanceType } from "@fjall/util/clickhouse";
|
|
67
107
|
/** Host memory reserved from the ClickHouse container: kernel + ECS agent +
|
|
68
108
|
* the host-metrics timer. The ECS agent advertises MemTotal minus
|
|
69
109
|
* CLICKHOUSE_ECS_RESERVED_MEMORY_MIB, and MemTotal itself runs below the
|
|
@@ -81,13 +121,25 @@ export declare const CLICKHOUSE_ECS_RESERVED_MEMORY_MIB = 256;
|
|
|
81
121
|
/** Hardware spec of every instance type the ClickHouse construct supports.
|
|
82
122
|
* Values are the AWS nominal figures. Single source of truth for BOTH the
|
|
83
123
|
* container memory limit (`clickHouseTaskMemoryMiB`) and the derived server
|
|
84
|
-
* tuning + default profiles (`clickhouseTuning.ts`)
|
|
85
|
-
*
|
|
124
|
+
* tuning + default profiles (`clickhouseTuning.ts`).
|
|
125
|
+
*
|
|
126
|
+
* Keyed on `ClickHouseInstanceType` — the supported-type vocabulary lives in
|
|
127
|
+
* `@fjall/util/clickhouse` so the generator can validate `instanceType` at
|
|
128
|
+
* scaffold time against the same list. The `Record` key type makes the
|
|
129
|
+
* parity two-way at compile time: a vocabulary entry without a spec row is a
|
|
130
|
+
* missing-property error, a spec row outside the vocabulary is an
|
|
131
|
+
* excess-property error. Supporting a new type = add it to the tuple there
|
|
132
|
+
* and the spec row here; everything else derives. */
|
|
86
133
|
export interface ClickHouseInstanceSpec {
|
|
87
134
|
vcpus: number;
|
|
88
135
|
memoryGib: number;
|
|
89
136
|
}
|
|
90
|
-
export declare const CLICKHOUSE_INSTANCE_SPECS: Record<
|
|
137
|
+
export declare const CLICKHOUSE_INSTANCE_SPECS: Record<ClickHouseInstanceType, ClickHouseInstanceSpec>;
|
|
138
|
+
/** Guarded string-keyed lookup into `CLICKHOUSE_INSTANCE_SPECS` for
|
|
139
|
+
* caller-supplied types (props carry `string`); `undefined` for unknown
|
|
140
|
+
* types, which every caller converts into its own named synth throw. The
|
|
141
|
+
* exhaustively-keyed Record above stays the compile-time contract. */
|
|
142
|
+
export declare function clickHouseInstanceSpec(instanceType: string): ClickHouseInstanceSpec | undefined;
|
|
91
143
|
/** Derived memory-only view of CLICKHOUSE_INSTANCE_SPECS. */
|
|
92
144
|
export declare const CLICKHOUSE_INSTANCE_MEMORY_GIB: Record<string, number>;
|
|
93
145
|
/** ECS container memory for the ClickHouse server task, derived from the
|
|
@@ -179,6 +231,7 @@ export declare const CLICKHOUSE_HOST_METRICS: {
|
|
|
179
231
|
readonly namespace: "CWAgent";
|
|
180
232
|
readonly memoryMetric: "mem_used_percent";
|
|
181
233
|
readonly diskMetric: "disk_used_percent";
|
|
234
|
+
readonly diskFreeMetric: "disk_free_gib";
|
|
182
235
|
readonly asgDimension: "AutoScalingGroupName";
|
|
183
236
|
};
|
|
184
237
|
/** Shared secret generation options (all ClickHouse users share the same policy). */
|
|
@@ -248,3 +301,62 @@ export declare const BACKUP_TASK_MEMORY_MIB = 256;
|
|
|
248
301
|
export declare const BACKUP_TASK_CPU_UNITS = 256;
|
|
249
302
|
/** Backup object expiration: 14 days (retains 14 daily snapshots). */
|
|
250
303
|
export declare const BACKUP_RETENTION_DAYS = 14;
|
|
304
|
+
/** Scratch database the backup task restores into to prove the backup it just
|
|
305
|
+
* wrote can be read back. Dropped on the way in and on the way out; never
|
|
306
|
+
* queried by the application. */
|
|
307
|
+
export declare const CLICKHOUSE_BACKUP_SCRATCH_DATABASE = "fjall_backup_verify";
|
|
308
|
+
/** Multiple of the critical free-space floor the verify restore must leave
|
|
309
|
+
* clear to proceed.
|
|
310
|
+
*
|
|
311
|
+
* A full restore writes a second copy of the database onto the volume it is
|
|
312
|
+
* verifying, so the check has to be bounded by the same quantity the disk
|
|
313
|
+
* alarm pages on — otherwise the mechanism that protects the data is also
|
|
314
|
+
* the mechanism most likely to fill the disk. Twice the floor rather than
|
|
315
|
+
* once so a verify can never itself be the thing that pages: at exactly the
|
|
316
|
+
* floor the restore would land the volume on the alarm threshold and hold it
|
|
317
|
+
* there until the scratch database is dropped. */
|
|
318
|
+
export declare const CLICKHOUSE_BACKUP_VERIFY_FREE_SPACE_MARGIN = 2;
|
|
319
|
+
/** Backup task log markers. Distinct full tokens, because the CloudWatch
|
|
320
|
+
* metric filters that read them match on substrings — a shared `BACKUP_`
|
|
321
|
+
* stem would make the OK filter count the failures too. */
|
|
322
|
+
export declare const CLICKHOUSE_BACKUP_VERIFY_OK_MARKER = "BACKUP_VERIFY_OK";
|
|
323
|
+
export declare const CLICKHOUSE_BACKUP_VERIFY_FAILED_MARKER = "BACKUP_VERIFY_FAILED";
|
|
324
|
+
export declare const CLICKHOUSE_BACKUP_VERIFY_SKIPPED_MARKER = "BACKUP_VERIFY_SKIPPED_FOR_SIZE";
|
|
325
|
+
/** Bounds on the verify restore's wall-clock ceiling, in seconds.
|
|
326
|
+
*
|
|
327
|
+
* Without a ceiling a hung RESTORE is the one backup failure with NO signal
|
|
328
|
+
* at all: BACKUP_CREATED has already been emitted so the heartbeat is
|
|
329
|
+
* satisfied, and the verify alarms count markers rather than their absence,
|
|
330
|
+
* so a task that never finishes reports nothing. `timeout` turns that
|
|
331
|
+
* silence into exit 124, which takes the same branch as a raised restore
|
|
332
|
+
* and pages.
|
|
333
|
+
*
|
|
334
|
+
* The ceiling scales with the volume (`clickHouseBackupVerifyTimeoutSeconds`
|
|
335
|
+
* below) because the largest restore the free-space gate admits scales with
|
|
336
|
+
* it: on the default 400 GiB the floor of one hour already covers it, but a
|
|
337
|
+
* multi-TiB volume can legitimately need hours, and a fixed hour would page
|
|
338
|
+
* FAILED on every healthy verify past the size where the restore outgrows
|
|
339
|
+
* it. The 20-hour cap keeps a daily-scheduled verify from overlapping its
|
|
340
|
+
* successor — two concurrent runs share the scratch database destructively,
|
|
341
|
+
* so the bound must land inside the schedule interval. */
|
|
342
|
+
export declare const CLICKHOUSE_BACKUP_VERIFY_MIN_TIMEOUT_SECONDS = 3600;
|
|
343
|
+
export declare const CLICKHOUSE_BACKUP_VERIFY_MAX_TIMEOUT_SECONDS = 72000;
|
|
344
|
+
/** Verify-restore timeout for a given data-volume size.
|
|
345
|
+
*
|
|
346
|
+
* Worst case admitted by the free-space gate is a database of roughly half
|
|
347
|
+
* the volume (it must fit beside itself). Writing that back at the gp3
|
|
348
|
+
* baseline throughput (`CLICKHOUSE_EBS_THROUGHPUT_MBPS`) bounds the healthy
|
|
349
|
+
* duration; doubled for read + merge overhead, then clamped to the
|
|
350
|
+
* floor/cap pair above. At the 400 GiB default this resolves to the
|
|
351
|
+
* one-hour floor, so the default behaviour is unchanged from the flat bound
|
|
352
|
+
* it replaced.
|
|
353
|
+
*
|
|
354
|
+
* Deliberately the BASELINE throughput, not the provisioned `throughputMbps`
|
|
355
|
+
* prop. The volume's provisioned figure is an upper bound the instance may
|
|
356
|
+
* not reach — an m7g.medium's EBS bandwidth sits under the gp3 baseline, and
|
|
357
|
+
* no default-tier host reaches a provisioned 1,000 MiB/s — so a bound that
|
|
358
|
+
* tightened with provisioning would page FAILED on healthy restores on
|
|
359
|
+
* exactly the hosts customers start on. The baseline-derived bound is loose
|
|
360
|
+
* on a provisioned volume, which only delays a hung-restore page; the cap
|
|
361
|
+
* keeps that delay inside the schedule interval either way. */
|
|
362
|
+
export declare function clickHouseBackupVerifyTimeoutSeconds(storageGb: number): number;
|
|
@@ -42,28 +42,66 @@ export const DEFAULT_CLICKHOUSE_INSTANCE_TYPE = "m7g.medium";
|
|
|
42
42
|
* that pulls once per launch. Upstream CH CI runs its full perf + stress
|
|
43
43
|
* matrix on the Ubuntu build; Alpine is community-tier coverage. */
|
|
44
44
|
export const CLICKHOUSE_IMAGE = "docker.io/clickhouse/clickhouse-server:26.3.17.56";
|
|
45
|
+
/** Absolute floor of the disk-critical alarm, in GiB.
|
|
46
|
+
*
|
|
47
|
+
* A pure percentage cannot express "about to break": 85 % used is 12 GiB
|
|
48
|
+
* free on an 80 GiB volume and 600 GiB free on a 4 TiB one, and only one of
|
|
49
|
+
* those is an incident. But a pure constant cannot either, because the other
|
|
50
|
+
* half of the failure scales with the data — ClickHouse will not select a
|
|
51
|
+
* merge whose source parts need more than the free space allows, and parts
|
|
52
|
+
* grow with the table. So the default floor is the LARGER of this constant
|
|
53
|
+
* and `CLICKHOUSE_DISK_FREE_CRITICAL_SHARE` of the volume: the constant
|
|
54
|
+
* guards response time on a small disk, the share guards merge headroom on a
|
|
55
|
+
* large one. See `resolveClickHouseStorage`.
|
|
56
|
+
*
|
|
57
|
+
* 20 GiB is days of ingest headroom at the observed rate — enough lead time
|
|
58
|
+
* to grow the volume, which takes an instance restart and cannot be done in
|
|
59
|
+
* minutes. */
|
|
60
|
+
export const CLICKHOUSE_DISK_FREE_CRITICAL_GIB = 20;
|
|
61
|
+
/** Share of the volume the disk-critical floor rises to on larger disks.
|
|
62
|
+
* At 5 %, a 4 TiB volume pages with 200 GiB free — roughly twice a large
|
|
63
|
+
* part, so merges still have room to complete while the operator responds.
|
|
64
|
+
* The two halves cross over at 400 GiB, the default volume size. */
|
|
65
|
+
export const CLICKHOUSE_DISK_FREE_CRITICAL_SHARE = 0.05;
|
|
45
66
|
/** EBS volume configuration.
|
|
46
67
|
*
|
|
47
|
-
*
|
|
48
|
-
* not
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
68
|
+
* `CLICKHOUSE_EBS_VOLUME_SIZE_GB` is the DEFAULT for
|
|
69
|
+
* `ClickHouseDatabaseProps.storageGb`, not a fixed size. 400 GiB is sized
|
|
70
|
+
* for a mid-size multi-tenant analytics workload — order of tens of tenant
|
|
71
|
+
* organisations each carrying full `log_events` / `application_metrics`
|
|
72
|
+
* retention windows — with enough headroom that the first resize is a
|
|
73
|
+
* deliberate capacity decision rather than an early surprise. (The
|
|
74
|
+
* reference deployment's history is the evidence: 80 GiB bound at roughly
|
|
75
|
+
* two-thirds of that scale.) gp3 storage is cheap relative to an
|
|
76
|
+
* out-of-disk ClickHouse — merges stop, inserts start failing, and the
|
|
77
|
+
* recovery is a restore rather than a resize. Workloads outside that
|
|
78
|
+
* profile set `storageGb` directly, exactly as they would size an RDS
|
|
79
|
+
* volume.
|
|
54
80
|
*
|
|
55
|
-
* Raising
|
|
56
|
-
* ext4 filesystem to match, because ModifyVolume resizes the block
|
|
57
|
-
* and nothing else. The pair moves together
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* volume per 6 hours.
|
|
81
|
+
* Raising the size alone is not enough — `buildClickHouseUserData` must grow
|
|
82
|
+
* the ext4 filesystem to match, because ModifyVolume resizes the block
|
|
83
|
+
* device and nothing else. The pair moves together: the declared size is
|
|
84
|
+
* embedded in the user data, so a `storageGb` change versions the launch
|
|
85
|
+
* template and triggers the instance refresh whose boot runs `resize2fs`.
|
|
86
|
+
* EBS permits one modification per volume per 6 hours.
|
|
61
87
|
*
|
|
62
|
-
* IOPS and throughput
|
|
63
|
-
*
|
|
88
|
+
* IOPS and throughput default to the gp3 baseline — the figures the volume
|
|
89
|
+
* price already includes — and are the construct's `iops` / `throughputMbps`
|
|
90
|
+
* props, the way RDS exposes provisioned IOPS and throughput on gp3. They are
|
|
91
|
+
* size-independent on gp3 and the default workload is merge- and scan-bound
|
|
92
|
+
* rather than IOPS-starved, so the baseline is the right default; a caller
|
|
93
|
+
* whose scans are bandwidth-bound raises them, within the bounds and ratios
|
|
94
|
+
* `resolveClickHouseVolumePerformance` enforces. */
|
|
64
95
|
export const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 400;
|
|
65
96
|
export const CLICKHOUSE_EBS_IOPS = 3000;
|
|
66
97
|
export const CLICKHOUSE_EBS_THROUGHPUT_MBPS = 125;
|
|
98
|
+
/** Volume and backup-retention bounds. Re-exported, NOT redeclared:
|
|
99
|
+
* `@fjall/generator` enforces the same numbers when it scaffolds or rewrites
|
|
100
|
+
* an `infrastructure.ts` declaration, and a second copy here is exactly the
|
|
101
|
+
* drift `backupRetentionDays` shipped for as long as it borrowed the RDS
|
|
102
|
+
* schema (generator 35 vs construct 3650). One source, two enforcement
|
|
103
|
+
* sites. */
|
|
104
|
+
export { CLICKHOUSE_MIN_STORAGE_GB, CLICKHOUSE_MAX_STORAGE_GB, CLICKHOUSE_MIN_IOPS, CLICKHOUSE_MAX_IOPS, CLICKHOUSE_MAX_IOPS_PER_GB, CLICKHOUSE_MIN_THROUGHPUT_MBPS, CLICKHOUSE_MAX_THROUGHPUT_MBPS, CLICKHOUSE_IOPS_PER_THROUGHPUT_MBPS, CLICKHOUSE_MIN_BACKUP_RETENTION_DAYS, CLICKHOUSE_MAX_BACKUP_RETENTION_DAYS, CLICKHOUSE_INSTANCE_TYPES } from "@fjall/util/clickhouse";
|
|
67
105
|
/** Host memory reserved from the ClickHouse container: kernel + ECS agent +
|
|
68
106
|
* the host-metrics timer. The ECS agent advertises MemTotal minus
|
|
69
107
|
* CLICKHOUSE_ECS_RESERVED_MEMORY_MIB, and MemTotal itself runs below the
|
|
@@ -93,6 +131,15 @@ export const CLICKHOUSE_INSTANCE_SPECS = {
|
|
|
93
131
|
"r7g.xlarge": { vcpus: 4, memoryGib: 32 },
|
|
94
132
|
"r8g.xlarge": { vcpus: 4, memoryGib: 32 }
|
|
95
133
|
};
|
|
134
|
+
/** Guarded string-keyed lookup into `CLICKHOUSE_INSTANCE_SPECS` for
|
|
135
|
+
* caller-supplied types (props carry `string`); `undefined` for unknown
|
|
136
|
+
* types, which every caller converts into its own named synth throw. The
|
|
137
|
+
* exhaustively-keyed Record above stays the compile-time contract. */
|
|
138
|
+
export function clickHouseInstanceSpec(instanceType) {
|
|
139
|
+
return Object.hasOwn(CLICKHOUSE_INSTANCE_SPECS, instanceType)
|
|
140
|
+
? CLICKHOUSE_INSTANCE_SPECS[instanceType]
|
|
141
|
+
: undefined;
|
|
142
|
+
}
|
|
96
143
|
/** Derived memory-only view of CLICKHOUSE_INSTANCE_SPECS. */
|
|
97
144
|
export const CLICKHOUSE_INSTANCE_MEMORY_GIB = Object.fromEntries(Object.entries(CLICKHOUSE_INSTANCE_SPECS).map(([type, spec]) => [
|
|
98
145
|
type,
|
|
@@ -210,6 +257,7 @@ export const CLICKHOUSE_HOST_METRICS = {
|
|
|
210
257
|
namespace: "CWAgent",
|
|
211
258
|
memoryMetric: "mem_used_percent",
|
|
212
259
|
diskMetric: "disk_used_percent",
|
|
260
|
+
diskFreeMetric: "disk_free_gib",
|
|
213
261
|
asgDimension: "AutoScalingGroupName"
|
|
214
262
|
};
|
|
215
263
|
/** Shared secret generation options (all ClickHouse users share the same policy). */
|
|
@@ -295,3 +343,66 @@ export const BACKUP_TASK_MEMORY_MIB = 256;
|
|
|
295
343
|
export const BACKUP_TASK_CPU_UNITS = 256;
|
|
296
344
|
/** Backup object expiration: 14 days (retains 14 daily snapshots). */
|
|
297
345
|
export const BACKUP_RETENTION_DAYS = 14;
|
|
346
|
+
/** Scratch database the backup task restores into to prove the backup it just
|
|
347
|
+
* wrote can be read back. Dropped on the way in and on the way out; never
|
|
348
|
+
* queried by the application. */
|
|
349
|
+
export const CLICKHOUSE_BACKUP_SCRATCH_DATABASE = "fjall_backup_verify";
|
|
350
|
+
/** Multiple of the critical free-space floor the verify restore must leave
|
|
351
|
+
* clear to proceed.
|
|
352
|
+
*
|
|
353
|
+
* A full restore writes a second copy of the database onto the volume it is
|
|
354
|
+
* verifying, so the check has to be bounded by the same quantity the disk
|
|
355
|
+
* alarm pages on — otherwise the mechanism that protects the data is also
|
|
356
|
+
* the mechanism most likely to fill the disk. Twice the floor rather than
|
|
357
|
+
* once so a verify can never itself be the thing that pages: at exactly the
|
|
358
|
+
* floor the restore would land the volume on the alarm threshold and hold it
|
|
359
|
+
* there until the scratch database is dropped. */
|
|
360
|
+
export const CLICKHOUSE_BACKUP_VERIFY_FREE_SPACE_MARGIN = 2;
|
|
361
|
+
/** Backup task log markers. Distinct full tokens, because the CloudWatch
|
|
362
|
+
* metric filters that read them match on substrings — a shared `BACKUP_`
|
|
363
|
+
* stem would make the OK filter count the failures too. */
|
|
364
|
+
export const CLICKHOUSE_BACKUP_VERIFY_OK_MARKER = "BACKUP_VERIFY_OK";
|
|
365
|
+
export const CLICKHOUSE_BACKUP_VERIFY_FAILED_MARKER = "BACKUP_VERIFY_FAILED";
|
|
366
|
+
export const CLICKHOUSE_BACKUP_VERIFY_SKIPPED_MARKER = "BACKUP_VERIFY_SKIPPED_FOR_SIZE";
|
|
367
|
+
/** Bounds on the verify restore's wall-clock ceiling, in seconds.
|
|
368
|
+
*
|
|
369
|
+
* Without a ceiling a hung RESTORE is the one backup failure with NO signal
|
|
370
|
+
* at all: BACKUP_CREATED has already been emitted so the heartbeat is
|
|
371
|
+
* satisfied, and the verify alarms count markers rather than their absence,
|
|
372
|
+
* so a task that never finishes reports nothing. `timeout` turns that
|
|
373
|
+
* silence into exit 124, which takes the same branch as a raised restore
|
|
374
|
+
* and pages.
|
|
375
|
+
*
|
|
376
|
+
* The ceiling scales with the volume (`clickHouseBackupVerifyTimeoutSeconds`
|
|
377
|
+
* below) because the largest restore the free-space gate admits scales with
|
|
378
|
+
* it: on the default 400 GiB the floor of one hour already covers it, but a
|
|
379
|
+
* multi-TiB volume can legitimately need hours, and a fixed hour would page
|
|
380
|
+
* FAILED on every healthy verify past the size where the restore outgrows
|
|
381
|
+
* it. The 20-hour cap keeps a daily-scheduled verify from overlapping its
|
|
382
|
+
* successor — two concurrent runs share the scratch database destructively,
|
|
383
|
+
* so the bound must land inside the schedule interval. */
|
|
384
|
+
export const CLICKHOUSE_BACKUP_VERIFY_MIN_TIMEOUT_SECONDS = 3600;
|
|
385
|
+
export const CLICKHOUSE_BACKUP_VERIFY_MAX_TIMEOUT_SECONDS = 72000;
|
|
386
|
+
/** Verify-restore timeout for a given data-volume size.
|
|
387
|
+
*
|
|
388
|
+
* Worst case admitted by the free-space gate is a database of roughly half
|
|
389
|
+
* the volume (it must fit beside itself). Writing that back at the gp3
|
|
390
|
+
* baseline throughput (`CLICKHOUSE_EBS_THROUGHPUT_MBPS`) bounds the healthy
|
|
391
|
+
* duration; doubled for read + merge overhead, then clamped to the
|
|
392
|
+
* floor/cap pair above. At the 400 GiB default this resolves to the
|
|
393
|
+
* one-hour floor, so the default behaviour is unchanged from the flat bound
|
|
394
|
+
* it replaced.
|
|
395
|
+
*
|
|
396
|
+
* Deliberately the BASELINE throughput, not the provisioned `throughputMbps`
|
|
397
|
+
* prop. The volume's provisioned figure is an upper bound the instance may
|
|
398
|
+
* not reach — an m7g.medium's EBS bandwidth sits under the gp3 baseline, and
|
|
399
|
+
* no default-tier host reaches a provisioned 1,000 MiB/s — so a bound that
|
|
400
|
+
* tightened with provisioning would page FAILED on healthy restores on
|
|
401
|
+
* exactly the hosts customers start on. The baseline-derived bound is loose
|
|
402
|
+
* on a provisioned volume, which only delays a hung-restore page; the cap
|
|
403
|
+
* keeps that delay inside the schedule interval either way. */
|
|
404
|
+
export function clickHouseBackupVerifyTimeoutSeconds(storageGb) {
|
|
405
|
+
const worstCaseRestoreMib = (storageGb / 2) * 1024;
|
|
406
|
+
const derived = Math.ceil((worstCaseRestoreMib / CLICKHOUSE_EBS_THROUGHPUT_MBPS) * 2);
|
|
407
|
+
return Math.min(CLICKHOUSE_BACKUP_VERIFY_MAX_TIMEOUT_SECONDS, Math.max(CLICKHOUSE_BACKUP_VERIFY_MIN_TIMEOUT_SECONDS, derived));
|
|
408
|
+
}
|