Skip to content

Commit e06f036

Browse files
committed
feat: Implement deterministic ID generation for metrics with normalized tags in ClickHouse and Database adapters
1 parent aa60dd1 commit e06f036

3 files changed

Lines changed: 33 additions & 18 deletions

File tree

src/Usage/Adapter/ClickHouse.php

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,9 @@ public function log(string $metric, int $value, string $period = Usage::PERIOD_1
603603
];
604604
Metric::validate($data);
605605

606+
// Normalize tags for deterministic hashing
607+
ksort($tags);
608+
606609
// Period-aligned time so increments fall into the correct bucket
607610
$now = new \DateTime();
608611
$time = $period === Usage::PERIOD_INF
@@ -611,11 +614,8 @@ public function log(string $metric, int $value, string $period = Usage::PERIOD_1
611614
$timestamp = $this->formatDateTime($time);
612615

613616
// Deterministic id so SummingMergeTree will aggregate increments for the same group
614-
$idComponents = [$timestamp, $period, $metric];
615-
if ($this->sharedTables) {
616-
$idComponents[] = (string)$this->tenant;
617-
}
618-
$id = md5(implode('_', $idComponents));
617+
$tenant = $this->sharedTables ? $this->tenant : null;
618+
$id = $this->buildDeterministicId($metric, $period, $timestamp, $tenant);
619619

620620
// Build insert columns dynamically from attributes
621621
$insertColumns = ['id'];
@@ -760,6 +760,10 @@ public function logBatch(array $metrics): bool
760760

761761
foreach ($metrics as $metricData) {
762762
$period = $metricData['period'] ?? Usage::PERIOD_1H;
763+
$metric = $metricData['metric'];
764+
$value = $metricData['value'];
765+
$tags = $metricData['tags'] ?? [];
766+
ksort($tags);
763767

764768
// Period-aligned time so increments fall into the correct bucket
765769
$now = new \DateTime();
@@ -768,14 +772,9 @@ public function logBatch(array $metrics): bool
768772
: $now->format(Usage::PERIODS[$period]);
769773
$timestamp = $this->formatDateTime($time);
770774

771-
$idComponents = [$timestamp, $period, $metric];
772-
if ($this->sharedTables) {
773-
$idComponents[] = (string)$this->tenant;
774-
}
775-
$id = md5(implode('_', $idComponents));
776-
777-
$metric = $metricData['metric'];
778-
$value = $metricData['value'];
775+
// Deterministic id for aggregation
776+
$tenant = $this->sharedTables ? $this->tenant : null;
777+
$id = $this->buildDeterministicId($metric, $period, $timestamp, $tenant);
779778

780779
$valuePlaceholders = [];
781780

@@ -867,7 +866,6 @@ public function find(array $queries = []): array
867866
// Build LIMIT and OFFSET
868867
$limitClause = isset($parsed['limit']) ? ' LIMIT {limit:UInt64}' : '';
869868
$offsetClause = isset($parsed['offset']) ? ' OFFSET {offset:UInt64}' : '';
870-
871869
$sql = "
872870
SELECT {$selectColumns}
873871
FROM {$escapedTable}{$whereClause}{$orderClause}{$limitClause}{$offsetClause}

src/Usage/Adapter/Database.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@ public function log(string $metric, int $value, string $period = '1h', array $ta
6868
? '1000-01-01 00:00:00'
6969
: $now->format(Usage::PERIODS[$period]);
7070

71-
$this->db->getAuthorization()->skip(function () use ($metric, $value, $period, $time, $tags) {
72-
$id = \md5("{$time}_{$period}_{$metric}");
71+
// Sort tags for consistent storage
72+
ksort($tags);
73+
$id = $this->buildDeterministicId($metric, $period, $time);
7374

75+
$this->db->getAuthorization()->skip(function () use ($metric, $value, $period, $time, $tags, $id) {
7476
$doc = new Document([
7577
'$id' => $id,
7678
'$permissions' => [],
@@ -103,7 +105,10 @@ public function logBatch(array $metrics): bool
103105
? '1000-01-01 00:00:00'
104106
: $now->format(Usage::PERIODS[$period]);
105107

106-
$id = \md5("{$time}_{$period}_{$metric['metric']}");
108+
$tags = $metric['tags'] ?? [];
109+
ksort($tags);
110+
111+
$id = $this->buildDeterministicId($metric['metric'], $period, $time);
107112

108113
$documents[] = new Document([
109114
'$id' => $id,
@@ -112,7 +117,7 @@ public function logBatch(array $metrics): bool
112117
'value' => $metric['value'],
113118
'period' => $period,
114119
'time' => $time,
115-
'tags' => $metric['tags'] ?? [],
120+
'tags' => $tags,
116121
]);
117122
}
118123

src/Usage/Adapter/SQL.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,16 @@ protected function getAllColumnDefinitions(): array
114114

115115
return $definitions;
116116
}
117+
118+
/**
119+
* Build deterministic document ID based on time bucket, period, metric, and tenant (when applicable).
120+
* Tags are intentionally excluded to ensure aggregation regardless of tag differences.
121+
*/
122+
protected function buildDeterministicId(string $metric, string $period, string $timeBucket, ?int $tenant = null): string
123+
{
124+
$tenantPart = $tenant !== null ? ('|' . $tenant) : '';
125+
$hashInput = $timeBucket . '|' . $period . '|' . $metric . $tenantPart;
126+
127+
return md5($hashInput);
128+
}
117129
}

0 commit comments

Comments
 (0)