Files
AfiS-Aktensystem/worker/healthcheck.js
T
Harlekin 7052238db7 Lebenszeichen des Workers zeitzonenfest pruefen
Der Healthcheck meldete den Worker dauerhaft als krank, obwohl er im
Sekundentakt schrieb: "Lebenszeichen 7214 s alt" - und 7214 Sekunden sind genau
zwei Stunden, der Abstand zwischen CEST und UTC.

`ServiceHeartbeat.at` ist ein timestamp ohne Zeitzone, und Prisma schreibt UTC
hinein. node-postgres liest so einen Wert aber als *lokale* Zeit; im Container
steht TZ=Europe/Berlin, und damit war jedes frisch geschriebene Lebenszeichen
beim Lesen schlagartig zwei Stunden alt.

Das Alter wird jetzt in der Datenbank gerechnet, gegen `now() AT TIME ZONE
'UTC'` - also in derselben Form, in der der Wert gespeichert ist. Die Rechnung
stimmt damit unabhaengig davon, welche Zeitzone im Container oder in der
Datenbank eingestellt ist.

Auffallen konnte das nur im Betrieb: lokal laeuft die Datenbank auf UTC, dort
ist die Abweichung null.
2026-09-04 10:45:38 +02:00

58 lines
1.9 KiB
JavaScript

// Healthcheck des Dienstes `worker`, wird vom Compose aufgerufen.
// Prueft das eigene Lebenszeichen in der Datenbank: ist es frisch und meldet
// `up`, gilt die Gateway-Verbindung als in Ordnung. Siehe README, Betrieb.
// `pg` wird als CommonJS ausgeliefert, deshalb der Default-Import.
import pg from 'pg'
const { Client } = pg
const MAX_AGE_S = 120
const client = new Client({ connectionString: process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL })
try {
await client.connect()
// Das Alter wird **in der Datenbank** gerechnet und nicht in JavaScript.
//
// `at` ist ein `timestamp` ohne Zeitzone, und Prisma schreibt UTC hinein.
// node-postgres liest so einen Wert aber als *lokale* Zeit; im Container
// steht TZ=Europe/Berlin, und damit war jedes Lebenszeichen beim Lesen
// schlagartig zwei Stunden alt - der Dienst galt dauerhaft als krank,
// obwohl er im Sekundentakt schrieb.
//
// `now() AT TIME ZONE 'UTC'` liefert die aktuelle UTC-Wanduhrzeit als
// zeitzonenlosen Wert, also genau in der Form, in der `at` gespeichert ist.
// Die Rechnung stimmt damit unabhaengig davon, welche Zeitzone im Container
// oder in der Datenbank eingestellt ist.
const { rows } = await client.query(
`SELECT status,
EXTRACT(EPOCH FROM (now() AT TIME ZONE 'UTC' - at)) AS alter_s
FROM "ServiceHeartbeat"
WHERE service = $1`,
['worker'],
)
const beat = rows[0]
if (!beat) {
console.error('kein Lebenszeichen')
process.exit(1)
}
const alter = Math.round(Number(beat.alter_s))
if (alter > MAX_AGE_S) {
console.error(`Lebenszeichen ${alter} s alt`)
process.exit(1)
}
if (beat.status !== 'up') {
console.error(`Zustand ${beat.status}`)
process.exit(1)
}
process.exit(0)
} catch (error) {
console.error(error instanceof Error ? error.message : error)
process.exit(1)
} finally {
await client.end().catch(() => undefined)
}