envio 3.3.0-alpha.0 → 3.3.0-alpha.1
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/package.json +6 -6
- package/src/ChainFetching.res +6 -8
- package/src/ChainFetching.res.mjs +3 -3
- package/src/ChainState.res +26 -1
- package/src/ChainState.res.mjs +15 -0
- package/src/ChainState.resi +2 -0
- package/src/CrossChainState.res +97 -72
- package/src/CrossChainState.res.mjs +68 -44
- package/src/CrossChainState.resi +1 -9
- package/src/Env.res +0 -7
- package/src/Env.res.mjs +0 -6
- package/src/FetchState.res +92 -22
- package/src/FetchState.res.mjs +87 -14
- package/src/IndexerLoop.res +2 -3
- package/src/IndexerLoop.res.mjs +1 -1
- package/src/IndexerState.res +0 -8
- package/src/IndexerState.res.mjs +4 -8
- package/src/IndexerState.resi +0 -4
- package/src/Main.res.mjs +1 -1
- package/src/Prometheus.res +0 -11
- package/src/Prometheus.res.mjs +83 -98
- package/src/sources/SourceManager.res +9 -8
- package/src/sources/SourceManager.res.mjs +21 -26
- package/src/sources/SourceManager.resi +2 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "3.3.0-alpha.
|
|
3
|
+
"version": "3.3.0-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
6
6
|
"bin": "./bin.mjs",
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
"tsx": "4.21.0"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|
|
72
|
-
"envio-linux-x64": "3.3.0-alpha.
|
|
73
|
-
"envio-linux-x64-musl": "3.3.0-alpha.
|
|
74
|
-
"envio-linux-arm64": "3.3.0-alpha.
|
|
75
|
-
"envio-darwin-x64": "3.3.0-alpha.
|
|
76
|
-
"envio-darwin-arm64": "3.3.0-alpha.
|
|
72
|
+
"envio-linux-x64": "3.3.0-alpha.1",
|
|
73
|
+
"envio-linux-x64-musl": "3.3.0-alpha.1",
|
|
74
|
+
"envio-linux-arm64": "3.3.0-alpha.1",
|
|
75
|
+
"envio-darwin-x64": "3.3.0-alpha.1",
|
|
76
|
+
"envio-darwin-arm64": "3.3.0-alpha.1"
|
|
77
77
|
}
|
|
78
78
|
}
|
package/src/ChainFetching.res
CHANGED
|
@@ -308,8 +308,7 @@ let finishWaitingForNewBlock = (
|
|
|
308
308
|
let fetchChain = async (
|
|
309
309
|
state: IndexerState.t,
|
|
310
310
|
chain,
|
|
311
|
-
~
|
|
312
|
-
~bufferLimit,
|
|
311
|
+
~action,
|
|
313
312
|
~stateId,
|
|
314
313
|
~scheduleFetch,
|
|
315
314
|
~scheduleProcessing,
|
|
@@ -321,14 +320,14 @@ let fetchChain = async (
|
|
|
321
320
|
let isRealtime = state->IndexerState.isRealtime
|
|
322
321
|
let sourceManager = chainState->ChainState.sourceManager
|
|
323
322
|
|
|
324
|
-
// Only affects the WaitingForNewBlock branch of
|
|
323
|
+
// Only affects the WaitingForNewBlock branch of dispatch, where
|
|
325
324
|
// there's nothing to fetch. During backfill any such chain is idle.
|
|
326
325
|
let reducedPolling = !isRealtime
|
|
327
326
|
|
|
328
327
|
// Owns its error boundary: launch doesn't catch, so any failure here (the
|
|
329
|
-
// query, response handling, or
|
|
328
|
+
// query, response handling, or dispatch itself) must stop the indexer.
|
|
330
329
|
try {
|
|
331
|
-
await sourceManager->SourceManager.
|
|
330
|
+
await sourceManager->SourceManager.dispatch(
|
|
332
331
|
~fetchState,
|
|
333
332
|
~waitForNewBlock=(~knownHeight) =>
|
|
334
333
|
sourceManager->SourceManager.waitForNewBlock(~knownHeight, ~isRealtime, ~reducedPolling),
|
|
@@ -343,7 +342,7 @@ let fetchChain = async (
|
|
|
343
342
|
),
|
|
344
343
|
~executeQuery=async query => {
|
|
345
344
|
// Caught here (not just by the outer try) so the query promise never
|
|
346
|
-
// rejects:
|
|
345
|
+
// rejects: dispatch spins a side-chain off it that would otherwise
|
|
347
346
|
// become an unhandled rejection.
|
|
348
347
|
try {
|
|
349
348
|
let response = await sourceManager->SourceManager.executeQuery(
|
|
@@ -363,8 +362,7 @@ let fetchChain = async (
|
|
|
363
362
|
| exn => IndexerState.errorExit(state, exn->ErrorHandling.make)
|
|
364
363
|
}
|
|
365
364
|
},
|
|
366
|
-
~
|
|
367
|
-
~bufferLimit,
|
|
365
|
+
~action,
|
|
368
366
|
~stateId,
|
|
369
367
|
)
|
|
370
368
|
} catch {
|
|
@@ -167,7 +167,7 @@ function finishWaitingForNewBlock(state, chain, knownHeight, stateId, scheduleFe
|
|
|
167
167
|
scheduleProcessing();
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
async function fetchChain(state, chain,
|
|
170
|
+
async function fetchChain(state, chain, action, stateId, scheduleFetch, scheduleProcessing, scheduleRollback) {
|
|
171
171
|
let chainState = IndexerState.getChainState(state, chain);
|
|
172
172
|
if (IndexerState.isResolvingReorg(state) || IndexerState.isStopped(state)) {
|
|
173
173
|
return;
|
|
@@ -177,7 +177,7 @@ async function fetchChain(state, chain, concurrencyLimit, bufferLimit, stateId,
|
|
|
177
177
|
let sourceManager = ChainState.sourceManager(chainState);
|
|
178
178
|
let reducedPolling = !isRealtime;
|
|
179
179
|
try {
|
|
180
|
-
return await SourceManager.
|
|
180
|
+
return await SourceManager.dispatch(sourceManager, fetchState, async query => {
|
|
181
181
|
try {
|
|
182
182
|
let response = await SourceManager.executeQuery(sourceManager, query, fetchState.knownHeight, isRealtime);
|
|
183
183
|
return await onQueryResponse(state, {
|
|
@@ -189,7 +189,7 @@ async function fetchChain(state, chain, concurrencyLimit, bufferLimit, stateId,
|
|
|
189
189
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
190
190
|
return IndexerState.errorExit(state, ErrorHandling.make(exn, undefined, undefined));
|
|
191
191
|
}
|
|
192
|
-
}, knownHeight => SourceManager.waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling), knownHeight => finishWaitingForNewBlock(state, chain, knownHeight, stateId, scheduleFetch, scheduleProcessing),
|
|
192
|
+
}, knownHeight => SourceManager.waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling), knownHeight => finishWaitingForNewBlock(state, chain, knownHeight, stateId, scheduleFetch, scheduleProcessing), action, stateId);
|
|
193
193
|
} catch (raw_exn) {
|
|
194
194
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
195
195
|
return IndexerState.errorExit(state, ErrorHandling.make(exn, undefined, IndexerState.unexpectedErrorMsg));
|
package/src/ChainState.res
CHANGED
|
@@ -11,6 +11,10 @@ type t = {
|
|
|
11
11
|
mutable timestampCaughtUpToHeadOrEndblock: option<Date.t>,
|
|
12
12
|
mutable committedProgressBlockNumber: int,
|
|
13
13
|
mutable numEventsProcessed: float,
|
|
14
|
+
// Running sum of in-flight queries' estResponseSize, kept here so the
|
|
15
|
+
// scheduler doesn't re-sum pending queries on every tick. Incremented when
|
|
16
|
+
// queries are dispatched, decremented as their responses land.
|
|
17
|
+
mutable pendingBudget: float,
|
|
14
18
|
mutable reorgDetection: ReorgDetection.t,
|
|
15
19
|
mutable safeCheckpointTracking: option<SafeCheckpointTracking.t>,
|
|
16
20
|
}
|
|
@@ -49,6 +53,7 @@ let make = (
|
|
|
49
53
|
timestampCaughtUpToHeadOrEndblock,
|
|
50
54
|
committedProgressBlockNumber,
|
|
51
55
|
numEventsProcessed,
|
|
56
|
+
pendingBudget: 0.,
|
|
52
57
|
reorgDetection,
|
|
53
58
|
safeCheckpointTracking,
|
|
54
59
|
}
|
|
@@ -380,8 +385,25 @@ let safeCheckpointTracking = (cs: t) => cs.safeCheckpointTracking
|
|
|
380
385
|
let isProgressAtHead = (cs: t) => cs.isProgressAtHead
|
|
381
386
|
let committedProgressBlockNumber = (cs: t) => cs.committedProgressBlockNumber
|
|
382
387
|
let numEventsProcessed = (cs: t) => cs.numEventsProcessed
|
|
388
|
+
let pendingBudget = (cs: t) => cs.pendingBudget
|
|
383
389
|
let timestampCaughtUpToHeadOrEndblock = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock
|
|
384
390
|
|
|
391
|
+
// Mark queries as in flight and reserve their estimated size against the shared
|
|
392
|
+
// buffer budget in one step, so the counter stays in sync with the pending
|
|
393
|
+
// queries it tracks.
|
|
394
|
+
let startFetchingQueries = (cs: t, ~queries: array<FetchState.query>) => {
|
|
395
|
+
cs.fetchState->FetchState.startFetchingQueries(~queries)
|
|
396
|
+
cs.pendingBudget =
|
|
397
|
+
cs.pendingBudget +. queries->Array.reduce(0., (acc, query) => acc +. query.estResponseSize)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Drop every in-flight query and release their reservations together, keeping
|
|
401
|
+
// pendingBudget coupled to the pending queries it tracks.
|
|
402
|
+
let resetPendingQueries = (cs: t) => {
|
|
403
|
+
cs.fetchState = cs.fetchState->FetchState.resetPendingQueries
|
|
404
|
+
cs.pendingBudget = 0.
|
|
405
|
+
}
|
|
406
|
+
|
|
385
407
|
// --- Derived (pure). ---
|
|
386
408
|
|
|
387
409
|
let hasProcessedToEndblock = (cs: t) => {
|
|
@@ -426,6 +448,9 @@ let handleQueryResult = (
|
|
|
426
448
|
fs
|
|
427
449
|
->FetchState.handleQueryResult(~query, ~latestFetchedBlock, ~newItems)
|
|
428
450
|
->FetchState.updateKnownHeight(~knownHeight)
|
|
451
|
+
|
|
452
|
+
// The query is no longer in flight, so release its reservation.
|
|
453
|
+
cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.estResponseSize)
|
|
429
454
|
}
|
|
430
455
|
|
|
431
456
|
// Run reorg detection against a fetch response and commit the updated guard.
|
|
@@ -447,7 +472,7 @@ let prepareReorg = (cs: t, ~eventsProcessedDiff) => {
|
|
|
447
472
|
| Some(diff) => cs.numEventsProcessed = cs.numEventsProcessed +. diff
|
|
448
473
|
| None => ()
|
|
449
474
|
}
|
|
450
|
-
cs
|
|
475
|
+
cs->resetPendingQueries
|
|
451
476
|
}
|
|
452
477
|
|
|
453
478
|
let updateKnownHeight = (cs: t, ~knownHeight) =>
|
package/src/ChainState.res.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
17
17
|
import * as ReorgDetection from "./ReorgDetection.res.mjs";
|
|
18
18
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
19
19
|
import * as HyperFuelSource from "./sources/HyperFuelSource.res.mjs";
|
|
20
|
+
import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
|
|
20
21
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
21
22
|
import * as SvmHyperSyncSource from "./sources/SvmHyperSyncSource.res.mjs";
|
|
22
23
|
import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
|
|
@@ -49,6 +50,7 @@ function make(chainConfig, fetchState, sourceManager, reorgDetection, committedP
|
|
|
49
50
|
timestampCaughtUpToHeadOrEndblock: timestampCaughtUpToHeadOrEndblock,
|
|
50
51
|
committedProgressBlockNumber: committedProgressBlockNumber,
|
|
51
52
|
numEventsProcessed: numEventsProcessed,
|
|
53
|
+
pendingBudget: 0,
|
|
52
54
|
reorgDetection: reorgDetection,
|
|
53
55
|
safeCheckpointTracking: safeCheckpointTracking
|
|
54
56
|
};
|
|
@@ -225,10 +227,19 @@ function numEventsProcessed(cs) {
|
|
|
225
227
|
return cs.numEventsProcessed;
|
|
226
228
|
}
|
|
227
229
|
|
|
230
|
+
function pendingBudget(cs) {
|
|
231
|
+
return cs.pendingBudget;
|
|
232
|
+
}
|
|
233
|
+
|
|
228
234
|
function timestampCaughtUpToHeadOrEndblock(cs) {
|
|
229
235
|
return cs.timestampCaughtUpToHeadOrEndblock;
|
|
230
236
|
}
|
|
231
237
|
|
|
238
|
+
function startFetchingQueries(cs, queries) {
|
|
239
|
+
FetchState.startFetchingQueries(cs.fetchState, queries);
|
|
240
|
+
cs.pendingBudget = cs.pendingBudget + Stdlib_Array.reduce(queries, 0, (acc, query) => acc + query.estResponseSize);
|
|
241
|
+
}
|
|
242
|
+
|
|
232
243
|
function hasProcessedToEndblock(cs) {
|
|
233
244
|
let fetchState = cs.fetchState;
|
|
234
245
|
let committedProgressBlockNumber = cs.committedProgressBlockNumber;
|
|
@@ -264,6 +275,7 @@ function isFetchingAtHead(cs) {
|
|
|
264
275
|
function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBlock, knownHeight) {
|
|
265
276
|
let fs = newItemsWithDcs.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, newItemsWithDcs) : cs.fetchState;
|
|
266
277
|
cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, query, latestFetchedBlock, newItems), knownHeight);
|
|
278
|
+
cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.estResponseSize);
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
function registerReorgGuard(cs, blockHashes, knownHeight) {
|
|
@@ -277,6 +289,7 @@ function prepareReorg(cs, eventsProcessedDiff) {
|
|
|
277
289
|
cs.numEventsProcessed = cs.numEventsProcessed + eventsProcessedDiff;
|
|
278
290
|
}
|
|
279
291
|
cs.fetchState = FetchState.resetPendingQueries(cs.fetchState);
|
|
292
|
+
cs.pendingBudget = 0;
|
|
280
293
|
}
|
|
281
294
|
|
|
282
295
|
function updateKnownHeight(cs, knownHeight) {
|
|
@@ -441,6 +454,8 @@ export {
|
|
|
441
454
|
isProgressAtHead,
|
|
442
455
|
committedProgressBlockNumber,
|
|
443
456
|
numEventsProcessed,
|
|
457
|
+
pendingBudget,
|
|
458
|
+
startFetchingQueries,
|
|
444
459
|
timestampCaughtUpToHeadOrEndblock,
|
|
445
460
|
hasProcessedToEndblock,
|
|
446
461
|
getHighestBlockBelowThreshold,
|
package/src/ChainState.resi
CHANGED
|
@@ -46,6 +46,8 @@ let safeCheckpointTracking: t => option<SafeCheckpointTracking.t>
|
|
|
46
46
|
let isProgressAtHead: t => bool
|
|
47
47
|
let committedProgressBlockNumber: t => int
|
|
48
48
|
let numEventsProcessed: t => float
|
|
49
|
+
let pendingBudget: t => float
|
|
50
|
+
let startFetchingQueries: (t, ~queries: array<FetchState.query>) => unit
|
|
49
51
|
let timestampCaughtUpToHeadOrEndblock: t => option<Date.t>
|
|
50
52
|
|
|
51
53
|
// Derived (pure).
|
package/src/CrossChainState.res
CHANGED
|
@@ -11,10 +11,6 @@ type t = {
|
|
|
11
11
|
// True once every chain has caught up to head/endBlock. Monotonic during a run.
|
|
12
12
|
mutable isRealtime: bool,
|
|
13
13
|
mutable isInReorgThreshold: bool,
|
|
14
|
-
// Indexer-wide caps on concurrent data-source queries, shared across all
|
|
15
|
-
// chains. The realtime budget applies once every chain is at head.
|
|
16
|
-
maxBackfillConcurrency: int,
|
|
17
|
-
maxRealtimeConcurrency: int,
|
|
18
14
|
// Indexer-wide fetch buffer pool (item count), shared across all chains.
|
|
19
15
|
targetBufferSize: int,
|
|
20
16
|
}
|
|
@@ -26,18 +22,10 @@ let calculateTargetBufferSize = () =>
|
|
|
26
22
|
| None => 100_000
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
// The concurrency budget in force for the current phase.
|
|
30
|
-
let maxConcurrency = (crossChainState: t) =>
|
|
31
|
-
crossChainState.isRealtime
|
|
32
|
-
? crossChainState.maxRealtimeConcurrency
|
|
33
|
-
: crossChainState.maxBackfillConcurrency
|
|
34
|
-
|
|
35
25
|
let make = (
|
|
36
26
|
~chainStates,
|
|
37
27
|
~isInReorgThreshold,
|
|
38
28
|
~isRealtime,
|
|
39
|
-
~maxBackfillConcurrency=Env.maxBackfillConcurrency,
|
|
40
|
-
~maxRealtimeConcurrency=Env.maxRealtimeConcurrency,
|
|
41
29
|
~targetBufferSize=calculateTargetBufferSize(),
|
|
42
30
|
): t => {
|
|
43
31
|
let crossChainState = {
|
|
@@ -45,11 +33,8 @@ let make = (
|
|
|
45
33
|
chainIds: chainStates->Dict.valuesToArray->Array.map(cs => (cs->ChainState.chainConfig).id),
|
|
46
34
|
isRealtime,
|
|
47
35
|
isInReorgThreshold,
|
|
48
|
-
maxBackfillConcurrency,
|
|
49
|
-
maxRealtimeConcurrency,
|
|
50
36
|
targetBufferSize,
|
|
51
37
|
}
|
|
52
|
-
Prometheus.IndexingMaxConcurrency.set(~maxConcurrency=crossChainState->maxConcurrency)
|
|
53
38
|
Prometheus.IndexingTargetBufferSize.set(~targetBufferSize)
|
|
54
39
|
crossChainState
|
|
55
40
|
}
|
|
@@ -65,17 +50,6 @@ let chainStates = (crossChainState: t) => crossChainState.chainStates
|
|
|
65
50
|
let isRealtime = (crossChainState: t) => crossChainState.isRealtime
|
|
66
51
|
let isInReorgThreshold = (crossChainState: t) => crossChainState.isInReorgThreshold
|
|
67
52
|
|
|
68
|
-
// Partition queries in flight across every chain — the live draw against
|
|
69
|
-
// maxConcurrency.
|
|
70
|
-
let inFlight = (crossChainState: t) => {
|
|
71
|
-
let total = ref(0)
|
|
72
|
-
for i in 0 to crossChainState.chainIds->Array.length - 1 {
|
|
73
|
-
let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
|
|
74
|
-
total := total.contents + cs->ChainState.sourceManager->SourceManager.inFlightCount
|
|
75
|
-
}
|
|
76
|
-
total.contents
|
|
77
|
-
}
|
|
78
|
-
|
|
79
53
|
// Ready-to-process items across every chain — the live draw against
|
|
80
54
|
// targetBufferSize, which is a budget of processable events (items stuck behind
|
|
81
55
|
// a gap don't count toward the goal of keeping ~targetBufferSize ready).
|
|
@@ -199,18 +173,13 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t) => {
|
|
|
199
173
|
Prometheus.ProgressReady.setAllReady()
|
|
200
174
|
}
|
|
201
175
|
|
|
202
|
-
let wasRealtime = crossChainState.isRealtime
|
|
203
176
|
crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady.contents
|
|
204
|
-
if !wasRealtime && crossChainState.isRealtime {
|
|
205
|
-
// The realtime budget takes over now that every chain is at head.
|
|
206
|
-
Prometheus.IndexingMaxConcurrency.set(~maxConcurrency=crossChainState->maxConcurrency)
|
|
207
|
-
}
|
|
208
177
|
}
|
|
209
178
|
|
|
210
179
|
// --- Fetch control. ---
|
|
211
180
|
|
|
212
|
-
// Chains ordered furthest-behind first, so the shared
|
|
213
|
-
//
|
|
181
|
+
// Chains ordered furthest-behind first, so the shared buffer pool goes to the
|
|
182
|
+
// chains with the most backfill work before the rest.
|
|
214
183
|
let priorityOrder = (crossChainState: t) =>
|
|
215
184
|
crossChainState.chainStates
|
|
216
185
|
->Dict.valuesToArray
|
|
@@ -221,49 +190,105 @@ let priorityOrder = (crossChainState: t) =>
|
|
|
221
190
|
)
|
|
222
191
|
)
|
|
223
192
|
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
193
|
+
// In-flight estimated items across every chain — the live draw against
|
|
194
|
+
// targetBufferSize alongside totalReadyCount, so the pool isn't re-dispatched
|
|
195
|
+
// while queries are still being fetched.
|
|
196
|
+
let totalReservedSize = (crossChainState: t) => {
|
|
197
|
+
let total = ref(0.)
|
|
198
|
+
for i in 0 to crossChainState.chainIds->Array.length - 1 {
|
|
199
|
+
let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
|
|
200
|
+
total := total.contents +. cs->ChainState.pendingBudget
|
|
201
|
+
}
|
|
202
|
+
total.contents
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Furthest-behind first: order candidate queries by the chain progress % at
|
|
206
|
+
// their fromBlock, so the most behind ranges across all chains are admitted
|
|
207
|
+
// before the rest.
|
|
208
|
+
let compareByProgress = (a: FetchState.query, b: FetchState.query) =>
|
|
209
|
+
Float.compare(a.progress, b.progress)
|
|
210
|
+
|
|
211
|
+
// Dispatch a fetch tick across the whole indexer from one shared pool of
|
|
212
|
+
// ~targetBufferSize ready events. Every chain proposes its candidate queries
|
|
213
|
+
// (each carrying an estimated response size) against the full free budget; the
|
|
214
|
+
// candidates are then pooled, ordered by chain progress (furthest-behind first),
|
|
215
|
+
// and admitted until the budget is consumed. So the budget is split per query
|
|
216
|
+
// across chains rather than per chain — a chain that can only use a little
|
|
217
|
+
// leaves the rest for the others automatically.
|
|
233
218
|
let checkAndFetch = async (
|
|
234
219
|
crossChainState: t,
|
|
235
|
-
~
|
|
236
|
-
~chain: ChainMap.Chain.t,
|
|
237
|
-
~concurrencyLimit: int,
|
|
238
|
-
~bufferLimit: int,
|
|
239
|
-
) => promise<unit>,
|
|
220
|
+
~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise<unit>,
|
|
240
221
|
) => {
|
|
241
|
-
let
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
let
|
|
222
|
+
let remaining = Pervasives.max(
|
|
223
|
+
0,
|
|
224
|
+
crossChainState.targetBufferSize -
|
|
225
|
+
crossChainState->totalReadyCount -
|
|
226
|
+
crossChainState->totalReservedSize->Float.toInt,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
let chainIds = crossChainState.chainIds
|
|
230
|
+
let actionByChain = Dict.make()
|
|
231
|
+
// Candidate queries from every chain. Each query carries its chain id and the
|
|
232
|
+
// chain progress % at its fromBlock (the admission sort key), set here so the
|
|
233
|
+
// pool can be ordered without a side tuple per query.
|
|
234
|
+
let candidates = []
|
|
235
|
+
for i in 0 to chainIds->Array.length - 1 {
|
|
236
|
+
let chainId = chainIds->Array.getUnsafe(i)
|
|
237
|
+
let cs = crossChainState->getChainState(chainId)
|
|
238
|
+
let fetchState = cs->ChainState.fetchState
|
|
239
|
+
switch fetchState->FetchState.getNextQuery(
|
|
240
|
+
~budget=remaining,
|
|
241
|
+
~chainPendingBudget=cs->ChainState.pendingBudget,
|
|
242
|
+
) {
|
|
243
|
+
| (WaitingForNewBlock | NothingToQuery) as action =>
|
|
244
|
+
actionByChain->Utils.Dict.setByInt(chainId, action)
|
|
245
|
+
| Ready(queries) =>
|
|
246
|
+
// Default to NothingToQuery; replaced below if any candidate is admitted.
|
|
247
|
+
actionByChain->Utils.Dict.setByInt(chainId, FetchState.NothingToQuery)
|
|
248
|
+
queries->Array.forEach(query => {
|
|
249
|
+
query.chainId = chainId
|
|
250
|
+
query.progress =
|
|
251
|
+
fetchState->FetchState.getProgressPercentageAt(~blockNumber=query.fromBlock)
|
|
252
|
+
candidates->Array.push(query)
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
candidates->Array.sort(compareByProgress)
|
|
258
|
+
|
|
259
|
+
// Admit furthest-behind first until the budget runs out. The condition is
|
|
260
|
+
// checked before each query, so as long as there's any budget left we admit
|
|
261
|
+
// the next one even when its estimate alone exceeds the remainder — otherwise a
|
|
262
|
+
// chain whose only query is bigger than the budget would never make progress.
|
|
263
|
+
let admittedByChain = Dict.make()
|
|
264
|
+
let running = ref(0.)
|
|
265
|
+
let remainingF = remaining->Int.toFloat
|
|
266
|
+
let idx = ref(0)
|
|
267
|
+
while running.contents < remainingF && idx.contents < candidates->Array.length {
|
|
268
|
+
let query = candidates->Array.getUnsafe(idx.contents)
|
|
269
|
+
admittedByChain->Utils.Dict.push(query.chainId->Int.toString, query)
|
|
270
|
+
running := running.contents +. query.estResponseSize
|
|
271
|
+
idx := idx.contents + 1
|
|
272
|
+
}
|
|
273
|
+
admittedByChain->Dict.forEachWithKey((queries, chainId) => {
|
|
274
|
+
actionByChain->Dict.set(chainId, FetchState.Ready(queries))
|
|
275
|
+
// Mark the admitted queries in flight and reserve their size against the
|
|
276
|
+
// shared budget; released as each response lands in handleQueryResult.
|
|
277
|
+
crossChainState
|
|
278
|
+
->getChainState(chainId->Int.fromString->Option.getUnsafe)
|
|
279
|
+
->ChainState.startFetchingQueries(~queries)
|
|
280
|
+
})
|
|
281
|
+
|
|
254
282
|
let promises = []
|
|
255
|
-
for i in 0 to
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
(
|
|
263
|
-
|
|
264
|
-
let inFlightBefore = sourceManager->SourceManager.inFlightCount
|
|
265
|
-
promises->Array.push(fetchChain(~chain, ~concurrencyLimit, ~bufferLimit))
|
|
266
|
-
inFlight := inFlight.contents + (sourceManager->SourceManager.inFlightCount - inFlightBefore)
|
|
283
|
+
for i in 0 to chainIds->Array.length - 1 {
|
|
284
|
+
let chainId = chainIds->Array.getUnsafe(i)
|
|
285
|
+
switch actionByChain->Utils.Dict.dangerouslyGetByIntNonOption(chainId) {
|
|
286
|
+
| Some(NothingToQuery)
|
|
287
|
+
| None => ()
|
|
288
|
+
| Some(action) =>
|
|
289
|
+
let chain = ChainMap.Chain.makeUnsafe(~chainId)
|
|
290
|
+
promises->Array.push(dispatchChain(~chain, ~action))
|
|
291
|
+
}
|
|
267
292
|
}
|
|
268
293
|
let _ = await promises->Promise.all
|
|
269
294
|
}
|
|
@@ -8,8 +8,9 @@ import * as ChainMap from "./ChainMap.res.mjs";
|
|
|
8
8
|
import * as ChainState from "./ChainState.res.mjs";
|
|
9
9
|
import * as FetchState from "./FetchState.res.mjs";
|
|
10
10
|
import * as Prometheus from "./Prometheus.res.mjs";
|
|
11
|
+
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
12
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
11
13
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
12
|
-
import * as SourceManager from "./sources/SourceManager.res.mjs";
|
|
13
14
|
import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
|
|
14
15
|
import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
|
|
15
16
|
|
|
@@ -21,17 +22,7 @@ function calculateTargetBufferSize() {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function
|
|
25
|
-
if (crossChainState.isRealtime) {
|
|
26
|
-
return crossChainState.maxRealtimeConcurrency;
|
|
27
|
-
} else {
|
|
28
|
-
return crossChainState.maxBackfillConcurrency;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function make(chainStates, isInReorgThreshold, isRealtime, maxBackfillConcurrencyOpt, maxRealtimeConcurrencyOpt, targetBufferSizeOpt) {
|
|
33
|
-
let maxBackfillConcurrency = maxBackfillConcurrencyOpt !== undefined ? maxBackfillConcurrencyOpt : Env.maxBackfillConcurrency;
|
|
34
|
-
let maxRealtimeConcurrency = maxRealtimeConcurrencyOpt !== undefined ? maxRealtimeConcurrencyOpt : Env.maxRealtimeConcurrency;
|
|
25
|
+
function make(chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt) {
|
|
35
26
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : (
|
|
36
27
|
Env.targetBufferSize !== undefined ? Env.targetBufferSize : 100000
|
|
37
28
|
);
|
|
@@ -40,11 +31,8 @@ function make(chainStates, isInReorgThreshold, isRealtime, maxBackfillConcurrenc
|
|
|
40
31
|
chainIds: Object.values(chainStates).map(cs => ChainState.chainConfig(cs).id),
|
|
41
32
|
isRealtime: isRealtime,
|
|
42
33
|
isInReorgThreshold: isInReorgThreshold,
|
|
43
|
-
maxBackfillConcurrency: maxBackfillConcurrency,
|
|
44
|
-
maxRealtimeConcurrency: maxRealtimeConcurrency,
|
|
45
34
|
targetBufferSize: targetBufferSize
|
|
46
35
|
};
|
|
47
|
-
Prometheus.IndexingMaxConcurrency.set(maxConcurrency(crossChainState));
|
|
48
36
|
Prometheus.IndexingTargetBufferSize.set(targetBufferSize);
|
|
49
37
|
return crossChainState;
|
|
50
38
|
}
|
|
@@ -61,15 +49,6 @@ function isInReorgThreshold(crossChainState) {
|
|
|
61
49
|
return crossChainState.isInReorgThreshold;
|
|
62
50
|
}
|
|
63
51
|
|
|
64
|
-
function inFlight(crossChainState) {
|
|
65
|
-
let total = 0;
|
|
66
|
-
for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
|
|
67
|
-
let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
|
|
68
|
-
total = total + SourceManager.inFlightCount(ChainState.sourceManager(cs)) | 0;
|
|
69
|
-
}
|
|
70
|
-
return total;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
52
|
function totalReadyCount(crossChainState) {
|
|
74
53
|
let total = 0;
|
|
75
54
|
for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
|
|
@@ -148,32 +127,79 @@ function applyBatchProgress(crossChainState, batch) {
|
|
|
148
127
|
if (allChainsReady) {
|
|
149
128
|
Prometheus.ProgressReady.setAllReady();
|
|
150
129
|
}
|
|
151
|
-
let wasRealtime = crossChainState.isRealtime;
|
|
152
130
|
crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady;
|
|
153
|
-
if (!wasRealtime && crossChainState.isRealtime) {
|
|
154
|
-
return Prometheus.IndexingMaxConcurrency.set(maxConcurrency(crossChainState));
|
|
155
|
-
}
|
|
156
131
|
}
|
|
157
132
|
|
|
158
133
|
function priorityOrder(crossChainState) {
|
|
159
134
|
return Object.values(crossChainState.chainStates).toSorted((a, b) => Primitive_float.compare(FetchState.getProgressPercentage(ChainState.fetchState(a)), FetchState.getProgressPercentage(ChainState.fetchState(b))));
|
|
160
135
|
}
|
|
161
136
|
|
|
162
|
-
|
|
163
|
-
let
|
|
164
|
-
let
|
|
165
|
-
|
|
166
|
-
|
|
137
|
+
function totalReservedSize(crossChainState) {
|
|
138
|
+
let total = 0;
|
|
139
|
+
for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
|
|
140
|
+
let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
|
|
141
|
+
total = total + ChainState.pendingBudget(cs);
|
|
142
|
+
}
|
|
143
|
+
return total;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function compareByProgress(a, b) {
|
|
147
|
+
return Primitive_float.compare(a.progress, b.progress);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function checkAndFetch(crossChainState, dispatchChain) {
|
|
151
|
+
let remaining = Primitive_int.max(0, (crossChainState.targetBufferSize - totalReadyCount(crossChainState) | 0) - (totalReservedSize(crossChainState) | 0) | 0);
|
|
152
|
+
let chainIds = crossChainState.chainIds;
|
|
153
|
+
let actionByChain = {};
|
|
154
|
+
let candidates = [];
|
|
155
|
+
for (let i = 0, i_finish = chainIds.length; i < i_finish; ++i) {
|
|
156
|
+
let chainId = chainIds[i];
|
|
157
|
+
let cs = crossChainState.chainStates[chainId];
|
|
158
|
+
let fetchState = ChainState.fetchState(cs);
|
|
159
|
+
let action = FetchState.getNextQuery(fetchState, remaining, ChainState.pendingBudget(cs));
|
|
160
|
+
if (typeof action !== "object") {
|
|
161
|
+
actionByChain[chainId] = action;
|
|
162
|
+
} else {
|
|
163
|
+
actionByChain[chainId] = "NothingToQuery";
|
|
164
|
+
action._0.forEach(query => {
|
|
165
|
+
query.chainId = chainId;
|
|
166
|
+
query.progress = FetchState.getProgressPercentageAt(fetchState, query.fromBlock);
|
|
167
|
+
candidates.push(query);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
candidates.sort(compareByProgress);
|
|
172
|
+
let admittedByChain = {};
|
|
173
|
+
let running = 0;
|
|
174
|
+
let idx = 0;
|
|
175
|
+
while (running < remaining && idx < candidates.length) {
|
|
176
|
+
let query = candidates[idx];
|
|
177
|
+
Utils.Dict.push(admittedByChain, query.chainId.toString(), query);
|
|
178
|
+
running = running + query.estResponseSize;
|
|
179
|
+
idx = idx + 1 | 0;
|
|
180
|
+
};
|
|
181
|
+
Stdlib_Dict.forEachWithKey(admittedByChain, (queries, chainId) => {
|
|
182
|
+
actionByChain[chainId] = {
|
|
183
|
+
TAG: "Ready",
|
|
184
|
+
_0: queries
|
|
185
|
+
};
|
|
186
|
+
let chainId$1 = Stdlib_Int.fromString(chainId, undefined);
|
|
187
|
+
ChainState.startFetchingQueries(crossChainState.chainStates[chainId$1], queries);
|
|
188
|
+
});
|
|
167
189
|
let promises = [];
|
|
168
|
-
for (let i = 0, i_finish =
|
|
169
|
-
let
|
|
170
|
-
let
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
for (let i$1 = 0, i_finish$1 = chainIds.length; i$1 < i_finish$1; ++i$1) {
|
|
191
|
+
let chainId$1 = chainIds[i$1];
|
|
192
|
+
let action$1 = actionByChain[chainId$1];
|
|
193
|
+
if (action$1 !== undefined) {
|
|
194
|
+
let exit = 0;
|
|
195
|
+
if (typeof action$1 === "object" || action$1 === "WaitingForNewBlock") {
|
|
196
|
+
exit = 1;
|
|
197
|
+
}
|
|
198
|
+
if (exit === 1) {
|
|
199
|
+
let chain = ChainMap.Chain.makeUnsafe(chainId$1);
|
|
200
|
+
promises.push(dispatchChain(chain, action$1));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
177
203
|
}
|
|
178
204
|
await Promise.all(promises);
|
|
179
205
|
}
|
|
@@ -184,8 +210,6 @@ export {
|
|
|
184
210
|
chainStates,
|
|
185
211
|
isRealtime,
|
|
186
212
|
isInReorgThreshold,
|
|
187
|
-
maxConcurrency,
|
|
188
|
-
inFlight,
|
|
189
213
|
nextItemIsNone,
|
|
190
214
|
getSafeCheckpointId,
|
|
191
215
|
createBatch,
|
package/src/CrossChainState.resi
CHANGED
|
@@ -9,8 +9,6 @@ let make: (
|
|
|
9
9
|
~chainStates: dict<ChainState.t>,
|
|
10
10
|
~isInReorgThreshold: bool,
|
|
11
11
|
~isRealtime: bool,
|
|
12
|
-
~maxBackfillConcurrency: int=?,
|
|
13
|
-
~maxRealtimeConcurrency: int=?,
|
|
14
12
|
~targetBufferSize: int=?,
|
|
15
13
|
) => t
|
|
16
14
|
|
|
@@ -18,8 +16,6 @@ let make: (
|
|
|
18
16
|
let chainStates: t => dict<ChainState.t>
|
|
19
17
|
let isRealtime: t => bool
|
|
20
18
|
let isInReorgThreshold: t => bool
|
|
21
|
-
let maxConcurrency: t => int
|
|
22
|
-
let inFlight: t => int
|
|
23
19
|
|
|
24
20
|
// Derived (pure).
|
|
25
21
|
let nextItemIsNone: t => bool
|
|
@@ -39,9 +35,5 @@ let applyBatchProgress: (t, ~batch: Batch.t) => unit
|
|
|
39
35
|
let priorityOrder: t => array<ChainState.t>
|
|
40
36
|
let checkAndFetch: (
|
|
41
37
|
t,
|
|
42
|
-
~
|
|
43
|
-
~chain: ChainMap.Chain.t,
|
|
44
|
-
~concurrencyLimit: int,
|
|
45
|
-
~bufferLimit: int,
|
|
46
|
-
) => promise<unit>,
|
|
38
|
+
~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise<unit>,
|
|
47
39
|
) => promise<unit>
|
package/src/Env.res
CHANGED
|
@@ -8,13 +8,6 @@ let updateSyncTimeOnRestart =
|
|
|
8
8
|
envSafe->EnvSafe.get("UPDATE_SYNC_TIME_ON_RESTART", S.bool, ~fallback=true)
|
|
9
9
|
let targetBufferSize = envSafe->EnvSafe.get("ENVIO_INDEXING_MAX_BUFFER_SIZE", S.option(S.int))
|
|
10
10
|
let maxAddrInPartition = envSafe->EnvSafe.get("MAX_PARTITION_SIZE", S.int, ~fallback=5_000)
|
|
11
|
-
// Global caps on concurrent data-source queries across the whole indexer (all
|
|
12
|
-
// chains share the budget), not per chain. Realtime allows far more since head
|
|
13
|
-
// queries are small and many chains follow the head at once.
|
|
14
|
-
let maxBackfillConcurrency =
|
|
15
|
-
envSafe->EnvSafe.get("ENVIO_MAX_BACKFILL_CONCURRENCY", S.int, ~fallback=30)
|
|
16
|
-
let maxRealtimeConcurrency =
|
|
17
|
-
envSafe->EnvSafe.get("ENVIO_MAX_REALTIME_CONCURRENCY", S.int, ~fallback=200)
|
|
18
11
|
|
|
19
12
|
// Target number of in-memory objects (uncommitted entity/effect changes plus
|
|
20
13
|
// unwritten batch items) the store holds before processing waits for the write
|