mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Merge pull request #246 from MBombeck/fix/disable-dense-retention
v1.10.0.2 — disable intra-day retention drain (P2002 fold collision)
This commit is contained in:
5 files changed
+52
-13
No files matched your search
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [1.10.0.2] — 2026-06-03 — Disable the intra-day retention drain
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Stops the post-deploy restart loop for good.** The intra-day retention drain (a storage optimisation behind the new stress reading) folded older samples into a daily summary, but on real data that summary collided with an existing daily row and the drain failed on every run, which kept the app cycling after a deploy. The drain is now switched off until it is reworked and tested against real data; the nightly pass is skipped, nothing is queued at start-up, and any work already queued completes harmlessly. No user-facing feature is lost — the stress reading simply falls back to its sparse-data behaviour.
|
||||
|
||||
## [1.10.0.1] — 2026-06-03 — Defer the intra-day retention drain past startup
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: HealthLog API
|
||||
version: 1.10.0.1
|
||||
version: 1.10.0.2
|
||||
description: >-
|
||||
Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "healthlog",
|
||||
"version": "1.10.0.1",
|
||||
"version": "1.10.0.2",
|
||||
"description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.",
|
||||
"license": "AGPL-3.0-only",
|
||||
"homepage": "https://healthlog.dev",
|
||||
|
||||
@@ -61,6 +61,22 @@ export const DENSE_INTRADAY_RETENTION_CRON = "50 3 * * *";
|
||||
*/
|
||||
export const DENSE_INTRADAY_RETENTION_BOOT_DELAY_SECONDS = 600;
|
||||
|
||||
/**
|
||||
* Kill-switch, default OFF. The drain folds out-of-window per-sample rows
|
||||
* into one daily-mean `stats:` row, but its canonical-timestamp upsert
|
||||
* collides with an already-present daily row on the
|
||||
* `(user_id, type, measured_at, source, sleep_stage)` unique constraint
|
||||
* (`P2002`) on real data — a fold-coexistence bug the mocked unit tests
|
||||
* could not surface. Until the fold is reworked + integration-tested
|
||||
* against real rows, the drain is disabled: the boot-discovery enqueues
|
||||
* nothing, the nightly walk is skipped, and the per-user handler no-ops so
|
||||
* any already-queued backlog completes harmlessly instead of erroring.
|
||||
* Re-enable with `DENSE_INTRADAY_RETENTION_ENABLED=true` once fixed. The
|
||||
* Stress proxy degrades gracefully on sparse intra-day data meanwhile.
|
||||
*/
|
||||
export const DENSE_INTRADAY_RETENTION_ENABLED =
|
||||
process.env.DENSE_INTRADAY_RETENTION_ENABLED === "true";
|
||||
|
||||
export interface DenseIntradayRetentionPayload {
|
||||
userId: string;
|
||||
enqueuedAt: string;
|
||||
@@ -73,6 +89,11 @@ export interface DenseIntradayRetentionPayload {
|
||||
export async function runDenseIntradayRetentionForUser(
|
||||
userId: string,
|
||||
): Promise<{ daysConsolidated: number; perSampleRowsSoftDeleted: number }> {
|
||||
// Kill-switch: no-op so any already-queued backlog completes cleanly
|
||||
// (drains the queue) instead of erroring on the P2002 fold collision.
|
||||
if (!DENSE_INTRADAY_RETENTION_ENABLED) {
|
||||
return { daysConsolidated: 0, perSampleRowsSoftDeleted: 0 };
|
||||
}
|
||||
const summary = await runDenseIntradayRetention(prisma, {
|
||||
userId,
|
||||
log: () => {
|
||||
@@ -114,6 +135,10 @@ export async function enqueueBootTimeDenseIntradayRetention(): Promise<{
|
||||
skipped: number;
|
||||
error: string | null;
|
||||
}> {
|
||||
// Kill-switch: enqueue nothing while the drain is disabled.
|
||||
if (!DENSE_INTRADAY_RETENTION_ENABLED) {
|
||||
return { enqueued: 0, skipped: 0, error: null };
|
||||
}
|
||||
const boss = getGlobalBoss();
|
||||
if (!boss) {
|
||||
return { enqueued: 0, skipped: 0, error: null };
|
||||
|
||||
@@ -88,6 +88,7 @@ import {
|
||||
DENSE_INTRADAY_RETENTION_CONCURRENCY,
|
||||
runDenseIntradayRetentionForUser,
|
||||
enqueueBootTimeDenseIntradayRetention,
|
||||
DENSE_INTRADAY_RETENTION_ENABLED,
|
||||
type DenseIntradayRetentionPayload,
|
||||
} from "@/lib/jobs/dense-intraday-retention";
|
||||
import { runDenseIntradayRetention } from "@/lib/measurements/dense-intraday-retention";
|
||||
@@ -2820,17 +2821,24 @@ export async function startReminderWorker() {
|
||||
// accounts that accumulated out-of-window raw rows before this
|
||||
// shipped.
|
||||
try {
|
||||
const denseSummary = await runDenseIntradayRetention(
|
||||
getWorkerPrisma(),
|
||||
{
|
||||
dryRun: false,
|
||||
log: (line) => workerLog("info", line),
|
||||
},
|
||||
);
|
||||
workerLog(
|
||||
"info",
|
||||
`[dense-intraday-retention] triggeredAt=${job.data.triggeredAt} usersScanned=${denseSummary.totals.usersScanned} daysConsolidated=${denseSummary.totals.daysConsolidated} perSampleRowsSoftDeleted=${denseSummary.totals.perSampleRowsSoftDeleted} dailyRowsUpserted=${denseSummary.totals.dailyRowsUpserted}`,
|
||||
);
|
||||
if (!DENSE_INTRADAY_RETENTION_ENABLED) {
|
||||
workerLog(
|
||||
"info",
|
||||
"[dense-intraday-retention] disabled — skipping the nightly walk (P2002 fold collision; re-enable via DENSE_INTRADAY_RETENTION_ENABLED once reworked)",
|
||||
);
|
||||
} else {
|
||||
const denseSummary = await runDenseIntradayRetention(
|
||||
getWorkerPrisma(),
|
||||
{
|
||||
dryRun: false,
|
||||
log: (line) => workerLog("info", line),
|
||||
},
|
||||
);
|
||||
workerLog(
|
||||
"info",
|
||||
`[dense-intraday-retention] triggeredAt=${job.data.triggeredAt} usersScanned=${denseSummary.totals.usersScanned} daysConsolidated=${denseSummary.totals.daysConsolidated} perSampleRowsSoftDeleted=${denseSummary.totals.perSampleRowsSoftDeleted} dailyRowsUpserted=${denseSummary.totals.dailyRowsUpserted}`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
recordError();
|
||||
workerLog(
|
||||
|
||||
Reference in new issue
Block a user