Skip to content

Commit 238c7bc

Browse files
committed
Remove period column from gauges table
Gauges are point-in-time resource counts (e.g., "42 projects right now"). The period column was a carryover from Appwrite's stats schema where it controlled time bucketing (1h/1d/inf), but that's a concern for events (counters), not gauges. - Remove period from gauges CREATE TABLE schema and ORDER BY key - Store raw timestamps instead of period-bucketed times - Remove period parameter from queryGauges() across Adapter, ClickHouse, Usage - Remove period from collectGauge() buffer key - Update tests accordingly https://claude.ai/code/session_01UCN4sKD3Zkdbimn3QgTHSB
1 parent 0799436 commit 238c7bc

4 files changed

Lines changed: 52 additions & 82 deletions

File tree

src/Usage/Adapter.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*
1515
* 2. **Gauges** – Point-in-time snapshots of resource counts
1616
* (projects, databases, functions, storage used, etc.)
17-
* ReplacingMergeTree keeps latest value per (metric, resource, period, time) key.
17+
* ReplacingMergeTree keeps latest value per (metric, resource, time) key.
1818
*/
1919
abstract class Adapter
2020
{
@@ -143,7 +143,7 @@ abstract public function countEvents(
143143
* - value (int|float)
144144
* - resource (string) optional resource identifier
145145
*
146-
* Uses replace-upsert: same (metric, resource, period, time_bucket) replaces.
146+
* Uses replace-upsert: same (metric, resource, time) replaces.
147147
*
148148
* @param array<array<string,mixed>> $gauges
149149
*/
@@ -153,7 +153,6 @@ abstract public function setGauges(array $gauges, int $batchSize = 1000): bool;
153153
* Query gauge values (latest or time-series).
154154
*
155155
* @param array<string,mixed> $filters
156-
* @param string $period '1h', '1d'
157156
* @param string|null $startDate
158157
* @param string|null $endDate
159158
* @param array<string> $groupBy
@@ -163,7 +162,6 @@ abstract public function setGauges(array $gauges, int $batchSize = 1000): bool;
163162
*/
164163
abstract public function queryGauges(
165164
array $filters = [],
166-
string $period = '1d',
167165
?string $startDate = null,
168166
?string $endDate = null,
169167
array $groupBy = [],

src/Usage/Adapter/ClickHouse.php

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
* Hourly and daily rollups via SummingMergeTree time-bucket grouping.
1919
*
2020
* 2. **gauges** (ReplacingMergeTree) – point-in-time resource counts.
21-
* Columns: time, period, metric, value, resource, resourceId, tenant.
22-
* ORDER BY (tenant, metric, resource, resourceId, period, time) so the latest snapshot per key is kept.
21+
* Columns: time, metric, value, resource, resourceId, tenant.
22+
* ORDER BY (tenant, metric, resource, resourceId, time) so the latest snapshot per key is kept.
2323
*/
2424
class ClickHouse extends Adapter
2525
{
@@ -166,13 +166,12 @@ public function setup(): void
166166
// Gauges table (ReplacingMergeTree – last insert per ORDER BY key wins)
167167
$gaugesTable = $this->gaugesTable();
168168
$gaugeOrderKey = $this->sharedTables
169-
? '(tenant, metric, resource, resourceId, period, time)'
170-
: '(metric, resource, resourceId, period, time)';
169+
? '(tenant, metric, resource, resourceId, time)'
170+
: '(metric, resource, resourceId, time)';
171171

172172
$this->query("
173173
CREATE TABLE IF NOT EXISTS {$gaugesTable} (
174174
{$tenantCol}time DateTime64(3),
175-
period LowCardinality(String),
176175
metric LowCardinality(String),
177176
value Int64,
178177
resource LowCardinality(String) DEFAULT '',
@@ -408,22 +407,20 @@ public function setGauges(array $gauges, int $batchSize = self::INSERT_BATCH_SIZ
408407

409408
public function queryGauges(
410409
array $filters = [],
411-
string $period = '1d',
412410
?string $startDate = null,
413411
?string $endDate = null,
414412
array $groupBy = [],
415413
int $limit = 100,
416414
int $offset = 0,
417415
): array {
418416
$this->setOperationContext('queryGauges()');
419-
$this->validatePeriod($period);
420417

421418
$table = $this->gaugesTableRef();
422419
$params = [];
423420
$paramCounter = 0;
424421

425422
// SELECT
426-
$selectParts = ['time', 'period'];
423+
$selectParts = ['time'];
427424
foreach ($groupBy as $dim) {
428425
$this->validateGaugeDimension($dim);
429426
$selectParts[] = $this->esc($dim);
@@ -435,11 +432,6 @@ public function queryGauges(
435432
$where = $this->addTimeRange($where, $params, $paramCounter, $startDate, $endDate, 'time');
436433
$where = $this->addTenantFilter($where, $params);
437434

438-
// Period filter
439-
$pn = 'p_period_' . $paramCounter++;
440-
$where[] = "period = {{$pn}:String}";
441-
$params[$pn] = $period === 'inf' ? 'inf' : $period;
442-
443435
$whereClause = !empty($where) ? ' WHERE ' . implode(' AND ', $where) : '';
444436
$orderClause = ' ORDER BY time DESC';
445437

@@ -555,20 +547,8 @@ private function buildEventRow(array $event): array
555547
*/
556548
private function buildGaugeRow(array $gauge): array
557549
{
558-
$period = $gauge['period'] ?? '1d';
559-
$now = new \DateTime();
560-
561-
if ($period === 'inf') {
562-
$timeBucket = '1970-01-01 00:00:00.000';
563-
} elseif ($period === '1h') {
564-
$timeBucket = $now->format('Y-m-d H:00:00.000');
565-
} else {
566-
$timeBucket = $now->format('Y-m-d 00:00:00.000');
567-
}
568-
569550
$row = [
570-
'time' => $gauge['time'] ?? $timeBucket,
571-
'period' => $period,
551+
'time' => $this->formatDateTime($gauge['time'] ?? null),
572552
'metric' => (string) ($gauge['metric'] ?? ''),
573553
'value' => (int) ($gauge['value'] ?? 0),
574554
'resource' => (string) ($gauge['resource'] ?? ''),

src/Usage/Usage.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,7 @@ public function setGauges(array $gauges, int $batchSize = 1000): bool
151151
public function collectGauge(array $gauge): self
152152
{
153153
$key = ($gauge['metric'] ?? '') . ':' . ($gauge['resource'] ?? '')
154-
. ':' . ($gauge['resourceId'] ?? '') . ':' . ($gauge['period'] ?? '')
155-
. ':' . ($gauge['time'] ?? '') . ':' . ($gauge['$tenant'] ?? '');
154+
. ':' . ($gauge['resourceId'] ?? '') . ':' . ($gauge['$tenant'] ?? '');
156155
$this->gaugeBuffer[$key] = $gauge;
157156
$this->bufferCount++;
158157
return $this;
@@ -165,7 +164,6 @@ public function collectGauge(array $gauge): self
165164
*/
166165
public function queryGauges(
167166
array $filters = [],
168-
string $period = '1d',
169167
?string $startDate = null,
170168
?string $endDate = null,
171169
array $groupBy = [],
@@ -174,7 +172,6 @@ public function queryGauges(
174172
): array {
175173
return $this->adapter->queryGauges(
176174
$filters,
177-
$period,
178175
$startDate,
179176
$endDate,
180177
$groupBy,

tests/Usage/Adapter/ClickHouseTest.php

Lines changed: 43 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,11 @@ public function testSetAndGetGauge(): void
324324
public function testSetGaugeReplaces(): void
325325
{
326326
$this->usage->setGauges([
327-
['metric' => 'storage.bytes', 'value' => 1000, 'period' => '1d'],
327+
['metric' => 'storage.bytes', 'value' => 1000],
328328
]);
329329

330330
$this->usage->setGauges([
331-
['metric' => 'storage.bytes', 'value' => 2000, 'period' => '1d'],
331+
['metric' => 'storage.bytes', 'value' => 2000],
332332
]);
333333

334334
$value = $this->usage->getGauge('storage.bytes');
@@ -338,27 +338,24 @@ public function testSetGaugeReplaces(): void
338338
public function testQueryGauges(): void
339339
{
340340
$this->usage->setGauges([
341-
['metric' => 'projects.count', 'value' => 10, 'period' => '1d'],
342-
['metric' => 'databases.count', 'value' => 5, 'period' => '1d'],
343-
['metric' => 'functions.count', 'value' => 3, 'period' => '1d'],
341+
['metric' => 'projects.count', 'value' => 10],
342+
['metric' => 'databases.count', 'value' => 5],
343+
['metric' => 'functions.count', 'value' => 3],
344344
]);
345345

346-
$results = $this->usage->queryGauges(
347-
period: '1d',
348-
);
346+
$results = $this->usage->queryGauges();
349347

350348
$this->assertCount(3, $results);
351349
}
352350

353351
public function testQueryGaugesGroupByMetric(): void
354352
{
355353
$this->usage->setGauges([
356-
['metric' => 'projects.count', 'value' => 10, 'period' => '1d'],
357-
['metric' => 'databases.count', 'value' => 5, 'period' => '1d'],
354+
['metric' => 'projects.count', 'value' => 10],
355+
['metric' => 'databases.count', 'value' => 5],
358356
]);
359357

360358
$results = $this->usage->queryGauges(
361-
period: '1d',
362359
groupBy: ['metric'],
363360
);
364361

@@ -368,34 +365,33 @@ public function testQueryGaugesGroupByMetric(): void
368365
public function testGaugeWithResource(): void
369366
{
370367
$this->usage->setGauges([
371-
['metric' => 'storage.bytes', 'value' => 1000, 'resource' => 'bucket-1', 'period' => '1d'],
372-
['metric' => 'storage.bytes', 'value' => 2000, 'resource' => 'bucket-2', 'period' => '1d'],
368+
['metric' => 'storage.bytes', 'value' => 1000, 'resource' => 'bucket-1'],
369+
['metric' => 'storage.bytes', 'value' => 2000, 'resource' => 'bucket-2'],
373370
]);
374371

375372
$value = $this->usage->getGauge('storage.bytes', ['resource' => 'bucket-1']);
376373
$this->assertEquals(1000, $value);
377374
}
378375

379-
public function testGaugeHourlyAndDaily(): void
376+
public function testGaugeLatestValueReturned(): void
380377
{
381378
$this->usage->setGauges([
382-
['metric' => 'active.users', 'value' => 100, 'period' => '1h'],
383-
['metric' => 'active.users', 'value' => 500, 'period' => '1d'],
379+
['metric' => 'active.users', 'value' => 100],
384380
]);
385381

386-
$hourlyResults = $this->usage->queryGauges(
387-
filters: ['metric' => 'active.users'],
388-
period: '1h',
389-
);
390-
$dailyResults = $this->usage->queryGauges(
382+
// Second write with a newer timestamp
383+
usleep(10_000); // 10ms to ensure different timestamp
384+
$this->usage->setGauges([
385+
['metric' => 'active.users', 'value' => 500],
386+
]);
387+
388+
$value = $this->usage->getGauge('active.users');
389+
$this->assertEquals(500, $value);
390+
391+
$results = $this->usage->queryGauges(
391392
filters: ['metric' => 'active.users'],
392-
period: '1d',
393393
);
394-
395-
$this->assertCount(1, $hourlyResults);
396-
$this->assertCount(1, $dailyResults);
397-
$this->assertEquals(100, $hourlyResults[0]['value']);
398-
$this->assertEquals(500, $dailyResults[0]['value']);
394+
$this->assertGreaterThanOrEqual(1, count($results));
399395
}
400396

401397
public function testSetGaugesEmpty(): void
@@ -437,7 +433,7 @@ public function testPurgeEventsWithFilter(): void
437433
public function testPurgeGauges(): void
438434
{
439435
$this->usage->setGauges([
440-
['metric' => 'projects.count', 'value' => 10, 'period' => '1d'],
436+
['metric' => 'projects.count', 'value' => 10],
441437
]);
442438

443439
$this->assertTrue($this->usage->purgeGauges());
@@ -449,7 +445,7 @@ public function testPurgeGauges(): void
449445
public function testPurgeAll(): void
450446
{
451447
$this->usage->addEvents([['metric' => 'test', 'value' => 1]]);
452-
$this->usage->setGauges([['metric' => 'test.gauge', 'value' => 1, 'period' => '1d']]);
448+
$this->usage->setGauges([['metric' => 'test.gauge', 'value' => 1]]);
453449

454450
$this->assertTrue($this->usage->purge());
455451

@@ -478,8 +474,8 @@ public function testCollectEventAndFlush(): void
478474

479475
public function testCollectGaugeAndFlush(): void
480476
{
481-
$this->usage->collectGauge(['metric' => 'projects.count', 'value' => 10, 'period' => '1d']);
482-
$this->usage->collectGauge(['metric' => 'projects.count', 'value' => 20, 'period' => '1d']);
477+
$this->usage->collectGauge(['metric' => 'projects.count', 'value' => 10]);
478+
$this->usage->collectGauge(['metric' => 'projects.count', 'value' => 20]);
483479

484480
$this->assertEquals(2, $this->usage->getBufferCount());
485481
$this->assertEquals(1, $this->usage->getBufferSize());
@@ -493,7 +489,7 @@ public function testCollectGaugeAndFlush(): void
493489
public function testMixedCollectAndFlush(): void
494490
{
495491
$this->usage->collectEvent(['metric' => 'api.request', 'value' => 1]);
496-
$this->usage->collectGauge(['metric' => 'projects.count', 'value' => 5, 'period' => '1d']);
492+
$this->usage->collectGauge(['metric' => 'projects.count', 'value' => 5]);
497493

498494
$this->assertEquals(2, $this->usage->getBufferSize());
499495

@@ -661,10 +657,10 @@ public function testAnalyticsTopPaths(): void
661657
public function testGaugeResourceInventory(): void
662658
{
663659
$this->usage->setGauges([
664-
['metric' => 'projects.count', 'value' => 15, 'period' => '1d'],
665-
['metric' => 'databases.count', 'value' => 42, 'period' => '1d'],
666-
['metric' => 'functions.count', 'value' => 8, 'period' => '1d'],
667-
['metric' => 'storage.bytes', 'value' => 1073741824, 'period' => '1d'],
660+
['metric' => 'projects.count', 'value' => 15],
661+
['metric' => 'databases.count', 'value' => 42],
662+
['metric' => 'functions.count', 'value' => 8],
663+
['metric' => 'storage.bytes', 'value' => 1073741824],
668664
]);
669665

670666
$this->assertEquals(15, $this->usage->getGauge('projects.count'));
@@ -676,8 +672,8 @@ public function testGaugeResourceInventory(): void
676672
public function testGaugePerResourceCounts(): void
677673
{
678674
$this->usage->setGauges([
679-
['metric' => 'databases.count', 'value' => 3, 'resource' => 'proj-a', 'period' => '1d'],
680-
['metric' => 'databases.count', 'value' => 7, 'resource' => 'proj-b', 'period' => '1d'],
675+
['metric' => 'databases.count', 'value' => 3, 'resource' => 'proj-a'],
676+
['metric' => 'databases.count', 'value' => 7, 'resource' => 'proj-b'],
681677
]);
682678

683679
$projA = $this->usage->getGauge('databases.count', ['resource' => 'proj-a']);
@@ -730,29 +726,28 @@ public function testSharedTablesIsolation(): void
730726
$usage->purge();
731727
}
732728

733-
// ─── Infinite Period (lifetime totals) ─────────────────────────
729+
// ─── Gauge: Multiple Metrics ────────────────────────────────────
734730

735-
public function testGaugeInfinitePeriod(): void
731+
public function testGaugeMultipleMetrics(): void
736732
{
737733
$this->usage->setGauges([
738-
['metric' => 'databases', 'value' => 5, 'period' => 'inf'],
739-
['metric' => 'collections', 'value' => 20, 'period' => 'inf'],
734+
['metric' => 'databases', 'value' => 5],
735+
['metric' => 'collections', 'value' => 20],
740736
]);
741737

742-
$results = $this->usage->queryGauges(
743-
period: 'inf',
744-
);
738+
$results = $this->usage->queryGauges();
745739

746740
$this->assertCount(2, $results);
747741
}
748742

749-
public function testGaugeInfinitePeriodReplace(): void
743+
public function testGaugeGetLatestAfterMultipleWrites(): void
750744
{
751745
$this->usage->setGauges([
752-
['metric' => 'databases', 'value' => 5, 'period' => 'inf'],
746+
['metric' => 'databases', 'value' => 5],
753747
]);
748+
usleep(10_000);
754749
$this->usage->setGauges([
755-
['metric' => 'databases', 'value' => 8, 'period' => 'inf'],
750+
['metric' => 'databases', 'value' => 8],
756751
]);
757752

758753
$value = $this->usage->getGauge('databases');

0 commit comments

Comments
 (0)