@oneuptime/common 10.5.2 → 10.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/StatusPageGroup.ts +212 -0
- package/Models/DatabaseModels/StatusPageResource.ts +86 -0
- package/Server/API/StatusPageAPI.ts +15 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.ts +61 -13
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.ts +65 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -3
- package/Server/Services/StatusPageService.ts +5 -0
- package/Types/Monitor/MonitorStep.ts +85 -0
- package/Types/StatusPage/StatusPageGroupViewMode.ts +6 -0
- package/UI/Components/Accordion/Accordion.tsx +32 -26
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js +217 -0
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +88 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +15 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.js +29 -5
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779879993421-MigrationName.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779882573463-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +3 -3
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +5 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +59 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageGroupViewMode.js +7 -0
- package/build/dist/Types/StatusPage/StatusPageGroupViewMode.js.map +1 -0
- package/build/dist/UI/Components/Accordion/Accordion.js +11 -11
- package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
- package/package.json +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779900000000-DedupeTelemetryExceptionsAndAddUniqueIndex.ts +0 -115
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779900000000-DedupeTelemetryExceptionsAndAddUniqueIndex.js +0 -106
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779900000000-DedupeTelemetryExceptionsAndAddUniqueIndex.js.map +0 -1
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* The OTel traces ingest path used to call
|
|
5
|
-
* ExceptionUtil.saveOrUpdateTelemetryException once per exception
|
|
6
|
-
* event with a findOneBy + updateOneBy/create pair, fire-and-forget,
|
|
7
|
-
* from inside the span loop. That has three problems we are fixing
|
|
8
|
-
* in tandem with this schema change:
|
|
9
|
-
*
|
|
10
|
-
* 1. Cost: each event is a Postgres round-trip. A worker batch
|
|
11
|
-
* with thousands of exception events drives thousands of
|
|
12
|
-
* parallel SELECT/UPDATE statements and starves the pool.
|
|
13
|
-
* 2. Lost increments: `occuranceCount = existing.occuranceCount + 1`
|
|
14
|
-
* is read-modify-write at the JS layer, so two workers
|
|
15
|
-
* seeing the same row at the same instant collapse to a
|
|
16
|
-
* single +1 instead of +2.
|
|
17
|
-
* 3. Duplicate rows: two workers both missing the row at the
|
|
18
|
-
* same time both INSERT, with no DB-level guard, producing
|
|
19
|
-
* two TelemetryException rows for the same fingerprint.
|
|
20
|
-
*
|
|
21
|
-
* The ingest path is moving to a single batched
|
|
22
|
-
* INSERT … ON CONFLICT ("projectId", "serviceId", "fingerprint")
|
|
23
|
-
* DO UPDATE SET "occuranceCount" =
|
|
24
|
-
* "TelemetryException"."occuranceCount" + EXCLUDED."occuranceCount",
|
|
25
|
-
* ...
|
|
26
|
-
* statement per worker batch, which needs the composite unique
|
|
27
|
-
* index this migration creates. Before we can create the index we
|
|
28
|
-
* have to clear out the duplicate rows produced by the legacy race
|
|
29
|
-
* (problem 3 above) — otherwise the CREATE UNIQUE INDEX would fail
|
|
30
|
-
* on production data.
|
|
31
|
-
*
|
|
32
|
-
* Strategy: pick one survivor per (projectId, serviceId, fingerprint)
|
|
33
|
-
* group and hard-delete the rest. We do NOT try to merge
|
|
34
|
-
* occuranceCount / firstSeenAt / lastSeenAt from the losers into the
|
|
35
|
-
* survivor — the simpler delete-only approach trades a small,
|
|
36
|
-
* one-time count discrepancy on duplicated fingerprints for a much
|
|
37
|
-
* simpler migration that is easy to reason about and roll back. The
|
|
38
|
-
* next exception occurrence for that fingerprint will re-increment
|
|
39
|
-
* the survivor via the new ON CONFLICT upsert, and the dashboard
|
|
40
|
-
* recovers within seconds.
|
|
41
|
-
*
|
|
42
|
-
* Survivor selection prefers the row that was carrying the most
|
|
43
|
-
* traffic before the unique index landed, because in the legacy
|
|
44
|
-
* code path `findOneBy` returned an implementation-defined row and
|
|
45
|
-
* all subsequent UPDATEs piled into that one — discarding it would
|
|
46
|
-
* be the most lossy choice. Order is:
|
|
47
|
-
* 1. Highest occuranceCount (the "real" row absorbing updates).
|
|
48
|
-
* 2. Most recent lastSeenAt (in case counts are tied).
|
|
49
|
-
* 3. Non-deleted before deleted (live data beats soft-deleted).
|
|
50
|
-
* 4. Smallest _id as a deterministic tiebreaker so re-runs pick
|
|
51
|
-
* the same survivor.
|
|
52
|
-
*
|
|
53
|
-
* TelemetryException is a leaf table — no other table holds an FK
|
|
54
|
-
* referencing it — so we do not need to reparent anything before
|
|
55
|
-
* deleting loser rows. NULL-fingerprint rows are left alone; the
|
|
56
|
-
* composite unique index treats NULLs as distinct, and the new
|
|
57
|
-
* ingest path never produces a NULL fingerprint anyway.
|
|
58
|
-
*/
|
|
59
|
-
export class DedupeTelemetryExceptionsAndAddUniqueIndex1779900000000
|
|
60
|
-
implements MigrationInterface
|
|
61
|
-
{
|
|
62
|
-
public name: string =
|
|
63
|
-
"DedupeTelemetryExceptionsAndAddUniqueIndex1779900000000";
|
|
64
|
-
|
|
65
|
-
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
66
|
-
// 1. Delete every row that is not the chosen survivor for its group.
|
|
67
|
-
await queryRunner.query(`
|
|
68
|
-
WITH survivors AS (
|
|
69
|
-
SELECT DISTINCT ON ("projectId", "serviceId", "fingerprint")
|
|
70
|
-
_id AS survivor_id
|
|
71
|
-
FROM "TelemetryException"
|
|
72
|
-
WHERE "fingerprint" IS NOT NULL
|
|
73
|
-
ORDER BY
|
|
74
|
-
"projectId",
|
|
75
|
-
"serviceId",
|
|
76
|
-
"fingerprint",
|
|
77
|
-
COALESCE("occuranceCount", 0) DESC,
|
|
78
|
-
"lastSeenAt" DESC NULLS LAST,
|
|
79
|
-
CASE WHEN "deletedAt" IS NULL THEN 0 ELSE 1 END,
|
|
80
|
-
_id ASC
|
|
81
|
-
)
|
|
82
|
-
DELETE FROM "TelemetryException" te
|
|
83
|
-
WHERE te."fingerprint" IS NOT NULL
|
|
84
|
-
AND te._id NOT IN (SELECT survivor_id FROM survivors)
|
|
85
|
-
AND EXISTS (
|
|
86
|
-
SELECT 1
|
|
87
|
-
FROM "TelemetryException" t2
|
|
88
|
-
WHERE t2."projectId" = te."projectId"
|
|
89
|
-
AND t2."serviceId" = te."serviceId"
|
|
90
|
-
AND t2."fingerprint" = te."fingerprint"
|
|
91
|
-
AND t2._id <> te._id
|
|
92
|
-
);
|
|
93
|
-
`);
|
|
94
|
-
|
|
95
|
-
/*
|
|
96
|
-
* 2. Create the DB-level composite unique index. Matches the
|
|
97
|
-
* @Index decorator on TelemetryException and is the conflict
|
|
98
|
-
* target for the batched upsert in ExceptionUtil.
|
|
99
|
-
*/
|
|
100
|
-
await queryRunner.query(
|
|
101
|
-
`CREATE UNIQUE INDEX "IDX_telemetry_exception_project_service_fingerprint" ON "TelemetryException" ("projectId", "serviceId", "fingerprint") `,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
106
|
-
await queryRunner.query(
|
|
107
|
-
`DROP INDEX "public"."IDX_telemetry_exception_project_service_fingerprint"`,
|
|
108
|
-
);
|
|
109
|
-
/*
|
|
110
|
-
* The duplicate rows deleted in up() are not resurrectable from
|
|
111
|
-
* a down-migration, and recreating them is not desirable — they
|
|
112
|
-
* only existed because of a race the unique index now prevents.
|
|
113
|
-
*/
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* The OTel traces ingest path used to call
|
|
3
|
-
* ExceptionUtil.saveOrUpdateTelemetryException once per exception
|
|
4
|
-
* event with a findOneBy + updateOneBy/create pair, fire-and-forget,
|
|
5
|
-
* from inside the span loop. That has three problems we are fixing
|
|
6
|
-
* in tandem with this schema change:
|
|
7
|
-
*
|
|
8
|
-
* 1. Cost: each event is a Postgres round-trip. A worker batch
|
|
9
|
-
* with thousands of exception events drives thousands of
|
|
10
|
-
* parallel SELECT/UPDATE statements and starves the pool.
|
|
11
|
-
* 2. Lost increments: `occuranceCount = existing.occuranceCount + 1`
|
|
12
|
-
* is read-modify-write at the JS layer, so two workers
|
|
13
|
-
* seeing the same row at the same instant collapse to a
|
|
14
|
-
* single +1 instead of +2.
|
|
15
|
-
* 3. Duplicate rows: two workers both missing the row at the
|
|
16
|
-
* same time both INSERT, with no DB-level guard, producing
|
|
17
|
-
* two TelemetryException rows for the same fingerprint.
|
|
18
|
-
*
|
|
19
|
-
* The ingest path is moving to a single batched
|
|
20
|
-
* INSERT … ON CONFLICT ("projectId", "serviceId", "fingerprint")
|
|
21
|
-
* DO UPDATE SET "occuranceCount" =
|
|
22
|
-
* "TelemetryException"."occuranceCount" + EXCLUDED."occuranceCount",
|
|
23
|
-
* ...
|
|
24
|
-
* statement per worker batch, which needs the composite unique
|
|
25
|
-
* index this migration creates. Before we can create the index we
|
|
26
|
-
* have to clear out the duplicate rows produced by the legacy race
|
|
27
|
-
* (problem 3 above) — otherwise the CREATE UNIQUE INDEX would fail
|
|
28
|
-
* on production data.
|
|
29
|
-
*
|
|
30
|
-
* Strategy: pick one survivor per (projectId, serviceId, fingerprint)
|
|
31
|
-
* group and hard-delete the rest. We do NOT try to merge
|
|
32
|
-
* occuranceCount / firstSeenAt / lastSeenAt from the losers into the
|
|
33
|
-
* survivor — the simpler delete-only approach trades a small,
|
|
34
|
-
* one-time count discrepancy on duplicated fingerprints for a much
|
|
35
|
-
* simpler migration that is easy to reason about and roll back. The
|
|
36
|
-
* next exception occurrence for that fingerprint will re-increment
|
|
37
|
-
* the survivor via the new ON CONFLICT upsert, and the dashboard
|
|
38
|
-
* recovers within seconds.
|
|
39
|
-
*
|
|
40
|
-
* Survivor selection prefers the row that was carrying the most
|
|
41
|
-
* traffic before the unique index landed, because in the legacy
|
|
42
|
-
* code path `findOneBy` returned an implementation-defined row and
|
|
43
|
-
* all subsequent UPDATEs piled into that one — discarding it would
|
|
44
|
-
* be the most lossy choice. Order is:
|
|
45
|
-
* 1. Highest occuranceCount (the "real" row absorbing updates).
|
|
46
|
-
* 2. Most recent lastSeenAt (in case counts are tied).
|
|
47
|
-
* 3. Non-deleted before deleted (live data beats soft-deleted).
|
|
48
|
-
* 4. Smallest _id as a deterministic tiebreaker so re-runs pick
|
|
49
|
-
* the same survivor.
|
|
50
|
-
*
|
|
51
|
-
* TelemetryException is a leaf table — no other table holds an FK
|
|
52
|
-
* referencing it — so we do not need to reparent anything before
|
|
53
|
-
* deleting loser rows. NULL-fingerprint rows are left alone; the
|
|
54
|
-
* composite unique index treats NULLs as distinct, and the new
|
|
55
|
-
* ingest path never produces a NULL fingerprint anyway.
|
|
56
|
-
*/
|
|
57
|
-
export class DedupeTelemetryExceptionsAndAddUniqueIndex1779900000000 {
|
|
58
|
-
constructor() {
|
|
59
|
-
this.name = "DedupeTelemetryExceptionsAndAddUniqueIndex1779900000000";
|
|
60
|
-
}
|
|
61
|
-
async up(queryRunner) {
|
|
62
|
-
// 1. Delete every row that is not the chosen survivor for its group.
|
|
63
|
-
await queryRunner.query(`
|
|
64
|
-
WITH survivors AS (
|
|
65
|
-
SELECT DISTINCT ON ("projectId", "serviceId", "fingerprint")
|
|
66
|
-
_id AS survivor_id
|
|
67
|
-
FROM "TelemetryException"
|
|
68
|
-
WHERE "fingerprint" IS NOT NULL
|
|
69
|
-
ORDER BY
|
|
70
|
-
"projectId",
|
|
71
|
-
"serviceId",
|
|
72
|
-
"fingerprint",
|
|
73
|
-
COALESCE("occuranceCount", 0) DESC,
|
|
74
|
-
"lastSeenAt" DESC NULLS LAST,
|
|
75
|
-
CASE WHEN "deletedAt" IS NULL THEN 0 ELSE 1 END,
|
|
76
|
-
_id ASC
|
|
77
|
-
)
|
|
78
|
-
DELETE FROM "TelemetryException" te
|
|
79
|
-
WHERE te."fingerprint" IS NOT NULL
|
|
80
|
-
AND te._id NOT IN (SELECT survivor_id FROM survivors)
|
|
81
|
-
AND EXISTS (
|
|
82
|
-
SELECT 1
|
|
83
|
-
FROM "TelemetryException" t2
|
|
84
|
-
WHERE t2."projectId" = te."projectId"
|
|
85
|
-
AND t2."serviceId" = te."serviceId"
|
|
86
|
-
AND t2."fingerprint" = te."fingerprint"
|
|
87
|
-
AND t2._id <> te._id
|
|
88
|
-
);
|
|
89
|
-
`);
|
|
90
|
-
/*
|
|
91
|
-
* 2. Create the DB-level composite unique index. Matches the
|
|
92
|
-
* @Index decorator on TelemetryException and is the conflict
|
|
93
|
-
* target for the batched upsert in ExceptionUtil.
|
|
94
|
-
*/
|
|
95
|
-
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_telemetry_exception_project_service_fingerprint" ON "TelemetryException" ("projectId", "serviceId", "fingerprint") `);
|
|
96
|
-
}
|
|
97
|
-
async down(queryRunner) {
|
|
98
|
-
await queryRunner.query(`DROP INDEX "public"."IDX_telemetry_exception_project_service_fingerprint"`);
|
|
99
|
-
/*
|
|
100
|
-
* The duplicate rows deleted in up() are not resurrectable from
|
|
101
|
-
* a down-migration, and recreating them is not desirable — they
|
|
102
|
-
* only existed because of a race the unique index now prevents.
|
|
103
|
-
*/
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
//# sourceMappingURL=1779900000000-DedupeTelemetryExceptionsAndAddUniqueIndex.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"1779900000000-DedupeTelemetryExceptionsAndAddUniqueIndex.js","sourceRoot":"","sources":["../../../../../../Server/Infrastructure/Postgres/SchemaMigrations/1779900000000-DedupeTelemetryExceptionsAndAddUniqueIndex.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,MAAM,OAAO,uDAAuD;IAApE;QAGS,SAAI,GACT,yDAAyD,CAAC;IAoD9D,CAAC;IAlDQ,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,qEAAqE;QACrE,MAAM,WAAW,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;KA0BvB,CAAC,CAAC;QAEH;;;;WAIG;QACH,MAAM,WAAW,CAAC,KAAK,CACrB,8IAA8I,CAC/I,CAAC;IACJ,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,WAAW,CAAC,KAAK,CACrB,2EAA2E,CAC5E,CAAC;QACF;;;;WAIG;IACL,CAAC;CACF"}
|