@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 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.2.35",
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.24.1",
18
- "@checkstack/common": "0.16.0",
19
- "@checkstack/healthcheck-common": "1.7.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.2"
27
+ "@checkstack/scripts": "0.6.4"
28
28
  },
29
29
  "description": "Checkstack healthcheck-postgres-backend plugin",
30
30
  "author": {
@@ -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
- "x-anomaly-enabled": true,
41
- "x-anomaly-direction": "deviation",
42
- "x-anomaly-min-absolute-delta": 1,
43
- "x-anomaly-min-relative-delta": 0.25,
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
- "x-anomaly-enabled": true,
105
- "x-anomaly-direction": "lower-is-better",
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
- "x-anomaly-enabled": true,
118
- "x-anomaly-direction": "lower-is-better",
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