Skip to content

Commit 6af6ec0

Browse files
committed
perf: maintain telemetry source rollups
1 parent 14ae18d commit 6af6ec0

4 files changed

Lines changed: 121 additions & 4 deletions

File tree

packages/ts-cloud/src/control-plane/migrations.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export interface ControlPlaneMigration {
44
sql: string
55
}
66

7-
export const CONTROL_PLANE_SCHEMA_VERSION: number = 35
7+
export const CONTROL_PLANE_SCHEMA_VERSION: number = 36
88

99
export const controlPlaneMigrations: readonly ControlPlaneMigration[] = [
1010
{
@@ -1420,4 +1420,49 @@ export const controlPlaneMigrations: readonly ControlPlaneMigration[] = [
14201420
CREATE INDEX dr_drill_status ON disaster_recovery_drills(project_id,status,created_at DESC);
14211421
`,
14221422
},
1423+
{
1424+
version: 36,
1425+
name: 'telemetry_source_rollups',
1426+
sql: `
1427+
CREATE TABLE telemetry_source_rollups (
1428+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
1429+
environment_id TEXT NOT NULL DEFAULT '',
1430+
resource_id TEXT NOT NULL DEFAULT '',
1431+
source TEXT NOT NULL,
1432+
first_observed_at TEXT NOT NULL,
1433+
latest_observed_at TEXT NOT NULL,
1434+
ingested_bytes INTEGER NOT NULL DEFAULT 0 CHECK (ingested_bytes >= 0),
1435+
record_count INTEGER NOT NULL DEFAULT 0 CHECK (record_count >= 0),
1436+
PRIMARY KEY(project_id, environment_id, resource_id, source)
1437+
) STRICT;
1438+
1439+
INSERT INTO telemetry_source_rollups (
1440+
project_id, environment_id, resource_id, source,
1441+
first_observed_at, latest_observed_at, ingested_bytes, record_count
1442+
)
1443+
SELECT
1444+
project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source,
1445+
MIN(observed_at), MAX(observed_at), SUM(ingested_bytes), COUNT(*)
1446+
FROM telemetry_records
1447+
GROUP BY project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source;
1448+
1449+
CREATE TRIGGER telemetry_source_rollups_insert
1450+
AFTER INSERT ON telemetry_records
1451+
BEGIN
1452+
INSERT INTO telemetry_source_rollups (
1453+
project_id, environment_id, resource_id, source,
1454+
first_observed_at, latest_observed_at, ingested_bytes, record_count
1455+
)
1456+
VALUES (
1457+
NEW.project_id, COALESCE(NEW.environment_id, ''), COALESCE(NEW.resource_id, ''), NEW.source,
1458+
NEW.observed_at, NEW.observed_at, NEW.ingested_bytes, 1
1459+
)
1460+
ON CONFLICT(project_id, environment_id, resource_id, source) DO UPDATE SET
1461+
first_observed_at = MIN(first_observed_at, excluded.first_observed_at),
1462+
latest_observed_at = MAX(latest_observed_at, excluded.latest_observed_at),
1463+
ingested_bytes = ingested_bytes + excluded.ingested_bytes,
1464+
record_count = record_count + 1;
1465+
END;
1466+
`,
1467+
},
14231468
]

packages/ts-cloud/src/control-plane/store.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ describe('ControlPlaneStore schema and persistence', () => {
8585
const backup = migrated.getSetting('storage.last_backup') as { path: string }
8686
expect(existsSync(backup.path)).toBe(true)
8787
expect(backup.path.startsWith(`${path}.v1.`)).toBe(true)
88-
expect(migrated.health().schemaVersion).toBe(35)
88+
expect(migrated.health().schemaVersion).toBe(36)
8989
expect(existsSync(parent)).toBe(true)
9090
migrated.close()
9191
})
9292

9393
it('keeps migration numbering contiguous', () => {
9494
expect(controlPlaneMigrations.map((migration) => migration.version)).toEqual([
9595
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
96-
32, 33, 34, 35,
96+
32, 33, 34, 35, 36,
9797
])
9898
})
9999
})

packages/ts-cloud/src/telemetry/store.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,52 @@ describe('telemetry safety and persistence', () => {
133133
expect(resumed.records.map((item) => item.message)).toEqual(['second'])
134134
})
135135

136+
it('maintains exact source status rollups across inserts and retention', () => {
137+
const { controlPlane, project, environment, resource, telemetry } = fixture()
138+
telemetry.appendMany([
139+
{
140+
projectId: project.id,
141+
environmentId: environment.id,
142+
resourceId: resource.id,
143+
kind: 'metric',
144+
source: 'host',
145+
name: 'cpu',
146+
timestamp: '2026-06-01T00:00:00Z',
147+
value: 10,
148+
},
149+
{
150+
projectId: project.id,
151+
environmentId: environment.id,
152+
resourceId: resource.id,
153+
kind: 'metric',
154+
source: 'host',
155+
name: 'cpu',
156+
timestamp: '2026-07-21T11:59:00Z',
157+
value: 20,
158+
},
159+
])
160+
161+
expect(telemetry.status(project.id, environment.id, 30, 1, [resource.id])).toMatchObject([
162+
{
163+
source: 'host',
164+
lastObservedAt: '2026-07-21T12:00:00.000Z',
165+
freshness: 'live',
166+
},
167+
])
168+
expect(telemetry.status(project.id, environment.id, 30, 1, [])).toEqual([])
169+
170+
telemetry.enforceRetention(
171+
{ rawDays: 30, downsampleAfterDays: 30, downsampleBucketMs: 3_600_000, maxRecords: 100 },
172+
project.id,
173+
)
174+
const rollup = controlPlane.database
175+
.query<{ count: number }, []>(
176+
'SELECT SUM(record_count) count FROM telemetry_source_rollups',
177+
)
178+
.get()
179+
expect(Number(rollup?.count)).toBe(1)
180+
})
181+
136182
it('saves bounded actor-scoped queries without allowing project changes', () => {
137183
const { controlPlane, project, environment, telemetry } = fixture()
138184
const actor = controlPlane.createActor({ kind: 'user', externalId: 'user:chris', displayName: 'Chris' })

packages/ts-cloud/src/telemetry/store.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,12 @@ export class TelemetryStore {
412412
const environment = environmentId ? ' AND environment_id=?' : ''
413413
if (environmentId) bindings.push(environmentId)
414414
const boundedResources = [...new Set(resourceIds ?? [])].slice(0, 100)
415+
if (resourceIds && boundedResources.length === 0) return []
415416
const resources = resourceIds ? ` AND resource_id IN (${boundedResources.map(() => '?').join(',') || "''"})` : ''
416417
bindings.push(...boundedResources)
417418
const rows = this.controlPlane.database
418419
.query<Row, SQLQueryBindings[]>(
419-
`SELECT source, MAX(observed_at) latest, SUM(ingested_bytes) bytes, COUNT(*) count, MIN(observed_at) first FROM telemetry_records WHERE project_id=?${environment}${resources} GROUP BY source`,
420+
`SELECT source, MAX(latest_observed_at) latest, SUM(ingested_bytes) bytes, SUM(record_count) count, MIN(first_observed_at) first FROM telemetry_source_rollups WHERE project_id=?${environment}${resources} GROUP BY source`,
420421
)
421422
.all(...bindings)
422423
const now = this.now().getTime()
@@ -590,6 +591,31 @@ export class TelemetryStore {
590591
`DELETE FROM telemetry_records WHERE id IN (SELECT id FROM telemetry_records${projectId ? ' WHERE project_id=?' : ''} ORDER BY timestamp ASC LIMIT ?)`,
591592
projectId ? [projectId, excess] : [excess],
592593
)
594+
this.rebuildStatusRollups(projectId)
593595
return { deleted: removed + excess, ...downsampled }
594596
}
597+
598+
private rebuildStatusRollups(projectId?: string): void {
599+
const scope = projectId ? ' WHERE project_id=?' : ''
600+
const bindings: SQLQueryBindings[] = projectId ? [projectId] : []
601+
const rebuild = this.controlPlane.database.transaction(() => {
602+
this.controlPlane.database.run(
603+
`DELETE FROM telemetry_source_rollups${scope}`,
604+
bindings,
605+
)
606+
this.controlPlane.database.run(
607+
`INSERT INTO telemetry_source_rollups (
608+
project_id, environment_id, resource_id, source,
609+
first_observed_at, latest_observed_at, ingested_bytes, record_count
610+
)
611+
SELECT
612+
project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source,
613+
MIN(observed_at), MAX(observed_at), SUM(ingested_bytes), COUNT(*)
614+
FROM telemetry_records${scope}
615+
GROUP BY project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source`,
616+
bindings,
617+
)
618+
})
619+
rebuild()
620+
}
595621
}

0 commit comments

Comments
 (0)