@fjall/components-infrastructure 15.0.0 → 16.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.
@@ -21,7 +21,7 @@ import { toPascalCase } from "../../utils/capitaliseString.js";
21
21
  import { resolveAlertsTopic } from "../../utils/resolveAlertsTopic.js";
22
22
  import { createClickHouseAlarms, validateClickHouseAlarmThresholds, BACKUP_HEARTBEAT_DEFAULT_WINDOW_HOURS, BACKUP_HEARTBEAT_MAX_WINDOW_HOURS, METRIC_NAMESPACE, stackScopedMetricNamespace } from "../../resources/aws/monitoring/index.js";
23
23
  import { ClickHouseSchemaAdminSchema, ManagedPasswordNameSchema, ProfileSpecSchema, PROFILE_NAME_PATTERN } from "../../resources/aws/database/clickhouseSchemas.js";
24
- import { deriveClickHouseDefaultProfiles, deriveClickHouseServerTuning } from "../../resources/aws/database/clickhouseTuning.js";
24
+ import { deriveClickHouseDefaultProfiles } from "../../resources/aws/database/clickhouseTuning.js";
25
25
  import { inferAmiHardwareType } from "../../resources/aws/compute/ecsConstants.js";
26
26
  import { CLICKHOUSE_DATABASE_NAME, DEFAULT_CLICKHOUSE_INSTANCE_TYPE, CLICKHOUSE_IMAGE, CLICKHOUSE_EBS_VOLUME_SIZE_GB, CLICKHOUSE_EBS_IOPS, CLICKHOUSE_EBS_THROUGHPUT_MBPS, clickHouseTaskMemoryMiB, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_HTTPS_PORT, CLICKHOUSE_NATIVE_PORT, CLICKHOUSE_TCP_SECURE_PORT, CLICKHOUSE_TLS_CERT_MOUNT_PATH, CLICKHOUSE_PROMETHEUS_PORT, CLICKHOUSE_DATA_MOUNT_PATH, CLICKHOUSE_SECRET_OPTIONS, CLICKHOUSE_SERVER_ROLE_TAG, CLICKHOUSE_HOST_METRICS, clickHouseUserSecretName, CLICKHOUSE_HEALTH_CHECK, CLICKHOUSE_STOP_TIMEOUT_SECONDS, CLICKHOUSE_EBS_DEVICE_NAME, CLICKHOUSE_CONFIG_SUBDIR, CLICKHOUSE_USERS_SUBDIR, userPasswordEnvName, OPTIMISE_FINAL_SCHEDULE, REPLACING_MERGE_TREE_TABLES, OPTIMISE_MV_TABLES, CLICKHOUSE_CLOUDMAP_SERVICE_NAME, CLICKHOUSE_SERVER_CONTAINER_NAME, CLICKHOUSE_SERVICE_NAME, OPTIMISE_TASK_MEMORY_MIB, OPTIMISE_TASK_CPU_UNITS, BACKUP_SCHEDULE, BACKUP_TASK_MEMORY_MIB, BACKUP_TASK_CPU_UNITS, BACKUP_RETENTION_DAYS } from "../../resources/aws/database/clickhouseConstants.js";
27
27
  import { TlsCertGenerator } from "../../resources/aws/utilities/tlsCertGenerator.js";
