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/src/Env.res.mjs CHANGED
@@ -19,10 +19,6 @@ let targetBufferSize = EnvSafe.get(envSafe, "ENVIO_INDEXING_MAX_BUFFER_SIZE", S$
19
19
 
20
20
  let maxAddrInPartition = EnvSafe.get(envSafe, "MAX_PARTITION_SIZE", S$RescriptSchema.int, undefined, 5000, undefined, undefined);
21
21
 
22
- let maxBackfillConcurrency = EnvSafe.get(envSafe, "ENVIO_MAX_BACKFILL_CONCURRENCY", S$RescriptSchema.int, undefined, 30, undefined, undefined);
23
-
24
- let maxRealtimeConcurrency = EnvSafe.get(envSafe, "ENVIO_MAX_REALTIME_CONCURRENCY", S$RescriptSchema.int, undefined, 200, undefined, undefined);
25
-
26
22
  let inMemoryObjectsTarget = EnvSafe.get(envSafe, "ENVIO_IN_MEMORY_OBJECTS_TARGET", S$RescriptSchema.int, undefined, 100000, undefined, undefined);
27
23
 
28
24
  let serverPort = EnvSafe.get(envSafe, "ENVIO_INDEXER_PORT", S$RescriptSchema.port(S$RescriptSchema.int, undefined), undefined, EnvSafe.get(envSafe, "METRICS_PORT", S$RescriptSchema.port(S$RescriptSchema.int, undefined), undefined, 9898, undefined, undefined), undefined, undefined);
@@ -224,8 +220,6 @@ export {
224
220
  updateSyncTimeOnRestart,
225
221
  targetBufferSize,
226
222
  maxAddrInPartition,
227
- maxBackfillConcurrency,
228
- maxRealtimeConcurrency,
229
223
  inMemoryObjectsTarget,
230
224
  serverPort,
231
225
  tuiEnvVar,
@@ -19,6 +19,9 @@ type pendingQuery = {
19
19
  fromBlock: int,
20
20
  toBlock: option<int>,
21
21
  isChunk: bool,
22
+ // Estimated items this in-flight query will add, carried from the query so the
23
+ // shared buffer budget can account for what's already being fetched.
24
+ estResponseSize: float,
22
25
  // Stores latestFetchedBlock when query completes. Only needed to persist
23
26
  // timestamp while earlier queries are still pending before updating
24
27
  // the partition's latestFetchedBlock.
@@ -47,6 +50,10 @@ type partition = {
47
50
  // Track last 3 successful query ranges for chunking heuristic (0 means no data)
48
51
  prevQueryRange: int,
49
52
  prevPrevQueryRange: int,
53
+ // Item count of the response that set prevQueryRange. Paired with it to derive
54
+ // the partition's event density (prevRangeSize / prevQueryRange) for sizing
55
+ // queries against the buffer budget.
56
+ prevRangeSize: int,
50
57
  // Tracks the latestFetchedBlock.blockNumber of the most recent response
51
58
  // that updated prevQueryRange. Prevents degradation of the chunking heuristic
52
59
  // when parallel query responses arrive out of order.
@@ -58,11 +65,39 @@ type query = {
58
65
  fromBlock: int,
59
66
  toBlock: option<int>,
60
67
  isChunk: bool,
68
+ // Estimated number of items the query will return, from the partition's
69
+ // density and the query's block range. Used to admit queries against the
70
+ // shared buffer budget.
71
+ estResponseSize: float,
72
+ // Owning chain and the chain progress % at the query's fromBlock. Set by the
73
+ // cross-chain scheduler so candidate queries can be pooled and ordered
74
+ // (furthest-behind first) without allocating a side tuple per query.
75
+ mutable chainId: int,
76
+ mutable progress: float,
61
77
  selection: selection,
62
78
  addressesByContractName: dict<array<Address.t>>,
63
79
  indexingAddresses: dict<indexingAddress>,
64
80
  }
65
81
 
82
+ // Default estimate for a query whose partition hasn't responded yet, so the
83
+ // shared budget still accounts for unknown queries instead of treating them as
84
+ // free.
85
+ let defaultEstResponseSize = 10_000.
86
+
87
+ // Estimated items a query will return, from the partition's event density
88
+ // (items/block derived from its last response) and the query's block range.
89
+ // toBlock None is the open-ended tail, capped at maxQueryBlockNumber. A partition
90
+ // that responded with no items has density 0, so its queries cost 0 — correct,
91
+ // they don't fill the buffer. Only a partition that has never responded
92
+ // (prevQueryRange 0) has no signal, so it falls back to defaultEstResponseSize.
93
+ let calculateEstResponseSize = (p: partition, ~fromBlock, ~toBlock, ~maxQueryBlockNumber) =>
94
+ if p.prevQueryRange > 0 {
95
+ let density = p.prevRangeSize->Int.toFloat /. p.prevQueryRange->Int.toFloat
96
+ (toBlock->Option.getOr(maxQueryBlockNumber) - fromBlock + 1)->Int.toFloat *. density
97
+ } else {
98
+ defaultEstResponseSize
99
+ }
100
+
66
101
  // Calculate the chunk range from history using min-of-last-3-ranges heuristic
67
102
  let getMinHistoryRange = (p: partition) => {
68
103
  switch (p.prevQueryRange, p.prevPrevQueryRange) {
@@ -160,6 +195,7 @@ module OptimizedPartitions = {
160
195
  mutPendingQueries: [],
161
196
  prevQueryRange: minRange,
162
197
  prevPrevQueryRange: minRange,
198
+ prevRangeSize: 0,
163
199
  latestBlockRangeUpdateBlock: 0,
164
200
  }
165
201
  }
@@ -397,6 +433,7 @@ module OptimizedPartitions = {
397
433
  optimizedPartitions: t,
398
434
  ~query,
399
435
  ~knownHeight,
436
+ ~itemsCount,
400
437
  ~latestFetchedBlock: blockNumberAndTimestamp,
401
438
  ) => {
402
439
  let p = optimizedPartitions->getOrThrow(~partitionId=query.partitionId)
@@ -428,6 +465,7 @@ module OptimizedPartitions = {
428
465
  }
429
466
  let updatedPrevQueryRange = shouldUpdateBlockRange ? blockRange : p.prevQueryRange
430
467
  let updatedPrevPrevQueryRange = shouldUpdateBlockRange ? p.prevQueryRange : p.prevPrevQueryRange
468
+ let updatedPrevRangeSize = shouldUpdateBlockRange ? itemsCount : p.prevRangeSize
431
469
 
432
470
  // Process fetched queries from front of queue for main partition
433
471
  let updatedLatestFetchedBlock = consumeFetchedQueries(
@@ -449,6 +487,7 @@ module OptimizedPartitions = {
449
487
  latestFetchedBlock: updatedLatestFetchedBlock,
450
488
  prevQueryRange: updatedPrevQueryRange,
451
489
  prevPrevQueryRange: updatedPrevPrevQueryRange,
490
+ prevRangeSize: updatedPrevRangeSize,
452
491
  latestBlockRangeUpdateBlock: shouldUpdateBlockRange
453
492
  ? latestFetchedBlock.blockNumber
454
493
  : p.latestBlockRangeUpdateBlock,
@@ -497,7 +536,7 @@ type t = {
497
536
  // Buffer of items ordered from earliest to latest
498
537
  buffer: array<Internal.item>,
499
538
  // Caps how far ahead onBlock items are pre-generated (set to 2x the batch
500
- // size). Fetch depth is bounded separately by getNextQuery's bufferLimit, the
539
+ // size). Fetch depth is bounded separately by getNextQuery's itemBudget, the
501
540
  // chain's per-tick slice of the indexer-wide pool.
502
541
  maxOnBlockBufferSize: int,
503
542
  onBlockConfigs: array<Internal.onBlockConfig>,
@@ -838,6 +877,7 @@ OptimizedPartitions.t => {
838
877
  mutPendingQueries: [],
839
878
  prevQueryRange: 0,
840
879
  prevPrevQueryRange: 0,
880
+ prevRangeSize: 0,
841
881
  latestBlockRangeUpdateBlock: 0,
842
882
  })
843
883
  nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
@@ -1179,6 +1219,7 @@ let registerDynamicContracts = (
1179
1219
  mutPendingQueries: p.mutPendingQueries,
1180
1220
  prevQueryRange: p.prevQueryRange,
1181
1221
  prevPrevQueryRange: p.prevPrevQueryRange,
1222
+ prevRangeSize: p.prevRangeSize,
1182
1223
  latestBlockRangeUpdateBlock: p.latestBlockRangeUpdateBlock,
1183
1224
  })
1184
1225
  }
@@ -1241,6 +1282,7 @@ let handleQueryResult = (
1241
1282
  ~optimizedPartitions=fetchState.optimizedPartitions->OptimizedPartitions.handleQueryResponse(
1242
1283
  ~query,
1243
1284
  ~knownHeight=fetchState.knownHeight,
1285
+ ~itemsCount=newItems->Array.length,
1244
1286
  ~latestFetchedBlock,
1245
1287
  ),
1246
1288
  ~mutItems=?{
@@ -1253,7 +1295,6 @@ let handleQueryResult = (
1253
1295
  }
1254
1296
 
1255
1297
  type nextQuery =
1256
- | ReachedMaxConcurrency
1257
1298
  | WaitingForNewBlock
1258
1299
  | NothingToQuery
1259
1300
  | Ready(array<query>)
@@ -1267,6 +1308,7 @@ let startFetchingQueries = ({optimizedPartitions}: t, ~queries: array<query>) =>
1267
1308
  fromBlock: q.fromBlock,
1268
1309
  toBlock: q.toBlock,
1269
1310
  isChunk: q.isChunk,
1311
+ estResponseSize: q.estResponseSize,
1270
1312
  fetchedBlock: None,
1271
1313
  }
1272
1314
 
@@ -1295,6 +1337,7 @@ let pushQueriesForRange = (
1295
1337
  ~rangeEndBlock: option<int>,
1296
1338
  ~maxQueryBlockNumber: int,
1297
1339
  ~maybeChunkRange: option<int>,
1340
+ ~partition: partition,
1298
1341
  ~selection: selection,
1299
1342
  ~addressesByContractName: dict<array<Address.t>>,
1300
1343
  ~indexingAddresses: dict<indexingAddress>,
@@ -1311,6 +1354,14 @@ let pushQueriesForRange = (
1311
1354
  toBlock: rangeEndBlock,
1312
1355
  selection,
1313
1356
  isChunk: false,
1357
+ estResponseSize: calculateEstResponseSize(
1358
+ partition,
1359
+ ~fromBlock=rangeFromBlock,
1360
+ ~toBlock=rangeEndBlock,
1361
+ ~maxQueryBlockNumber,
1362
+ ),
1363
+ chainId: 0,
1364
+ progress: 0.,
1314
1365
  addressesByContractName,
1315
1366
  indexingAddresses,
1316
1367
  })
@@ -1338,6 +1389,14 @@ let pushQueriesForRange = (
1338
1389
  toBlock: Some(chunkToBlock),
1339
1390
  isChunk: true,
1340
1391
  selection,
1392
+ estResponseSize: calculateEstResponseSize(
1393
+ partition,
1394
+ ~fromBlock=chunkFromBlock.contents,
1395
+ ~toBlock=Some(chunkToBlock),
1396
+ ~maxQueryBlockNumber,
1397
+ ),
1398
+ chainId: 0,
1399
+ progress: 0.,
1341
1400
  addressesByContractName,
1342
1401
  indexingAddresses,
1343
1402
  })
@@ -1352,6 +1411,14 @@ let pushQueriesForRange = (
1352
1411
  toBlock: rangeEndBlock,
1353
1412
  selection,
1354
1413
  isChunk: rangeEndBlock !== None,
1414
+ estResponseSize: calculateEstResponseSize(
1415
+ partition,
1416
+ ~fromBlock=rangeFromBlock,
1417
+ ~toBlock=rangeEndBlock,
1418
+ ~maxQueryBlockNumber,
1419
+ ),
1420
+ chainId: 0,
1421
+ progress: 0.,
1355
1422
  addressesByContractName,
1356
1423
  indexingAddresses,
1357
1424
  })
@@ -1373,15 +1440,13 @@ let getNextQuery = (
1373
1440
  latestOnBlockBlockNumber,
1374
1441
  knownHeight,
1375
1442
  } as fetchState: t,
1376
- ~concurrencyLimit,
1377
- ~bufferLimit,
1443
+ ~budget,
1444
+ ~chainPendingBudget,
1378
1445
  ) => {
1379
1446
  let headBlockNumber = knownHeight - blockLag
1380
1447
  if headBlockNumber <= 0 {
1381
1448
  WaitingForNewBlock
1382
- } else if concurrencyLimit === 0 {
1383
- ReachedMaxConcurrency
1384
- } else if bufferLimit <= 0 {
1449
+ } else if budget <= 0 {
1385
1450
  // No room left in the shared buffer pool for this chain; wait for processing
1386
1451
  // to drain before fetching more.
1387
1452
  NothingToQuery
@@ -1395,12 +1460,11 @@ let getNextQuery = (
1395
1460
  !isOnBlockBehindTheHead,
1396
1461
  )
1397
1462
 
1398
- // Limit how far ahead we fetch to bufferLimit items (this chain's share of
1399
- // the indexer-wide buffer pool) so processing always has buffer without
1400
- // ballooning memory. A partition that fetched further is skipped until the
1401
- // buffer drains.
1463
+ // Limit how far ahead we fetch to budget items (plus what's already in
1464
+ // flight) so processing always has buffer without ballooning memory. A
1465
+ // partition that fetched further is skipped until the buffer drains.
1402
1466
  let maxQueryBlockNumber = {
1403
- switch buffer->Array.get(bufferLimit - 1) {
1467
+ switch buffer->Array.get(budget + chainPendingBudget->Float.toInt - 1) {
1404
1468
  | Some(item) =>
1405
1469
  // Just in case check that we don't query beyond the current block
1406
1470
  Pervasives.min(item->Internal.getItemBlockNumber, knownHeight)
@@ -1469,6 +1533,7 @@ let getNextQuery = (
1469
1533
  ~rangeEndBlock=Utils.Math.minOptInt(Some(pq.fromBlock - 1), queryEndBlock),
1470
1534
  ~maxQueryBlockNumber,
1471
1535
  ~maybeChunkRange,
1536
+ ~partition=p,
1472
1537
  ~selection=p.selection,
1473
1538
  ~addressesByContractName=p.addressesByContractName,
1474
1539
  ~indexingAddresses,
@@ -1493,6 +1558,7 @@ let getNextQuery = (
1493
1558
  ~rangeEndBlock=queryEndBlock,
1494
1559
  ~maxQueryBlockNumber,
1495
1560
  ~maybeChunkRange,
1561
+ ~partition=p,
1496
1562
  ~selection=p.selection,
1497
1563
  ~addressesByContractName=p.addressesByContractName,
1498
1564
  ~indexingAddresses,
@@ -1500,9 +1566,8 @@ let getNextQuery = (
1500
1566
  }
1501
1567
 
1502
1568
  // Cap parallel in-flight chunks per partition so a single partition can't
1503
- // drain the whole concurrency budget (especially the high realtime one).
1504
- // Keep the earliest new chunks; the furthest-ahead ones wait for the next
1505
- // round once these resolve.
1569
+ // monopolize fetching. Keep the earliest new chunks; the furthest-ahead
1570
+ // ones wait for the next round once these resolve.
1506
1571
  let maxNewChunks = Pervasives.max(0, maxPendingChunksPerPartition - pendingCount)
1507
1572
  let generatedCount = queries->Array.length - partitionQueriesStart
1508
1573
  if generatedCount > maxNewChunks {
@@ -1525,13 +1590,6 @@ let getNextQuery = (
1525
1590
  NothingToQuery
1526
1591
  }
1527
1592
  } else {
1528
- // Enforce concurrency limit: sort by fromBlock and take the first concurrencyLimit
1529
- let queries = if queries->Array.length > concurrencyLimit {
1530
- queries->Array.sort((a, b) => Int.compare(a.fromBlock, b.fromBlock))
1531
- queries->Array.slice(~start=0, ~end=concurrencyLimit)
1532
- } else {
1533
- queries
1534
- }
1535
1593
  Ready(queries)
1536
1594
  }
1537
1595
  }
@@ -1641,6 +1699,7 @@ let make = (
1641
1699
  mutPendingQueries: [],
1642
1700
  prevQueryRange: 0,
1643
1701
  prevPrevQueryRange: 0,
1702
+ prevRangeSize: 0,
1644
1703
  latestBlockRangeUpdateBlock: 0,
1645
1704
  })
1646
1705
  }
@@ -2025,6 +2084,17 @@ let getProgressPercentage = (fetchState: t) => {
2025
2084
  }
2026
2085
  }
2027
2086
 
2087
+ // Progress a specific block sits at along the chain, used to order queries from
2088
+ // different chains: a query starting further back (lower %) is fetched first.
2089
+ let getProgressPercentageAt = (fetchState: t, ~blockNumber) => {
2090
+ switch fetchState.firstEventBlock {
2091
+ | None => 0.
2092
+ | Some(firstEventBlock) =>
2093
+ let totalRange = fetchState.knownHeight - firstEventBlock
2094
+ totalRange <= 0 ? 0. : (blockNumber - firstEventBlock)->Int.toFloat /. totalRange->Int.toFloat
2095
+ }
2096
+ }
2097
+
2028
2098
  let sortForBatch = {
2029
2099
  let hasFullBatch = ({buffer} as fetchState: t, ~batchSizeTarget) => {
2030
2100
  switch buffer->Array.get(batchSizeTarget - 1) {