Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: track world state metrics #8109

Merged
merged 2 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export class AztecNodeService implements AztecNode {
);

// now create the merkle trees and the world state synchronizer
const worldStateSynchronizer = await createWorldStateSynchronizer(config, store, archiver);
const worldStateSynchronizer = await createWorldStateSynchronizer(config, store, archiver, telemetry);

// start both and wait for them to sync from the block source
await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]);
Expand Down Expand Up @@ -722,7 +722,7 @@ export class AztecNodeService implements AztecNode {
// Instantiate merkle trees so uncommitted updates by this simulation are local to it.
// TODO we should be able to remove this after https://github.com/AztecProtocol/aztec-packages/issues/1869
// So simulation of public functions doesn't affect the merkle trees.
const merkleTrees = await MerkleTrees.new(this.merkleTreesDb, this.log);
const merkleTrees = await MerkleTrees.new(this.merkleTreesDb, new NoopTelemetryClient(), this.log);

const publicProcessorFactory = new PublicProcessorFactory(
merkleTrees.asLatest(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ describe('L1Publisher integration', () => {
});

const tmpStore = openTmpStore();
builderDb = await MerkleTrees.new(tmpStore);
builderDb = await MerkleTrees.new(tmpStore, new NoopTelemetryClient());
blockSource = mock<ArchiveSource>();
blockSource.getBlocks.mockResolvedValue([]);
const worldStateConfig: WorldStateConfig = {
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/kv-store/src/interfaces/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,9 @@ export interface AztecKVStore {
* Deletes the store
*/
delete(): Promise<void>;

/**
* Estimates the size of the store in bytes.
*/
estimateSize(): { bytes: number };
}
12 changes: 12 additions & 0 deletions yarn-project/kv-store/src/lmdb/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,16 @@ export class AztecLmdbStore implements AztecKVStore {
async delete() {
await this.#rootDb.drop();
}

estimateSize(): { bytes: 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.
// http://www.lmdb.tech/doc/group__mdb.html#a4bde3c8b676457342cba2fe27aed5fbd
if ('mapSize' in stats && typeof stats.mapSize === 'number') {
return { bytes: stats.mapSize };
} else {
return { bytes: 0 };
}
}
}
2 changes: 1 addition & 1 deletion yarn-project/prover-client/src/mocks/test_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export class TestContext {
const publicContractsDB = mock<ContractsDataSourcePublicDB>();
const publicWorldStateDB = mock<WorldStatePublicDB>();
const publicKernel = new RealPublicKernelCircuitSimulator(new WASMSimulator());
const actualDb = await MerkleTrees.new(openTmpStore()).then(t => t.asLatest());
const telemetry = new NoopTelemetryClient();
const actualDb = await MerkleTrees.new(openTmpStore(), telemetry).then(t => t.asLatest());
const processor = new PublicProcessor(
actualDb,
publicExecutor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { range } from '@aztec/foundation/array';
import { times } from '@aztec/foundation/collection';
import { createDebugLogger } from '@aztec/foundation/log';
import { openTmpStore } from '@aztec/kv-store/utils';
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
import { type MerkleTreeOperations, MerkleTrees } from '@aztec/world-state';

import { makeBloatedProcessedTx, updateExpectedTreesFromTxs } from '../mocks/fixtures.js';
Expand All @@ -18,7 +19,7 @@ describe('prover/orchestrator/mixed-blocks', () => {

beforeEach(async () => {
context = await TestContext.new(logger);
expectsDb = await MerkleTrees.new(openTmpStore()).then(t => t.asLatest());
expectsDb = await MerkleTrees.new(openTmpStore(), new NoopTelemetryClient()).then(t => t.asLatest());
});

afterEach(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { range } from '@aztec/foundation/array';
import { createDebugLogger } from '@aztec/foundation/log';
import { sleep } from '@aztec/foundation/sleep';
import { openTmpStore } from '@aztec/kv-store/utils';
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
import { type MerkleTreeOperations, MerkleTrees } from '@aztec/world-state';

import { makeBloatedProcessedTx, updateExpectedTreesFromTxs } from '../mocks/fixtures.js';
Expand All @@ -18,7 +19,7 @@ describe('prover/orchestrator/blocks', () => {

beforeEach(async () => {
context = await TestContext.new(logger);
expectsDb = await MerkleTrees.new(openTmpStore()).then(t => t.asLatest());
expectsDb = await MerkleTrees.new(openTmpStore(), new NoopTelemetryClient()).then(t => t.asLatest());
});

afterEach(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ describe('prover/orchestrator', () => {
let mockProver: MockProxy<ServerCircuitProver>;
let actualDb: MerkleTreeOperations;
beforeEach(async () => {
actualDb = await MerkleTrees.new(openTmpStore()).then(t => t.asLatest());
const telemetryClient = new NoopTelemetryClient();
actualDb = await MerkleTrees.new(openTmpStore(), telemetryClient).then(t => t.asLatest());
mockProver = mock<ServerCircuitProver>();
orchestrator = new ProvingOrchestrator(actualDb, mockProver, new NoopTelemetryClient());
orchestrator = new ProvingOrchestrator(actualDb, mockProver, telemetryClient);
});

it('calls root parity circuit only when ready', async () => {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/prover-node/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function createProverNode(
log.verbose(`Created archiver and synced to block ${await archiver.getBlockNumber()}`);

const worldStateConfig = { ...config, worldStateProvenBlocksOnly: true };
const worldStateSynchronizer = await createWorldStateSynchronizer(worldStateConfig, store, archiver);
const worldStateSynchronizer = await createWorldStateSynchronizer(worldStateConfig, store, archiver, telemetry);
await worldStateSynchronizer.start();

const simulationProvider = await createSimulationProvider(config, log);
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/telemetry-client/src/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ export const L1_TX_TYPE = 'aztec.l1.tx_type';
export const TX_PHASE_NAME = 'aztec.tx.phase_name';
/** The proving job type */
export const PROVING_JOB_TYPE = 'aztec.proving.job_type';

export const MERKLE_TREE_NAME = 'aztec.merkle_tree.name';
5 changes: 5 additions & 0 deletions yarn-project/telemetry-client/src/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@ export const PROVING_ORCHESTRATOR_BASE_ROLLUP_INPUTS_DURATION =

export const PROVING_QUEUE_JOB_SIZE = 'aztec.proving_queue.job_size';
export const PROVING_QUEUE_SIZE = 'aztec.proving_queue.size';

export const WORLD_STATE_FORK_DURATION = 'aztec.world_state.fork.duration';
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';
3 changes: 2 additions & 1 deletion yarn-project/txe/src/txe_service/txe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { type Logger } from '@aztec/foundation/log';
import { KeyStore } from '@aztec/key-store';
import { openTmpStore } from '@aztec/kv-store/utils';
import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator';
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
import { MerkleTrees } from '@aztec/world-state';

import { TXE } from '../oracle/txe_oracle.js';
Expand All @@ -38,7 +39,7 @@ export class TXEService {

static async init(logger: Logger) {
const store = openTmpStore(true);
const trees = await MerkleTrees.new(store, logger);
const trees = await MerkleTrees.new(store, new NoopTelemetryClient(), logger);
const packedValuesCache = new PackedValuesCache();
const txHash = new Fr(1); // The txHash is used for computing the revertible nullifiers for non-revertible note hashes. It can be any value for testing.
const noteCache = new ExecutionNoteCache(txHash);
Expand Down
1 change: 1 addition & 0 deletions yarn-project/world-state/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@aztec/foundation": "workspace:^",
"@aztec/kv-store": "workspace:^",
"@aztec/merkle-tree": "workspace:^",
"@aztec/telemetry-client": "workspace:^",
"@aztec/types": "workspace:^",
"tslib": "^2.4.0"
},
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/world-state/src/synchronizer/factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type L1ToL2MessageSource, type L2BlockSource } from '@aztec/circuit-types';
import { type AztecKVStore } from '@aztec/kv-store';
import { type TelemetryClient } from '@aztec/telemetry-client';

import { MerkleTrees } from '../world-state-db/merkle_trees.js';
import { type WorldStateConfig } from './config.js';
Expand All @@ -9,7 +10,8 @@ export async function createWorldStateSynchronizer(
config: WorldStateConfig,
store: AztecKVStore,
l2BlockSource: L2BlockSource & L1ToL2MessageSource,
client: TelemetryClient,
) {
const merkleTrees = await MerkleTrees.new(store);
const merkleTrees = await MerkleTrees.new(store, client);
return new ServerWorldStateSynchronizer(store, merkleTrees, l2BlockSource, config);
}
33 changes: 24 additions & 9 deletions yarn-project/world-state/src/world-state-db/merkle_trees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import { padArrayEnd } from '@aztec/foundation/collection';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { SerialQueue } from '@aztec/foundation/queue';
import { Timer, elapsed } from '@aztec/foundation/timer';
import { type IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
import { type AztecKVStore, type AztecSingleton } from '@aztec/kv-store';
import {
Expand All @@ -45,6 +46,7 @@ import {
loadTree,
newTree,
} from '@aztec/merkle-tree';
import { type TelemetryClient } from '@aztec/telemetry-client';
import { type Hasher } from '@aztec/types/interfaces';

import {
Expand All @@ -55,6 +57,7 @@ import {
} from './merkle_tree_db.js';
import { type MerkleTreeMap } from './merkle_tree_map.js';
import { MerkleTreeOperationsFacade } from './merkle_tree_operations_facade.js';
import { WorldStateMetrics } from './metrics.js';

/**
* The nullifier tree is an indexed tree.
Expand Down Expand Up @@ -98,18 +101,20 @@ export class MerkleTrees implements MerkleTreeDb {
private trees: MerkleTreeMap = null as any;
private jobQueue = new SerialQueue();
private initialStateReference: AztecSingleton<Buffer>;
private metrics: WorldStateMetrics;

private constructor(private store: AztecKVStore, private log: DebugLogger) {
private constructor(private store: AztecKVStore, private telemetryClient: TelemetryClient, private log: DebugLogger) {
this.initialStateReference = store.openSingleton('merkle_trees_initial_state_reference');
this.metrics = new WorldStateMetrics(telemetryClient);
}

/**
* Method to asynchronously create and initialize a MerkleTrees instance.
* @param store - The db instance to use for data persistance.
* @returns - A fully initialized MerkleTrees instance.
*/
public static async new(store: AztecKVStore, log = createDebugLogger('aztec:merkle_trees')) {
const merkleTrees = new MerkleTrees(store, log);
public static async new(store: AztecKVStore, client: TelemetryClient, log = createDebugLogger('aztec:merkle_trees')) {
const merkleTrees = new MerkleTrees(store, client, log);
await merkleTrees.#init();
return merkleTrees;
}
Expand Down Expand Up @@ -181,12 +186,17 @@ export class MerkleTrees implements MerkleTreeDb {
}

public async fork(): Promise<MerkleTrees> {
// TODO(palla/prover-node): If the underlying store is being shared with other components, we're unnecessarily
// copying a lot of data unrelated to merkle trees. This may be fine for now, and we may be able to ditch backup-based
// forking in favor of a more elegant proposal. But if we see this operation starts taking a lot of time, we may want
// to open separate stores for merkle trees and other components.
const forked = await this.store.fork();
return MerkleTrees.new(forked, this.log);
const [ms, db] = await elapsed(async () => {
// TODO(palla/prover-node): If the underlying store is being shared with other components, we're unnecessarily
// copying a lot of data unrelated to merkle trees. This may be fine for now, and we may be able to ditch backup-based
// forking in favor of a more elegant proposal. But if we see this operation starts taking a lot of time, we may want
// to open separate stores for merkle trees and other components.
const forked = await this.store.fork();
return MerkleTrees.new(forked, this.telemetryClient, this.log);
});

this.metrics.recordForkDuration(ms);
return db;
}

public async delete() {
Expand Down Expand Up @@ -581,6 +591,8 @@ export class MerkleTrees implements MerkleTreeDb {
* @param l1ToL2Messages - The L1 to L2 messages for the block.
*/
async #handleL2BlockAndMessages(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise<HandleL2BlockAndMessagesResult> {
const timer = new Timer();

const treeRootWithIdPairs = [
[l2Block.header.state.partial.nullifierTree.root, MerkleTreeId.NULLIFIER_TREE],
[l2Block.header.state.partial.noteHashTree.root, MerkleTreeId.NOTE_HASH_TREE],
Expand Down Expand Up @@ -664,10 +676,13 @@ export class MerkleTrees implements MerkleTreeDb {
);
} else {
this.log.debug(`Tree ${treeName} synched with size ${info.size} root ${rootStr}`);
this.metrics.recordTreeSize(treeName, info.size);
}
}
await this.#snapshot(l2Block.number);

this.metrics.recordDbSize(this.store.estimateSize().bytes);
this.metrics.recordSyncDuration(ourBlock ? 'commit' : 'rollback_and_update', timer);
return { isBlockOurs: ourBlock };
}

Expand Down
64 changes: 64 additions & 0 deletions yarn-project/world-state/src/world-state-db/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { type Timer } from '@aztec/foundation/timer';
import {
Attributes,
type Gauge,
type Histogram,
Metrics,
type TelemetryClient,
ValueType,
} from '@aztec/telemetry-client';

export class WorldStateMetrics {
private treeSize: Gauge;
private dbSize: Gauge;
private forkDuration: Histogram;
private syncDuration: Histogram;

constructor(client: TelemetryClient, name = 'MerkleTreesDb') {
const meter = client.getMeter(name);
this.treeSize = meter.createGauge(Metrics.WORLD_STATE_MERKLE_TREE_SIZE, {
description: 'The size of Merkle trees',
valueType: ValueType.INT,
});

this.dbSize = meter.createGauge(Metrics.WORLD_STATE_DB_SIZE, {
description: 'The size of the World State DB',
valueType: ValueType.INT,
unit: 'By',
});

this.forkDuration = meter.createHistogram(Metrics.WORLD_STATE_FORK_DURATION, {
description: 'The duration of a fork operation',
unit: 'ms',
valueType: ValueType.INT,
});

this.syncDuration = meter.createHistogram(Metrics.WORLD_STATE_SYNC_DURATION, {
description: 'The duration of a sync operation',
unit: 'ms',
valueType: ValueType.INT,
});
}

recordTreeSize(treeName: string, treeSize: bigint) {
this.treeSize.record(Number(treeSize), {
[Attributes.MERKLE_TREE_NAME]: treeName,
});
}

recordDbSize(dbSizeInBytes: number) {
this.dbSize.record(dbSizeInBytes);
}

recordForkDuration(timerOrMs: Timer | number) {
const ms = Math.ceil(typeof timerOrMs === 'number' ? timerOrMs : timerOrMs.ms());
this.forkDuration.record(ms);
}

recordSyncDuration(syncType: 'commit' | 'rollback_and_update', timerOrMs: Timer | number) {
const ms = Math.ceil(typeof timerOrMs === 'number' ? timerOrMs : timerOrMs.ms());
this.syncDuration.record(ms, {
[Attributes.STATUS]: syncType,
});
}
}
3 changes: 3 additions & 0 deletions yarn-project/world-state/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
{
"path": "../merkle-tree"
},
{
"path": "../telemetry-client"
},
{
"path": "../types"
}
Expand Down
1 change: 1 addition & 0 deletions yarn-project/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,7 @@ __metadata:
"@aztec/foundation": "workspace:^"
"@aztec/kv-store": "workspace:^"
"@aztec/merkle-tree": "workspace:^"
"@aztec/telemetry-client": "workspace:^"
"@aztec/types": "workspace:^"
"@jest/globals": ^29.5.0
"@types/jest": ^29.5.0
Expand Down
Loading