diff --git a/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.test.cpp b/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.test.cpp index b905aafe1a3..cb4d342b09a 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.test.cpp +++ b/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.test.cpp @@ -254,4 +254,4 @@ TEST_F(LMDBTreeStoreTest, can_write_and_read_leaves_by_hash) success = store.read_leaf_by_hash(VALUES[9], readBack, *transaction); EXPECT_FALSE(success); } -} \ No newline at end of file +} diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 1cb3d874d7e..9dc079a4cf0 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -272,6 +272,9 @@ export class Archiver implements ArchiveSource { // the chain locally before we start unwinding stuff. This can be optimized by figuring out // up to which point we're pruning, and then requesting L2 blocks up to that point only. await this.handleEpochPrune(provenBlockNumber, currentL1BlockNumber); + + const storeSizes = this.store.estimateSize(); + this.instrumentation.recordDBMetrics(storeSizes); } } @@ -1040,6 +1043,9 @@ class ArchiverStoreHelper getTotalL1ToL2MessageCount(): Promise { return this.store.getTotalL1ToL2MessageCount(); } + estimateSize(): { mappingSize: number; actualSize: number; numItems: number } { + return this.store.estimateSize(); + } } type L1RollupConstants = { diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index 12a6d0e1f96..c7be4ddbd13 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -268,4 +268,9 @@ export interface ArchiverDataStore { addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise; getContractArtifact(address: AztecAddress): Promise; + + /** + * Estimates the size of the store in bytes. + */ + estimateSize(): { mappingSize: number; actualSize: number; numItems: number }; } diff --git a/yarn-project/archiver/src/archiver/instrumentation.ts b/yarn-project/archiver/src/archiver/instrumentation.ts index 6a53027f460..1d6343b8f9d 100644 --- a/yarn-project/archiver/src/archiver/instrumentation.ts +++ b/yarn-project/archiver/src/archiver/instrumentation.ts @@ -4,6 +4,7 @@ import { Attributes, type Gauge, type Histogram, + LmdbMetrics, Metrics, type TelemetryClient, type UpDownCounter, @@ -18,6 +19,7 @@ export class ArchiverInstrumentation { private syncDuration: Histogram; private proofsSubmittedDelay: Histogram; private proofsSubmittedCount: UpDownCounter; + private dbMetrics: LmdbMetrics; private log = createDebugLogger('aztec:archiver:instrumentation'); @@ -55,6 +57,26 @@ export class ArchiverInstrumentation { explicitBucketBoundaries: millisecondBuckets(1, 80), // 10ms -> ~3hs }, }); + + this.dbMetrics = new LmdbMetrics( + meter, + { + name: Metrics.ARCHIVER_DB_MAP_SIZE, + description: 'Database map size for the archiver', + }, + { + name: Metrics.ARCHIVER_DB_USED_SIZE, + description: 'Database used size for the archiver', + }, + { + name: Metrics.ARCHIVER_DB_NUM_ITEMS, + description: 'Num items in the archiver database', + }, + ); + } + + public recordDBMetrics(metrics: { mappingSize: number; numItems: number; actualSize: number }) { + this.dbMetrics.recordDBMetrics(metrics); } public isEnabled(): boolean { diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts index dcbf3d3691e..134bbf1c4f7 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts @@ -49,7 +49,7 @@ export class KVArchiverDataStore implements ArchiverDataStore { #log = createDebugLogger('aztec:archiver:data-store'); - constructor(db: AztecKVStore, logsMaxPageSize: number = 1000) { + constructor(private db: AztecKVStore, logsMaxPageSize: number = 1000) { this.#blockStore = new BlockStore(db); this.#logStore = new LogStore(db, this.#blockStore, logsMaxPageSize); this.#messageStore = new MessageStore(db); @@ -344,4 +344,8 @@ export class KVArchiverDataStore implements ArchiverDataStore { messagesSynchedTo: this.#messageStore.getSynchedL1BlockNumber(), }); } + + public estimateSize(): { mappingSize: number; actualSize: number; numItems: number } { + return this.db.estimateSize(); + } } diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts index 33d8a09128d..f103343da35 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts @@ -736,4 +736,8 @@ export class MemoryArchiverStore implements ArchiverDataStore { public getContractArtifact(address: AztecAddress): Promise { return Promise.resolve(this.contractArtifacts.get(address.toString())); } + + public estimateSize(): { mappingSize: number; actualSize: number; numItems: number } { + return { mappingSize: 0, actualSize: 0, numItems: 0 }; + } } diff --git a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts index 7b47386c6ac..d2775d2eda4 100644 --- a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts @@ -146,7 +146,12 @@ describe('L1Publisher integration', () => { worldStateProvenBlocksOnly: false, worldStateDbMapSizeKb: 10 * 1024 * 1024, }; - worldStateSynchronizer = new ServerWorldStateSynchronizer(builderDb, blockSource, worldStateConfig); + worldStateSynchronizer = new ServerWorldStateSynchronizer( + builderDb, + blockSource, + worldStateConfig, + new NoopTelemetryClient(), + ); await worldStateSynchronizer.start(); fork = await worldStateSynchronizer.fork(); builder = new LightweightBlockBuilder(fork, new NoopTelemetryClient()); diff --git a/yarn-project/kv-store/src/interfaces/store.ts b/yarn-project/kv-store/src/interfaces/store.ts index df37d45e0a6..9764a474546 100644 --- a/yarn-project/kv-store/src/interfaces/store.ts +++ b/yarn-project/kv-store/src/interfaces/store.ts @@ -72,5 +72,5 @@ export interface AztecKVStore { /** * Estimates the size of the store in bytes. */ - estimateSize(): { bytes: number }; + estimateSize(): { mappingSize: number; actualSize: number; numItems: number }; } diff --git a/yarn-project/kv-store/src/lmdb/store.ts b/yarn-project/kv-store/src/lmdb/store.ts index 031cb554c0f..3e43972f088 100644 --- a/yarn-project/kv-store/src/lmdb/store.ts +++ b/yarn-project/kv-store/src/lmdb/store.ts @@ -182,15 +182,52 @@ export class AztecLmdbStore implements AztecKVStore { } } - estimateSize(): { bytes: number } { + estimateSize(): { mappingSize: number; actualSize: number; numItems: number } { const stats = this.#rootDb.getStats(); - // `mapSize` represents to total amount of memory currently being used by the database. - // since the database is mmap'd, this is a good estimate of the size of the database for now. + // The 'mapSize' is the total amount of virtual address space allocated to the DB (effectively the maximum possible size) // http://www.lmdb.tech/doc/group__mdb.html#a4bde3c8b676457342cba2fe27aed5fbd + let mapSize = 0; if ('mapSize' in stats && typeof stats.mapSize === 'number') { - return { bytes: stats.mapSize }; - } else { - return { bytes: 0 }; + mapSize = stats.mapSize; } + const dataResult = this.estimateSubDBSize(this.#data); + const multiResult = this.estimateSubDBSize(this.#multiMapData); + return { + mappingSize: mapSize, + actualSize: dataResult.actualSize + multiResult.actualSize, + numItems: dataResult.numItems + multiResult.numItems, + }; + } + + private estimateSubDBSize(db: Database): { actualSize: number; numItems: number } { + const stats = db.getStats(); + let branchPages = 0; + let leafPages = 0; + let overflowPages = 0; + let pageSize = 0; + let totalSize = 0; + let numItems = 0; + // This is the total number of key/value pairs present in the DB + if ('entryCount' in stats && typeof stats.entryCount === 'number') { + numItems = stats.entryCount; + } + // The closest value we can get to the actual size of the database is the number of consumed pages * the page size + if ( + 'treeBranchPageCount' in stats && + typeof stats.treeBranchPageCount === 'number' && + 'treeLeafPageCount' in stats && + typeof stats.treeLeafPageCount === 'number' && + 'overflowPages' in stats && + typeof stats.overflowPages === 'number' && + 'pageSize' in stats && + typeof stats.pageSize === 'number' + ) { + branchPages = stats.treeBranchPageCount; + leafPages = stats.treeLeafPageCount; + overflowPages = stats.overflowPages; + pageSize = stats.pageSize; + totalSize = (branchPages + leafPages + overflowPages) * pageSize; + } + return { actualSize: totalSize, numItems }; } } diff --git a/yarn-project/p2p/src/mem_pools/instrumentation.ts b/yarn-project/p2p/src/mem_pools/instrumentation.ts index 102235a406e..8f335b149fb 100644 --- a/yarn-project/p2p/src/mem_pools/instrumentation.ts +++ b/yarn-project/p2p/src/mem_pools/instrumentation.ts @@ -1,5 +1,12 @@ import { type Gossipable } from '@aztec/circuit-types'; -import { Attributes, type Histogram, Metrics, type TelemetryClient, type UpDownCounter } from '@aztec/telemetry-client'; +import { + Attributes, + type Histogram, + LmdbMetrics, + Metrics, + type TelemetryClient, + type UpDownCounter, +} from '@aztec/telemetry-client'; /** * Instrumentation class for the Pools (TxPool, AttestationPool, etc). @@ -10,6 +17,8 @@ export class PoolInstrumentation { /** Tracks tx size */ private objectSize: Histogram; + private dbMetrics: LmdbMetrics; + private defaultAttributes; constructor(telemetry: TelemetryClient, name: string) { @@ -35,6 +44,26 @@ export class PoolInstrumentation { ], }, }); + + this.dbMetrics = new LmdbMetrics( + meter, + { + name: Metrics.MEMPOOL_DB_MAP_SIZE, + description: 'Database map size for the Tx mempool', + }, + { + name: Metrics.MEMPOOL_DB_USED_SIZE, + description: 'Database used size for the Tx mempool', + }, + { + name: Metrics.MEMPOOL_DB_NUM_ITEMS, + description: 'Num items in database for the Tx mempool', + }, + ); + } + + public recordDBMetrics(metrics: { mappingSize: number; numItems: number; actualSize: number }) { + this.dbMetrics.recordDBMetrics(metrics); } public recordSize(poolObject: PoolObject) { diff --git a/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts b/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts index 04d931c4240..9a937284697 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts @@ -53,6 +53,8 @@ export class AztecKVTxPool implements TxPool { } this.#metrics.recordRemovedObjects(deleted, 'pending'); this.#metrics.recordAddedObjects(txHashes.length, 'mined'); + const storeSizes = this.#store.estimateSize(); + this.#metrics.recordDBMetrics(storeSizes); }); } diff --git a/yarn-project/telemetry-client/src/index.ts b/yarn-project/telemetry-client/src/index.ts index 962f158dcca..ce7d17939bf 100644 --- a/yarn-project/telemetry-client/src/index.ts +++ b/yarn-project/telemetry-client/src/index.ts @@ -2,3 +2,4 @@ export * from './telemetry.js'; export * from './histogram_utils.js'; export * from './with_tracer.js'; export * from './prom_otel_adapter.js'; +export * from './lmdb_metrics.js'; diff --git a/yarn-project/telemetry-client/src/lmdb_metrics.ts b/yarn-project/telemetry-client/src/lmdb_metrics.ts new file mode 100644 index 00000000000..c8efc91a801 --- /dev/null +++ b/yarn-project/telemetry-client/src/lmdb_metrics.ts @@ -0,0 +1,38 @@ +import { type Gauge, type Meter, type Metrics, ValueType } from './telemetry.js'; + +export type LmdbMetricDescriptor = { + name: Metrics; + description: string; +}; + +export class LmdbMetrics { + private dbMapSize: Gauge; + private dbUsedSize: Gauge; + private dbNumItems: Gauge; + + constructor( + meter: Meter, + dbMapSizeDescriptor: LmdbMetricDescriptor, + dbUsedSizeDescriptor: LmdbMetricDescriptor, + dbNumItemsDescriptor: LmdbMetricDescriptor, + ) { + this.dbMapSize = meter.createGauge(dbMapSizeDescriptor.name, { + description: dbMapSizeDescriptor.description, + valueType: ValueType.INT, + }); + this.dbUsedSize = meter.createGauge(dbUsedSizeDescriptor.name, { + description: dbUsedSizeDescriptor.description, + valueType: ValueType.INT, + }); + this.dbNumItems = meter.createGauge(dbNumItemsDescriptor.name, { + description: dbNumItemsDescriptor.description, + valueType: ValueType.INT, + }); + } + + public recordDBMetrics(metrics: { mappingSize: number; numItems: number; actualSize: number }) { + this.dbMapSize.record(metrics.mappingSize); + this.dbNumItems.record(metrics.actualSize); + this.dbUsedSize.record(metrics.actualSize); + } +} diff --git a/yarn-project/telemetry-client/src/metrics.ts b/yarn-project/telemetry-client/src/metrics.ts index 0d66d0cb8da..6e097754e03 100644 --- a/yarn-project/telemetry-client/src/metrics.ts +++ b/yarn-project/telemetry-client/src/metrics.ts @@ -25,12 +25,18 @@ export const CIRCUIT_SIZE = 'aztec.circuit.size'; export const MEMPOOL_TX_COUNT = 'aztec.mempool.tx_count'; export const MEMPOOL_TX_SIZE = 'aztec.mempool.tx_size'; +export const MEMPOOL_DB_NUM_ITEMS = 'aztec.mempool.db.num_items'; +export const MEMPOOL_DB_MAP_SIZE = 'aztec.mempool.db.map_size'; +export const MEMPOOL_DB_USED_SIZE = 'aztec.mempool.db.used_size'; export const ARCHIVER_SYNC_DURATION = 'aztec.archiver.sync_duration'; export const ARCHIVER_BLOCK_HEIGHT = 'aztec.archiver.block_height'; export const ARCHIVER_BLOCK_SIZE = 'aztec.archiver.block_size'; export const ARCHIVER_ROLLUP_PROOF_DELAY = 'aztec.archiver.rollup_proof_delay'; export const ARCHIVER_ROLLUP_PROOF_COUNT = 'aztec.archiver.rollup_proof_count'; +export const ARCHIVER_DB_NUM_ITEMS = 'aztec.archiver.db.num_items'; +export const ARCHIVER_DB_MAP_SIZE = 'aztec.archiver.db.map_size'; +export const ARCHIVER_DB_USED_SIZE = 'aztec.archiver.db.used_size'; export const NODE_RECEIVE_TX_DURATION = 'aztec.node.receive_tx.duration'; export const NODE_RECEIVE_TX_COUNT = 'aztec.node.receive_tx.count'; @@ -73,6 +79,92 @@ export const WORLD_STATE_SYNC_DURATION = 'aztec.world_state.sync.duration'; export const WORLD_STATE_MERKLE_TREE_SIZE = 'aztec.world_state.merkle_tree_size'; export const WORLD_STATE_DB_SIZE = 'aztec.world_state.db_size'; +export const WORLD_STATE_DB_MAP_SIZE_NULLIFIER = 'aztec.world_state.db_map_size.nullifier'; +export const WORLD_STATE_DB_MAP_SIZE_PUBLIC_DATA = 'aztec.world_state.db_map_size.public_data'; +export const WORLD_STATE_DB_MAP_SIZE_ARCHIVE = 'aztec.world_state.db_map_size.archive'; +export const WORLD_STATE_DB_MAP_SIZE_MESSAGE = 'aztec.world_state.db_map_size.message'; +export const WORLD_STATE_DB_MAP_SIZE_NOTE_HASH = 'aztec.world_state.db_map_size.note_hash'; + +export const WORLD_STATE_TREE_SIZE_NULLIFIER = 'aztec.world_state.tree_size.nullifier'; +export const WORLD_STATE_TREE_SIZE_PUBLIC_DATA = 'aztec.world_state.tree_size.public_data'; +export const WORLD_STATE_TREE_SIZE_ARCHIVE = 'aztec.world_state.tree_size.archive'; +export const WORLD_STATE_TREE_SIZE_MESSAGE = 'aztec.world_state.tree_size.message'; +export const WORLD_STATE_TREE_SIZE_NOTE_HASH = 'aztec.world_state.tree_size.note_hash'; + +export const WORLD_STATE_UNFINALISED_HEIGHT_NULLIFIER = 'aztec.world_state.unfinalised_height.nullifier'; +export const WORLD_STATE_UNFINALISED_HEIGHT_PUBLIC_DATA = 'aztec.world_state.unfinalised_height.public_data'; +export const WORLD_STATE_UNFINALISED_HEIGHT_ARCHIVE = 'aztec.world_state.unfinalised_height.archive'; +export const WORLD_STATE_UNFINALISED_HEIGHT_MESSAGE = 'aztec.world_state.unfinalised_height.message'; +export const WORLD_STATE_UNFINALISED_HEIGHT_NOTE_HASH = 'aztec.world_state.unfinalised_height.note_hash'; + +export const WORLD_STATE_FINALISED_HEIGHT_NULLIFIER = 'aztec.world_state.finalised_height.nullifier'; +export const WORLD_STATE_FINALISED_HEIGHT_PUBLIC_DATA = 'aztec.world_state.finalised_height.public_data'; +export const WORLD_STATE_FINALISED_HEIGHT_ARCHIVE = 'aztec.world_state.finalised_height.archive'; +export const WORLD_STATE_FINALISED_HEIGHT_MESSAGE = 'aztec.world_state.finalised_height.message'; +export const WORLD_STATE_FINALISED_HEIGHT_NOTE_HASH = 'aztec.world_state.finalised_height.note_hash'; + +export const WORLD_STATE_OLDEST_BLOCK_NULLIFIER = 'aztec.world_state.oldest_block.nullifier'; +export const WORLD_STATE_OLDEST_BLOCK_PUBLIC_DATA = 'aztec.world_state.oldest_block.public_data'; +export const WORLD_STATE_OLDEST_BLOCK_ARCHIVE = 'aztec.world_state.oldest_block.archive'; +export const WORLD_STATE_OLDEST_BLOCK_MESSAGE = 'aztec.world_state.oldest_block.message'; +export const WORLD_STATE_OLDEST_BLOCK_NOTE_HASH = 'aztec.world_state.oldest_block.note_hash'; + +export const WORLD_STATE_BLOCKS_DB_USED_SIZE_NULLIFIER = 'aztec.world_state.db_used_size.blocks.nullifier'; +export const WORLD_STATE_BLOCKS_DB_USED_SIZE_PUBLIC_DATA = 'aztec.world_state.db_used_size.blocks.public_data'; +export const WORLD_STATE_BLOCKS_DB_USED_SIZE_ARCHIVE = 'aztec.world_state.db_used_size.blocks.archive'; +export const WORLD_STATE_BLOCKS_DB_USED_SIZE_MESSAGE = 'aztec.world_state.db_used_size.blocks.message'; +export const WORLD_STATE_BLOCKS_DB_USED_SIZE_NOTE_HASH = 'aztec.world_state.db_used_size.blocks.note_hash'; + +export const WORLD_STATE_BLOCKS_DB_NUM_ITEMS_NULLIFIER = 'aztec.world_state.db_num_items.blocks.nullifier'; +export const WORLD_STATE_BLOCKS_DB_NUM_ITEMS_PUBLIC_DATA = 'aztec.world_state.db_num_items.blocks.public_data'; +export const WORLD_STATE_BLOCKS_DB_NUM_ITEMS_ARCHIVE = 'aztec.world_state.db_num_items.blocks.archive'; +export const WORLD_STATE_BLOCKS_DB_NUM_ITEMS_MESSAGE = 'aztec.world_state.db_num_items.blocks.message'; +export const WORLD_STATE_BLOCKS_DB_NUM_ITEMS_NOTE_HASH = 'aztec.world_state.db_num_items.blocks.note_hash'; + +export const WORLD_STATE_NODES_DB_USED_SIZE_NULLIFIER = 'aztec.world_state.db_used_size.nodes.nullifier'; +export const WORLD_STATE_NODES_DB_USED_SIZE_PUBLIC_DATA = 'aztec.world_state.db_used_size.nodes.public_data'; +export const WORLD_STATE_NODES_DB_USED_SIZE_ARCHIVE = 'aztec.world_state.db_used_size.nodes.archive'; +export const WORLD_STATE_NODES_DB_USED_SIZE_MESSAGE = 'aztec.world_state.db_used_size.nodes.message'; +export const WORLD_STATE_NODES_DB_USED_SIZE_NOTE_HASH = 'aztec.world_state.db_used_size.nodes.note_hash'; + +export const WORLD_STATE_NODES_DB_NUM_ITEMS_NULLIFIER = 'aztec.world_state.db_num_items.nodes.nullifier'; +export const WORLD_STATE_NODES_DB_NUM_ITEMS_PUBLIC_DATA = 'aztec.world_state.db_num_items.nodes.public_data'; +export const WORLD_STATE_NODES_DB_NUM_ITEMS_ARCHIVE = 'aztec.world_state.db_num_items.nodes.archive'; +export const WORLD_STATE_NODES_DB_NUM_ITEMS_MESSAGE = 'aztec.world_state.db_num_items.nodes.message'; +export const WORLD_STATE_NODES_DB_NUM_ITEMS_NOTE_HASH = 'aztec.world_state.db_num_items.nodes.note_hash'; + +export const WORLD_STATE_LEAF_PREIMAGE_DB_USED_SIZE_NULLIFIER = + 'aztec.world_state.db_used_size.leaf_preimage.nullifier'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_USED_SIZE_PUBLIC_DATA = + 'aztec.world_state.db_used_size.leaf_preimage.public_data'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_USED_SIZE_ARCHIVE = 'aztec.world_state.db_used_size.leaf_preimage.archive'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_USED_SIZE_MESSAGE = 'aztec.world_state.db_used_size.leaf_preimage.message'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_USED_SIZE_NOTE_HASH = + 'aztec.world_state.db_used_size.leaf_preimage.note_hash'; + +export const WORLD_STATE_LEAF_PREIMAGE_DB_NUM_ITEMS_NULLIFIER = + 'aztec.world_state.db_num_items.leaf_preimage.nullifier'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_NUM_ITEMS_PUBLIC_DATA = + 'aztec.world_state.db_num_items.leaf_preimage.public_data'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_NUM_ITEMS_ARCHIVE = 'aztec.world_state.db_num_items.leaf_preimage.archive'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_NUM_ITEMS_MESSAGE = 'aztec.world_state.db_num_items.leaf_preimage.message'; +export const WORLD_STATE_LEAF_PREIMAGE_DB_NUM_ITEMS_NOTE_HASH = + 'aztec.world_state.db_num_items.leaf_preimage.note_hash'; + +export const WORLD_STATE_LEAF_INDICES_DB_USED_SIZE_NULLIFIER = 'aztec.world_state.db_used_size.leaf_indices.nullifier'; +export const WORLD_STATE_LEAF_INDICES_DB_USED_SIZE_PUBLIC_DATA = + 'aztec.world_state.db_used_size.leaf_indices.public_data'; +export const WORLD_STATE_LEAF_INDICES_DB_USED_SIZE_ARCHIVE = 'aztec.world_state.db_used_size.leaf_indices.archive'; +export const WORLD_STATE_LEAF_INDICES_DB_USED_SIZE_MESSAGE = 'aztec.world_state.db_used_size.leaf_indices.message'; +export const WORLD_STATE_LEAF_INDICES_DB_USED_SIZE_NOTE_HASH = 'aztec.world_state.db_used_size.leaf_indices.note_hash'; + +export const WORLD_STATE_LEAF_INDICES_DB_NUM_ITEMS_NULLIFIER = 'aztec.world_state.db_num_items.leaf_indices.nullifier'; +export const WORLD_STATE_LEAF_INDICES_DB_NUM_ITEMS_PUBLIC_DATA = + 'aztec.world_state.db_num_items.leaf_indices.public_data'; +export const WORLD_STATE_LEAF_INDICES_DB_NUM_ITEMS_ARCHIVE = 'aztec.world_state.db_num_items.leaf_indices.archive'; +export const WORLD_STATE_LEAF_INDICES_DB_NUM_ITEMS_MESSAGE = 'aztec.world_state.db_num_items.leaf_indices.message'; +export const WORLD_STATE_LEAF_INDICES_DB_NUM_ITEMS_NOTE_HASH = 'aztec.world_state.db_num_items.leaf_indices.note_hash'; + export const PROOF_VERIFIER_COUNT = 'aztec.proof_verifier.count'; export const VALIDATOR_RE_EXECUTION_TIME = 'aztec.validator.re_execution_time'; diff --git a/yarn-project/world-state/src/synchronizer/factory.ts b/yarn-project/world-state/src/synchronizer/factory.ts index fa2a7c43ef4..3aff058e66e 100644 --- a/yarn-project/world-state/src/synchronizer/factory.ts +++ b/yarn-project/world-state/src/synchronizer/factory.ts @@ -16,7 +16,7 @@ export async function createWorldStateSynchronizer( client: TelemetryClient, ) { const merkleTrees = await createWorldState(config, client); - return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config); + return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, client); } export async function createWorldState( diff --git a/yarn-project/world-state/src/synchronizer/instrumentation.ts b/yarn-project/world-state/src/synchronizer/instrumentation.ts new file mode 100644 index 00000000000..d52dca0aef4 --- /dev/null +++ b/yarn-project/world-state/src/synchronizer/instrumentation.ts @@ -0,0 +1,150 @@ +import { MerkleTreeId } from '@aztec/circuit-types'; +import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; +import { type Gauge, type Meter, type TelemetryClient, ValueType } from '@aztec/telemetry-client'; + +import { type DBStats, type TreeDBStats, type TreeMeta, type WorldStateStatusFull } from '../native/message.js'; + +type TreeTypeString = 'nullifier' | 'note_hash' | 'archive' | 'message' | 'public_data'; +type DBTypeString = 'leaf_preimage' | 'leaf_indices' | 'nodes' | 'blocks'; + +class TreeDBInstrumentation { + private dbNumItems: Gauge; + private dbUsedSize: Gauge; + + constructor(meter: Meter, treeName: TreeTypeString, dbName: DBTypeString) { + this.dbUsedSize = meter.createGauge(`aztec.world_state.db_used_size.${dbName}.${treeName}`, { + description: `The current used database size for the ${treeName} tree ${dbName} database`, + valueType: ValueType.INT, + }); + + this.dbNumItems = meter.createGauge(`aztec.world_state.db_num_items.${dbName}.${treeName}`, { + description: `The current number of items in the ${treeName} tree ${dbName} database`, + valueType: ValueType.INT, + }); + } + + public updateMetrics(treeDbStats: DBStats) { + this.dbNumItems.record(Number(treeDbStats.numDataItems)); + this.dbUsedSize.record(Number(treeDbStats.totalUsedSize)); + } +} + +class TreeInstrumentation { + private treeDbInstrumentation: Map = new Map< + DBTypeString, + TreeDBInstrumentation + >(); + private dbMapSize: Gauge; + private treeSize: Gauge; + private unfinalisedHeight: Gauge; + private finalisedHeight: Gauge; + private oldestBlock: Gauge; + + constructor(meter: Meter, treeName: TreeTypeString, private log: DebugLogger) { + this.dbMapSize = meter.createGauge(`aztec.world_state.db_map_size.${treeName}`, { + description: `The current configured map size for the ${treeName} tree`, + valueType: ValueType.INT, + }); + + this.treeSize = meter.createGauge(`aztec.world_state.tree_size.${treeName}`, { + description: `The current number of leaves in the ${treeName} tree`, + valueType: ValueType.INT, + }); + + this.unfinalisedHeight = meter.createGauge(`aztec.world_state.unfinalised_height.${treeName}`, { + description: `The unfinalised block height of the ${treeName} tree`, + valueType: ValueType.INT, + }); + + this.finalisedHeight = meter.createGauge(`aztec.world_state.finalised_height.${treeName}`, { + description: `The finalised block height of the ${treeName} tree`, + valueType: ValueType.INT, + }); + + this.oldestBlock = meter.createGauge(`aztec.world_state.oldest_block.${treeName}`, { + description: `The oldest historical block of the ${treeName} tree`, + valueType: ValueType.INT, + }); + + this.treeDbInstrumentation.set('blocks', new TreeDBInstrumentation(meter, treeName, 'blocks')); + this.treeDbInstrumentation.set('nodes', new TreeDBInstrumentation(meter, treeName, 'nodes')); + this.treeDbInstrumentation.set('leaf_preimage', new TreeDBInstrumentation(meter, treeName, 'leaf_preimage')); + this.treeDbInstrumentation.set('leaf_indices', new TreeDBInstrumentation(meter, treeName, 'leaf_indices')); + } + + private updateDBMetrics(dbName: DBTypeString, dbStats: DBStats) { + const inst = this.treeDbInstrumentation.get(dbName); + if (!inst) { + this.log.error(`Failed to find instrumentation for ${dbName}`); + return; + } + inst.updateMetrics(dbStats); + } + + public updateMetrics(treeDbStats: TreeDBStats, treeMeta: TreeMeta) { + this.dbMapSize.record(Number(treeDbStats.mapSize)); + this.treeSize.record(Number(treeMeta.committedSize)); + this.finalisedHeight.record(Number(treeMeta.finalisedBlockHeight)); + this.unfinalisedHeight.record(Number(treeMeta.unfinalisedBlockHeight)); + this.oldestBlock.record(Number(treeMeta.oldestHistoricBlock)); + + this.updateDBMetrics('leaf_indices', treeDbStats.leafIndicesDBStats); + this.updateDBMetrics('leaf_preimage', treeDbStats.leafPreimagesDBStats); + this.updateDBMetrics('blocks', treeDbStats.blocksDBStats); + this.updateDBMetrics('nodes', treeDbStats.nodesDBStats); + } +} + +export class WorldStateInstrumentation { + private treeInstrumentation: Map = new Map(); + + constructor(telemetry: TelemetryClient, private log = createDebugLogger('aztec:world-state:instrumentation')) { + const meter = telemetry.getMeter('World State'); + this.treeInstrumentation.set(MerkleTreeId.ARCHIVE, new TreeInstrumentation(meter, 'archive', log)); + this.treeInstrumentation.set(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, new TreeInstrumentation(meter, 'message', log)); + this.treeInstrumentation.set(MerkleTreeId.NOTE_HASH_TREE, new TreeInstrumentation(meter, 'note_hash', log)); + this.treeInstrumentation.set(MerkleTreeId.NULLIFIER_TREE, new TreeInstrumentation(meter, 'nullifier', log)); + this.treeInstrumentation.set(MerkleTreeId.PUBLIC_DATA_TREE, new TreeInstrumentation(meter, 'public_data', log)); + } + + private updateTreeStats(treeDbStats: TreeDBStats, treeMeta: TreeMeta, tree: MerkleTreeId) { + const instrumentation = this.treeInstrumentation.get(tree); + if (!instrumentation) { + this.log.error(`Failed to retrieve instrumentation for tree ${MerkleTreeId[tree]}`); + return; + } + instrumentation.updateMetrics(treeDbStats, treeMeta); + } + + public updateWorldStateMetrics(worldStateStatus: WorldStateStatusFull) { + this.updateTreeStats( + worldStateStatus.dbStats.archiveTreeStats, + worldStateStatus.meta.archiveTreeMeta, + MerkleTreeId.ARCHIVE, + ); + + this.updateTreeStats( + worldStateStatus.dbStats.messageTreeStats, + worldStateStatus.meta.messageTreeMeta, + MerkleTreeId.L1_TO_L2_MESSAGE_TREE, + ); + + this.updateTreeStats( + worldStateStatus.dbStats.noteHashTreeStats, + worldStateStatus.meta.noteHashTreeMeta, + MerkleTreeId.NOTE_HASH_TREE, + ); + + this.updateTreeStats( + worldStateStatus.dbStats.nullifierTreeStats, + worldStateStatus.meta.nullifierTreeMeta, + MerkleTreeId.NULLIFIER_TREE, + ); + + this.updateTreeStats( + worldStateStatus.dbStats.publicDataTreeStats, + worldStateStatus.meta.publicDataTreeMeta, + MerkleTreeId.PUBLIC_DATA_TREE, + ); + } +} diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts index a8e0a3098bb..2cdcd42e1e1 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts @@ -12,6 +12,7 @@ import { times } from '@aztec/foundation/collection'; import { randomInt } from '@aztec/foundation/crypto'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { SHA256Trunc } from '@aztec/merkle-tree'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -211,7 +212,7 @@ class TestWorldStateSynchronizer extends ServerWorldStateSynchronizer { worldStateConfig: WorldStateConfig, private mockBlockStream: L2BlockStream, ) { - super(merkleTrees, blockAndMessagesSource, worldStateConfig); + super(merkleTrees, blockAndMessagesSource, worldStateConfig, new NoopTelemetryClient()); } protected override createBlockStream(): L2BlockStream { diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts index 1678b22e41a..7f5d67a3aa8 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts @@ -23,10 +23,12 @@ import { createDebugLogger } from '@aztec/foundation/log'; import { promiseWithResolvers } from '@aztec/foundation/promise'; import { elapsed } from '@aztec/foundation/timer'; import { SHA256Trunc } from '@aztec/merkle-tree'; +import { type TelemetryClient } from '@aztec/telemetry-client'; -import { type WorldStateStatusSummary } from '../native/message.js'; +import { type WorldStateStatusFull } from '../native/message.js'; import { type MerkleTreeAdminDatabase } from '../world-state-db/merkle_tree_db.js'; import { type WorldStateConfig } from './config.js'; +import { WorldStateInstrumentation } from './instrumentation.js'; /** * Synchronizes the world state with the L2 blocks from a L2BlockSource via a block stream. @@ -43,13 +45,16 @@ export class ServerWorldStateSynchronizer private syncPromise = promiseWithResolvers(); protected blockStream: L2BlockStream | undefined; + private instrumentation: WorldStateInstrumentation; constructor( private readonly merkleTreeDb: MerkleTreeAdminDatabase, private readonly l2BlockSource: L2BlockSource & L1ToL2MessageSource, private readonly config: WorldStateConfig, + telemetry: TelemetryClient, private readonly log = createDebugLogger('aztec:world_state'), ) { + this.instrumentation = new WorldStateInstrumentation(telemetry); this.merkleTreeCommitted = this.merkleTreeDb.getCommitted(); } @@ -205,18 +210,24 @@ export class ServerWorldStateSynchronizer this.log.verbose(`Handling new L2 blocks from ${l2Blocks[0].number} to ${l2Blocks[l2Blocks.length - 1].number}`); const messagePromises = l2Blocks.map(block => this.l2BlockSource.getL1ToL2Messages(BigInt(block.number))); const l1ToL2Messages: Fr[][] = await Promise.all(messagePromises); + let updateStatus: WorldStateStatusFull | undefined = undefined; for (let i = 0; i < l2Blocks.length; i++) { const [duration, result] = await elapsed(() => this.handleL2Block(l2Blocks[i], l1ToL2Messages[i])); this.log.verbose(`Handled new L2 block`, { eventName: 'l2-block-handled', duration, - unfinalisedBlockNumber: result.unfinalisedBlockNumber, - finalisedBlockNumber: result.finalisedBlockNumber, - oldestHistoricBlock: result.oldestHistoricalBlock, + unfinalisedBlockNumber: result.summary.unfinalisedBlockNumber, + finalisedBlockNumber: result.summary.finalisedBlockNumber, + oldestHistoricBlock: result.summary.oldestHistoricalBlock, ...l2Blocks[i].getStats(), } satisfies L2BlockHandledStats); + updateStatus = result; } + if (!updateStatus) { + return; + } + this.instrumentation.updateWorldStateMetrics(updateStatus); } /** @@ -225,7 +236,7 @@ export class ServerWorldStateSynchronizer * @param l1ToL2Messages - The L1 to L2 messages for the block. * @returns Whether the block handled was produced by this same node. */ - private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise { + private async handleL2Block(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise { // First we check that the L1 to L2 messages hash to the block inHash. // Note that we cannot optimize this check by checking the root of the subtree after inserting the messages // to the real L1_TO_L2_MESSAGE_TREE (like we do in merkleTreeDb.handleL2BlockAndMessages(...)) because that @@ -240,7 +251,7 @@ export class ServerWorldStateSynchronizer this.syncPromise.resolve(); } - return result.summary; + return result; } private async handleChainFinalized(blockNumber: number) { @@ -255,7 +266,8 @@ export class ServerWorldStateSynchronizer private async handleChainPruned(blockNumber: number) { this.log.info(`Chain pruned to block ${blockNumber}`); - await this.merkleTreeDb.unwindBlocks(BigInt(blockNumber)); + const status = await this.merkleTreeDb.unwindBlocks(BigInt(blockNumber)); + this.instrumentation.updateWorldStateMetrics(status); } /** diff --git a/yarn-project/world-state/src/test/integration.test.ts b/yarn-project/world-state/src/test/integration.test.ts index 8f839a4d9eb..20093e48e18 100644 --- a/yarn-project/world-state/src/test/integration.test.ts +++ b/yarn-project/world-state/src/test/integration.test.ts @@ -4,6 +4,7 @@ import { EthAddress, type Fr } from '@aztec/circuits.js'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; import { type DataStoreConfig } from '@aztec/kv-store/config'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { jest } from '@jest/globals'; @@ -49,7 +50,7 @@ describe('world-state integration', () => { archiver = new MockPrefilledArchiver(blocks, messages); db = (await createWorldState(config)) as NativeWorldStateService; - synchronizer = new TestWorldStateSynchronizer(db, archiver, config); + synchronizer = new TestWorldStateSynchronizer(db, archiver, config, new NoopTelemetryClient()); log.info(`Created synchronizer`); }); @@ -142,7 +143,7 @@ describe('world-state integration', () => { await expectSynchedToBlock(5); await synchronizer.stopBlockStream(); - synchronizer = new TestWorldStateSynchronizer(db, archiver, config); + synchronizer = new TestWorldStateSynchronizer(db, archiver, config, new NoopTelemetryClient()); archiver.createBlocks(3); await synchronizer.start(); @@ -159,7 +160,12 @@ describe('world-state integration', () => { }); it('syncs only proven blocks when instructed', async () => { - synchronizer = new TestWorldStateSynchronizer(db, archiver, { ...config, worldStateProvenBlocksOnly: true }); + synchronizer = new TestWorldStateSynchronizer( + db, + archiver, + { ...config, worldStateProvenBlocksOnly: true }, + new NoopTelemetryClient(), + ); archiver.createBlocks(5); archiver.setProvenBlockNumber(3); @@ -193,7 +199,12 @@ describe('world-state integration', () => { describe('immediate sync', () => { beforeEach(() => { // Set up a synchronizer with a longer block check interval to avoid interference with immediate sync - synchronizer = new TestWorldStateSynchronizer(db, archiver, { ...config, worldStateBlockCheckIntervalMS: 1000 }); + synchronizer = new TestWorldStateSynchronizer( + db, + archiver, + { ...config, worldStateBlockCheckIntervalMS: 1000 }, + new NoopTelemetryClient(), + ); }); it('syncs immediately to the latest block', async () => { diff --git a/yarn-project/world-state/src/world-state-db/merkle_trees.ts b/yarn-project/world-state/src/world-state-db/merkle_trees.ts index 335efdee061..2842eebdae1 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_trees.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_trees.ts @@ -712,7 +712,7 @@ export class MerkleTrees implements MerkleTreeAdminDatabase { } await this.#snapshot(l2Block.number); - this.metrics.recordDbSize(this.store.estimateSize().bytes); + this.metrics.recordDbSize(this.store.estimateSize().actualSize); this.metrics.recordSyncDuration('commit', timer); return buildEmptyWorldStateStatusFull(); }