envio 3.3.0 → 3.3.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 -7
- package/src/ChainFetching.res +4 -32
- package/src/ChainFetching.res.mjs +2 -12
- package/src/ChainState.res +73 -53
- package/src/ChainState.res.mjs +61 -30
- package/src/ChainState.resi +13 -17
- package/src/Config.res +5 -0
- package/src/Config.res.mjs +1 -0
- package/src/CrossChainState.res +2 -8
- package/src/CrossChainState.res.mjs +6 -8
- package/src/CrossChainState.resi +1 -0
- package/src/EffectState.res +183 -44
- package/src/EffectState.res.mjs +134 -26
- package/src/EffectState.resi +26 -3
- package/src/EventProcessing.res +14 -9
- package/src/EventProcessing.res.mjs +7 -7
- package/src/FetchState.res +18 -29
- package/src/FetchState.res.mjs +20 -37
- package/src/IndexerState.res +280 -31
- package/src/IndexerState.res.mjs +234 -25
- package/src/IndexerState.resi +22 -0
- package/src/LoadLayer.res +19 -65
- package/src/LoadLayer.res.mjs +13 -55
- package/src/Main.res +42 -23
- package/src/Main.res.mjs +32 -35
- package/src/Metrics.res +943 -66
- package/src/Metrics.res.mjs +367 -50
- package/src/Persistence.res +3 -0
- package/src/PgStorage.res +3 -8
- package/src/PgStorage.res.mjs +3 -4
- package/src/PruneStaleHistory.res +1 -1
- package/src/PruneStaleHistory.res.mjs +1 -2
- package/src/Rollback.res +4 -5
- package/src/Rollback.res.mjs +2 -3
- package/src/SimulateItems.res.mjs +3 -21
- package/src/TestIndexer.res.mjs +4 -23
- package/src/TestIndexerProxyStorage.res +1 -0
- package/src/TestIndexerProxyStorage.res.mjs +1 -1
- package/src/Writing.res +3 -5
- package/src/Writing.res.mjs +3 -6
- package/src/bindings/ClickHouse.res +101 -17
- package/src/bindings/ClickHouse.res.mjs +58 -19
- package/src/bindings/NodeJs.res +64 -0
- package/src/bindings/NodeJs.res.mjs +6 -0
- package/src/sources/HyperSyncClient.res +5 -11
- package/src/sources/SourceManager.res +72 -27
- package/src/sources/SourceManager.res.mjs +81 -11
- package/src/sources/SourceManager.resi +14 -0
- package/src/tui/Tui.res +1 -1
- package/src/tui/Tui.res.mjs +2 -2
- package/src/Prometheus.res +0 -789
- package/src/Prometheus.res.mjs +0 -847
- package/src/bindings/PromClient.res +0 -72
- package/src/bindings/PromClient.res.mjs +0 -38
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -614,31 +614,12 @@ function initTestWorker() {
|
|
|
614
614
|
}
|
|
615
615
|
let patchConfig = (config, registrationsByChainId) => {
|
|
616
616
|
let config$1 = SimulateItems.patchConfig(config, processConfig, registrationsByChainId);
|
|
617
|
-
if (exitAfterFirstEventBlock) {
|
|
618
|
-
return {
|
|
619
|
-
name: config$1.name,
|
|
620
|
-
description: config$1.description,
|
|
621
|
-
handlers: config$1.handlers,
|
|
622
|
-
contractHandlers: config$1.contractHandlers,
|
|
623
|
-
shouldRollbackOnReorg: config$1.shouldRollbackOnReorg,
|
|
624
|
-
shouldSaveFullHistory: config$1.shouldSaveFullHistory,
|
|
625
|
-
storage: config$1.storage,
|
|
626
|
-
chainMap: config$1.chainMap,
|
|
627
|
-
defaultChain: config$1.defaultChain,
|
|
628
|
-
ecosystem: config$1.ecosystem,
|
|
629
|
-
enableRawEvents: config$1.enableRawEvents,
|
|
630
|
-
maxAddrInPartition: config$1.maxAddrInPartition,
|
|
631
|
-
batchSize: 1,
|
|
632
|
-
lowercaseAddresses: config$1.lowercaseAddresses,
|
|
633
|
-
isDev: config$1.isDev,
|
|
634
|
-
userEntitiesByName: config$1.userEntitiesByName,
|
|
635
|
-
userEntities: config$1.userEntities,
|
|
636
|
-
allEntities: config$1.allEntities,
|
|
637
|
-
allEnums: config$1.allEnums
|
|
638
|
-
};
|
|
639
|
-
} else {
|
|
617
|
+
if (!exitAfterFirstEventBlock) {
|
|
640
618
|
return config$1;
|
|
641
619
|
}
|
|
620
|
+
let newrecord = {...config$1};
|
|
621
|
+
newrecord.batchSize = 1;
|
|
622
|
+
return newrecord;
|
|
642
623
|
};
|
|
643
624
|
Stdlib_Promise.$$catch(Main.start(persistence, undefined, true, exitAfterFirstEventBlock, patchConfig), exn => {
|
|
644
625
|
let toThrow = exn.RE_EXN_ID === Main.FatalError ? exn._1 : Utils.prettifyExn(exn);
|
|
@@ -138,6 +138,7 @@ let makeStorage = (proxy: t): Persistence.storage => {
|
|
|
138
138
|
~updatedEffectsCache as _,
|
|
139
139
|
~updatedEntities,
|
|
140
140
|
~chainMetaData as _,
|
|
141
|
+
~onWrite as _,
|
|
141
142
|
) => {
|
|
142
143
|
// Encode entities to JSON for serialization across worker boundary
|
|
143
144
|
let serializableEntities = updatedEntities->Array.map((
|
|
@@ -74,7 +74,7 @@ function makeStorage(proxy) {
|
|
|
74
74
|
getRollbackTargetCheckpoint: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. Set rollbackOnReorg to false in config."),
|
|
75
75
|
getRollbackProgressDiff: async param => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. Set rollbackOnReorg to false in config."),
|
|
76
76
|
getRollbackData: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. Set rollbackOnReorg to false in config."),
|
|
77
|
-
writeBatch: async (batch, param, param$1, param$2, param$3, param$4, updatedEntities, param$5) => {
|
|
77
|
+
writeBatch: async (batch, param, param$1, param$2, param$3, param$4, updatedEntities, param$5, param$6) => {
|
|
78
78
|
let serializableEntities = updatedEntities.map(param => {
|
|
79
79
|
let entityConfig = param.entityConfig;
|
|
80
80
|
let encodeChange = change => {
|
package/src/Writing.res
CHANGED
|
@@ -61,11 +61,7 @@ let snapshotEffects = (state: IndexerState.t, ~cache): array<Persistence.updated
|
|
|
61
61
|
}
|
|
62
62
|
let shouldInitialize = effectCacheRecord.count === 0
|
|
63
63
|
effectCacheRecord.count = effectCacheRecord.count + items->Array.length - invalidationsCount
|
|
64
|
-
|
|
65
|
-
~count=effectCacheRecord.count,
|
|
66
|
-
~effectName,
|
|
67
|
-
~scope=scope->Internal.EffectCache.scopeToString,
|
|
68
|
-
)
|
|
64
|
+
inMemTable->EffectState.commitCacheCount(~count=effectCacheRecord.count)
|
|
69
65
|
acc
|
|
70
66
|
->Array.push(
|
|
71
67
|
(
|
|
@@ -155,6 +151,8 @@ let runOneWrite = async (state: IndexerState.t) => {
|
|
|
155
151
|
~updatedEntities,
|
|
156
152
|
~updatedEffectsCache,
|
|
157
153
|
~chainMetaData,
|
|
154
|
+
~onWrite=(~storage, ~timeSeconds) =>
|
|
155
|
+
state->IndexerState.recordStorageWrite(~storage, ~timeSeconds),
|
|
158
156
|
),
|
|
159
157
|
PruneStaleHistory.runConcurrent(state, ~targets=pruneTargets),
|
|
160
158
|
))
|
package/src/Writing.res.mjs
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Env from "./Env.res.mjs";
|
|
4
4
|
import * as Utils from "./Utils.res.mjs";
|
|
5
|
-
import * as Internal from "./Internal.res.mjs";
|
|
6
5
|
import * as Throttler from "./Throttler.res.mjs";
|
|
7
|
-
import * as Prometheus from "./Prometheus.res.mjs";
|
|
8
6
|
import * as EffectState from "./EffectState.res.mjs";
|
|
9
7
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
10
8
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
@@ -43,7 +41,6 @@ function snapshotEffects(state, cache) {
|
|
|
43
41
|
let invalidationsCount = inMemTable.invalidationsCount;
|
|
44
42
|
let dict = inMemTable.dict;
|
|
45
43
|
let table = inMemTable.table;
|
|
46
|
-
let scope = inMemTable.scope;
|
|
47
44
|
let effect = inMemTable.effect;
|
|
48
45
|
if (idsToStore.length !== 0) {
|
|
49
46
|
let items = Stdlib_Array.filterMap(idsToStore, id => {
|
|
@@ -64,7 +61,7 @@ function snapshotEffects(state, cache) {
|
|
|
64
61
|
} else {
|
|
65
62
|
let c$1 = {
|
|
66
63
|
effectName: effectName,
|
|
67
|
-
scope: scope,
|
|
64
|
+
scope: inMemTable.scope,
|
|
68
65
|
tableName: tableName,
|
|
69
66
|
count: 0
|
|
70
67
|
};
|
|
@@ -73,7 +70,7 @@ function snapshotEffects(state, cache) {
|
|
|
73
70
|
}
|
|
74
71
|
let shouldInitialize = effectCacheRecord.count === 0;
|
|
75
72
|
effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
|
|
76
|
-
|
|
73
|
+
EffectState.commitCacheCount(inMemTable, effectCacheRecord.count);
|
|
77
74
|
acc.push({
|
|
78
75
|
table: table,
|
|
79
76
|
itemSchema: effect.storageMeta.itemSchema,
|
|
@@ -126,7 +123,7 @@ async function runOneWrite(state) {
|
|
|
126
123
|
});
|
|
127
124
|
let pruneTargets = PruneStaleHistory.select(state, writtenEntityNames, Stdlib_Option.isSome(rollback));
|
|
128
125
|
await Promise.all([
|
|
129
|
-
persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData),
|
|
126
|
+
persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
|
|
130
127
|
PruneStaleHistory.runConcurrent(state, pruneTargets)
|
|
131
128
|
]);
|
|
132
129
|
IndexerState.markCommitted(state, upToCheckpointId);
|
|
@@ -307,11 +307,34 @@ let setUpdatesOrThrow = async (
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
// A plain database created with ON CLUSTER doesn't turn subsequent DDL into
|
|
311
|
+
// cluster-wide statements; ClickHouse keeps no "this database is clustered"
|
|
312
|
+
// flag. Without a Replicated database engine, every CREATE must carry its own
|
|
313
|
+
// ON CLUSTER to reach all replicas, otherwise it runs only on the connected
|
|
314
|
+
// node. With a Replicated database engine the DDL propagates via the database's
|
|
315
|
+
// own log, and combining it with ON CLUSTER is rejected/double-applied — so
|
|
316
|
+
// table-level DDL must carry the clause only in the plain-database case.
|
|
317
|
+
// The '{cluster}' macro resolves to each node's configured cluster name.
|
|
318
|
+
let onClusterClause = (~onCluster: bool) => onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
319
|
+
|
|
320
|
+
// Strip both engine arguments `(...)` and a trailing `SETTINGS ...` clause to
|
|
321
|
+
// get the bare engine name, e.g. `Replicated('/p','{shard}','{replica}') SETTINGS x=1`
|
|
322
|
+
// and `Replicated SETTINGS x=1` both yield `Replicated`.
|
|
323
|
+
let databaseEngineName = (engineSpec: string) =>
|
|
324
|
+
engineSpec
|
|
325
|
+
->String.trim
|
|
326
|
+
->String.split("(")
|
|
327
|
+
->Array.getUnsafe(0)
|
|
328
|
+
->String.split(" ")
|
|
329
|
+
->Array.getUnsafe(0)
|
|
330
|
+
->String.trim
|
|
331
|
+
|
|
310
332
|
// Generate CREATE TABLE query for entity history table
|
|
311
333
|
let makeCreateHistoryTableQuery = (
|
|
312
334
|
~entityConfig: Internal.entityConfig,
|
|
313
335
|
~database: string,
|
|
314
336
|
~replicated: bool=false,
|
|
337
|
+
~onCluster: bool=false,
|
|
315
338
|
) => {
|
|
316
339
|
let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()"
|
|
317
340
|
let fieldDefinitions = entityConfig.table.fields->Array.filterMap(field => {
|
|
@@ -333,7 +356,7 @@ let makeCreateHistoryTableQuery = (
|
|
|
333
356
|
`CREATE TABLE IF NOT EXISTS ${database}.\`${EntityHistory.historyTableName(
|
|
334
357
|
~entityName=entityConfig.name,
|
|
335
358
|
~entityIndex=entityConfig.index,
|
|
336
|
-
)}
|
|
359
|
+
)}\`${onClusterClause(~onCluster)} (
|
|
337
360
|
${fieldDefinitions->Array.joinUnsafe(",\n ")},
|
|
338
361
|
\`${EntityHistory.checkpointIdFieldName}\` ${getClickHouseFieldType(
|
|
339
362
|
~fieldType=UInt64,
|
|
@@ -351,7 +374,11 @@ ORDER BY (${Table.idFieldName}, ${EntityHistory.checkpointIdFieldName})`
|
|
|
351
374
|
}
|
|
352
375
|
|
|
353
376
|
// Generate CREATE TABLE query for checkpoints
|
|
354
|
-
let makeCreateCheckpointsTableQuery = (
|
|
377
|
+
let makeCreateCheckpointsTableQuery = (
|
|
378
|
+
~database: string,
|
|
379
|
+
~replicated: bool=false,
|
|
380
|
+
~onCluster: bool=false,
|
|
381
|
+
) => {
|
|
355
382
|
let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()"
|
|
356
383
|
let idField = (#id: InternalTable.Checkpoints.field :> string)
|
|
357
384
|
let chainIdField = (#chain_id: InternalTable.Checkpoints.field :> string)
|
|
@@ -359,7 +386,9 @@ let makeCreateCheckpointsTableQuery = (~database: string, ~replicated: bool=fals
|
|
|
359
386
|
let blockHashField = (#block_hash: InternalTable.Checkpoints.field :> string)
|
|
360
387
|
let eventsProcessedField = (#events_processed: InternalTable.Checkpoints.field :> string)
|
|
361
388
|
|
|
362
|
-
`CREATE TABLE IF NOT EXISTS ${database}.\`${InternalTable.Checkpoints.table.tableName}
|
|
389
|
+
`CREATE TABLE IF NOT EXISTS ${database}.\`${InternalTable.Checkpoints.table.tableName}\`${onClusterClause(
|
|
390
|
+
~onCluster,
|
|
391
|
+
)} (
|
|
363
392
|
\`${idField}\` ${getClickHouseFieldType(~fieldType=UInt64, ~isNullable=false, ~isArray=false)},
|
|
364
393
|
\`${chainIdField}\` ${getClickHouseFieldType(
|
|
365
394
|
~fieldType=Int32,
|
|
@@ -387,7 +416,11 @@ ORDER BY (${idField})`
|
|
|
387
416
|
}
|
|
388
417
|
|
|
389
418
|
// Generate CREATE VIEW query for entity current state
|
|
390
|
-
let makeCreateViewQuery = (
|
|
419
|
+
let makeCreateViewQuery = (
|
|
420
|
+
~entityConfig: Internal.entityConfig,
|
|
421
|
+
~database: string,
|
|
422
|
+
~onCluster: bool=false,
|
|
423
|
+
) => {
|
|
391
424
|
let historyTableName = EntityHistory.historyTableName(
|
|
392
425
|
~entityName=entityConfig.name,
|
|
393
426
|
~entityIndex=entityConfig.index,
|
|
@@ -409,7 +442,7 @@ let makeCreateViewQuery = (~entityConfig: Internal.entityConfig, ~database: stri
|
|
|
409
442
|
})
|
|
410
443
|
->Array.joinUnsafe(", ")
|
|
411
444
|
|
|
412
|
-
`CREATE VIEW IF NOT EXISTS ${database}.\`${entityConfig.name}
|
|
445
|
+
`CREATE VIEW IF NOT EXISTS ${database}.\`${entityConfig.name}\`${onClusterClause(~onCluster)} AS
|
|
413
446
|
SELECT ${entityFields}
|
|
414
447
|
FROM (
|
|
415
448
|
SELECT ${entityFields}, \`${EntityHistory.changeFieldName}\`
|
|
@@ -429,20 +462,33 @@ let initialize = async (
|
|
|
429
462
|
~enums as _: array<Table.enumConfig<Table.enum>>,
|
|
430
463
|
) => {
|
|
431
464
|
try {
|
|
432
|
-
let replicated = Env.ClickHouse.replicated()
|
|
433
465
|
let databaseEngine = Env.ClickHouse.databaseEngine()
|
|
434
466
|
let databaseEngineClause = switch databaseEngine {
|
|
435
467
|
| Some(engine) => ` ENGINE = ${engine}`
|
|
436
468
|
| None => ""
|
|
437
469
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
470
|
+
let hasReplicatedDatabaseEngine = switch databaseEngine {
|
|
471
|
+
| Some(engine) => engine->databaseEngineName === "Replicated"
|
|
472
|
+
| None => false
|
|
473
|
+
}
|
|
474
|
+
let envReplicated = Env.ClickHouse.replicated()
|
|
475
|
+
// A Replicated database engine only replicates data when its tables use the
|
|
476
|
+
// ReplicatedMergeTree engine, so it implies replicated mode even when
|
|
477
|
+
// ENVIO_CLICKHOUSE_REPLICATED is unset.
|
|
478
|
+
let replicated = envReplicated || hasReplicatedDatabaseEngine
|
|
479
|
+
if hasReplicatedDatabaseEngine && !envReplicated {
|
|
480
|
+
Logging.info(
|
|
481
|
+
"ENVIO_CLICKHOUSE_DATABASE_ENGINE is Replicated; enabling replicated mode so tables use the ReplicatedMergeTree engine.",
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
let databaseOnClusterClause = onClusterClause(~onCluster=replicated)
|
|
485
|
+
// DDL that a Replicated database engine propagates itself must not carry
|
|
486
|
+
// ON CLUSTER on top of it — the clause is only for the plain-database case.
|
|
487
|
+
let ddlOnCluster = replicated && !hasReplicatedDatabaseEngine
|
|
442
488
|
|
|
443
489
|
switch databaseEngine {
|
|
444
490
|
| Some(engineSpec) => {
|
|
445
|
-
let expectedEngineName = engineSpec->
|
|
491
|
+
let expectedEngineName = engineSpec->databaseEngineName
|
|
446
492
|
let existingResult = await client->query({
|
|
447
493
|
query: `SELECT engine FROM system.databases WHERE name = '${database}'`,
|
|
448
494
|
})
|
|
@@ -458,22 +504,60 @@ let initialize = async (
|
|
|
458
504
|
| None => ()
|
|
459
505
|
}
|
|
460
506
|
|
|
461
|
-
|
|
507
|
+
if hasReplicatedDatabaseEngine {
|
|
508
|
+
// TRUNCATE DATABASE is unsupported on Replicated databases, so a reset
|
|
509
|
+
// has to DROP and recreate instead (plain databases keep the TRUNCATE
|
|
510
|
+
// fallback below). This requires the ClickHouse user to hold the DROP
|
|
511
|
+
// privilege; without it the reset fails here with ACCESS_DENIED. ON
|
|
512
|
+
// CLUSTER removes the database from every node — the engine's own log
|
|
513
|
+
// can't replicate the drop of the database it lives in — and SYNC waits
|
|
514
|
+
// for the drop to finish before the CREATE below.
|
|
515
|
+
await client->exec({
|
|
516
|
+
query: `DROP DATABASE IF EXISTS ${database} ON CLUSTER '{cluster}' SYNC`,
|
|
517
|
+
})
|
|
518
|
+
} else {
|
|
519
|
+
await client->exec({
|
|
520
|
+
query: `TRUNCATE DATABASE IF EXISTS ${database}${onClusterClause(~onCluster=ddlOnCluster)}`,
|
|
521
|
+
})
|
|
522
|
+
}
|
|
462
523
|
await client->exec({
|
|
463
|
-
query: `CREATE DATABASE IF NOT EXISTS ${database}${
|
|
524
|
+
query: `CREATE DATABASE IF NOT EXISTS ${database}${databaseOnClusterClause}${databaseEngineClause}`,
|
|
464
525
|
})
|
|
465
|
-
await client->exec({query: `USE ${database}`})
|
|
466
526
|
|
|
467
527
|
await Promise.all(
|
|
468
528
|
entities->Array.map(entityConfig =>
|
|
469
|
-
client->exec({
|
|
529
|
+
client->exec({
|
|
530
|
+
query: makeCreateHistoryTableQuery(
|
|
531
|
+
~entityConfig,
|
|
532
|
+
~database,
|
|
533
|
+
~replicated,
|
|
534
|
+
~onCluster=ddlOnCluster,
|
|
535
|
+
),
|
|
536
|
+
})
|
|
470
537
|
),
|
|
471
538
|
)->Utils.Promise.ignoreValue
|
|
472
|
-
await client->exec({
|
|
539
|
+
await client->exec({
|
|
540
|
+
query: makeCreateCheckpointsTableQuery(~database, ~replicated, ~onCluster=ddlOnCluster),
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
// The client pools HTTP connections, so consecutive statements may reach
|
|
544
|
+
// different replicas, while a Replicated database applies DDL from its
|
|
545
|
+
// Keeper log asynchronously. A CREATE VIEW is analyzed against the node's
|
|
546
|
+
// local metadata and can land on a replica that hasn't applied the table
|
|
547
|
+
// creates yet, failing with UNKNOWN_TABLE. Block until every replica has
|
|
548
|
+
// caught up before creating the views. ON CLUSTER must precede the
|
|
549
|
+
// database name in this command's grammar.
|
|
550
|
+
if hasReplicatedDatabaseEngine {
|
|
551
|
+
await client->exec({
|
|
552
|
+
query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ${database}`,
|
|
553
|
+
})
|
|
554
|
+
}
|
|
473
555
|
|
|
474
556
|
await Promise.all(
|
|
475
557
|
entities->Array.map(entityConfig =>
|
|
476
|
-
client->exec({
|
|
558
|
+
client->exec({
|
|
559
|
+
query: makeCreateViewQuery(~entityConfig, ~database, ~onCluster=ddlOnCluster),
|
|
560
|
+
})
|
|
477
561
|
),
|
|
478
562
|
)->Utils.Promise.ignoreValue
|
|
479
563
|
|
|
@@ -235,8 +235,21 @@ async function setUpdatesOrThrow(client, cache, changes, entityConfig, database)
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
function
|
|
238
|
+
function onClusterClause(onCluster) {
|
|
239
|
+
if (onCluster) {
|
|
240
|
+
return ` ON CLUSTER '{cluster}'`;
|
|
241
|
+
} else {
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function databaseEngineName(engineSpec) {
|
|
247
|
+
return engineSpec.trim().split("(")[0].split(" ")[0].trim();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onClusterOpt) {
|
|
239
251
|
let replicated = replicatedOpt !== undefined ? replicatedOpt : false;
|
|
252
|
+
let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
|
|
240
253
|
let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()";
|
|
241
254
|
let fieldDefinitions = Stdlib_Array.filterMap(entityConfig.table.fields, field => {
|
|
242
255
|
if (field.TAG !== "Field") {
|
|
@@ -247,7 +260,9 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt) {
|
|
|
247
260
|
let clickHouseType = getClickHouseFieldType(field$1.fieldType, field$1.isNullable, field$1.isArray);
|
|
248
261
|
return `\`` + fieldName + `\` ` + clickHouseType;
|
|
249
262
|
});
|
|
250
|
-
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) +
|
|
263
|
+
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + `\`` + (
|
|
264
|
+
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
265
|
+
) + ` (
|
|
251
266
|
` + fieldDefinitions.join(",\n ") + `,
|
|
252
267
|
\`` + EntityHistory.checkpointIdFieldName + `\` ` + getClickHouseFieldType("UInt64", false, false) + `,
|
|
253
268
|
\`` + EntityHistory.changeFieldName + `\` ` + getClickHouseFieldType({
|
|
@@ -259,10 +274,13 @@ ENGINE = ` + tableEngine + `
|
|
|
259
274
|
ORDER BY (` + Table.idFieldName + `, ` + EntityHistory.checkpointIdFieldName + `)`;
|
|
260
275
|
}
|
|
261
276
|
|
|
262
|
-
function makeCreateCheckpointsTableQuery(database, replicatedOpt) {
|
|
277
|
+
function makeCreateCheckpointsTableQuery(database, replicatedOpt, onClusterOpt) {
|
|
263
278
|
let replicated = replicatedOpt !== undefined ? replicatedOpt : false;
|
|
279
|
+
let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
|
|
264
280
|
let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()";
|
|
265
|
-
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + InternalTable.Checkpoints.table.tableName +
|
|
281
|
+
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + InternalTable.Checkpoints.table.tableName + `\`` + (
|
|
282
|
+
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
283
|
+
) + ` (
|
|
266
284
|
\`` + "id" + `\` ` + getClickHouseFieldType("UInt64", false, false) + `,
|
|
267
285
|
\`` + "chain_id" + `\` ` + getClickHouseFieldType("Int32", false, false) + `,
|
|
268
286
|
\`` + "block_number" + `\` ` + getClickHouseFieldType("Int32", false, false) + `,
|
|
@@ -273,7 +291,8 @@ ENGINE = ` + tableEngine + `
|
|
|
273
291
|
ORDER BY (` + "id" + `)`;
|
|
274
292
|
}
|
|
275
293
|
|
|
276
|
-
function makeCreateViewQuery(entityConfig, database) {
|
|
294
|
+
function makeCreateViewQuery(entityConfig, database, onClusterOpt) {
|
|
295
|
+
let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
|
|
277
296
|
let historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
|
|
278
297
|
let checkpointsTableName = InternalTable.Checkpoints.table.tableName;
|
|
279
298
|
let entityFields = Stdlib_Array.filterMap(entityConfig.table.fields, field => {
|
|
@@ -283,7 +302,9 @@ function makeCreateViewQuery(entityConfig, database) {
|
|
|
283
302
|
let fieldName = Table.getClickHouseDbFieldName(field._0);
|
|
284
303
|
return `\`` + fieldName + `\``;
|
|
285
304
|
}).join(", ");
|
|
286
|
-
return `CREATE VIEW IF NOT EXISTS ` + database + `.\`` + entityConfig.name +
|
|
305
|
+
return `CREATE VIEW IF NOT EXISTS ` + database + `.\`` + entityConfig.name + `\`` + (
|
|
306
|
+
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
307
|
+
) + ` AS
|
|
287
308
|
SELECT ` + entityFields + `
|
|
288
309
|
FROM (
|
|
289
310
|
SELECT ` + entityFields + `, \`` + EntityHistory.changeFieldName + `\`
|
|
@@ -297,12 +318,18 @@ WHERE \`` + EntityHistory.changeFieldName + `\` = '` + "SET" + `'`;
|
|
|
297
318
|
|
|
298
319
|
async function initialize(client, database, entities, param) {
|
|
299
320
|
try {
|
|
300
|
-
let replicated = Env.ClickHouse.replicated();
|
|
301
321
|
let databaseEngine = Env.ClickHouse.databaseEngine();
|
|
302
322
|
let databaseEngineClause = databaseEngine !== undefined ? ` ENGINE = ` + databaseEngine : "";
|
|
303
|
-
let
|
|
323
|
+
let hasReplicatedDatabaseEngine = databaseEngine !== undefined ? databaseEngineName(databaseEngine) === "Replicated" : false;
|
|
324
|
+
let envReplicated = Env.ClickHouse.replicated();
|
|
325
|
+
let replicated = envReplicated || hasReplicatedDatabaseEngine;
|
|
326
|
+
if (hasReplicatedDatabaseEngine && !envReplicated) {
|
|
327
|
+
Logging.info("ENVIO_CLICKHOUSE_DATABASE_ENGINE is Replicated; enabling replicated mode so tables use the ReplicatedMergeTree engine.");
|
|
328
|
+
}
|
|
329
|
+
let databaseOnClusterClause = replicated ? ` ON CLUSTER '{cluster}'` : "";
|
|
330
|
+
let ddlOnCluster = replicated && !hasReplicatedDatabaseEngine;
|
|
304
331
|
if (databaseEngine !== undefined) {
|
|
305
|
-
let expectedEngineName = databaseEngine
|
|
332
|
+
let expectedEngineName = databaseEngineName(databaseEngine);
|
|
306
333
|
let existingResult = await client.query({
|
|
307
334
|
query: `SELECT engine FROM system.databases WHERE name = '` + database + `'`
|
|
308
335
|
});
|
|
@@ -315,23 +342,33 @@ async function initialize(client, database, entities, param) {
|
|
|
315
342
|
}
|
|
316
343
|
}
|
|
317
344
|
}
|
|
345
|
+
if (hasReplicatedDatabaseEngine) {
|
|
346
|
+
await client.exec({
|
|
347
|
+
query: `DROP DATABASE IF EXISTS ` + database + ` ON CLUSTER '{cluster}' SYNC`
|
|
348
|
+
});
|
|
349
|
+
} else {
|
|
350
|
+
await client.exec({
|
|
351
|
+
query: `TRUNCATE DATABASE IF EXISTS ` + database + (
|
|
352
|
+
ddlOnCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
353
|
+
)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
318
356
|
await client.exec({
|
|
319
|
-
query: `
|
|
320
|
-
});
|
|
321
|
-
await client.exec({
|
|
322
|
-
query: `CREATE DATABASE IF NOT EXISTS ` + database + onClusterClause + databaseEngineClause
|
|
323
|
-
});
|
|
324
|
-
await client.exec({
|
|
325
|
-
query: `USE ` + database
|
|
357
|
+
query: `CREATE DATABASE IF NOT EXISTS ` + database + databaseOnClusterClause + databaseEngineClause
|
|
326
358
|
});
|
|
327
359
|
await Promise.all(entities.map(entityConfig => client.exec({
|
|
328
|
-
query: makeCreateHistoryTableQuery(entityConfig, database, replicated)
|
|
360
|
+
query: makeCreateHistoryTableQuery(entityConfig, database, replicated, ddlOnCluster)
|
|
329
361
|
})));
|
|
330
362
|
await client.exec({
|
|
331
|
-
query: makeCreateCheckpointsTableQuery(database, replicated)
|
|
363
|
+
query: makeCreateCheckpointsTableQuery(database, replicated, ddlOnCluster)
|
|
332
364
|
});
|
|
365
|
+
if (hasReplicatedDatabaseEngine) {
|
|
366
|
+
await client.exec({
|
|
367
|
+
query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ` + database
|
|
368
|
+
});
|
|
369
|
+
}
|
|
333
370
|
await Promise.all(entities.map(entityConfig => client.exec({
|
|
334
|
-
query: makeCreateViewQuery(entityConfig, database)
|
|
371
|
+
query: makeCreateViewQuery(entityConfig, database, ddlOnCluster)
|
|
335
372
|
})));
|
|
336
373
|
return Logging.trace("ClickHouse storage initialization completed successfully");
|
|
337
374
|
} catch (raw_exn) {
|
|
@@ -382,6 +419,8 @@ export {
|
|
|
382
419
|
insertWithRetry,
|
|
383
420
|
setCheckpointsOrThrow,
|
|
384
421
|
setUpdatesOrThrow,
|
|
422
|
+
onClusterClause,
|
|
423
|
+
databaseEngineName,
|
|
385
424
|
makeCreateHistoryTableQuery,
|
|
386
425
|
makeCreateCheckpointsTableQuery,
|
|
387
426
|
makeCreateViewQuery,
|
package/src/bindings/NodeJs.res
CHANGED
|
@@ -46,6 +46,70 @@ module Process = {
|
|
|
46
46
|
@module external process: t = "process"
|
|
47
47
|
@module("process") external cwd: unit => string = "cwd"
|
|
48
48
|
@get external execPath: t => string = "execPath"
|
|
49
|
+
|
|
50
|
+
type memoryUsage = {
|
|
51
|
+
rss: float,
|
|
52
|
+
heapTotal: float,
|
|
53
|
+
heapUsed: float,
|
|
54
|
+
@as("external") external_: float,
|
|
55
|
+
arrayBuffers: float,
|
|
56
|
+
}
|
|
57
|
+
@module("process") external memoryUsage: unit => memoryUsage = "memoryUsage"
|
|
58
|
+
|
|
59
|
+
// Microseconds since process start.
|
|
60
|
+
type cpuUsage = {user: float, system: float}
|
|
61
|
+
@module("process") external cpuUsage: unit => cpuUsage = "cpuUsage"
|
|
62
|
+
|
|
63
|
+
@module("process") external uptime: unit => float = "uptime"
|
|
64
|
+
@module("process") external version: string = "version"
|
|
65
|
+
@module("process")
|
|
66
|
+
external getActiveResourcesInfo: unit => array<string> = "getActiveResourcesInfo"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module V8 = {
|
|
70
|
+
type heapSpaceStatistics = {
|
|
71
|
+
@as("space_name") spaceName: string,
|
|
72
|
+
@as("space_size") spaceSize: float,
|
|
73
|
+
@as("space_used_size") spaceUsedSize: float,
|
|
74
|
+
@as("space_available_size") spaceAvailableSize: float,
|
|
75
|
+
}
|
|
76
|
+
@module("v8")
|
|
77
|
+
external getHeapSpaceStatistics: unit => array<heapSpaceStatistics> = "getHeapSpaceStatistics"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module PerfHooks = {
|
|
81
|
+
type eventLoopUtilization = {idle: float, active: float, utilization: float}
|
|
82
|
+
type performance
|
|
83
|
+
@module("perf_hooks") external performance: performance = "performance"
|
|
84
|
+
@send external eventLoopUtilization: performance => eventLoopUtilization = "eventLoopUtilization"
|
|
85
|
+
|
|
86
|
+
// Sampled event-loop delay histogram; values are nanoseconds.
|
|
87
|
+
type intervalHistogram = {
|
|
88
|
+
mean: float,
|
|
89
|
+
min: float,
|
|
90
|
+
max: float,
|
|
91
|
+
stddev: float,
|
|
92
|
+
}
|
|
93
|
+
type monitorOptions = {resolution?: int}
|
|
94
|
+
@module("perf_hooks")
|
|
95
|
+
external monitorEventLoopDelay: (~options: monitorOptions=?) => intervalHistogram =
|
|
96
|
+
"monitorEventLoopDelay"
|
|
97
|
+
@send external enable: intervalHistogram => bool = "enable"
|
|
98
|
+
@send external reset: intervalHistogram => unit = "reset"
|
|
99
|
+
@send external percentile: (intervalHistogram, int) => float = "percentile"
|
|
100
|
+
|
|
101
|
+
type performanceEntry = {
|
|
102
|
+
duration: float, // milliseconds
|
|
103
|
+
detail: {"kind": int},
|
|
104
|
+
}
|
|
105
|
+
type observerList
|
|
106
|
+
@send external getEntries: observerList => array<performanceEntry> = "getEntries"
|
|
107
|
+
type performanceObserver
|
|
108
|
+
type observeOptions = {entryTypes: array<string>}
|
|
109
|
+
@new @module("perf_hooks")
|
|
110
|
+
external makePerformanceObserver: (observerList => unit) => performanceObserver =
|
|
111
|
+
"PerformanceObserver"
|
|
112
|
+
@send external observe: (performanceObserver, observeOptions) => unit = "observe"
|
|
49
113
|
}
|
|
50
114
|
|
|
51
115
|
module ChildProcess = {
|
|
@@ -18,6 +18,10 @@ let Util$1 = {
|
|
|
18
18
|
|
|
19
19
|
let Process = {};
|
|
20
20
|
|
|
21
|
+
let V8 = {};
|
|
22
|
+
|
|
23
|
+
let PerfHooks = {};
|
|
24
|
+
|
|
21
25
|
let ChildProcess = {};
|
|
22
26
|
|
|
23
27
|
let Url$1 = {};
|
|
@@ -45,6 +49,8 @@ let Fs = {
|
|
|
45
49
|
export {
|
|
46
50
|
Util$1 as Util,
|
|
47
51
|
Process,
|
|
52
|
+
V8,
|
|
53
|
+
PerfHooks,
|
|
48
54
|
ChildProcess,
|
|
49
55
|
Url$1 as Url,
|
|
50
56
|
ImportMeta,
|
|
@@ -269,8 +269,8 @@ module Registration = {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
let fromOnEventRegistrations = (
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
onEventRegistrations: array<Internal.evmOnEventRegistration>,
|
|
273
|
+
): array<input> => {
|
|
274
274
|
onEventRegistrations->Array.map(reg => {
|
|
275
275
|
let event = reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.evmEventConfig)
|
|
276
276
|
{
|
|
@@ -282,9 +282,7 @@ module Registration = {
|
|
|
282
282
|
isWildcard: reg.isWildcard,
|
|
283
283
|
dependsOnAddresses: reg.dependsOnAddresses,
|
|
284
284
|
params: event.paramsMetadata,
|
|
285
|
-
topicSelections: reg.resolvedWhere.topicSelections->Array.map((
|
|
286
|
-
ts
|
|
287
|
-
): topicSelectionInput => {
|
|
285
|
+
topicSelections: reg.resolvedWhere.topicSelections->Array.map((ts): topicSelectionInput => {
|
|
288
286
|
topic0: ts.topic0->EvmTypes.Hex.toStrings,
|
|
289
287
|
topic1: ts.topic1->toTopicFilterInput,
|
|
290
288
|
topic2: ts.topic2->toTopicFilterInput,
|
|
@@ -360,12 +358,8 @@ type t = {
|
|
|
360
358
|
}
|
|
361
359
|
|
|
362
360
|
@send
|
|
363
|
-
external classNew: (
|
|
364
|
-
|
|
365
|
-
cfg,
|
|
366
|
-
string,
|
|
367
|
-
array<Registration.input>,
|
|
368
|
-
) => t = "new"
|
|
361
|
+
external classNew: (Core.evmHypersyncClientCtor, cfg, string, array<Registration.input>) => t =
|
|
362
|
+
"new"
|
|
369
363
|
|
|
370
364
|
let makeWithAgent = (cfg, ~userAgent, ~eventRegistrations) =>
|
|
371
365
|
Core.getAddon().evmHypersyncClient->classNew(cfg, userAgent, eventRegistrations)
|