pg-boss 12.29.0 → 12.30.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"attorney.d.ts","sourceRoot":"","sources":["../src/attorney.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;AA+ED,iBAAS,iBAAiB,CAAE,MAAM,GAAE,GAAQ,QAc3C;AAgBD,iBAAS,mBAAmB,CAAE,KAAK,EAAE,MAAM,UAE1C;AAED,iBAAS,aAAa,CAAE,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,OAAO,CAgDhD;AAOD,iBAAS,eAAe,CAAE,IAAI,EAAE,GAAG,EAAE,EAAE,MAAc,EAAE;;CAAK,GAAG,KAAK,CAAC,OAAO,CA4D3E;AAED,iBAAS,mBAAmB,CAAE,MAAM,EAAE,GAAG,QAOxC;AAED,iBAAS,gBAAgB,CAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,QA2D/C;AA2GD,iBAAS,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAClD,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAA;IAClC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;CACjC,CA8BA;AAED,iBAAS,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAQlD;AAED,iBAAS,SAAS,CAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,0BAA0B,CAyB9F;AA4FD,iBAAS,wBAAwB,CAAE,IAAI,EAAE,MAAM,QAiC9C;AAED,iBAAS,eAAe,CAAE,IAAI,EAAE,MAAM,QAIrC;AAED,iBAAS,SAAS,CAAE,GAAG,EAAE,MAAM,QAI9B;AAuLD,OAAO,EACL,SAAS,EACT,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,MAAM,EACN,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EAClB,CAAA"}
1
+ {"version":3,"file":"attorney.d.ts","sourceRoot":"","sources":["../src/attorney.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;AAkFD,iBAAS,iBAAiB,CAAE,MAAM,GAAE,GAAQ,QAc3C;AAgBD,iBAAS,mBAAmB,CAAE,KAAK,EAAE,MAAM,UAE1C;AAED,iBAAS,aAAa,CAAE,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,OAAO,CAgDhD;AAOD,iBAAS,eAAe,CAAE,IAAI,EAAE,GAAG,EAAE,EAAE,MAAc,EAAE;;CAAK,GAAG,KAAK,CAAC,OAAO,CA4D3E;AAED,iBAAS,mBAAmB,CAAE,MAAM,EAAE,GAAG,QAOxC;AAED,iBAAS,gBAAgB,CAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,QA2D/C;AA2GD,iBAAS,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAClD,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAA;IAClC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;CACjC,CA8BA;AAED,iBAAS,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAQlD;AAED,iBAAS,SAAS,CAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,0BAA0B,CAyB9F;AA4FD,iBAAS,wBAAwB,CAAE,IAAI,EAAE,MAAM,QAiC9C;AAED,iBAAS,eAAe,CAAE,IAAI,EAAE,MAAM,QAIrC;AAED,iBAAS,SAAS,CAAE,GAAG,EAAE,MAAM,QAI9B;AA+LD,OAAO,EACL,SAAS,EACT,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,MAAM,EACN,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EAClB,CAAA"}
package/dist/attorney.js CHANGED
@@ -17,7 +17,8 @@ const COMPATIBILITY_FLAGS = [
17
17
  'noCoveringIndexes',
18
18
  'noListenNotify',
19
19
  'noIndexProgressView',
20
- 'noReindex'
20
+ 'noReindex',
21
+ 'noMonitorVacuum'
21
22
  ];
22
23
  // The single source of truth for backend presets, mirrored by test/testHelper.ts.
23
24
  const BACKEND_PROFILES = {
@@ -39,7 +40,8 @@ const BACKEND_PROFILES = {
39
40
  // CONCURRENTLY, "unimplemented: this syntax" without — and the bloat check itself cannot run:
40
41
  // there is no pg_relation_size(), and reltuples / relpages is an "unsupported binary
41
42
  // operator: <float4> / <int4>".
42
- noReindex: true
43
+ noReindex: true,
44
+ noMonitorVacuum: true
43
45
  }
44
46
  },
45
47
  yugabytedb: {
@@ -53,7 +55,8 @@ const BACKEND_PROFILES = {
53
55
  // "REINDEX not supported yet" with or without CONCURRENTLY, and DocDB compaction owns
54
56
  // reclamation. The bloat check runs but can never match: relpages and pg_relation_size() are
55
57
  // 0 for every relation.
56
- noReindex: true
58
+ noReindex: true,
59
+ noMonitorVacuum: true
57
60
  }
58
61
  },
59
62
  // No noIndexProgressView: pg-boss keeps its tables coordinator-local (it never calls
@@ -570,6 +573,7 @@ function applyOpsConfig(config) {
570
573
  }
571
574
  config.queueStatRetentionDays = config.queueStatRetentionDays || 7;
572
575
  validateReindexConfig(config);
576
+ validateVacuumMonitorConfig(config);
573
577
  }
