agrs-sequelize-sdk 1.4.42 → 1.4.44

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.
@@ -292,6 +292,25 @@ module.exports = (sequelize, DataTypes) => {
292
292
  {
293
293
  fields: ["createdAt"],
294
294
  },
295
+ // Partial index mirroring the V2 side (idx_v2_awaiting_by_feed_campaign).
296
+ // The hourly Tarzo reconciler runs the SAME awaiting-materialization
297
+ // WHERE clause against both V1 and V2 (dual-write parity). Without this
298
+ // index the V1 UPDATE seq-scans 15.8M rows / 11GB and hits the 60s
299
+ // statement_timeout (verified in prod 2026-07-19: 207s EXPLAIN).
300
+ //
301
+ // Composite (feed_campaign_id, createdAt DESC) covers both:
302
+ // - step 2/5 join key (rfc.AGRS_CID = l.feed_campaign_id)
303
+ // - step 3/5 stale-age filter (l.createdAt < now() - 6h)
304
+ //
305
+ // Equality predicate — LIKE would be ignored by the planner (proven
306
+ // 2026-07-19). Self-pruning: rows drop out on COMPLETED/FAILED flip.
307
+ {
308
+ name: "idx_v1_awaiting_by_feed_campaign",
309
+ fields: ["feed_campaign_id", { name: "createdAt", order: "DESC" }],
310
+ where: sequelize.literal(
311
+ `status = 'IN_PROGRESS' AND (details->>'ai_stage') = 'tarzo_awaiting_materialization'`
312
+ ),
313
+ },
295
314
  ],
296
315
  }
297
316
  );
@@ -285,28 +285,56 @@ module.exports = (sequelize, DataTypes) => {
285
285
  { fields: ["vertical", "feed_provider"] },
286
286
  { fields: ["details"], using: "gin" },
287
287
  { fields: ["platform_code"] },
288
- // Expression index on details->>'ai_stage' for Tarzo-only rows.
289
- // Powers the hourly reconciler steps 2/5 (awaiting → COMPLETED) and
290
- // 3/5 (stale → FAILED) added by tarzoMaterializationReconciler.
291
- // Without this the queries do a full-table scan of the 50GB+ table
292
- // and time out (verified in prod 2026-07-19: errors=3 every hour).
293
- // Partial WHERE keeps the index tiny only Tarzo awaiting/failed
294
- // rows are indexed, not the whole table.
288
+ // Partial indexes for the hourly Tarzo reconciler (steps 2/5 and 3/5
289
+ // in reconcilerService.js). Both steps filter on:
290
+ // status='IN_PROGRESS'
291
+ // AND details->>'ai_stage'='tarzo_awaiting_materialization'
292
+ // Predicate is equality NOT `LIKE 'tarzo_%'` because Postgres's
293
+ // partial-index prover doesn't cross equality→LIKE, so a LIKE-predicate
294
+ // index would be ignored (verified in prod 2026-07-19). Equality
295
+ // matches the queries verbatim, so the planner picks these up.
296
+ //
297
+ // Column choices:
298
+ // - feed_campaign_id: drives the nested-loop join to
299
+ // rsoc_feed_campaigns.AGRS_CID in step 2/5.
300
+ // - created_at: powers the stale age filter in step 3/5.
301
+ //
302
+ // Self-pruning: rows leave the index as soon as status flips to
303
+ // COMPLETED/FAILED, so index size stays proportional to in-flight
304
+ // rows only.
295
305
  {
296
- name: "idx_v2_ai_stage_tarzo",
297
- fields: [sequelize.literal(`((details->>'ai_stage'))`)],
298
- where: sequelize.literal(`(details->>'ai_stage') LIKE 'tarzo_%'`),
306
+ name: "idx_v2_awaiting_by_feed_campaign",
307
+ fields: ["feed_campaign_id"],
308
+ where: sequelize.literal(
309
+ `status = 'IN_PROGRESS' AND (details->>'ai_stage') = 'tarzo_awaiting_materialization'`
310
+ ),
299
311
  },
300
- // Composite index for the stale-cleanup query (step 3/5) which
301
- // filters by ai_stage AND created_at < now() - 6h. Same partial
302
- // WHERE keeps size minimal.
303
312
  {
304
- name: "idx_v2_ai_stage_tarzo_created_at",
305
- fields: [
306
- sequelize.literal(`((details->>'ai_stage'))`),
307
- { name: "created_at", order: "DESC" },
308
- ],
309
- where: sequelize.literal(`(details->>'ai_stage') LIKE 'tarzo_%'`),
313
+ name: "idx_v2_awaiting_created_at",
314
+ fields: [{ name: "created_at", order: "DESC" }],
315
+ where: sequelize.literal(
316
+ `status = 'IN_PROGRESS' AND (details->>'ai_stage') = 'tarzo_awaiting_materialization'`
317
+ ),
318
+ },
319
+ // Partial index for the ai_campaign_queue backfill query in step 2/5
320
+ // (completeAdoptedAwaitingLogs's third UPDATE). After the awaiting log
321
+ // flips to COMPLETED, ai_stage becomes 'tarzo_materialized_and_adopted'
322
+ // and the queue's facebook_campaign_id gets backfilled by joining V2
323
+ // to ai_campaign_queue via process_id.
324
+ //
325
+ // Without this, the query bitmap-scans by status='COMPLETED' (returns
326
+ // 1.2M rows), then filters ai_stage in memory (rejects 1.18M) → 175s
327
+ // seq scan, 60s timeout in prod (verified 2026-07-19).
328
+ //
329
+ // Composite (process_id, feed_campaign_id) supports both the join to
330
+ // ai_campaign_queue.creation_logs_process_id AND the further join to
331
+ // rsoc_feed_campaigns.AGRS_CID.
332
+ {
333
+ name: "idx_v2_adopted_by_process",
334
+ fields: ["process_id", "feed_campaign_id"],
335
+ where: sequelize.literal(
336
+ `status = 'COMPLETED' AND (details->>'ai_stage') = 'tarzo_materialized_and_adopted'`
337
+ ),
310
338
  },
311
339
  ],
312
340
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agrs-sequelize-sdk",
3
- "version": "1.4.42",
3
+ "version": "1.4.44",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "start": "node index.js",