Skip to content

Commit

Permalink
Performance fixes - limit block search space and filter blocks earlie…
Browse files Browse the repository at this point in the history
…r in query (#114)
  • Loading branch information
45930 authored Jan 27, 2025
1 parent c405653 commit eecdea0
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 53 deletions.
2 changes: 2 additions & 0 deletions .env.example.compose
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ JAEGER_ENDPOINT=http://jaeger:14268/api/traces
COLLECTOR_ZIPKIN_HTTP_PORT=9411
JAEGER_FRONTEND=16686
JAEGER_LOG_PORT=14268

BLOCK_RANGE_SIZE=10000
2 changes: 2 additions & 0 deletions .env.example.lightnet
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ ENABLE_LOGGING="true"
ENABLE_JAEGER="true"
JAEGER_SERVICE_NAME="archive-api"
JAEGER_ENDPOINT='http://localhost:14268/api/traces'

BLOCK_RANGE_SIZE=10000
50 changes: 49 additions & 1 deletion schema.graphql
Original file line number Diff line number Diff line change
@@ -1,24 +1,66 @@
"""
Filter for the consensus status of the block
"""
enum BlockStatusFilter {
"""
All blocks
"""
ALL
"""
Only pending blocks
"""
PENDING
"""
Only canonical (finalized) blocks
"""
CANONICAL
}

"""
Filter events from a specific account
**WARNING**: The graphQL schema server will limit the block scan range to a fixed number of blocks. The default is 10,000 blocks, but can be changed by the host.
It is the responsibility of the client to use a block range that is within the limit, which will guarantee that all events are eventually returned. It is possible to get a partial result if you do not specify both a `from` and a `to` parameter.
"""
input EventFilterOptionsInput {
address: String!
tokenId: String
status: BlockStatusFilter
"""
Mina block height to filter events to, exclusive
"""
to: Int
"""
Mina block height to filter events from, inclusive
"""
from: Int
}

"""
Filter actions from a specific account
**WARNING**: The graphQL schema server will limit the block scan range to a fixed number of blocks. The default is 10,000 blocks, but can be changed by the host.
It is the responsibility of the client to use a block range that is within the limit, which will guarantee that all actions are eventually returned. It is possible to get a partial result if you do not specify both a `from` and a `to` parameter.
"""
input ActionFilterOptionsInput {
address: String!
tokenId: String
status: BlockStatusFilter
"""
Mina block height to filter actions to, exclusive
"""
to: Int
"""
Mina block height to filter actions from, inclusive
"""
from: Int
"""
Filter for actions that happened after this action state, inclusive
"""
fromActionState: String
"""
Filter for actions that happened before this action state, inclusive
"""
endActionState: String
}

Expand Down Expand Up @@ -56,7 +98,7 @@ type TransactionInfo {
hash: String!
memo: String!
authorizationKind: String!
sequenceNumber: Int! # TODO: Is it ok to make this required?
sequenceNumber: Int!
zkappAccountUpdateIds: [Int]!
}

Expand All @@ -80,7 +122,13 @@ type ActionOutput {
actionState: ActionStates!
}

"""
Metadata about the network
"""
type NetworkStateOutput {
"""
Returns the latest pending and canonical block heights that are synced by the archive node. If the archive node is not fully synced, the pending block height will be lower than the actual network state. Wait some time for the archive node to get back in sync.
"""
maxBlockHeight: MaxBlockHeightInfo
}

Expand Down
45 changes: 29 additions & 16 deletions src/db/sql/events-actions/queries.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import type postgres from 'postgres';
import { ArchiveNodeDatabaseRow } from './types.js';
import { BlockStatusFilter } from '../../../blockchain/types.js';
import { BLOCK_RANGE_SIZE } from '../../../server/server.js';

function fullChainCTE(db_client: postgres.Sql) {
function fullChainCTE(db_client: postgres.Sql, from?: string, to?: string) {
let toAsNum = to ? Number(to) : undefined;
let fromAsNum = from ? Number(from) : undefined;
if (fromAsNum) {
const maxRange = fromAsNum + BLOCK_RANGE_SIZE;
toAsNum = toAsNum ? Math.min(toAsNum, maxRange) : maxRange;
} else if (toAsNum) {
fromAsNum = toAsNum - BLOCK_RANGE_SIZE;
}
return db_client`
RECURSIVE pending_chain AS (
(
Expand All @@ -18,9 +27,10 @@ function fullChainCTE(db_client: postgres.Sql) {
b.id, b.state_hash, b.parent_hash, b.parent_id, b.height, b.global_slot_since_genesis, b.global_slot_since_hard_fork, b.timestamp, b.chain_status, b.ledger_hash, b.last_vrf_output
FROM
blocks b
INNER JOIN pending_chain ON b.id = pending_chain.parent_id
AND pending_chain.id <> pending_chain.parent_id
AND pending_chain.chain_status <> 'canonical'
INNER JOIN pending_chain ON b.id = pending_chain.parent_id
AND pending_chain.id <> pending_chain.parent_id
AND pending_chain.chain_status <> 'canonical'
WHERE 1=1
),
full_chain AS (
SELECT
Expand All @@ -38,6 +48,16 @@ function fullChainCTE(db_client: postgres.Sql) {
blocks b
WHERE
chain_status = 'canonical'
${
// If fromAsNum is not undefined, then we have also set toAsNum and can safely query the range
// If no params ar provided, then we query the last BLOCK_RANGE_SIZE blocks
fromAsNum
? db_client`AND b.height >= ${fromAsNum} AND b.height < ${toAsNum!}`
: db_client`AND b.height >= (
SELECT MAX(b2.height)
FROM blocks b2
) - ${BLOCK_RANGE_SIZE}`
}
) AS full_chain
)
`;
Expand All @@ -61,12 +81,7 @@ function accountIdentifierCTE(
)`;
}

function blocksAccessedCTE(
db_client: postgres.Sql,
status: BlockStatusFilter,
to?: string,
from?: string
) {
function blocksAccessedCTE(db_client: postgres.Sql, status: BlockStatusFilter) {
return db_client`
blocks_accessed AS
(
Expand Down Expand Up @@ -97,8 +112,6 @@ function blocksAccessedCTE(
? db_client``
: db_client`AND chain_status = ${status.toLowerCase()}`
}
${to ? db_client`AND b.height <= ${to}` : db_client``}
${from ? db_client`AND b.height >= ${from}` : db_client``}
)`;
}

Expand Down Expand Up @@ -308,9 +321,9 @@ export function getEventsQuery(
) {
return db_client<ArchiveNodeDatabaseRow[]>`
WITH
${fullChainCTE(db_client)},
${fullChainCTE(db_client, from, to)},
${accountIdentifierCTE(db_client, address, tokenId)},
${blocksAccessedCTE(db_client, status, to, from)},
${blocksAccessedCTE(db_client, status)},
${emittedZkAppCommandsCTE(db_client)},
${emittedEventsCTE(db_client)}
SELECT
Expand Down Expand Up @@ -360,9 +373,9 @@ export function getActionsQuery(
) {
return db_client<ArchiveNodeDatabaseRow[]>`
WITH
${fullChainCTE(db_client)},
${fullChainCTE(db_client, from, to)},
${accountIdentifierCTE(db_client, address, tokenId)},
${blocksAccessedCTE(db_client, status, to, from)},
${blocksAccessedCTE(db_client, status)},
${emittedZkAppCommandsCTE(db_client)},
${emittedActionsCTE(db_client)},
${emittedActionStateCTE(db_client, fromActionState, endActionState)}
Expand Down
6 changes: 5 additions & 1 deletion src/errors/error.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GraphQLError } from 'graphql';

export { throwGraphQLError, throwActionStateError };
export { throwGraphQLError, throwActionStateError, throwBlockRangeError };

function throwGraphQLError(message: string, code?: string, status?: number) {
throw new GraphQLError(message, {
Expand All @@ -14,3 +14,7 @@ function throwGraphQLError(message: string, code?: string, status?: number) {
function throwActionStateError(message: string) {
throwGraphQLError(message, 'ACTION_STATE_NOT_FOUND', 400);
}

function throwBlockRangeError(message: string) {
throwGraphQLError(message, 'BLOCK_RANGE_ERROR', 400);
}
6 changes: 5 additions & 1 deletion src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { createServer } from 'http';
import { Plugin } from '@envelop/core';
import { schema } from '../resolvers.js';
import type { GraphQLContext } from '../context.js';
import dotenv from 'dotenv';

export { buildServer };
dotenv.config();

export { BLOCK_RANGE_SIZE, buildServer };

const LOG_LEVEL = (process.env.LOG_LEVEL as LogLevel) || 'info';
const BLOCK_RANGE_SIZE = Number(process.env.BLOCK_RANGE_SIZE) || 10000;

function buildServer(context: GraphQLContext, plugins: Plugin[]) {
const yoga = createYoga<GraphQLContext>({
Expand Down
13 changes: 11 additions & 2 deletions src/services/actions-service/actions-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import {
TracingState,
extractTraceStateFromOptions,
} from '../../tracing/tracer.js';
import { throwActionStateError } from '../../errors/error.js';
import {
throwActionStateError,
throwBlockRangeError,
} from '../../errors/error.js';
import { BLOCK_RANGE_SIZE } from '../../server/server.js';

export { ActionsService };

Expand Down Expand Up @@ -97,7 +101,12 @@ class ActionsService implements IActionsService {
tokenId ||= DEFAULT_TOKEN_ID;
status ||= BlockStatusFilter.all;
if (to && from && to < from) {
throw new Error('to must be greater than from');
throwBlockRangeError('to must be greater than from');
}
if (to && from && to - from > BLOCK_RANGE_SIZE) {
throwBlockRangeError(
`The block range is too large. The maximum range is ${BLOCK_RANGE_SIZE}`
);
}

return getActionsQuery(
Expand Down
9 changes: 8 additions & 1 deletion src/services/events-service/events-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
TracingState,
extractTraceStateFromOptions,
} from '../../tracing/tracer.js';
import { BLOCK_RANGE_SIZE } from '../../server/server.js';
import { throwBlockRangeError } from '../../errors/error.js';

export { EventsService };

Expand Down Expand Up @@ -67,7 +69,12 @@ class EventsService implements IEventsService {
tokenId ||= DEFAULT_TOKEN_ID;
status ||= BlockStatusFilter.all;
if (to && from && to < from) {
throw new Error('to must be greater than from');
throwBlockRangeError('to must be greater than from');
}
if (to && from && to - from > BLOCK_RANGE_SIZE) {
throwBlockRangeError(
`The block range is too large. The maximum range is ${BLOCK_RANGE_SIZE}`
);
}

return getEventsQuery(
Expand Down
Loading

0 comments on commit eecdea0

Please sign in to comment.