@secondlayer/subgraphs 3.26.0 → 3.26.2
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.
- package/dist/src/index.js +367 -76
- package/dist/src/index.js.map +7 -5
- package/dist/src/runtime/block-processor.js +29 -9
- package/dist/src/runtime/block-processor.js.map +3 -3
- package/dist/src/runtime/catchup.d.ts +34 -1
- package/dist/src/runtime/catchup.js +42 -11
- package/dist/src/runtime/catchup.js.map +4 -4
- package/dist/src/runtime/context.d.ts +20 -0
- package/dist/src/runtime/context.js +29 -9
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +344 -313
- package/dist/src/runtime/processor.js.map +7 -7
- package/dist/src/runtime/reindex.js +342 -51
- package/dist/src/runtime/reindex.js.map +7 -5
- package/dist/src/runtime/reorg.js +42 -13
- package/dist/src/runtime/reorg.js.map +4 -4
- package/dist/src/runtime/runner.d.ts +20 -0
- package/dist/src/service.js +346 -315
- package/dist/src/service.js.map +7 -7
- package/dist/src/testing/index.js +29 -9
- package/dist/src/testing/index.js.map +3 -3
- package/package.json +1 -1
|
@@ -348,6 +348,39 @@ interface SubgraphDefinition {
|
|
|
348
348
|
/** Run `fn` (a single block's write, or a reorg's whole rewind) holding the
|
|
349
349
|
* per-subgraph block lock. Exported for reorg.ts. */
|
|
350
350
|
declare function withSubgraphBlockLock<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
|
351
|
+
/**
|
|
352
|
+
* Cross-process half of the block lock: a transaction-scoped Postgres advisory
|
|
353
|
+
* lock keyed `subgraph-block:<name>`, held for the duration of `fn`.
|
|
354
|
+
*
|
|
355
|
+
* Held, not merely acquired: each block's own write transaction is opened
|
|
356
|
+
* INSIDE `processBlock` (block-processor.ts — `targetDb.transaction()` for
|
|
357
|
+
* managed subgraphs, `route.dataDb.transaction()` for BYO), and that boundary
|
|
358
|
+
* is load-bearing for the f069 replay guard, so the lock cannot be taken on
|
|
359
|
+
* the writing connection. Instead this wraps the write in a separate
|
|
360
|
+
* lock-holding transaction on the target DB, exactly as the reorg lock does
|
|
361
|
+
* (reorg.ts): `pg_advisory_xact_lock` releases only when THIS transaction
|
|
362
|
+
* commits, regardless of which other connections `fn` awaits meanwhile.
|
|
363
|
+
* Transaction-scoped rather than session-scoped so a crashed walk can never
|
|
364
|
+
* strand the lock.
|
|
365
|
+
*
|
|
366
|
+
* The lock lives on the target DB even for BYO subgraphs — same as the reorg
|
|
367
|
+
* lock — because it coordinates control-plane walkers, not data-plane rows.
|
|
368
|
+
*
|
|
369
|
+
* Connection budget: this holds one pooled target-DB connection while `fn`
|
|
370
|
+
* takes another for the write, so an in-flight block costs 2 of
|
|
371
|
+
* `DATABASE_POOL_MAX` (default 20). Concurrent block writes are bounded by
|
|
372
|
+
* catch-up fan-out (5 subgraphs) plus operation concurrency (1) = 6 blocks =
|
|
373
|
+
* 12 connections. Raising either concurrency knob must be checked against the
|
|
374
|
+
* pool, or the wrapping transactions can starve the writes they wrap.
|
|
375
|
+
*/
|
|
376
|
+
declare function withSubgraphBlockAdvisoryLock<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
|
377
|
+
/**
|
|
378
|
+
* One block's write, holding BOTH halves of the block lock in the documented
|
|
379
|
+
* order: the in-process mutex, then the cross-process advisory lock. Every
|
|
380
|
+
* walk that writes a subgraph's schema — live catch-up, reindex, backfill —
|
|
381
|
+
* must go through this, or it serializes against nothing.
|
|
382
|
+
*/
|
|
383
|
+
declare function withSubgraphBlockWriteLock<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
|
351
384
|
/** Bump a subgraph's reorg epoch. Call BEFORE acquiring the block lock so the
|
|
352
385
|
* bump is visible to any concurrent catch-up immediately. Exported for
|
|
353
386
|
* reorg.ts. */
|
|
@@ -371,4 +404,4 @@ declare function resolveCatchupBatchConfig(env?: CatchupBatchEnv): CatchupBatchC
|
|
|
371
404
|
* plan-aware pipeline prefetching.
|
|
372
405
|
*/
|
|
373
406
|
declare function catchUpSubgraph(subgraph: SubgraphDefinition, subgraphName: string): Promise<number>;
|
|
374
|
-
export { withSubgraphBlockLock, resolveCatchupBatchConfig, catchUpSubgraph, bumpReorgEpoch };
|
|
407
|
+
export { withSubgraphBlockWriteLock, withSubgraphBlockLock, withSubgraphBlockAdvisoryLock, resolveCatchupBatchConfig, catchUpSubgraph, bumpReorgEpoch };
|
|
@@ -605,14 +605,10 @@ class SubgraphContext {
|
|
|
605
605
|
this.ops.length = 0;
|
|
606
606
|
const statements = this.buildStatements(opsToFlush);
|
|
607
607
|
if ("isTransaction" in this.db) {
|
|
608
|
-
|
|
609
|
-
await sql.raw(stmt).execute(this.db);
|
|
610
|
-
}
|
|
608
|
+
await this.execStatements(this.db, statements);
|
|
611
609
|
} else {
|
|
612
610
|
await this.db.transaction().execute(async (tx) => {
|
|
613
|
-
|
|
614
|
-
await sql.raw(stmt).execute(tx);
|
|
615
|
-
}
|
|
611
|
+
await this.execStatements(tx, statements);
|
|
616
612
|
});
|
|
617
613
|
}
|
|
618
614
|
const writes = opsToFlush.map((op, rowIndex) => {
|
|
@@ -631,6 +627,25 @@ class SubgraphContext {
|
|
|
631
627
|
});
|
|
632
628
|
return { count: opsToFlush.length, writes };
|
|
633
629
|
}
|
|
630
|
+
async execStatements(executor, statements) {
|
|
631
|
+
const affected = (r) => r.numAffectedRows ?? 0n;
|
|
632
|
+
for (const stmt of statements) {
|
|
633
|
+
if (typeof stmt === "string") {
|
|
634
|
+
await sql.raw(stmt).execute(executor);
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const updated = await sql.raw(stmt.update).execute(executor);
|
|
638
|
+
if (affected(updated) > 0n)
|
|
639
|
+
continue;
|
|
640
|
+
const inserted = await sql.raw(stmt.insert).execute(executor);
|
|
641
|
+
if (affected(inserted) > 0n)
|
|
642
|
+
continue;
|
|
643
|
+
const retried = await sql.raw(stmt.update).execute(executor);
|
|
644
|
+
if (affected(retried) === 0n) {
|
|
645
|
+
throw new Error(`increment could not be applied to ${this.pgSchemaName}.${stmt.table} where ${stmt.keyPred}: row neither updated nor inserted`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
634
649
|
prepareInsert(op) {
|
|
635
650
|
const upsertKeys = op.data._upsert_keys;
|
|
636
651
|
const data = { ...op.data };
|
|
@@ -710,7 +725,6 @@ class SubgraphContext {
|
|
|
710
725
|
for (const r of batch.rows.values()) {
|
|
711
726
|
const keyPred = batch.keyCols.map((k) => `"${k}" = ${escapeLiteral(r.keys[k])}`).join(" AND ");
|
|
712
727
|
const updSet = batch.deltaCols.map((c) => `"${c}" = COALESCE("${c}", 0) + (${String(r.deltas[c] ?? 0n)})`).join(", ");
|
|
713
|
-
statements.push(`UPDATE ${qualifiedTable} SET ${updSet} WHERE ${keyPred}`);
|
|
714
728
|
const insertVals = [
|
|
715
729
|
...batch.keyCols.map((k) => escapeLiteral(r.keys[k])),
|
|
716
730
|
...batch.deltaCols.map((c) => String(r.deltas[c] ?? 0n)),
|
|
@@ -718,7 +732,13 @@ class SubgraphContext {
|
|
|
718
732
|
escapeLiteral(r.meta.txId),
|
|
719
733
|
"NOW()"
|
|
720
734
|
];
|
|
721
|
-
statements.push(
|
|
735
|
+
statements.push({
|
|
736
|
+
kind: "increment",
|
|
737
|
+
table: batch.table,
|
|
738
|
+
keyPred,
|
|
739
|
+
update: `UPDATE ${qualifiedTable} SET ${updSet} WHERE ${keyPred}`,
|
|
740
|
+
insert: `INSERT INTO ${qualifiedTable} (${cols.map((c) => `"${c}"`).join(", ")}) ` + `SELECT ${insertVals.join(", ")} ` + `WHERE NOT EXISTS (SELECT 1 FROM ${qualifiedTable} WHERE ${keyPred})`
|
|
741
|
+
});
|
|
722
742
|
}
|
|
723
743
|
incBatch = null;
|
|
724
744
|
incBatchKey = "";
|
|
@@ -3025,7 +3045,7 @@ class StatsAccumulator {
|
|
|
3025
3045
|
}
|
|
3026
3046
|
|
|
3027
3047
|
// src/runtime/catchup.ts
|
|
3028
|
-
import { getTargetDb as getTargetDb2 } from "@secondlayer/shared/db";
|
|
3048
|
+
import { getTargetDb as getTargetDb2, sql as sql4 } from "@secondlayer/shared/db";
|
|
3029
3049
|
import { getSubgraph } from "@secondlayer/shared/db/queries/subgraphs";
|
|
3030
3050
|
import { logger as logger8 } from "@secondlayer/shared/logger";
|
|
3031
3051
|
|
|
@@ -3096,6 +3116,15 @@ async function withSubgraphBlockLock(name, fn) {
|
|
|
3096
3116
|
release();
|
|
3097
3117
|
}
|
|
3098
3118
|
}
|
|
3119
|
+
async function withSubgraphBlockAdvisoryLock(name, fn) {
|
|
3120
|
+
return getTargetDb2().transaction().execute(async (lockTx) => {
|
|
3121
|
+
await sql4`SELECT pg_advisory_xact_lock(hashtext(${`subgraph-block:${name}`}))`.execute(lockTx);
|
|
3122
|
+
return fn();
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
async function withSubgraphBlockWriteLock(name, fn) {
|
|
3126
|
+
return withSubgraphBlockLock(name, () => withSubgraphBlockAdvisoryLock(name, fn));
|
|
3127
|
+
}
|
|
3099
3128
|
function bumpReorgEpoch(name) {
|
|
3100
3129
|
reorgEpoch.set(name, (reorgEpoch.get(name) ?? 0) + 1);
|
|
3101
3130
|
}
|
|
@@ -3224,7 +3253,7 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3224
3253
|
const preloaded = blockData;
|
|
3225
3254
|
let result;
|
|
3226
3255
|
try {
|
|
3227
|
-
result = await
|
|
3256
|
+
result = await withSubgraphBlockWriteLock(subgraphName, async () => {
|
|
3228
3257
|
if (getReorgEpoch(subgraphName) !== walkEpoch) {
|
|
3229
3258
|
throw new ReorgEpochChangedError(subgraphName);
|
|
3230
3259
|
}
|
|
@@ -3284,11 +3313,13 @@ async function catchUpSubgraph(subgraph, subgraphName) {
|
|
|
3284
3313
|
}
|
|
3285
3314
|
}
|
|
3286
3315
|
export {
|
|
3316
|
+
withSubgraphBlockWriteLock,
|
|
3287
3317
|
withSubgraphBlockLock,
|
|
3318
|
+
withSubgraphBlockAdvisoryLock,
|
|
3288
3319
|
resolveCatchupBatchConfig,
|
|
3289
3320
|
catchUpSubgraph,
|
|
3290
3321
|
bumpReorgEpoch
|
|
3291
3322
|
};
|
|
3292
3323
|
|
|
3293
|
-
//# debugId=
|
|
3324
|
+
//# debugId=376967D5C07D633664756E2164756E21
|
|
3294
3325
|
//# sourceMappingURL=catchup.js.map
|