| … | … | @@ -57,6 +57,12 @@ export const DEFAULT_NAME_REGISTRY_STORE = path.join(homedir(), '.config', 'pgh' |
| 57 | 57 | |
| 58 | 58 | const SIDECAR_VERSION = '0.2.0' |
| 59 | 59 | |
| 60 | // Store retention: each app's newest N releases stay; trees only older |
| 61 | // releases use are swept whatever their age (pgh store.gc keepReleases). |
| 62 | // The 14-day age gate alone kept 75 hyper-tps .pck copies (~44 GiB) on |
| 63 | // tabertron, 2026-10-11 — that app publishes several times a day. |
| 64 | const KEEP_RELEASES = Math.max(1, parseInt(process.env.PGH_KEEP_RELEASES, 10) || 2) |
| 65 | |
| 60 | 66 | // Recursive disk size for an on-disk dir. Used for compact() before/after |
| 61 | 67 | // reporting and for storage sweep logs. Returns 0 on any error (e.g. dir |
| 62 | 68 | // doesn't exist) — sweep logging is best-effort, never load-bearing. |
| … | … | @@ -269,16 +275,15 @@ export class Sidecar { |
| 269 | 275 | // block boot. Re-run on a daily interval (see _gcTimer below) so a |
| 270 | 276 | // long-lived sidecar stays bounded without needing a restart. |
| 271 | 277 | try { |
| 272 | | const r = this._treeStore.gc({ maxAgeDays: 14 }) |
| 273 | | if (r) console.log('[pgh-sidecar] store gc on boot: pruned ' + r.removed.length + ' superseded tree(s), kept ' + r.kept.length) |
| 278 | const r = await this._storeGc() |
| 279 | if (r) console.log('[pgh-sidecar] store gc on boot: pruned ' + r.removed.length + ' superseded tree(s), kept ' + r.kept.length + ' (keep ' + KEEP_RELEASES + ' releases/app)') |
| 274 | 280 | } catch (err) { |
| 275 | 281 | console.error('[pgh-sidecar] store gc (non-fatal):', err && err.message) |
| 276 | 282 | } |
| 277 | 283 | this._gcTimer = setInterval(() => { |
| 278 | | try { |
| 279 | | const r = this._treeStore.gc({ maxAgeDays: 14 }) |
| 284 | this._storeGc().then((r) => { |
| 280 | 285 | if (r && r.removed.length) console.log('[pgh-sidecar] periodic store gc: pruned ' + r.removed.length + ' tree(s)') |
| 281 | | } catch (err) { console.error('[pgh-sidecar] periodic gc (non-fatal):', err && err.message) } |
| 286 | }, (err) => { console.error('[pgh-sidecar] periodic gc (non-fatal):', err && err.message) }) |
| 282 | 287 | }, 24 * 60 * 60 * 1000) |
| 283 | 288 | if (this._gcTimer.unref) this._gcTimer.unref() |
| 284 | 289 | |
| … | … | @@ -1111,6 +1116,20 @@ export class Sidecar { |
| 1111 | 1116 | * Drop any older than 14 days (preserves a window for operator |
| 1112 | 1117 | * recovery but doesn't accumulate forever). |
| 1113 | 1118 | */ |
| 1119 | /** |
| 1120 | * Tree-store gc with release retention. Runs HERE, not in the CLI: the |
| 1121 | * release list carries every entry's full dep closure, far past hyper-svc's |
| 1122 | * 16 MiB frame, so only the process holding the registry can read it whole. |
| 1123 | * Own registry + warm replicas: a replica app's newest releases are kept too. |
| 1124 | */ |
| 1125 | async _storeGc ({ dryRun = false, keepReleases = KEEP_RELEASES } = {}) { |
| 1126 | const releases = await this._registry.list() |
| 1127 | for (const r of this._releaseReplicas) { |
| 1128 | try { releases.push(...(await r.registry.list())) } catch {} |
| 1129 | } |
| 1130 | return this._treeStore.gc({ dryRun, maxAgeDays: 14, keepReleases, releases }) |
| 1131 | } |
| 1132 | |
| 1114 | 1133 | _sweepLeftoverState () { |
| 1115 | 1134 | const ONE_HOUR = 60 * 60 * 1000 |
| 1116 | 1135 | const FOURTEEN_DAYS = 14 * 24 * ONE_HOUR |
| … | … | @@ -1386,6 +1405,21 @@ export class Sidecar { |
| 1386 | 1405 | } |
| 1387 | 1406 | }) |
| 1388 | 1407 | |
| 1408 | // `pgh store gc --keep-releases N` lands here (see _storeGc for why). |
| 1409 | svc.handle('store-gc', async (ctx, params = {}) => { |
| 1410 | const keep = parseInt(params.keepReleases, 10) |
| 1411 | const r = await this._storeGc({ |
| 1412 | dryRun: !!params.dryRun, |
| 1413 | keepReleases: Number.isFinite(keep) && keep > 0 ? keep : KEEP_RELEASES |
| 1414 | }) |
| 1415 | return { |
| 1416 | candidates: r.candidates, |
| 1417 | removed: r.removed, |
| 1418 | kept: r.kept.length, |
| 1419 | blobsRemoved: r.blobsRemoved |
| 1420 | } |
| 1421 | }) |
| 1422 | |
| 1389 | 1423 | svc.handle('store-stats', async () => { |
| 1390 | 1424 | return this._treeStore.stats() |
| 1391 | 1425 | }) |