574
578
  function validateReindexConfig(config) {
575
579
  assert(!('reindex' in config) || typeof config.reindex === 'boolean' || (typeof config.reindex === 'object' && config.reindex !== null), 'configuration assert: reindex must be a boolean or an options object');
@@ -587,6 +591,11 @@ function validateReindexConfig(config) {
587
591
  config.reindexIntervalSeconds = config.reindexIntervalSeconds || POLICY.MAX_EXPIRATION_HOURS * 60 * 60;
588
592
  assert(config.reindexIntervalSeconds / 60 / 60 <= POLICY.MAX_EXPIRATION_HOURS, `configuration assert: reindexIntervalSeconds cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
589
593
  }
594
+ function validateVacuumMonitorConfig(config) {
595
+ // A switch, not a threshold: the check derives its trigger from the server's own autovacuum
596
+ // settings and from measured dead tuples, so there is no number here for a caller to get wrong.
597
+ assert(!('monitorVacuum' in config) || typeof config.monitorVacuum === 'boolean', 'configuration assert: monitorVacuum must be a boolean');
598
+ }
590
599
  function validateDeletionConfig(config) {
591
600
  assert(!('deleteAfterSeconds' in config) || config.deleteAfterSeconds >= 0, 'configuration assert: deleteAfterSeconds must be at least 0 (0 disables deletion)');
592
601
  }
@@ -1 +1 @@
1
- {"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AA0BnC,cAAM,IAAK,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAoB1D,MAAM;;;MAAS;gBAGb,EAAE,EAAE,KAAK,CAAC,SAAS,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAa1C,IAAI,WAAW,IAAK,OAAO,CAE1B;IAEK,KAAK;IAWL,IAAI;IA8FJ,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,gBAAgB;IAsSvF;;;;OAIG;IACG,kBAAkB,CAAE,OAAO,CAAC,EAAE,KAAK,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAwB7E;AAED,eAAe,IAAI,CAAA"}
1
+ {"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAgHnC,cAAM,IAAK,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAsC1D,MAAM;;;MAAS;gBAGb,EAAE,EAAE,KAAK,CAAC,SAAS,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAa1C,IAAI,WAAW,IAAK,OAAO,CAE1B;IAEK,KAAK;IAWL,IAAI;IA8FJ,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,gBAAgB;IAwqBvF;;;;OAIG;IACG,kBAAkB,CAAE,OAAO,CAAC,EAAE,KAAK,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAwB7E;AAED,eAAe,IAAI,CAAA"}
package/dist/boss.js CHANGED
@@ -10,13 +10,62 @@ const events = {
10
10
  // Default thresholds and warning messages
11
11
  const WARNINGS = {
12
12
  SLOW_QUERY: { seconds: 30, message: 'Warning: slow query. Your queues and/or database server should be reviewed' },
13
- LARGE_QUEUE: { size: 10_000, message: 'Warning: large queue backlog. Your queue should be reviewed' }
13
+ LARGE_QUEUE: { size: 10_000, message: 'Warning: large queue backlog. Your queue should be reviewed' },
14
+ // No threshold constant: the trigger is measured, not configured. See #checkVacuum.
15
+ XMIN_HORIZON: { message: 'Warning: the database transaction horizon is pinned, so completed jobs cannot be cleaned up' },
16
+ AUTOVACUUM_DISABLED: { message: 'Warning: autovacuum is disabled, so nothing is reclaiming completed jobs' },
17
+ // Deliberately weaker than XMIN_HORIZON, and the difference is the evidence behind each. That one
18
+ // asserts the horizon is pinned and that jobs cannot be reclaimed, having watched a vacuum run and
19
+ // reclaim nothing across two passes. This one has a stopwatch and nothing else. The aggregate does
20
+ // pin the horizon while it runs - that much is definitional - but whether any reclamation was
21
+ // actually lost to it is not measured here, so this reports the exposure and the action taken and
22
+ // leaves the diagnosis to XMIN_HORIZON, which will fire on its own if the harm is real.
23
+ MONITOR_BACKOFF: { message: 'Warning: queue monitoring spent long enough scanning the job table to risk holding back autovacuum, so the next stats refresh is deferred' }
14
24
  };
15
25
  const WARNING_TYPES = {
16
26
  SLOW_QUERY: 'slow_query',
17
27
  QUEUE_BACKLOG: 'queue_backlog',
18
- INDEX_BLOAT: 'index_bloat'
28
+ INDEX_BLOAT: 'index_bloat',
29
+ XMIN_HORIZON: 'xmin_horizon',
30
+ AUTOVACUUM_DISABLED: 'autovacuum_disabled',
31
+ MONITOR_BACKOFF: 'monitor_backoff'
19
32
  };
33
+ // Which holder class each source in the horizon query represents, phrased so the warning names
34
+ // something an operator can go and look at.
35
+ const XMIN_HOLDERS = {
36
+ backends: 'a backend holding an open transaction',
37
+ slots: 'a replication slot',
38
+ slotsCatalog: 'a replication slot (catalog xmin)',
39
+ standbys: 'a standby with hot_standby_feedback enabled',
40
+ prepared: 'a prepared transaction'
41
+ };
42
+ // Name the holder as specifically as the catalog allowed. Falls back to the holder class when the
43
+ // source is not a backend (a replication slot has no pid to report) or when no backend row came
44
+ // back, so the warning never loses the description it had before.
45
+ function describeXminHolder(source, row) {
46
+ const holder = source === 'backends' ? row.backendHolder : null;
47
+ if (!holder)
48
+ return XMIN_HOLDERS[source];
49
+ const app = holder.applicationName || null;
50
+ const where = [
51
+ app && `application_name '${app}'`,
52
+ holder.userName && `role ${holder.userName}`,
53
+ `pid ${holder.pid}`
54
+ ].filter(Boolean).join(', ');
55
+ const open = holder.xactSeconds === null || holder.xactSeconds === undefined
56
+ ? 'for an unreadable length of time'
57
+ : `for ${holder.xactSeconds}s`;
58
+ // Three outcomes, not two, because an empty application_name cannot be classified either way and
59
+ // saying "another application" would be asserting something unmeasured. Comparison is against
60
+ // this connection's own value rather than the literal 'pgboss': an adapter-supplied pool carries
61
+ // whatever the host app set, so "same application_name as us" is the strongest true claim.
62
+ const who = !app
63
+ ? 'a backend with no application_name'
64
+ : app === row.selfApplicationName
65
+ ? "this application's own connection"
66
+ : 'another application';
67
+ return `${who} (${where}) has held a transaction open ${open}`;
68
+ }
20
69
  // SQLSTATE 25001 (active_sql_transaction): "REINDEX CONCURRENTLY cannot run inside a transaction
21
70
  // block". Raised when a user-supplied adapter wraps executeSql in a transaction — a property of the
22
71
  // adapter, not of this pass, so it disables rebuilds for the life of the instance rather than
@@ -41,6 +90,24 @@ class Boss extends EventEmitter {
41
90
  // Local rate limit for passes that only report bloat, which deliberately leave the shared interval
42
91
  // claim to whichever instance can act on it.
43
92
  #detectOnly = 0;
93
+ // Latched while a job table is over its autovacuum budget, so a sustained pin warns once rather
94
+ // than every pass. Cleared when every table is back under budget, so a later episode warns again.
95
+ #warnedXminHorizon = false;
96
+ // Same latching for the sibling diagnosis: garbage growing on a table nothing vacuums at all.
97
+ #warnedAutovacuumDisabled = false;
98
+ // Per job table: the dead-tuple count and vacuum timestamp last seen while the table was over its
99
+ // autovacuum budget. Two observations are what turn "there is garbage" into "a vacuum ran and
100
+ // reclaimed none of it" - see #checkXminHorizon.
101
+ #garbageEvidence = new Map();
102
+ // Sources the connected role could not read. Narrowed once on first failure rather than retried
103
+ // every pass; an empty-but-unreadable source must never be reported as healthy.
104
+ #xminHorizonSources = null;
105
+ // Seconds this supervise pass held the MVCC horizon inside the queue-stats aggregate, as the
106
+ // server measured it, summed across every table and every 100-name chunk. The sum, not the worst
107
+ // single query: chunking and partitioned queues both multiply the aggregate within one pass (each
108
+ // chunk re-reads the whole heap - measured at 384,632 buffers for 2 of 20 queue names), and it is
109
+ // the total contiguous pin that starves autovacuum, not any one scan.
110
+ #statsElapsedSeconds = 0;
44
111
  events = events;
45
112
  constructor(db, manager, config) {
46
113
  super();
@@ -176,6 +243,12 @@ class Boss extends EventEmitter {
176
243
  }
177
244
  if (this.#stopping)
178
245
  return;
246
+ // Immediately after the aggregates it measures, not at the head of the next pass. Deferring the
247
+ // write left a whole supervise interval between a slow aggregate finishing and the gate closing
248
+ // behind it, and a getQueueStats({ force: true }) landing in that window ran exactly the scan
249
+ // the backoff was about to prevent.
250
+ await this.#applyMonitorBackoff();
251
+ await this.#checkVacuum();
179
252
  await this.#maintainWarnings();
180
253
  await this.#maintainQueueStats();
181
254
  // Last in the pass: a rebuild is DDL that can run for seconds, so nothing time-sensitive
@@ -191,21 +264,44 @@ class Boss extends EventEmitter {
191
264
  return;
192
265
  if (rows.length) {
193
266
  const queues = rows.map((q) => q.name);
194
- const cacheStatsSql = plans.cacheQueueStats(this.#config.schema, table, queues, this.#config.noAdvisoryLocks);
195
- const { rows: rowsCacheStats } = await this.#executeQuery(cacheStatsSql);
196
- if (this.#config.persistQueueStats) {
197
- const insertSql = plans.insertQueueStats(this.#config.schema, queues, this.#config.noAdvisoryLocks);
198
- await this.#executeQuery(insertSql);
267
+ // The vacuum-safety backoff defers the stats aggregate, not the whole pass. Job expiry and
268
+ // heartbeat failure below are narrow indexed updates that pin nothing, and gating them on the
269
+ // horizon would turn a vacuum-safety valve into a job-expiry outage lasting two naptimes at
270
+ // minimum. See the note on plans.trySetQueueMonitorTime. The flag is a property of the
271
+ // statement, not of a queue, so every row carries the same value.
272
+ const refreshStats = rows[0].refreshStats !== false;
273
+ if (refreshStats) {
274
+ const cacheStatsSql = plans.cacheQueueStats(this.#config.schema, table, queues, this.#config.noAdvisoryLocks);
275
+ // The pin this pass cost, taken from the server's own clock (see the pinSeconds column in
276
+ // cacheQueueStats) and not from a stopwatch around the call - that would count pool wait,
277
+ // network and event-loop lag, none of which hold the horizon. The client measurement stays as
278
+ // a fallback for a backend or adapter that returns no rows to read it from.
279
+ const statsStarted = Date.now();
280
+ const { rows: rowsCacheStats } = await this.#executeQuery(cacheStatsSql);
281
+ const pinned = rowsCacheStats.reduce((max, row) => Math.max(max, Number(row.pinSeconds) || 0), 0);
282
+ // Only when the aggregate actually ran. An empty result means another instance holds the
283
+ // stats try-lock and this statement returned without scanning anything, so the elapsed
284
+ // client time is pool and network latency - exactly the measurement the server-side
285
+ // pinSeconds column exists to avoid backing off on.
286
+ if (rowsCacheStats.length) {
287
+ this.#statsElapsedSeconds += pinned || (Date.now() - statsStarted) / 1000;
288
+ }
289
+ if (this.#config.persistQueueStats) {
290
+ const insertSql = plans.insertQueueStats(this.#config.schema, queues, this.#config.noAdvisoryLocks);
291
+ await this.#executeQuery(insertSql);
292
+ }
293
+ if (this.#stopping)
294
+ return;
295
+ // Coerce with Number(): CockroachDB returns these integer columns as strings, so a bare `>`
296
+ // would compare lexicographically ("100" > "9" === false) and silently miss the backlog. On
297
+ // standard Postgres these are already numbers, so Number() is a no-op.
298
+ const warnings = rowsCacheStats.filter(i => Number(i.queuedCount) > (Number(i.warningQueueSize) || this.#largeQueueSize));
299
+ for (const warning of warnings) {
300
+ await emitAndPersistWarning(this.#warningContext, WARNING_TYPES.QUEUE_BACKLOG, WARNINGS.LARGE_QUEUE.message, warning);
301
+ }
199
302
  }
200
303
  if (this.#stopping)
201
304
  return;
202
- // Coerce with Number(): CockroachDB returns these integer columns as strings, so a bare `>`
203
- // would compare lexicographically ("100" > "9" === false) and silently miss the backlog. On
204
- // standard Postgres these are already numbers, so Number() is a no-op.
205
- const warnings = rowsCacheStats.filter(i => Number(i.queuedCount) > (Number(i.warningQueueSize) || this.#largeQueueSize));
206
- for (const warning of warnings) {
207
- await emitAndPersistWarning(this.#warningContext, WARNING_TYPES.QUEUE_BACKLOG, WARNINGS.LARGE_QUEUE.message, warning);
208
- }
209
305
  // CockroachDB rejects the multi-mutation failJobs() CTE these use, so under noMultiMutationCte
210
306
  // route expiry through the manager's split select/delete/re-insert variants instead.
211
307
  if (this.#config.noMultiMutationCte) {
@@ -241,6 +337,317 @@ class Boss extends EventEmitter {
241
337
  await this.#executeQuery(depSql);
242
338
  }
243
339
  }
340
+ /**
341
+ * Defer the next queue-stats aggregate when this pass spent long enough inside it to threaten
342
+ * autovacuum, and tell the operator that it happened.
343
+ *
344
+ * The aggregate in cacheQueueStats is a whole-heap scan - the queue-name filter does not reduce
345
+ * the I/O, only the number of rows fed to the aggregate transitions - and for its whole duration
346
+ * it advertises a snapshot Postgres will not vacuum past. Measured on a 3 GB / 10M-row job table:
347
+ * 0.19 s at 1M rows, 2.1 s with parallel workers and 3.6 s without at 10M, and 24-75 s for the
348
+ * same 3 GB read cold from network storage. Against that, a 200,000-row delete stayed
349
+ * "dead but not yet removable" for exactly as long as one such snapshot was held open, and was
350
+ * removed by the next vacuum after it closed.
351
+ *
352
+ * What is measured is the server's own transaction duration, not the latency of the call: a pool
353
+ * with no free connection makes executeSql slow without pinning anything (a 1 ms transaction timed
354
+ * at 1.97 s behind one busy connection), and backing off on that would defer monitoring for pool
355
+ * contention and then tell the operator to go and shrink their job table.
356
+ *
357
+ * So the danger is not the aggregate; it is the aggregate's duty cycle. Three things push it up
358
+ * without anyone changing a default - a slow enough scan that a pass outruns the interval, the
359
+ * 100-name chunking that makes one pass re-read the heap once per chunk, and one aggregate per
360
+ * table on a partitioned deployment - and one thing pushes it up deliberately, which is tuning
361
+ * monitorIntervalSeconds down. All four land in the same place, and #statsElapsedSeconds measures
362
+ * all four the same way.
363
+ *
364
+ * The threshold and the length both come from the server's own autovacuum_naptime rather than
365
+ * from a constant here; see plans.setMonitorBackoff for why the free window has to be longer than
366
+ * a naptime and why two of them is the right number. The statement returns a row only when it
367
+ * engaged, so this warns exactly when monitoring is actually being held back.
368
+ *
369
+ * PostgreSQL-only, and off with the rest of the vacuum subsystem: distributed backends have no
370
+ * autovacuum_naptime to read and reclaim on their own schedule, and an operator who set
371
+ * monitorVacuum: false has taken vacuum health into their own hands.
372
+ */
373
+ async #applyMonitorBackoff() {
374
+ const elapsed = this.#config.__test__monitor_stats_seconds ?? this.#statsElapsedSeconds;
375
+ this.#statsElapsedSeconds = 0;
376
+ if (!elapsed)
377
+ return;
378
+ if (this.#config.monitorVacuum === false || this.#config.noMonitorVacuum)
379
+ return;
380
+ const { rows } = await this.#executeQuery(plans.setMonitorBackoff(this.#config.schema, elapsed));
381
+ const engaged = rows.at(0);
382
+ if (!engaged)
383
+ return;
384
+ await emitAndPersistWarning(this.#warningContext, WARNING_TYPES.MONITOR_BACKOFF, `${WARNINGS.MONITOR_BACKOFF.message}: ${elapsed.toFixed(1)}s of aggregate against an ` +
385
+ `autovacuum_naptime of ${Number(engaged.naptimeSeconds)}s. ` +
386
+ 'Shrink the job table (retention, deleteAfterSeconds) or partition its busiest queues.', {
387
+ elapsedSeconds: elapsed,
388
+ naptimeSeconds: Number(engaged.naptimeSeconds),
389
+ backoffSeconds: Number(engaged.backoffSeconds),
390
+ backoffUntil: engaged.backoffUntil
391
+ });
392
+ }
393
+ /**
394
+ * Warn when the queues are producing garbage that nothing is reclaiming. Every documented
395
+ * Postgres-queue collapse has this as its precondition, and until now pg-boss reported only the
396
+ * symptom (`queue_backlog`) and never the cause: a backlog caused by too few workers and one
397
+ * caused by unreclaimed garbage look identical from the queue's own counters, and have opposite
398
+ * fixes.
399
+ *
400
+ * One measurement, two diagnoses, because the fixes are completely different:
401
+ *
402
+ * `xmin_horizon` vacuum runs and reclaims nothing - something is pinning the MVCC
403
+ * horizon, and the fix is to find and release the holder.
404
+ * `autovacuum_disabled` nothing runs at all - autovacuum is turned off on the table, and the
405
+ * fix is to turn it back on or vacuum on a schedule that keeps up.
406
+ *
407
+ * The trigger is the harm itself, read from `pg_stat_user_tables`, not a transaction-count proxy.
408
+ * A measured comparison of the two (scripts/xmin-horizon-experiment.js) is what settled this: the
409
+ * transaction count that corresponds to a given amount of unreclaimable garbage moves by ~9x with
410
+ * producer batching alone, and without bound with unrelated traffic on the same cluster, because
411
+ * `age()` is cluster-global. No constant survives that. Dead tuples do not move: the same 40,000
412
+ * jobs left the same 120,000 unreclaimable rows across both producer shapes.
413
+ *
414
+ * Both diagnoses share the first condition - a job table past the point Postgres itself would
415
+ * vacuum it (`autovacuum_vacuum_threshold + scale_factor * live`, per-table storage parameters
416
+ * honoured) - and then split on what two consecutive observations show:
417
+ *
418
+ * a vacuum ran and the dead-tuple count did not fall -> the horizon is pinned
419
+ * no vacuum ran and the dead-tuple count grew -> nothing is vacuuming this table
420
+ *
421
+ * Either way it takes two passes to establish, so the first pass after a problem starts never
422
+ * warns. That is the intended cost of not guessing: one supervise interval of delay buys a signal
423
+ * that means what it says. It is also what keeps an operator who has turned autovacuum off
424
+ * deliberately and vacuums on their own schedule quiet - their manual vacuum both moves the
425
+ * timestamp and drops the count, so neither branch matches.
426
+ *
427
+ * PostgreSQL-only: CockroachDB and YugabyteDB reclaim on their engine's own schedule rather than
428
+ * from the oldest live snapshot, and their profiles set noMonitorVacuum.
429
+ */
430
+ async #checkVacuum() {
431
+ if (this.#stopping)
432
+ return;
433
+ if (this.#config.monitorVacuum === false || this.#config.noMonitorVacuum)
434
+ return;
435
+ const { rows } = await this.#executeQuery(plans.getJobTableGarbage(this.#config.schema));
436
+ if (this.#stopping)
437
+ return;
438
+ // Vacuum ran and reclaimed nothing, versus nothing ran at all.
439
+ const stuck = [];
440
+ const unvacuumed = [];
441
+ // Tracked separately so one table sitting permanently over budget with autovacuum off cannot
442
+ // pin the other diagnosis's latch and suppress a later, unrelated episode.
443
+ let vacuumedOverBudget = false;
444
+ let disabledOverBudget = false;
445
+ // Partitioned queues come and go, so drop evidence for tables the query no longer returns
446
+ // rather than holding a row per table this instance has ever seen.
447
+ const present = new Set(rows.map(row => row.name));
448
+ for (const name of this.#garbageEvidence.keys()) {
449
+ if (!present.has(name))
450
+ this.#garbageEvidence.delete(name);
451
+ }
452
+ for (const row of rows) {
453
+ const name = row.name;
454
+ const liveTuples = Number(row.liveTuples);
455
+ const deadTuples = Number(row.deadTuples);
456
+ const budget = Number(row.threshold) + Number(row.scaleFactor) * liveTuples;
457
+ const disabled = row.autovacuumEnabled === false;
458
+ // Compared only against the previous observation of the same table, so any monotonic clock
459
+ // works; a table never vacuumed reads as 0 and stays there, which is exactly the state the
460
+ // autovacuum_disabled branch is looking for.
461
+ const lastVacuum = row.lastVacuum ? new Date(row.lastVacuum).getTime() : 0;
462
+ if (deadTuples <= budget) {
463
+ this.#garbageEvidence.delete(name);
464
+ continue;
465
+ }
466
+ if (disabled) {
467
+ disabledOverBudget = true;
468
+ }
469
+ else {
470
+ vacuumedOverBudget = true;
471
+ }
472
+ const previous = this.#garbageEvidence.get(name);
473
+ this.#garbageEvidence.set(name, { deadTuples, lastVacuum });
474
+ if (!previous)
475
+ continue;
476
+ const garbage = {
477
+ name,
478
+ liveTuples,
479
+ deadTuples,
480
+ budget,
481
+ // Null rather than 0 when the table has never been vacuumed at all - "never" and "just now"
482
+ // are opposite readings and must not share a value.
483
+ vacuumAgeSeconds: row.vacuumAgeSeconds === null ? null : Number(row.vacuumAgeSeconds),
484
+ lastVacuum
485
+ };
486
+ if (disabled) {
487
+ // Nothing ran between the two observations and the pile grew.
488
+ if (lastVacuum === previous.lastVacuum && deadTuples > previous.deadTuples) {
489
+ unvacuumed.push(garbage);
490
+ }
491
+ }
492
+ else if (lastVacuum > previous.lastVacuum && deadTuples >= previous.deadTuples) {
493
+ // A vacuum ran between the two observations and left at least as much garbage as it found.
494
+ stuck.push(garbage);
495
+ }
496
+ }
497
+ if (!vacuumedOverBudget)
498
+ this.#warnedXminHorizon = false;
499
+ if (!disabledOverBudget)
500
+ this.#warnedAutovacuumDisabled = false;
501
+ await this.#warnAutovacuumDisabled(unvacuumed);
502
+ await this.#warnXminHorizon(stuck);
503
+ }
504
+ /**
505
+ * Garbage piling up on a table nothing vacuums. No horizon lookup: there is no vacuum here that
506
+ * could have failed, so there is no holder to blame, and naming one would send the operator after
507
+ * the wrong thing entirely. The fix is autovacuum, not a stuck transaction.
508
+ */
509
+ async #warnAutovacuumDisabled(unvacuumed) {
510
+ if (!unvacuumed.length || this.#warnedAutovacuumDisabled || this.#stopping)
511
+ return;
512
+ this.#warnedAutovacuumDisabled = true;
513
+ const worst = unvacuumed.reduce((a, b) => (b.deadTuples > a.deadTuples ? b : a));
514
+ const since = worst.vacuumAgeSeconds === null
515
+ ? 'has never been vacuumed'
516
+ : `was last vacuumed ${worst.vacuumAgeSeconds}s ago`;
517
+ await emitAndPersistWarning(this.#warningContext, WARNING_TYPES.AUTOVACUUM_DISABLED, `${WARNINGS.AUTOVACUUM_DISABLED.message}: ${worst.name} has autovacuum_enabled = false, holds ` +
518
+ `${worst.deadTuples} dead rows and is still growing, and ${since} ` +
519
+ `(Postgres would vacuum this table at ${Math.round(worst.budget)})`, {
520
+ table: worst.name,
521
+ tables: unvacuumed.map(t => t.name),
522
+ liveTuples: worst.liveTuples,
523
+ deadTuples: worst.deadTuples,
524
+ budget: Math.round(worst.budget),
525
+ vacuumAgeSeconds: worst.vacuumAgeSeconds
526
+ });
527
+ }
528
+ /** Garbage that survived a vacuum, attributed to whatever is holding the horizon back. */
529
+ async #warnXminHorizon(stuck) {
530
+ if (!stuck.length || this.#warnedXminHorizon || this.#stopping)
531
+ return;
532
+ // The vacuum a holder has to predate is the most recent one that failed, so the newest wins.
533
+ const lastVacuum = new Date(Math.max(...stuck.map(t => t.lastVacuum)));
534
+ const horizon = await this.#readXminHorizon(lastVacuum);
535
+ if (!horizon || this.#stopping)
536
+ return;
537
+ const holder = this.#attributeXminHorizon(horizon.row);
538
+ // Garbage that survived a vacuum with no holder old enough to explain it is a real problem, but
539
+ // not this one - staying quiet is what keeps the warning worth acting on.
540
+ if (!holder)
541
+ return;
542
+ this.#warnedXminHorizon = true;
543
+ const worst = stuck.reduce((a, b) => (b.deadTuples > a.deadTuples ? b : a));
544
+ const seconds = Number(horizon.row.oldestTransactionSeconds);
545
+ const backend = holder.source === 'backends' ? horizon.row.backendHolder ?? null : null;
546
+ const opaque = Number(horizon.row.opaqueBackends) || 0;
547
+ // Only worth saying when the holder could not be named: with a name in hand the operator has
548
+ // what they need, and the grant is beside the point.
549
+ const opaqueNote = opaque && !backend
550
+ ? ` ${opaque} backend(s) could not be inspected by this role; GRANT pg_read_all_stats to name them.`
551
+ : '';
552
+ await emitAndPersistWarning(this.#warningContext, WARNING_TYPES.XMIN_HORIZON, `${WARNINGS.XMIN_HORIZON.message}: ${describeXminHolder(holder.source, horizon.row)}, ` +
553
+ `holding it ${holder.age} transactions back. ` +
554
+ `${worst.name} has ${worst.deadTuples} dead rows that a vacuum ${worst.vacuumAgeSeconds}s ago could not reclaim ` +
555
+ `(Postgres vacuums this table at ${Math.round(worst.budget)}).${opaqueNote}`, {
556
+ source: holder.source,
557
+ holder: describeXminHolder(holder.source, horizon.row),
558
+ holderClass: XMIN_HOLDERS[holder.source],
559
+ // Null unless the holder is a backend this role could read a row for. `self` says whether it
560
+ // shares this connection's application_name — pg-boss pinning its own horizon and an
561
+ // external reporting tool pinning it have opposite fixes.
562
+ holderPid: backend?.pid ?? null,
563
+ holderApplicationName: backend?.applicationName ?? null,
564
+ holderUserName: backend?.userName ?? null,
565
+ holderState: backend?.state ?? null,
566
+ holderTransactionSeconds: backend?.xactSeconds ?? null,
567
+ self: backend ? backend.applicationName === horizon.row.selfApplicationName : null,
568
+ // Backends whose transaction this role is not allowed to time. Non-zero means the picture is
569
+ // partial, whether or not a holder was named.
570
+ opaqueBackends: opaque,
571
+ transactions: holder.age,
572
+ table: worst.name,
573
+ liveTuples: worst.liveTuples,
574
+ deadTuples: worst.deadTuples,
575
+ budget: Math.round(worst.budget),
576
+ vacuumAgeSeconds: worst.vacuumAgeSeconds,
577
+ tables: stuck.map(t => t.name),
578
+ oldestTransactionSeconds: Number.isFinite(seconds) ? seconds : null,
579
+ // Named so a partial answer is never mistaken for a clean one.
580
+ unreadableSources: horizon.unreadable
581
+ });
582
+ }
583
+ /**
584
+ * The widest holder in the row. Every source the query returns has already been qualified — the
585
+ * backends column is filtered server-side to transactions that predate the failed vacuum, and a
586
+ * slot, standby or prepared transaction advertises an xmin only while something is genuinely
587
+ * stuck — so this is a straight maximum. The horizon is pinned to the oldest of them, which makes
588
+ * the widest the one worth naming.
589
+ */
590
+ #attributeXminHorizon(row) {
591
+ let worst = null;
592
+ // Iterates the known holder classes, not the row's columns: the row also carries
593
+ // oldestTransactionSeconds, which is informational and would otherwise read as a holder named
594
+ // after a column with an age of 0.
595
+ for (const source of Object.keys(XMIN_HOLDERS)) {
596
+ const value = row[source];
597
+ const age = value === undefined || value === null ? NaN : Number(value);
598
+ if (Number.isFinite(age) && (!worst || age > worst.age)) {
599
+ worst = { source, age };
600
+ }
601
+ }
602
+ return worst;
603
+ }
604
+ /**
605
+ * Read the horizon, narrowing to the sources this role can actually read. Called only when a
606
+ * table already shows unreclaimable garbage, so an installation with a healthy horizon never
607
+ * pays for it.
608
+ */
609
+ async #readXminHorizon(lastVacuum) {
610
+ const attempted = this.#xminHorizonSources ?? plans.XMIN_HORIZON_QUERY_SOURCES;
611
+ // Narrowing has already found that this role can read none of them, and the check said so once.
612
+ // Without this the empty list would be handed back to getXminHorizon, which cannot build a
613
+ // statement with no columns, and every later pass would re-fail, re-probe and re-warn.
614
+ if (!attempted.length)
615
+ return null;
616
+ try {
617
+ const { rows } = await this.#executeQuery(plans.getXminHorizon(lastVacuum, attempted));
618
+ const unreadable = plans.XMIN_HORIZON_QUERY_SOURCES.filter(name => !attempted.includes(name));
619
+ return { row: (rows[0] ?? {}), unreadable };
620
+ }
621
+ catch (err) {
622
+ // A role that cannot read one of the catalogs fails the whole statement. Narrow to the
623
+ // sources that do work rather than losing the check entirely - but remember what was dropped,
624
+ // because "no rows" from an unreadable view must not read as "horizon is healthy".
625
+ const readable = await this.#narrowXminHorizonSources(attempted, lastVacuum);
626
+ this.#xminHorizonSources = readable;
627
+ if (!readable.length) {
628
+ this.emit(events.warning, {
629
+ message: 'Unable to read the transaction horizon; the xmin_horizon check is disabled for this instance.',
630
+ data: { type: WARNING_TYPES.XMIN_HORIZON, error: err?.message }
631
+ });
632
+ }
633
+ return null;
634
+ }
635
+ }
636
+ // Probe each source alone to find which ones this role can actually read. Runs only after the
637
+ // combined query has already failed, so the cost is paid once per instance, not per pass.
638
+ async #narrowXminHorizonSources(attempted, lastVacuum) {
639
+ const readable = [];
640
+ for (const source of attempted) {
641
+ try {
642
+ await this.#executeQuery(plans.getXminHorizon(lastVacuum, [source]));
643
+ readable.push(source);
644
+ }
645
+ catch {
646
+ // Unreadable for this role; reported alongside the warning as unreadableSources.
647
+ }
648
+ }
649
+ return readable;
650
+ }
244
651
  // DDL runs outside the slow-query timer. A REINDEX is expected to take seconds — routing it
245
652
  // through #executeQuery would emit a bogus slow_query warning on every rebuild.
246
653
  async #executeDdl(sql) {
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"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"}