diff --git a/src/Usage/Adapter/ClickHouse.php b/src/Usage/Adapter/ClickHouse.php index 0ad833a..fdd977e 100644 --- a/src/Usage/Adapter/ClickHouse.php +++ b/src/Usage/Adapter/ClickHouse.php @@ -137,8 +137,8 @@ class ClickHouse extends SQL /** * Retention window in days. When set, setup() applies a TTL to the raw - * events table so rows older than the window are dropped by background - * merges. The aggregated events_daily table is left untouched. Null + * events and aggregated events_daily tables so rows older than the window + * are dropped by background merges. Gauges are left untouched. Null * disables TTL (the default). */ private readonly ?int $retention; @@ -164,10 +164,10 @@ class ClickHouse extends SQL * re-executed against the raw events table and logs a `warning` route * entry if the totals diverge by >1%. Use 0.01 for a production canary * or 1.0 in CI. - * @param int|null $retention Retention window in days for the raw events - * table. When set, setup() applies a TTL that drops rows older than the - * window; the aggregated events_daily table (long-term usage/billing - * history) is left untouched. Null disables TTL (default). Must be positive. + * @param int|null $retention Retention window in days for the events and + * events_daily tables. When set, setup() applies a TTL that drops rows + * older than the window; gauges are left untouched. Null disables TTL + * (default). Must be positive. */ public function __construct( string $host, @@ -746,11 +746,13 @@ public function setup(): void $this->ensureEventDimColumns(); - $this->applyEventsRetention(); + $this->applyRetention($this->getEventsTableName()); // --- Events daily table (SummingMergeTree) --- $this->createDailyTable(); + $this->applyRetention($this->getEventsDailyTableName()); + // --- Events daily materialized view --- $this->createDailyMaterializedView(); @@ -785,20 +787,19 @@ public function setup(): void } /** - * Apply (or strip) the retention TTL on the raw events table as a separate - * idempotent ALTER. CREATE TABLE IF NOT EXISTS won't add a TTL to an - * existing table, and MODIFY TTL is a no-op when unchanged, so setup() - * stays re-runnable. The aggregated events_daily table is intentionally - * left untouched — it backs long-term usage/billing history. - * materialize_ttl_after_modify = 0 defers the purge to background merges - * rather than an immediate mutation. + * Apply (or strip) the retention TTL on a table as a separate idempotent + * ALTER. CREATE TABLE IF NOT EXISTS won't add a TTL to an existing table, + * and MODIFY TTL is a no-op when unchanged, so setup() stays re-runnable. + * The raw events and aggregated events_daily tables share the same window; + * gauges are left untouched. materialize_ttl_after_modify = 0 defers the + * purge to background merges rather than an immediate mutation. * * @throws Exception */ - private function applyEventsRetention(): void + private function applyRetention(string $tableName): void { $escapedTable = $this->escapeIdentifier($this->database) - . '.' . $this->escapeIdentifier($this->getEventsTableName()); + . '.' . $this->escapeIdentifier($tableName); if ($this->retention !== null) { $this->query( diff --git a/tests/Usage/Adapter/ClickHouseSchemaTest.php b/tests/Usage/Adapter/ClickHouseSchemaTest.php index c5bd17c..4829c27 100644 --- a/tests/Usage/Adapter/ClickHouseSchemaTest.php +++ b/tests/Usage/Adapter/ClickHouseSchemaTest.php @@ -209,7 +209,7 @@ public function testSetupBackfillsIpOnLegacyEventsTable(): void } } - public function testRetentionAppliesTtlToEventsTableOnly(): void + public function testRetentionAppliesTtlToEventsAndDailyTables(): void { $adapter = new ClickHouseAdapter( getenv('CLICKHOUSE_HOST') ?: 'clickhouse', @@ -225,6 +225,7 @@ public function testRetentionAppliesTtlToEventsTableOnly(): void $database = $this->databaseName($adapter); $eventsTable = $this->resolveTableName($adapter, 'getEventsTableName'); $dailyTable = $this->resolveTableName($adapter, 'getEventsDailyTableName'); + $gaugesTable = $this->resolveTableName($adapter, 'getGaugesTableName'); $dailyMv = $this->resolveTableName($adapter, 'getTableName') . '_events_daily_mv'; // Start clean so the TTL is applied by setup(), not left over. @@ -238,9 +239,13 @@ public function testRetentionAppliesTtlToEventsTableOnly(): void $eventsDdl = $this->showCreateFor($adapter, "`{$database}`.`{$eventsTable}`"); $this->assertStringContainsString('TTL toDateTime(time)', $eventsDdl); - // Aggregated billing history must never carry a TTL. + // Aggregated daily table shares the same retention window. $dailyDdl = $this->showCreateFor($adapter, "`{$database}`.`{$dailyTable}`"); - $this->assertStringNotContainsString('TTL', $dailyDdl); + $this->assertStringContainsString('TTL toDateTime(time)', $dailyDdl); + + // Gauges are point-in-time state and never carry a TTL. + $gaugesDdl = $this->showCreateFor($adapter, "`{$database}`.`{$gaugesTable}`"); + $this->assertStringNotContainsString('TTL', $gaugesDdl); } public function testRetentionRejectsNonPositiveDays(): void