Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions src/Usage/Adapter/ClickHouse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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(
Expand Down
11 changes: 8 additions & 3 deletions tests/Usage/Adapter/ClickHouseSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public function testSetupBackfillsIpOnLegacyEventsTable(): void
}
}

public function testRetentionAppliesTtlToEventsTableOnly(): void
public function testRetentionAppliesTtlToEventsAndDailyTables(): void
{
$adapter = new ClickHouseAdapter(
getenv('CLICKHOUSE_HOST') ?: 'clickhouse',
Expand All @@ -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.
Expand All @@ -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
Expand Down
Loading