@checkstack/healthcheck-postgres-backend 0.2.35 → 0.3.1
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/CHANGELOG.md +87 -0
- package/package.json +5 -5
- package/src/query-collector.ts +16 -4
- package/src/strategy.ts +31 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# @checkstack/healthcheck-postgres-backend
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [2e20792]
|
|
8
|
+
- Updated dependencies [2e20792]
|
|
9
|
+
- @checkstack/backend-api@0.26.0
|
|
10
|
+
- @checkstack/healthcheck-common@1.8.1
|
|
11
|
+
- @checkstack/common@0.17.0
|
|
12
|
+
|
|
13
|
+
## 0.3.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 8cad340: Add a finer per-run transport timing breakdown to health checks.
|
|
18
|
+
|
|
19
|
+
Each run now records an optional structured `metadata.timings` (DNS, connect,
|
|
20
|
+
TLS, wait/time-to-first-byte, transfer, and a `processing` catch-all for
|
|
21
|
+
non-HTTP operation time). The run-detail view renders the phases it has, in
|
|
22
|
+
transport order, and falls back to the previous Connection + Processing split
|
|
23
|
+
for older runs that lack the finer data.
|
|
24
|
+
|
|
25
|
+
For HTTP the request is issued verbatim through `fetch` (original URL, headers,
|
|
26
|
+
and body), so request behavior is identical to a plain `fetch`. The timing is
|
|
27
|
+
measured around it: `fetch` resolves at the response headers, so wait
|
|
28
|
+
(time-to-first-byte) and transfer (body) are measured exactly on the request,
|
|
29
|
+
DNS is timed at the resolve step, and connect/TLS come from a short-lived,
|
|
30
|
+
best-effort raw `net`/`tls` probe to the same already-validated IP (the request
|
|
31
|
+
socket exposes no connect/handshake events on the Bun runtime). The probe is
|
|
32
|
+
timing-only and never fails the check. The probe validates the TLS certificate
|
|
33
|
+
(against the original hostname via SNI) like the real request does - it does not
|
|
34
|
+
disable certificate validation; an unverifiable cert simply yields no TLS-phase
|
|
35
|
+
timing rather than aborting. Other transports surface the connect and operation
|
|
36
|
+
times they already measure.
|
|
37
|
+
|
|
38
|
+
The SSRF guard now validates the resolved host (rejecting cloud-metadata /
|
|
39
|
+
link-local and operator-denied ranges) as a pre-flight check and no longer pins
|
|
40
|
+
the request to the resolved IP. Pinning rewrote the URL to the IP literal and
|
|
41
|
+
moved the host to the `Host` header, which breaks HTTP/2 origins (their
|
|
42
|
+
authority comes from the URL's `:authority`, not `Host`) - that is why real
|
|
43
|
+
hosts such as `google.com` started answering 404/429 instead of 200. The
|
|
44
|
+
pre-flight validation keeps blocking static metadata/link-local targets and
|
|
45
|
+
direct denied IP literals; the only thing dropped is DNS-rebind TOCTOU
|
|
46
|
+
protection (a narrow window that pinning closed at the cost of breaking
|
|
47
|
+
legitimate HTTP/2 requests).
|
|
48
|
+
|
|
49
|
+
The run-detail "slowest" badge no longer collides with the timing bar, and a
|
|
50
|
+
genuinely sub-millisecond phase reads as "<1 ms" instead of a bare "0 ms".
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- 8cad340: Retune anomaly-detection defaults across every health-check strategy and the
|
|
55
|
+
hardware collector for a low-noise, problem-focused out-of-the-box experience.
|
|
56
|
+
|
|
57
|
+
The detection engine already learns a per-metric baseline, debounces with a
|
|
58
|
+
confirmation window, and applies practical-significance floors. This pass tunes
|
|
59
|
+
the per-metric **defaults** so a fresh install alerts only on genuine,
|
|
60
|
+
statistically-significant, problem-mapping deviations instead of flooding on
|
|
61
|
+
every metric that wiggles. 264 metrics were reviewed:
|
|
62
|
+
|
|
63
|
+
- **Default-disabled** the high-noise and un-baselineable classes that were
|
|
64
|
+
alerting for no good reason: raw identifiers and counts (status codes, error
|
|
65
|
+
and row counts, build counts, player and executor counts), config echoes and
|
|
66
|
+
near-constants (probe packet counts, CPU core count, total/swap memory),
|
|
67
|
+
payload-size and other run-to-run-volatile values, and deterministic values
|
|
68
|
+
like certificate days-remaining (governed by the check's own static-threshold
|
|
69
|
+
health logic, not statistics). These stay chartable and can be re-enabled per
|
|
70
|
+
field.
|
|
71
|
+
- **Hardened** the signals that should alert - latency/response/execution time
|
|
72
|
+
and availability/success/saturation percentages - with confirmation windows
|
|
73
|
+
and absolute + relative floors so brief spikes and sub-threshold jitter no
|
|
74
|
+
longer flap, and prefer percentage metrics over their absolute twins.
|
|
75
|
+
|
|
76
|
+
No detection-engine or schema changes; only per-metric `x-anomaly-*` defaults.
|
|
77
|
+
Users who had opted into any now-disabled metric keep their explicit override.
|
|
78
|
+
|
|
79
|
+
- Updated dependencies [8cad340]
|
|
80
|
+
- Updated dependencies [8cad340]
|
|
81
|
+
- Updated dependencies [8cad340]
|
|
82
|
+
- Updated dependencies [8cad340]
|
|
83
|
+
- Updated dependencies [8cad340]
|
|
84
|
+
- Updated dependencies [8cad340]
|
|
85
|
+
- Updated dependencies [8cad340]
|
|
86
|
+
- @checkstack/backend-api@0.25.0
|
|
87
|
+
- @checkstack/healthcheck-common@1.8.0
|
|
88
|
+
- @checkstack/common@0.17.0
|
|
89
|
+
|
|
3
90
|
## 0.2.35
|
|
4
91
|
|
|
5
92
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-postgres-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"checkstack": {
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"pack": "bunx @checkstack/scripts plugin-pack"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@checkstack/backend-api": "0.
|
|
18
|
-
"@checkstack/common": "0.
|
|
19
|
-
"@checkstack/healthcheck-common": "1.
|
|
17
|
+
"@checkstack/backend-api": "0.26.0",
|
|
18
|
+
"@checkstack/common": "0.17.0",
|
|
19
|
+
"@checkstack/healthcheck-common": "1.8.1",
|
|
20
20
|
"pg": "^8.11.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@types/pg": "^8.11.0",
|
|
25
25
|
"typescript": "^5.0.0",
|
|
26
26
|
"@checkstack/tsconfig": "0.0.7",
|
|
27
|
-
"@checkstack/scripts": "0.6.
|
|
27
|
+
"@checkstack/scripts": "0.6.4"
|
|
28
28
|
},
|
|
29
29
|
"description": "Checkstack healthcheck-postgres-backend plugin",
|
|
30
30
|
"author": {
|
package/src/query-collector.ts
CHANGED
|
@@ -37,10 +37,12 @@ const queryResultSchema = healthResultSchema({
|
|
|
37
37
|
rowCount: healthResultNumber({
|
|
38
38
|
"x-chart-type": "counter",
|
|
39
39
|
"x-chart-label": "Row Count",
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
// Row counts of arbitrary user-supplied SQL have no stable, universal
|
|
41
|
+
// baseline (a JOIN, a COUNT(*), a paginated SELECT all behave wildly
|
|
42
|
+
// differently) and no inherent good/bad direction. Baselining this would
|
|
43
|
+
// fire on routine data growth, so it is off by default. Still chartable
|
|
44
|
+
// and opt-in per check.
|
|
45
|
+
"x-anomaly-enabled": false,
|
|
44
46
|
}),
|
|
45
47
|
executionTimeMs: healthResultNumber({
|
|
46
48
|
"x-chart-type": "line",
|
|
@@ -71,6 +73,12 @@ const queryAggregatedFields = {
|
|
|
71
73
|
"x-chart-unit": "ms",
|
|
72
74
|
"x-anomaly-enabled": true,
|
|
73
75
|
"x-anomaly-direction": "lower-is-better",
|
|
76
|
+
// Latency saturation signal. Err wider and require sustained drift plus a
|
|
77
|
+
// practical floor so a fast query is not flagged on sub-perceptible jitter.
|
|
78
|
+
"x-anomaly-sensitivity": 2,
|
|
79
|
+
"x-anomaly-confirmation-window": 3,
|
|
80
|
+
"x-anomaly-min-absolute-delta": 50,
|
|
81
|
+
"x-anomaly-min-relative-delta": 0.5,
|
|
74
82
|
}),
|
|
75
83
|
successRate: aggregatedRate({
|
|
76
84
|
"x-chart-type": "gauge",
|
|
@@ -78,6 +86,10 @@ const queryAggregatedFields = {
|
|
|
78
86
|
"x-chart-unit": "%",
|
|
79
87
|
"x-anomaly-enabled": true,
|
|
80
88
|
"x-anomaly-direction": "higher-is-better",
|
|
89
|
+
// Availability percent. Require a few consecutive degraded buckets and a
|
|
90
|
+
// meaningful absolute drop so a single transient failure does not alert.
|
|
91
|
+
"x-anomaly-confirmation-window": 3,
|
|
92
|
+
"x-anomaly-min-absolute-delta": 5,
|
|
81
93
|
}),
|
|
82
94
|
};
|
|
83
95
|
|
package/src/strategy.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
configNumber,
|
|
18
18
|
configBoolean,
|
|
19
19
|
type ConnectedClient,
|
|
20
|
+
type TransportTimings,
|
|
20
21
|
type InferAggregatedResult,
|
|
21
22
|
baseStrategyConfigSchema,
|
|
22
23
|
} from "@checkstack/backend-api";
|
|
@@ -96,13 +97,22 @@ const postgresAggregatedFields = {
|
|
|
96
97
|
"x-chart-unit": "ms",
|
|
97
98
|
"x-anomaly-enabled": true,
|
|
98
99
|
"x-anomaly-direction": "lower-is-better",
|
|
100
|
+
// Connection latency saturation. Err wider and require sustained drift plus
|
|
101
|
+
// a practical floor so a fast handshake is not flagged on small jitter.
|
|
102
|
+
"x-anomaly-sensitivity": 2,
|
|
103
|
+
"x-anomaly-confirmation-window": 3,
|
|
104
|
+
"x-anomaly-min-absolute-delta": 50,
|
|
105
|
+
"x-anomaly-min-relative-delta": 0.5,
|
|
99
106
|
}),
|
|
100
107
|
maxConnectionTime: aggregatedMinMax({
|
|
101
108
|
"x-chart-type": "line",
|
|
102
109
|
"x-chart-label": "Max Connection Time",
|
|
103
110
|
"x-chart-unit": "ms",
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
// The per-bucket maximum is inherently spiky: one slow handshake (GC pause,
|
|
112
|
+
// transient network blip) moves it sharply, so baselining it produces noisy
|
|
113
|
+
// alerts. Average connection time already covers the latency-saturation
|
|
114
|
+
// signal, so the max is off by default and remains chartable.
|
|
115
|
+
"x-anomaly-enabled": false,
|
|
106
116
|
}),
|
|
107
117
|
successRate: aggregatedRate({
|
|
108
118
|
"x-chart-type": "gauge",
|
|
@@ -110,12 +120,19 @@ const postgresAggregatedFields = {
|
|
|
110
120
|
"x-chart-unit": "%",
|
|
111
121
|
"x-anomaly-enabled": true,
|
|
112
122
|
"x-anomaly-direction": "higher-is-better",
|
|
123
|
+
// Availability percent. Require a few consecutive degraded buckets and a
|
|
124
|
+
// meaningful absolute drop so a single transient failure does not alert.
|
|
125
|
+
"x-anomaly-confirmation-window": 3,
|
|
126
|
+
"x-anomaly-min-absolute-delta": 5,
|
|
113
127
|
}),
|
|
114
128
|
errorCount: aggregatedCounter({
|
|
115
129
|
"x-chart-type": "counter",
|
|
116
130
|
"x-chart-label": "Errors",
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
// Raw error count per bucket scales with check frequency and bucket size,
|
|
132
|
+
// so it has no stable universal baseline. Success rate already expresses
|
|
133
|
+
// failures as a normalized percent, so this absolute twin is off by default
|
|
134
|
+
// to avoid duplicate, volume-sensitive alerts.
|
|
135
|
+
"x-anomaly-enabled": false,
|
|
119
136
|
}),
|
|
120
137
|
};
|
|
121
138
|
|
|
@@ -237,6 +254,7 @@ export class PostgresHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
237
254
|
): Promise<ConnectedClient<PostgresTransportClient>> {
|
|
238
255
|
const validatedConfig = this.config.validate(config);
|
|
239
256
|
|
|
257
|
+
const connectStart = performance.now();
|
|
240
258
|
const connection = await this.dbClient.connect({
|
|
241
259
|
host: validatedConfig.host,
|
|
242
260
|
port: validatedConfig.port,
|
|
@@ -246,11 +264,19 @@ export class PostgresHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
246
264
|
ssl: validatedConfig.ssl ? { rejectUnauthorized: false } : undefined,
|
|
247
265
|
connectionTimeoutMillis: validatedConfig.timeout,
|
|
248
266
|
});
|
|
267
|
+
const timings: TransportTimings = {
|
|
268
|
+
connectMs: Math.max(0, Math.round(performance.now() - connectStart)),
|
|
269
|
+
};
|
|
249
270
|
|
|
250
271
|
const client: PostgresTransportClient = {
|
|
251
272
|
async exec(request: SqlQueryRequest): Promise<SqlQueryResult> {
|
|
252
273
|
try {
|
|
274
|
+
const queryStart = performance.now();
|
|
253
275
|
const result = await connection.query(request.query);
|
|
276
|
+
timings.processingMs = Math.max(
|
|
277
|
+
0,
|
|
278
|
+
Math.round(performance.now() - queryStart),
|
|
279
|
+
);
|
|
254
280
|
return { rowCount: result.rowCount ?? 0 };
|
|
255
281
|
} catch (error) {
|
|
256
282
|
return {
|
|
@@ -263,6 +289,7 @@ export class PostgresHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
263
289
|
|
|
264
290
|
return {
|
|
265
291
|
client,
|
|
292
|
+
timings,
|
|
266
293
|
close: () => {
|
|
267
294
|
connection.end().catch(() => {
|
|
268
295
|
// Ignore close errors
|