envio 3.11.0 → 3.12.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.
- package/index.d.ts +3 -0
- package/package.json +6 -6
- package/src/Batch.res +111 -78
- package/src/Batch.res.mjs +53 -46
- package/src/BatchProcessing.res +1 -11
- package/src/BatchProcessing.res.mjs +1 -4
- package/src/ChainState.res +72 -19
- package/src/ChainState.res.mjs +50 -12
- package/src/ChainState.resi +5 -0
- package/src/Config.res +58 -37
- package/src/Config.res.mjs +63 -53
- package/src/CrossChainState.res +49 -52
- package/src/CrossChainState.res.mjs +25 -23
- package/src/CrossChainState.resi +6 -13
- package/src/EffectState.res +13 -4
- package/src/EffectState.resi +7 -1
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +6 -6
- package/src/FetchState.res +14 -0
- package/src/FetchState.res.mjs +14 -0
- package/src/FinalizeBackfill.res +16 -12
- package/src/FinalizeBackfill.res.mjs +3 -3
- package/src/HistoryPolicy.res +46 -0
- package/src/HistoryPolicy.res.mjs +50 -0
- package/src/InMemoryStore.res +30 -32
- package/src/InMemoryStore.res.mjs +33 -28
- package/src/IndexerState.res +66 -45
- package/src/IndexerState.res.mjs +57 -29
- package/src/IndexerState.resi +8 -13
- package/src/Internal.res +4 -2
- package/src/LoadLayer.res +11 -4
- package/src/LoadLayer.res.mjs +8 -5
- package/src/Main.res +25 -24
- package/src/Main.res.mjs +24 -25
- package/src/Metrics.res +49 -1
- package/src/Metrics.res.mjs +26 -3
- package/src/Persistence.res +55 -16
- package/src/Persistence.res.mjs +18 -3
- package/src/PgStorage.res +194 -118
- package/src/PgStorage.res.mjs +132 -72
- package/src/PruneStaleHistory.res +9 -15
- package/src/PruneStaleHistory.res.mjs +11 -20
- package/src/Rollback.res +9 -14
- package/src/Rollback.res.mjs +5 -3
- package/src/Sink.res +7 -5
- package/src/Sink.res.mjs +4 -4
- package/src/Staging.res +298 -0
- package/src/Staging.res.mjs +257 -0
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +7 -6
- package/src/UserContext.res +7 -4
- package/src/UserContext.res.mjs +5 -2
- package/src/Utils.res +9 -0
- package/src/Utils.res.mjs +9 -0
- package/src/Writing.res +34 -20
- package/src/Writing.res.mjs +15 -20
- package/src/bindings/ClickHouse.res +41 -34
- package/src/bindings/ClickHouse.res.mjs +36 -30
- package/src/bindings/ClickHouseSink.res +45 -186
- package/src/bindings/ClickHouseSink.res.mjs +21 -174
- package/src/db/CheckpointSequence.res +109 -0
- package/src/db/CheckpointSequence.res.mjs +99 -0
- package/src/db/EntityHistory.res +6 -5
- package/src/db/EntityHistory.res.mjs +5 -5
- package/src/db/Frontier.res +86 -0
- package/src/db/Frontier.res.mjs +126 -0
- package/src/db/InternalTable.res +145 -27
- package/src/db/InternalTable.res.mjs +115 -29
- package/src/db/RollbackFloors.res +34 -41
- package/src/db/RollbackFloors.res.mjs +27 -71
- package/src/sources/BlockStore.res +3 -0
- package/src/sources/EvmHyperSyncSource.res +3 -1
- package/src/sources/EvmHyperSyncSource.res.mjs +2 -1
- package/src/sources/HyperSync.res +2 -0
- package/src/sources/HyperSync.res.mjs +1 -0
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncClient.res +3 -0
- package/src/sources/RequestStat.res +8 -1
- package/src/sources/Source.res +4 -3
- package/src/sources/SourceManager.res +30 -10
- package/src/sources/SourceManager.res.mjs +24 -7
- package/src/sources/SourceManager.resi +2 -0
- package/src/tui/Tui.res +56 -143
- package/src/tui/Tui.res.mjs +70 -166
- package/src/tui/components/TuiData.res +61 -16
- package/src/tui/components/TuiData.res.mjs +53 -15
- package/svm.schema.json +0 -7
- package/src/db/CheckpointBounds.res +0 -53
- package/src/db/CheckpointBounds.res.mjs +0 -44
package/src/PgStorage.res
CHANGED
|
@@ -28,15 +28,46 @@ let formatSeconds = (timeRef: Performance.timeRef) =>
|
|
|
28
28
|
// stalled-looking indexer is explainable from the logs alone.
|
|
29
29
|
let slowOnLargeDatabaseNotice = "This can take a long time on a large database."
|
|
30
30
|
|
|
31
|
+
// A per-chain entity's rows are partitioned by the chain that owns them, so a
|
|
32
|
+
// chain-filtered read scans one chain's partition rather than the whole table.
|
|
33
|
+
// `$` can't occur in a GraphQL entity name, so a partition name can never
|
|
34
|
+
// collide with the table another entity claims; past the identifier limit the
|
|
35
|
+
// entity index keeps what survives truncation unique.
|
|
36
|
+
let partitionTableName = (~entityConfig: Internal.entityConfig, ~chainId: ChainId.t) => {
|
|
37
|
+
let chainIdStr = chainId->ChainId.toString
|
|
38
|
+
Table.fitPgTableName(
|
|
39
|
+
`${entityConfig.table.tableName}$${chainIdStr}`,
|
|
40
|
+
~uniqueSuffix=`$${entityConfig.index->Int.toString}$${chainIdStr}`,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The physical tables an index on the entity is built on. A per-chain entity's
|
|
45
|
+
// rows are partitioned by chain, and an index declared on the parent cascades
|
|
46
|
+
// to every partition, which is what a run driving every chain wants: one
|
|
47
|
+
// declaration, one build. An isolated run instead builds on its own chains'
|
|
48
|
+
// partitions only: the planner uses a partition's own index either way, and
|
|
49
|
+
// building one chain's index then neither waits for nor locks the rows of a
|
|
50
|
+
// chain a sibling process drives.
|
|
51
|
+
let indexTableNames = (entityConfig: Internal.entityConfig, ~partitionChainIds) =>
|
|
52
|
+
switch (entityConfig.table->Table.getChainIdField, partitionChainIds) {
|
|
53
|
+
| (Some(_), Some(chainIds)) =>
|
|
54
|
+
chainIds->Array.map(chainId => partitionTableName(~entityConfig, ~chainId))
|
|
55
|
+
| _ => [entityConfig.table.tableName]
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
// Every index the entity schema promises: an `@index` field, a composite index,
|
|
32
|
-
// or the index backing a derived relationship. Deferred past the initial DDL
|
|
33
|
-
// created
|
|
34
|
-
//
|
|
59
|
+
// or the index backing a derived relationship. Deferred past the initial DDL
|
|
60
|
+
// and created once backfill completes, so a chain that reports itself ready
|
|
61
|
+
// always has all of them. With `partitionChainIds`, a per-chain entity's index
|
|
62
|
+
// is one per partition of those chains rather than one on the parent.
|
|
35
63
|
//
|
|
36
64
|
// `entities` is the Postgres-backed set, and every `@derivedFrom` target within
|
|
37
65
|
// it resolves: config parsing rejects a Postgres entity deriving from one that
|
|
38
66
|
// isn't in Postgres (`validate_relationship_storage`).
|
|
39
|
-
let getSchemaIndexes = (
|
|
67
|
+
let getSchemaIndexes = (
|
|
68
|
+
~entities: array<Internal.entityConfig>,
|
|
69
|
+
~partitionChainIds: option<array<ChainId.t>>=?,
|
|
70
|
+
): array<IndexDefinition.t> => {
|
|
40
71
|
let derivedSchema = Schema.make(entities->Array.map(e => e.table))
|
|
41
72
|
let all = []
|
|
42
73
|
|
|
@@ -70,7 +101,13 @@ let getSchemaIndexes = (~entities: array<Internal.entityConfig>): array<IndexDef
|
|
|
70
101
|
// An `@index` field and a derived relationship pointing at it describe the
|
|
71
102
|
// same index, so the list is deduped on identity rather than on name.
|
|
72
103
|
let seen = Utils.Set.make()
|
|
73
|
-
|
|
104
|
+
let entityByTableName = Dict.make()
|
|
105
|
+
entities->Array.forEach(entityConfig =>
|
|
106
|
+
entityByTableName->Dict.set(entityConfig.table.tableName, entityConfig)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
all
|
|
110
|
+
->Array.filter(definition => {
|
|
74
111
|
let key = definition->IndexDefinition.key
|
|
75
112
|
if seen->Utils.Set.has(key) {
|
|
76
113
|
false
|
|
@@ -79,6 +116,13 @@ let getSchemaIndexes = (~entities: array<Internal.entityConfig>): array<IndexDef
|
|
|
79
116
|
true
|
|
80
117
|
}
|
|
81
118
|
})
|
|
119
|
+
->Array.flatMap(definition =>
|
|
120
|
+
entityByTableName
|
|
121
|
+
->Dict.get(definition.tableName)
|
|
122
|
+
->Option.getOrThrow
|
|
123
|
+
->indexTableNames(~partitionChainIds)
|
|
124
|
+
->Array.map(tableName => {...definition, IndexDefinition.tableName})
|
|
125
|
+
)
|
|
82
126
|
}
|
|
83
127
|
|
|
84
128
|
let makeCreateTableQuery = (
|
|
@@ -122,19 +166,6 @@ let makeCreateTableQuery = (
|
|
|
122
166
|
}};`
|
|
123
167
|
}
|
|
124
168
|
|
|
125
|
-
// A per-chain entity's rows are partitioned by the chain that owns them, so a
|
|
126
|
-
// chain-filtered read scans one chain's partition rather than the whole table.
|
|
127
|
-
// `$` can't occur in a GraphQL entity name, so a partition name can never
|
|
128
|
-
// collide with the table another entity claims; past the identifier limit the
|
|
129
|
-
// entity index keeps what survives truncation unique.
|
|
130
|
-
let partitionTableName = (~entityConfig: Internal.entityConfig, ~chainId: ChainId.t) => {
|
|
131
|
-
let chainIdStr = chainId->ChainId.toString
|
|
132
|
-
Table.fitPgTableName(
|
|
133
|
-
`${entityConfig.table.tableName}$${chainIdStr}`,
|
|
134
|
-
~uniqueSuffix=`$${entityConfig.index->Int.toString}$${chainIdStr}`,
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
169
|
// The entity as it's stored: the handler-visible schema plus the chain-id
|
|
139
170
|
// column a per-chain entity's table carries. The value for that column is
|
|
140
171
|
// stamped from the flush group's scope right before serialization, so it never
|
|
@@ -277,6 +308,10 @@ let makeInitializeTransaction = (
|
|
|
277
308
|
~pgSchema,
|
|
278
309
|
~pgUser,
|
|
279
310
|
~isHasuraEnabled,
|
|
311
|
+
// The whole schema's sequence, not one derived from `entities`: those are the
|
|
312
|
+
// entities Postgres stores, and an entity kept only in a sink still decides
|
|
313
|
+
// how the run counts its checkpoints.
|
|
314
|
+
~checkpointSequence: CheckpointSequence.t,
|
|
280
315
|
~chainConfigs=[],
|
|
281
316
|
~entities=[],
|
|
282
317
|
~enums=[],
|
|
@@ -292,7 +327,7 @@ let makeInitializeTransaction = (
|
|
|
292
327
|
InternalTable.EnvioInfo.table,
|
|
293
328
|
InternalTable.EnvioContracts.table,
|
|
294
329
|
InternalTable.EnvioAddresses.table,
|
|
295
|
-
InternalTable.Checkpoints.
|
|
330
|
+
InternalTable.Checkpoints.tableFor(checkpointSequence),
|
|
296
331
|
InternalTable.RawEvents.table,
|
|
297
332
|
]
|
|
298
333
|
|
|
@@ -1042,12 +1077,21 @@ let executeSet = (
|
|
|
1042
1077
|
}
|
|
1043
1078
|
}
|
|
1044
1079
|
|
|
1080
|
+
// The checkpoints a write inserts: every one the batch made, or those of the
|
|
1081
|
+
// chains whose history it keeps.
|
|
1082
|
+
type pickedCheckpoints = AllCheckpoints | CheckpointIndexes(array<int>)
|
|
1083
|
+
|
|
1084
|
+
let pickCheckpoints = (column, picked) =>
|
|
1085
|
+
switch picked {
|
|
1086
|
+
| AllCheckpoints => column
|
|
1087
|
+
| CheckpointIndexes(indexes) => indexes->Array.map(index => column->Array.getUnsafe(index))
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1045
1090
|
let rec writeBatch = async (
|
|
1046
1091
|
sql,
|
|
1047
1092
|
~batch: Batch.t,
|
|
1048
1093
|
~pgSchema,
|
|
1049
1094
|
~rollback: option<Persistence.rollback>,
|
|
1050
|
-
~isInReorgThreshold,
|
|
1051
1095
|
~config: Config.t,
|
|
1052
1096
|
~allEntities: array<Internal.entityConfig>,
|
|
1053
1097
|
~setEffectCacheOrThrow,
|
|
@@ -1061,7 +1105,22 @@ let rec writeBatch = async (
|
|
|
1061
1105
|
) => {
|
|
1062
1106
|
try {
|
|
1063
1107
|
let chainIdMode = config.chainIdMode
|
|
1064
|
-
|
|
1108
|
+
// A checkpoint anchors the history its chain keeps, so the batch's
|
|
1109
|
+
// decision picks the checkpoints chain by chain.
|
|
1110
|
+
let pickedCheckpoints = {
|
|
1111
|
+
let indexes =
|
|
1112
|
+
batch.checkpointChainIds->Array.filterMapWithIndex((chainId, index) =>
|
|
1113
|
+
batch.history->HistoryPolicy.forChain(chainId) ? Some(index) : None
|
|
1114
|
+
)
|
|
1115
|
+
if indexes->Utils.Array.isEmpty {
|
|
1116
|
+
None
|
|
1117
|
+
} else if indexes->Array.length === batch.checkpointIds->Array.length {
|
|
1118
|
+
Some(AllCheckpoints)
|
|
1119
|
+
} else {
|
|
1120
|
+
Some(CheckpointIndexes(indexes))
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
let writtenFrontier = Persistence.writtenFrontier(~batch, ~rollback)
|
|
1065
1124
|
|
|
1066
1125
|
let specificError = ref(None)
|
|
1067
1126
|
|
|
@@ -1111,7 +1170,12 @@ let rec writeBatch = async (
|
|
|
1111
1170
|
}
|
|
1112
1171
|
}
|
|
1113
1172
|
|
|
1114
|
-
let setEntities = updatedEntities->Array.map(({
|
|
1173
|
+
let setEntities = updatedEntities->Array.map(({
|
|
1174
|
+
entityConfig,
|
|
1175
|
+
scope,
|
|
1176
|
+
changes,
|
|
1177
|
+
shouldSaveHistory,
|
|
1178
|
+
}) => {
|
|
1115
1179
|
let entitiesToSet = []
|
|
1116
1180
|
let idsToDelete = []
|
|
1117
1181
|
|
|
@@ -1121,8 +1185,6 @@ let rec writeBatch = async (
|
|
|
1121
1185
|
| Internal.CrossChain => None
|
|
1122
1186
|
| Chain(chainId) => Some(chainId)
|
|
1123
1187
|
}
|
|
1124
|
-
let shouldSaveHistory =
|
|
1125
|
-
config->Config.shouldSaveHistory(~isInReorgThreshold, ~chainId=?scopeChainId)
|
|
1126
1188
|
let changes = switch (entityConfig.table->Table.getChainIdField, scopeChainId) {
|
|
1127
1189
|
| (Some(field), Some(chainId)) =>
|
|
1128
1190
|
changes->Array.map(change =>
|
|
@@ -1147,7 +1209,10 @@ let rec writeBatch = async (
|
|
|
1147
1209
|
|
|
1148
1210
|
// The rollback-diff change is written to the entity table only, never the
|
|
1149
1211
|
// history table; when present it is an id's oldest change.
|
|
1150
|
-
let diffCheckpointId =
|
|
1212
|
+
let diffCheckpointId =
|
|
1213
|
+
rollback->Option.flatMap(r =>
|
|
1214
|
+
config.checkpointSequence->CheckpointSequence.findForScope(r.diffFrontier, ~scope)
|
|
1215
|
+
)
|
|
1151
1216
|
|
|
1152
1217
|
// History batches, populated only when saving history.
|
|
1153
1218
|
let batchSetUpdates = []
|
|
@@ -1384,6 +1449,7 @@ let rec writeBatch = async (
|
|
|
1384
1449
|
): InternalTable.Chains.progressedChain => {
|
|
1385
1450
|
chainId: chainAfterBatch.fetchState.chainId,
|
|
1386
1451
|
progressBlockNumber: chainAfterBatch.progressBlockNumber,
|
|
1452
|
+
progressBlockTime: chainAfterBatch.progressBlockTime,
|
|
1387
1453
|
sourceBlockNumber: chainAfterBatch.sourceBlockNumber,
|
|
1388
1454
|
totalEventsProcessed: chainAfterBatch.totalEventsProcessed,
|
|
1389
1455
|
}),
|
|
@@ -1411,18 +1477,30 @@ let rec writeBatch = async (
|
|
|
1411
1477
|
)
|
|
1412
1478
|
}
|
|
1413
1479
|
|
|
1414
|
-
if
|
|
1480
|
+
if !(writtenFrontier->Utils.Dict.isEmpty) {
|
|
1481
|
+
setOperations->Array.push(sql =>
|
|
1482
|
+
sql->InternalTable.Chains.setCheckpointFrontier(
|
|
1483
|
+
~pgSchema,
|
|
1484
|
+
~frontier=writtenFrontier,
|
|
1485
|
+
~chainIdMode,
|
|
1486
|
+
)
|
|
1487
|
+
)
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
switch pickedCheckpoints {
|
|
1491
|
+
| Some(picked) =>
|
|
1415
1492
|
setOperations->Array.push(sql =>
|
|
1416
1493
|
sql->InternalTable.Checkpoints.insert(
|
|
1417
1494
|
~pgSchema,
|
|
1418
|
-
~checkpointIds=batch.checkpointIds,
|
|
1419
|
-
~checkpointChainIds=batch.checkpointChainIds,
|
|
1420
|
-
~checkpointBlockNumbers=batch.checkpointBlockNumbers,
|
|
1421
|
-
~checkpointBlockHashes=batch.checkpointBlockHashes,
|
|
1422
|
-
~checkpointEventsProcessed=batch.checkpointEventsProcessed,
|
|
1495
|
+
~checkpointIds=batch.checkpointIds->pickCheckpoints(picked),
|
|
1496
|
+
~checkpointChainIds=batch.checkpointChainIds->pickCheckpoints(picked),
|
|
1497
|
+
~checkpointBlockNumbers=batch.checkpointBlockNumbers->pickCheckpoints(picked),
|
|
1498
|
+
~checkpointBlockHashes=batch.checkpointBlockHashes->pickCheckpoints(picked),
|
|
1499
|
+
~checkpointEventsProcessed=batch.checkpointEventsProcessed->pickCheckpoints(picked),
|
|
1423
1500
|
~chainIdMode,
|
|
1424
1501
|
)
|
|
1425
1502
|
)
|
|
1503
|
+
| None => ()
|
|
1426
1504
|
}
|
|
1427
1505
|
|
|
1428
1506
|
await setOperations
|
|
@@ -1479,7 +1557,6 @@ let rec writeBatch = async (
|
|
|
1479
1557
|
~pgSchema,
|
|
1480
1558
|
~setQueryCache,
|
|
1481
1559
|
~rollback,
|
|
1482
|
-
~isInReorgThreshold,
|
|
1483
1560
|
~config,
|
|
1484
1561
|
~setEffectCacheOrThrow,
|
|
1485
1562
|
~updatedEffectsCache,
|
|
@@ -1532,7 +1609,7 @@ let makeGetRollbackPreTargetRowsQuery = (
|
|
|
1532
1609
|
let keyMatch =
|
|
1533
1610
|
keyColumns->Array.map(c => `h."${c}" = ${tableRef}."${c}"`)->Array.joinUnsafe(" AND ")
|
|
1534
1611
|
let bounds =
|
|
1535
|
-
floors.
|
|
1612
|
+
floors.checkpointBounds->CheckpointSequence.sql(
|
|
1536
1613
|
~chainIdColumn=entityConfig.table->Table.getPgChainIdColumn,
|
|
1537
1614
|
~tableRef,
|
|
1538
1615
|
)
|
|
@@ -1565,7 +1642,7 @@ let makeGetRollbackRemovedIdsQuery = (
|
|
|
1565
1642
|
let keyMatch =
|
|
1566
1643
|
keyColumns->Array.map(c => `h."${c}" = ${tableRef}."${c}"`)->Array.joinUnsafe(" AND ")
|
|
1567
1644
|
let bounds =
|
|
1568
|
-
floors.
|
|
1645
|
+
floors.checkpointBounds->CheckpointSequence.sql(
|
|
1569
1646
|
~chainIdColumn=entityConfig.table->Table.getPgChainIdColumn,
|
|
1570
1647
|
~tableRef,
|
|
1571
1648
|
)
|
|
@@ -1623,8 +1700,11 @@ let make = (
|
|
|
1623
1700
|
// encoded at initialize and when stored rows are grouped on resume.
|
|
1624
1701
|
~ecosystem: Ecosystem.name,
|
|
1625
1702
|
~sink: option<Sink.t>=?,
|
|
1703
|
+
// An `envio start --chain` process: builds and looks for a per-chain entity's
|
|
1704
|
+
// indexes on its own chains' partitions, never on the parent table, so no
|
|
1705
|
+
// build reaches into the rows a sibling process drives.
|
|
1706
|
+
~isolated=false,
|
|
1626
1707
|
~onInitialize=?,
|
|
1627
|
-
~onNewTables=?,
|
|
1628
1708
|
): Persistence.storage => {
|
|
1629
1709
|
// Must match PG_CONTAINER in packages/cli/src/docker_env.rs
|
|
1630
1710
|
let containerName = "envio-postgres"
|
|
@@ -1784,19 +1864,6 @@ let make = (
|
|
|
1784
1864
|
|
|
1785
1865
|
let cacheTableInfo = await queryCacheTableInfo()
|
|
1786
1866
|
|
|
1787
|
-
if withUpload && cacheTableInfo->Utils.Array.notEmpty {
|
|
1788
|
-
// Integration with other tools like Hasura
|
|
1789
|
-
switch onNewTables {
|
|
1790
|
-
| Some(onNewTables) =>
|
|
1791
|
-
await onNewTables(
|
|
1792
|
-
~tableNames=cacheTableInfo->Array.map(info => {
|
|
1793
|
-
info.tableName
|
|
1794
|
-
}),
|
|
1795
|
-
)
|
|
1796
|
-
| None => ()
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
1867
|
let cache = Dict.make()
|
|
1801
1868
|
cacheTableInfo->Array.forEach(({tableName, count}) => {
|
|
1802
1869
|
switch Internal.EffectCache.fromTableName(tableName) {
|
|
@@ -1856,6 +1923,7 @@ let make = (
|
|
|
1856
1923
|
let queries = makeInitializeTransaction(
|
|
1857
1924
|
~pgSchema,
|
|
1858
1925
|
~pgUser,
|
|
1926
|
+
~checkpointSequence=CheckpointSequence.fromEntities(entities),
|
|
1859
1927
|
~entities=pgEntities,
|
|
1860
1928
|
~enums,
|
|
1861
1929
|
~chainConfigs,
|
|
@@ -1925,9 +1993,10 @@ let make = (
|
|
|
1925
1993
|
firstEventBlockNumber: None,
|
|
1926
1994
|
timestampCaughtUpToHeadOrEndblock: None,
|
|
1927
1995
|
addressRows: rowsByChain->Array.getUnsafe(idx)->AddressRows.seedRowsOf,
|
|
1996
|
+
progressBlockTime: None,
|
|
1928
1997
|
sourceBlockNumber: 0,
|
|
1929
1998
|
}),
|
|
1930
|
-
|
|
1999
|
+
checkpointFrontier: Frontier.empty(),
|
|
1931
2000
|
}
|
|
1932
2001
|
}
|
|
1933
2002
|
|
|
@@ -2012,11 +2081,31 @@ let make = (
|
|
|
2012
2081
|
})
|
|
2013
2082
|
}
|
|
2014
2083
|
|
|
2015
|
-
let
|
|
2016
|
-
|
|
2084
|
+
let partitionChainIds = chainIds => isolated ? Some(chainIds) : None
|
|
2085
|
+
|
|
2086
|
+
// The physical table a query index for `scope` is built on. A per-chain
|
|
2087
|
+
// entity's query carries the scope's chain id, so it is planned against that
|
|
2088
|
+
// chain's partition, which an index on the parent covers by cascading.
|
|
2089
|
+
let queryTableName = (~entityConfig: Internal.entityConfig, ~scope: Internal.chainScope) =>
|
|
2090
|
+
switch scope {
|
|
2091
|
+
| Chain(chainId) =>
|
|
2092
|
+
indexTableNames(
|
|
2093
|
+
entityConfig,
|
|
2094
|
+
~partitionChainIds=partitionChainIds([chainId]),
|
|
2095
|
+
)->Array.getUnsafe(0)
|
|
2096
|
+
| CrossChain => entityConfig.table.tableName
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
let ensureQueryIndexes = async (
|
|
2100
|
+
~entityConfig: Internal.entityConfig,
|
|
2101
|
+
~scope: Internal.chainScope,
|
|
2102
|
+
~filters: array<EntityFilter.t>,
|
|
2103
|
+
) => {
|
|
2104
|
+
let tableName = queryTableName(~entityConfig, ~scope)
|
|
2105
|
+
let columns = filterColumns(~table=entityConfig.table, ~filters)
|
|
2017
2106
|
let _ = await columns
|
|
2018
2107
|
->Array.map(column => {
|
|
2019
|
-
let definition = IndexDefinition.single(~tableName
|
|
2108
|
+
let definition = IndexDefinition.single(~tableName, ~column)
|
|
2020
2109
|
indexManager
|
|
2021
2110
|
->IndexManager.ensure(~definition, ~coverage=LeadingColumns, ~build=async () => {
|
|
2022
2111
|
// Resolved before logging so a rebuild is reported as one, and an
|
|
@@ -2033,7 +2122,7 @@ let make = (
|
|
|
2033
2122
|
// actually creates the index, not the ones waiting on it.
|
|
2034
2123
|
Logging.info({
|
|
2035
2124
|
"storage": storageName,
|
|
2036
|
-
"msg": `${verb} "${prepared.name}" to serve a getWhere query on "${
|
|
2125
|
+
"msg": `${verb} "${prepared.name}" to serve a getWhere query on "${tableName}". Writes to the table are paused until it completes. ${slowOnLargeDatabaseNotice}`,
|
|
2037
2126
|
})
|
|
2038
2127
|
let timeRef = Performance.now()
|
|
2039
2128
|
let entry = await sql->runAndVerify(prepared)
|
|
@@ -2049,7 +2138,7 @@ let make = (
|
|
|
2049
2138
|
->Promise.catch(async exn => {
|
|
2050
2139
|
Logging.warn({
|
|
2051
2140
|
"storage": storageName,
|
|
2052
|
-
"msg": `Failed to create an index on "${
|
|
2141
|
+
"msg": `Failed to create an index on "${tableName}"("${column}") for a getWhere query. The query runs without it.`,
|
|
2053
2142
|
"err": exn->Utils.prettifyExn,
|
|
2054
2143
|
})
|
|
2055
2144
|
await resyncIndex(definition->IndexDefinition.name)
|
|
@@ -2063,9 +2152,10 @@ let make = (
|
|
|
2063
2152
|
// is already ready, so handlers may be issuing getWhere queries alongside it
|
|
2064
2153
|
// and the per-table queues are what keep the two from colliding. Nothing here
|
|
2065
2154
|
// writes `ready_at` — the chains already carry theirs.
|
|
2066
|
-
let ensureSchemaIndexes = async (~entities: array<Internal.entityConfig
|
|
2155
|
+
let ensureSchemaIndexes = async (~entities: array<Internal.entityConfig>, ~chainIds) => {
|
|
2067
2156
|
let schemaIndexes = getSchemaIndexes(
|
|
2068
2157
|
~entities=entities->Array.filter((e: Internal.entityConfig) => e.storage.postgres),
|
|
2158
|
+
~partitionChainIds=?partitionChainIds(chainIds),
|
|
2069
2159
|
)
|
|
2070
2160
|
|
|
2071
2161
|
let _ = await schemaIndexes
|
|
@@ -2116,6 +2206,7 @@ let make = (
|
|
|
2116
2206
|
) => {
|
|
2117
2207
|
let schemaIndexes = getSchemaIndexes(
|
|
2118
2208
|
~entities=entities->Array.filter((e: Internal.entityConfig) => e.storage.postgres),
|
|
2209
|
+
~partitionChainIds=?partitionChainIds(chainIds),
|
|
2119
2210
|
)
|
|
2120
2211
|
|
|
2121
2212
|
// Resolved up front so a name held by an unrelated index fails before any
|
|
@@ -2181,9 +2272,9 @@ let make = (
|
|
|
2181
2272
|
// crash either leaves `ready_at` null and the retry finds the indexes
|
|
2182
2273
|
// already built, or commits readiness the schema backs.
|
|
2183
2274
|
//
|
|
2184
|
-
// One transaction for the whole set:
|
|
2185
|
-
// a crash part way through would otherwise leave some
|
|
2186
|
-
//
|
|
2275
|
+
// One transaction for the whole set: the chains this process drives caught
|
|
2276
|
+
// up together, and a crash part way through would otherwise leave some of
|
|
2277
|
+
// them stamped and some not.
|
|
2187
2278
|
let setReadyAtQuery = InternalTable.Chains.makeSetReadyAtQuery(~pgSchema)
|
|
2188
2279
|
let _ = await sql->Postgres.beginSql(async sql => {
|
|
2189
2280
|
for idx in 0 to chainIds->Array.length - 1 {
|
|
@@ -2232,11 +2323,6 @@ let make = (
|
|
|
2232
2323
|
let _ = await sql->Postgres.unsafe(
|
|
2233
2324
|
makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText=false),
|
|
2234
2325
|
)
|
|
2235
|
-
// Integration with other tools like Hasura
|
|
2236
|
-
switch onNewTables {
|
|
2237
|
-
| Some(onNewTables) => await onNewTables(~tableNames=[table.tableName])
|
|
2238
|
-
| None => ()
|
|
2239
|
-
}
|
|
2240
2326
|
}
|
|
2241
2327
|
|
|
2242
2328
|
await setOrThrow(~items, ~table, ~itemSchema)
|
|
@@ -2313,36 +2399,49 @@ let make = (
|
|
|
2313
2399
|
}
|
|
2314
2400
|
}
|
|
2315
2401
|
|
|
2316
|
-
let resumeInitialState = async (
|
|
2402
|
+
let resumeInitialState = async (
|
|
2403
|
+
~entities,
|
|
2404
|
+
~chainIds,
|
|
2405
|
+
~throwIfIncompatible,
|
|
2406
|
+
): Persistence.initialState => {
|
|
2317
2407
|
let (
|
|
2318
2408
|
cache,
|
|
2319
|
-
chains,
|
|
2320
|
-
checkpointIdResult,
|
|
2409
|
+
(chains, checkpointFrontier),
|
|
2321
2410
|
reorgCheckpoints,
|
|
2322
2411
|
(storedEnvioInfo, storedContractMapping),
|
|
2323
|
-
) = await Promise.
|
|
2412
|
+
) = await Promise.all4((
|
|
2324
2413
|
restoreEffectCache(~withUpload=false),
|
|
2325
2414
|
InternalTable.Chains.getInitialState(
|
|
2326
2415
|
sql,
|
|
2327
2416
|
~pgSchema,
|
|
2328
2417
|
)->Promise.thenResolve(rawInitialStates => {
|
|
2329
|
-
rawInitialStates
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2418
|
+
let rawInitialStates =
|
|
2419
|
+
rawInitialStates->Array.filter(rawInitialState =>
|
|
2420
|
+
chainIds->Array.includes(rawInitialState.id)
|
|
2421
|
+
)
|
|
2422
|
+
(
|
|
2423
|
+
rawInitialStates->Array.map((rawInitialState): Persistence.initialChainState => {
|
|
2424
|
+
id: rawInitialState.id,
|
|
2425
|
+
startBlock: rawInitialState.startBlock,
|
|
2426
|
+
endBlock: rawInitialState.endBlock->Null.toOption,
|
|
2427
|
+
maxReorgDepth: rawInitialState.maxReorgDepth,
|
|
2428
|
+
firstEventBlockNumber: rawInitialState.firstEventBlockNumber->Null.toOption,
|
|
2429
|
+
timestampCaughtUpToHeadOrEndblock: rawInitialState.timestampCaughtUpToHeadOrEndblock->Null.toOption,
|
|
2430
|
+
numEventsProcessed: rawInitialState.numEventsProcessed,
|
|
2431
|
+
progressBlockNumber: rawInitialState.progressBlockNumber,
|
|
2432
|
+
progressBlockTime: rawInitialState.progressBlockTime->InternalTable.Chains.blockTimeFromDb,
|
|
2433
|
+
addressRows: rawInitialState.addressRows,
|
|
2434
|
+
sourceBlockNumber: rawInitialState.sourceBlockNumber,
|
|
2435
|
+
}),
|
|
2436
|
+
Frontier.fromEntries(
|
|
2437
|
+
rawInitialStates->Array.map(rawInitialState => (
|
|
2438
|
+
rawInitialState.id,
|
|
2439
|
+
rawInitialState.checkpointId->BigInt.fromStringOrThrow,
|
|
2440
|
+
)),
|
|
2441
|
+
),
|
|
2442
|
+
)
|
|
2341
2443
|
}),
|
|
2342
2444
|
sql
|
|
2343
|
-
->Postgres.unsafe(InternalTable.Checkpoints.makeCommitedCheckpointIdQuery(~pgSchema))
|
|
2344
|
-
->(Utils.magic: promise<array<unknown>> => promise<array<{"id": string}>>),
|
|
2345
|
-
sql
|
|
2346
2445
|
->Postgres.unsafe(InternalTable.Checkpoints.makeGetReorgCheckpointsQuery(~pgSchema))
|
|
2347
2446
|
->(
|
|
2348
2447
|
Utils.magic: promise<array<unknown>> => promise<
|
|
@@ -2372,8 +2471,6 @@ let make = (
|
|
|
2372
2471
|
|
|
2373
2472
|
await reloadIndexCatalog()
|
|
2374
2473
|
|
|
2375
|
-
let checkpointId = (checkpointIdResult->Array.getUnsafe(0))["id"]->BigInt.fromStringOrThrow
|
|
2376
|
-
|
|
2377
2474
|
// Convert string checkpoint IDs from DB to bigint
|
|
2378
2475
|
let reorgCheckpoints = Array.map(reorgCheckpoints, (raw): Internal.reorgCheckpoint => {
|
|
2379
2476
|
checkpointId: raw["id"]->BigInt.fromStringOrThrow,
|
|
@@ -2384,7 +2481,7 @@ let make = (
|
|
|
2384
2481
|
|
|
2385
2482
|
// Resume sink if present - needed to rollback any reorg changes
|
|
2386
2483
|
switch sink {
|
|
2387
|
-
| Some(sink) => await sink.resume(~
|
|
2484
|
+
| Some(sink) => await sink.resume(~frontier=checkpointFrontier, ~chains, ~entities)
|
|
2388
2485
|
| None => ()
|
|
2389
2486
|
}
|
|
2390
2487
|
|
|
@@ -2393,7 +2490,7 @@ let make = (
|
|
|
2393
2490
|
reorgCheckpoints,
|
|
2394
2491
|
cache,
|
|
2395
2492
|
chains,
|
|
2396
|
-
|
|
2493
|
+
checkpointFrontier,
|
|
2397
2494
|
contractMapping: storedContractMapping,
|
|
2398
2495
|
envioInfo: storedEnvioInfo,
|
|
2399
2496
|
}
|
|
@@ -2434,7 +2531,7 @@ let make = (
|
|
|
2434
2531
|
InternalTable.Checkpoints.getRollbackProgressDiff(sql, ~pgSchema, ~floors)
|
|
2435
2532
|
|
|
2436
2533
|
let getRollbackData = async (~entityConfig: Internal.entityConfig, ~floors: RollbackFloors.t) => {
|
|
2437
|
-
let params = floors.
|
|
2534
|
+
let params = floors.checkpointBounds->CheckpointSequence.params
|
|
2438
2535
|
let (removedIdRows, rollbackRows) = await Promise.all2((
|
|
2439
2536
|
// Get IDs of entities that should be deleted (created after rollback target with no prior history)
|
|
2440
2537
|
sql
|
|
@@ -2482,7 +2579,6 @@ let make = (
|
|
|
2482
2579
|
let writeBatchMethod = async (
|
|
2483
2580
|
~batch,
|
|
2484
2581
|
~rollback,
|
|
2485
|
-
~isInReorgThreshold,
|
|
2486
2582
|
~config,
|
|
2487
2583
|
~allEntities,
|
|
2488
2584
|
~updatedEffectsCache,
|
|
@@ -2509,7 +2605,14 @@ let make = (
|
|
|
2509
2605
|
| Some(sink) => {
|
|
2510
2606
|
let timerRef = Performance.now()
|
|
2511
2607
|
Some(
|
|
2512
|
-
sink.writeBatch(
|
|
2608
|
+
sink.writeBatch(
|
|
2609
|
+
~batch,
|
|
2610
|
+
~diffCheckpoints=switch (rollback: option<Persistence.rollback>) {
|
|
2611
|
+
| Some({diffCheckpoints}) => diffCheckpoints
|
|
2612
|
+
| None => []
|
|
2613
|
+
},
|
|
2614
|
+
~updatedEntities=chUpdates,
|
|
2615
|
+
)
|
|
2513
2616
|
->Promise.thenResolve(_ => {
|
|
2514
2617
|
onWrite(~storage=sink.name, ~timeSeconds=timerRef->Performance.secondsSince)
|
|
2515
2618
|
None
|
|
@@ -2528,7 +2631,6 @@ let make = (
|
|
|
2528
2631
|
~pgSchema,
|
|
2529
2632
|
~setQueryCache,
|
|
2530
2633
|
~rollback,
|
|
2531
|
-
~isInReorgThreshold,
|
|
2532
2634
|
~config,
|
|
2533
2635
|
~allEntities,
|
|
2534
2636
|
~setEffectCacheOrThrow,
|
|
@@ -2581,6 +2683,7 @@ let makeStorageFromEnv = (
|
|
|
2581
2683
|
~pgPassword=Env.Db.password,
|
|
2582
2684
|
~chainIdMode=config.chainIdMode,
|
|
2583
2685
|
~ecosystem=config.ecosystem.name,
|
|
2686
|
+
~isolated=config.isolated,
|
|
2584
2687
|
~sink=?{
|
|
2585
2688
|
// Internally ClickHouse storage is implemented as a sync of the
|
|
2586
2689
|
// Postgres storage. Required env vars are validated here only when
|
|
@@ -2613,6 +2716,7 @@ let makeStorageFromEnv = (
|
|
|
2613
2716
|
~database=database->Option.getUnsafe,
|
|
2614
2717
|
~username=username->Option.getUnsafe,
|
|
2615
2718
|
~password=password->Option.getUnsafe,
|
|
2719
|
+
~sequence=config.checkpointSequence,
|
|
2616
2720
|
~chainIdMode=config.chainIdMode,
|
|
2617
2721
|
),
|
|
2618
2722
|
)
|
|
@@ -2644,34 +2748,6 @@ let makeStorageFromEnv = (
|
|
|
2644
2748
|
None
|
|
2645
2749
|
}
|
|
2646
2750
|
},
|
|
2647
|
-
~onNewTables=?{
|
|
2648
|
-
if isHasuraEnabled {
|
|
2649
|
-
Some(
|
|
2650
|
-
(~tableNames) => {
|
|
2651
|
-
Hasura.trackTables(
|
|
2652
|
-
~endpoint=Env.Hasura.graphqlEndpoint,
|
|
2653
|
-
~auth={
|
|
2654
|
-
role: Env.Hasura.role,
|
|
2655
|
-
secret: Env.Hasura.secret,
|
|
2656
|
-
},
|
|
2657
|
-
~pgSchema,
|
|
2658
|
-
~tableConfigs=tableNames->Array.map(tableName => {
|
|
2659
|
-
Hasura.tableName,
|
|
2660
|
-
description: None,
|
|
2661
|
-
columnConfigs: dict{},
|
|
2662
|
-
}),
|
|
2663
|
-
)->Promise.catch(err => {
|
|
2664
|
-
Logging.errorWithExn(
|
|
2665
|
-
err->Utils.prettifyExn,
|
|
2666
|
-
`Error tracking new tables`,
|
|
2667
|
-
)->Promise.resolve
|
|
2668
|
-
})
|
|
2669
|
-
},
|
|
2670
|
-
)
|
|
2671
|
-
} else {
|
|
2672
|
-
None
|
|
2673
|
-
}
|
|
2674
|
-
},
|
|
2675
2751
|
~isHasuraEnabled,
|
|
2676
2752
|
)
|
|
2677
2753
|
}
|