@@ -65,15 +65,35 @@ function resolveClickHouseDesiredCount(contextValue, propValue) {
65
65
  * position cannot.
66
66
  */
67
67
  const CLICKHOUSE_ERROR_LINE_PATTERN = '[w1, w2, w3, w4, w5="<Error>" || w5="<Fatal>", ...]';
68
+ /**
69
+ * Failed `executeQuery:` lines per 5 minutes before the alarm fires, sustained
70
+ * across 2 of 3 evaluation periods.
71
+ *
72
+ * ABSOLUTE, deliberately. This default used to be `3 x maxConcurrentQueries`,
73
+ * which read as principled and was not: a concurrency ceiling and a failure
74
+ * RATE are unrelated quantities, and scaling the threshold with the host size
75
+ * pointed the wrong way — a larger instance serves more tenants, so a given
76
+ * failure rate there is worse news, not more tolerable. On the 1-vCPU host it
77
+ * resolved to 24, meaning a steady 4-per-minute stream of failing queries
78
+ * never alarmed. That is the exact shape of the 2026-08-20 incident, where
79
+ * rejected queries surfaced to customers as missing data while every alarm
80
+ * stayed green.
81
+ *
82
+ * 10 is chosen against what the client now does rather than against the host:
83
+ * the webapp gates reads to its share of the per-user cap and retries a
84
+ * concurrency rejection before giving up, so a query that reaches the server
85
+ * AND fails is already exceptional. The 2-of-3 datapoint rule is what keeps a
86
+ * deploy blip or a single bad statement from paging.
87
+ */
88
+ const DEFAULT_FAILED_QUERIES_PER_5_MIN = 10;
68
89
  const CLICKHOUSE_FAILED_QUERY_PATTERN = '[w1, w2, w3, w4, w5="<Error>", w6="executeQuery:", ...]';
69
90
  /**
70
91
  * Server-log signal alarm specs for the ClickHouse service's declarative
71
92
  * `logAlarms`. Three always-on signals plus a cold-tier S3 signal when the
72
93
  * cold tier is enabled. Thresholds come from the `alarms` knob with defaults
73
- * documented on `ClickHouseAlarmThresholds`; the failed-queries default
74
- * derives from the instance's `maxConcurrentQueries` so it tracks host size.
94
+ * documented on `ClickHouseAlarmThresholds`.
75
95
  */
76
- function buildClickHouseLogAlarmSpecs(thresholds, coldTierEnabled, maxConcurrentQueries) {
96
+ function buildClickHouseLogAlarmSpecs(thresholds, coldTierEnabled) {
77
97
  const specs = [
78
98
  {
79
99
  idStem: "ClickHouseServerErrors",
@@ -90,7 +110,7 @@ function buildClickHouseLogAlarmSpecs(thresholds, coldTierEnabled, maxConcurrent
90
110
  metricName: "ClickHouseFailedQueryCount",
91
111
  description: "ClickHouse failed-query rate exceeds threshold — check system.query_log for the failing statement and error code",
92
112
  literal: CLICKHOUSE_FAILED_QUERY_PATTERN,
93
- threshold: thresholds.failedQueriesPer5Min ?? 3 * maxConcurrentQueries,
113
+ threshold: thresholds.failedQueriesPer5Min ?? DEFAULT_FAILED_QUERIES_PER_5_MIN,
94
114
  period: Duration.minutes(5),
95
115
  evaluationPeriods: 3,
96
116
  datapointsToAlarm: 2
@@ -441,10 +461,9 @@ export class ClickHouseDatabase extends Construct {
441
461
  (App.hasInstance() ? App.getInstance().getName() : undefined);
442
462
  const alarmThresholds = typeof props.alarms === "object" ? props.alarms : {};
443
463
  validateClickHouseAlarmThresholds(alarmThresholds);
444
- const serverTuning = deriveClickHouseServerTuning(instanceType);
445
464
  const serviceLogAlarms = alarmsDisabled
446
465
  ? undefined
447
- : buildClickHouseLogAlarmSpecs(alarmThresholds, coldTierEnabled, serverTuning.maxConcurrentQueries);
466
+ : buildClickHouseLogAlarmSpecs(alarmThresholds, coldTierEnabled);
448
467
  const ecsCompute = new EcsCompute(this, "Compute", {
449
468
  type: "ecs",
450
469
  vpc,
@@ -42,8 +42,26 @@ export declare 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 declare const CLICKHOUSE_IMAGE = "docker.io/clickhouse/clickhouse-server:26.3.17.56";
45
- /** EBS volume configuration. */
46
- export declare const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 80;
45
+ /** EBS volume configuration.
46
+ *
47
+ * Sized for the 30-organisation baseline the 2026-08 capacity review set,
48
+ * not for today's tenant count: the 80 GiB the cluster launched on binds at
49
+ * roughly 20-25 organisations once `log_events` and `application_metrics`
50
+ * carry a full retention window each, which is BELOW that baseline. 400 GiB
51
+ * buys the headroom to reach it without a second resize, and gp3 storage is
52
+ * cheap relative to an out-of-disk ClickHouse (merges stop, inserts start
53
+ * failing, and the recovery is a restore rather than a resize).
54
+ *
55
+ * Raising this alone is not enough — `buildClickHouseUserData` must grow the
56
+ * ext4 filesystem to match, because ModifyVolume resizes the block device
57
+ * and nothing else. The pair moves together; see the `resize2fs` step there.
58
+ * Applying an increase to a RUNNING cluster therefore needs the instance to
59
+ * relaunch (user data runs at boot), and EBS permits one modification per
60
+ * volume per 6 hours.
61
+ *
62
+ * IOPS and throughput stay at the gp3 baseline: they are size-independent on
63
+ * gp3, and the workload is merge- and scan-bound rather than IOPS-starved. */
64
+ export declare const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 400;
47
65
  export declare const CLICKHOUSE_EBS_IOPS = 3000;
48
66
  export declare const CLICKHOUSE_EBS_THROUGHPUT_MBPS = 125;
49
67
  /** Host memory reserved from the ClickHouse container: kernel + ECS agent +
@@ -42,8 +42,26 @@ 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
- /** EBS volume configuration. */
46
- export const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 80;
45
+ /** EBS volume configuration.
46
+ *
47
+ * Sized for the 30-organisation baseline the 2026-08 capacity review set,
48
+ * not for today's tenant count: the 80 GiB the cluster launched on binds at
49
+ * roughly 20-25 organisations once `log_events` and `application_metrics`
50
+ * carry a full retention window each, which is BELOW that baseline. 400 GiB
51
+ * buys the headroom to reach it without a second resize, and gp3 storage is
52
+ * cheap relative to an out-of-disk ClickHouse (merges stop, inserts start
53
+ * failing, and the recovery is a restore rather than a resize).
54
+ *
55
+ * Raising this alone is not enough — `buildClickHouseUserData` must grow the
56
+ * ext4 filesystem to match, because ModifyVolume resizes the block device
57
+ * and nothing else. The pair moves together; see the `resize2fs` step there.
58
+ * Applying an increase to a RUNNING cluster therefore needs the instance to
59
+ * relaunch (user data runs at boot), and EBS permits one modification per
60
+ * volume per 6 hours.
61
+ *
62
+ * IOPS and throughput stay at the gp3 baseline: they are size-independent on
63
+ * gp3, and the workload is merge- and scan-bound rather than IOPS-starved. */
64
+ export const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 400;
47
65
  export const CLICKHOUSE_EBS_IOPS = 3000;
48
66
  export const CLICKHOUSE_EBS_THROUGHPUT_MBPS = 125;
49
67
  /** Host memory reserved from the ClickHouse container: kernel + ECS agent +
@@ -490,6 +490,19 @@ fi
490
490
  mkdir -p "$MOUNT_POINT"
491
491
  mount "$DEVICE" "$MOUNT_POINT"
492
492
 
493
+ # Grow the filesystem to fill the volume. Raising the CDK volume size issues a
494
+ # ModifyVolume against the block device only — ext4 keeps its old size until
495
+ # resize2fs runs, so without this line every capacity increase is INERT and the
496
+ # disk fills at whatever ceiling the volume was first formatted at. Online
497
+ # (post-mount) rather than offline: growing a mounted ext4 needs no prior
498
+ # e2fsck, growing an unmounted one does.
499
+ #
500
+ # Idempotent — a no-op when the filesystem already spans the device, so it is
501
+ # safe on every boot. Non-fatal: a resize failure leaves the previous, smaller
502
+ # filesystem working, and refusing to boot over it would take the server down
503
+ # to fix a headroom problem. The disk alarms are what escalate from here.
504
+ resize2fs "$DEVICE" || echo "WARN: resize2fs failed — filesystem may not span the volume" >&2
505
+
493
506
  # Persist across reboots
494
507
  if ! grep -q "$MOUNT_POINT" /etc/fstab; then
495
508
  echo "$DEVICE $MOUNT_POINT ext4 defaults,nofail 0 2" >> /etc/fstab
@@ -29,8 +29,10 @@ export interface ClickHouseAlarmThresholds {
29
29
  diskCriticalThreshold?: number;
30
30
  /** `<Error>`/`<Fatal>` server-log lines per 5 min. Default 30. */
31
31
  serverErrorsPer5Min?: number;
32
- /** Failed `executeQuery:` error lines per 5 min. Default derived:
33
- * 3 × `deriveClickHouseServerTuning(instanceType).maxConcurrentQueries`. */
32
+ /** Failed `executeQuery:` error lines per 5 min. Default 10 — absolute, not
33
+ * derived from the host's concurrency ceiling; see
34
+ * `DEFAULT_FAILED_QUERIES_PER_5_MIN` in `patterns/aws/clickhouseDatabase.ts`
35
+ * for why the size-derived default was the wrong shape. */
34
36
  failedQueriesPer5Min?: number;
35
37
  /** Background-backpressure lines ("Temporarily pause scheduling" /
36
38
  * "Too many parts") per 15 min. Default 50. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "15.0.0",
3
+ "version": "16.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -80,8 +80,8 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@aws-sdk/client-organizations": "^3.1098.0",
83
- "@fjall/generator": "^15.0.0",
84
- "@fjall/util": "^15.0.0",
83
+ "@fjall/generator": "^16.0.0",
84
+ "@fjall/util": "^16.0.0",
85
85
  "constructs": "^10.7.2"
86
86
  },
87
87
  "overrides": {