Skip to content

Commit eecdea0

Browse files
authored
Performance fixes - limit block search space and filter blocks earlier in query (#114)
1 parent c405653 commit eecdea0

File tree

9 files changed

+228
-53
lines changed

9 files changed

+228
-53
lines changed

.env.example.compose

+2
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@ JAEGER_ENDPOINT=http://jaeger:14268/api/traces
3838
COLLECTOR_ZIPKIN_HTTP_PORT=9411
3939
JAEGER_FRONTEND=16686
4040
JAEGER_LOG_PORT=14268
41+
42+
BLOCK_RANGE_SIZE=10000

.env.example.lightnet

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ ENABLE_LOGGING="true"
1212
ENABLE_JAEGER="true"
1313
JAEGER_SERVICE_NAME="archive-api"
1414
JAEGER_ENDPOINT='http://localhost:14268/api/traces'
15+
16+
BLOCK_RANGE_SIZE=10000

schema.graphql

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,66 @@
1+
"""
2+
Filter for the consensus status of the block
3+
"""
14
enum BlockStatusFilter {
5+
"""
6+
All blocks
7+
"""
28
ALL
9+
"""
10+
Only pending blocks
11+
"""
312
PENDING
13+
"""
14+
Only canonical (finalized) blocks
15+
"""
416
CANONICAL
517
}
618

19+
"""
20+
Filter events from a specific account
21+
22+
**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.
23+
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.
24+
"""
725
input EventFilterOptionsInput {
826
address: String!
927
tokenId: String
1028
status: BlockStatusFilter
29+
"""
30+
Mina block height to filter events to, exclusive
31+
"""
1132
to: Int
33+
"""
34+
Mina block height to filter events from, inclusive
35+
"""
1236
from: Int
1337
}
1438

39+
"""
40+
Filter actions from a specific account
41+
42+
**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.
43+
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.
44+
"""
1545
input ActionFilterOptionsInput {
1646
address: String!
1747
tokenId: String
1848
status: BlockStatusFilter
49+
"""
50+
Mina block height to filter actions to, exclusive
51+
"""
1952
to: Int
53+
"""
54+
Mina block height to filter actions from, inclusive
55+
"""
2056
from: Int
57+
"""
58+
Filter for actions that happened after this action state, inclusive
59+
"""
2160
fromActionState: String
61+
"""
62+
Filter for actions that happened before this action state, inclusive
63+
"""
2264
endActionState: String
2365
}
2466

@@ -56,7 +98,7 @@ type TransactionInfo {
5698
hash: String!
5799
memo: String!
58100
authorizationKind: String!
59-
sequenceNumber: Int! # TODO: Is it ok to make this required?
101+
sequenceNumber: Int!
60102
zkappAccountUpdateIds: [Int]!
61103
}
62104

@@ -80,7 +122,13 @@ type ActionOutput {
80122
actionState: ActionStates!
81123
}
82124

125+
"""
126+
Metadata about the network
127+
"""
83128
type NetworkStateOutput {
129+
"""
130+
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.
131+
"""
84132
maxBlockHeight: MaxBlockHeightInfo
85133
}
86134

src/db/sql/events-actions/queries.ts

+29-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import type postgres from 'postgres';
22
import { ArchiveNodeDatabaseRow } from './types.js';
33
import { BlockStatusFilter } from '../../../blockchain/types.js';
4+
import { BLOCK_RANGE_SIZE } from '../../../server/server.js';
45

5-
function fullChainCTE(db_client: postgres.Sql) {
6+
function fullChainCTE(db_client: postgres.Sql, from?: string, to?: string) {
7+
let toAsNum = to ? Number(to) : undefined;
8+
let fromAsNum = from ? Number(from) : undefined;
9+
if (fromAsNum) {
10+
const maxRange = fromAsNum + BLOCK_RANGE_SIZE;
11+
toAsNum = toAsNum ? Math.min(toAsNum, maxRange) : maxRange;
12+
} else if (toAsNum) {
13+
fromAsNum = toAsNum - BLOCK_RANGE_SIZE;
14+
}
615
return db_client`
716
RECURSIVE pending_chain AS (
817
(
@@ -18,9 +27,10 @@ function fullChainCTE(db_client: postgres.Sql) {
1827
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
1928
FROM
2029
blocks b
21-
INNER JOIN pending_chain ON b.id = pending_chain.parent_id
22-
AND pending_chain.id <> pending_chain.parent_id
23-
AND pending_chain.chain_status <> 'canonical'
30+
INNER JOIN pending_chain ON b.id = pending_chain.parent_id
31+
AND pending_chain.id <> pending_chain.parent_id
32+
AND pending_chain.chain_status <> 'canonical'
33+
WHERE 1=1
2434
),
2535
full_chain AS (
2636
SELECT
@@ -38,6 +48,16 @@ function fullChainCTE(db_client: postgres.Sql) {
3848
blocks b
3949
WHERE
4050
chain_status = 'canonical'
51+
${
52+
// If fromAsNum is not undefined, then we have also set toAsNum and can safely query the range
53+
// If no params ar provided, then we query the last BLOCK_RANGE_SIZE blocks
54+
fromAsNum
55+
? db_client`AND b.height >= ${fromAsNum} AND b.height < ${toAsNum!}`
56+
: db_client`AND b.height >= (
57+
SELECT MAX(b2.height)
58+
FROM blocks b2
59+
) - ${BLOCK_RANGE_SIZE}`
60+
}
4161
) AS full_chain
4262
)
4363
`;
@@ -61,12 +81,7 @@ function accountIdentifierCTE(
6181
)`;
6282
}
6383

64-
function blocksAccessedCTE(
65-
db_client: postgres.Sql,
66-
status: BlockStatusFilter,
67-
to?: string,
68-
from?: string
69-
) {
84+
function blocksAccessedCTE(db_client: postgres.Sql, status: BlockStatusFilter) {
7085
return db_client`
7186
blocks_accessed AS
7287
(
@@ -97,8 +112,6 @@ function blocksAccessedCTE(
97112
? db_client``
98113
: db_client`AND chain_status = ${status.toLowerCase()}`
99114
}
100-
${to ? db_client`AND b.height <= ${to}` : db_client``}
101-
${from ? db_client`AND b.height >= ${from}` : db_client``}
102115
)`;
103116
}
104117

@@ -308,9 +321,9 @@ export function getEventsQuery(
308321
) {
309322
return db_client<ArchiveNodeDatabaseRow[]>`
310323
WITH
311-
${fullChainCTE(db_client)},
324+
${fullChainCTE(db_client, from, to)},
312325
${accountIdentifierCTE(db_client, address, tokenId)},
313-
${blocksAccessedCTE(db_client, status, to, from)},
326+
${blocksAccessedCTE(db_client, status)},
314327
${emittedZkAppCommandsCTE(db_client)},
315328
${emittedEventsCTE(db_client)}
316329
SELECT
@@ -360,9 +373,9 @@ export function getActionsQuery(
360373
) {
361374
return db_client<ArchiveNodeDatabaseRow[]>`
362375
WITH
363-
${fullChainCTE(db_client)},
376+
${fullChainCTE(db_client, from, to)},
364377
${accountIdentifierCTE(db_client, address, tokenId)},
365-
${blocksAccessedCTE(db_client, status, to, from)},
378+
${blocksAccessedCTE(db_client, status)},
366379
${emittedZkAppCommandsCTE(db_client)},
367380
${emittedActionsCTE(db_client)},
368381
${emittedActionStateCTE(db_client, fromActionState, endActionState)}

src/errors/error.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { GraphQLError } from 'graphql';
22

3-
export { throwGraphQLError, throwActionStateError };
3+
export { throwGraphQLError, throwActionStateError, throwBlockRangeError };
44

55
function throwGraphQLError(message: string, code?: string, status?: number) {
66
throw new GraphQLError(message, {
@@ -14,3 +14,7 @@ function throwGraphQLError(message: string, code?: string, status?: number) {
1414
function throwActionStateError(message: string) {
1515
throwGraphQLError(message, 'ACTION_STATE_NOT_FOUND', 400);
1616
}
17+
18+
function throwBlockRangeError(message: string) {
19+
throwGraphQLError(message, 'BLOCK_RANGE_ERROR', 400);
20+
}

src/server/server.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { createServer } from 'http';
33
import { Plugin } from '@envelop/core';
44
import { schema } from '../resolvers.js';
55
import type { GraphQLContext } from '../context.js';
6+
import dotenv from 'dotenv';
67

7-
export { buildServer };
8+
dotenv.config();
9+
10+
export { BLOCK_RANGE_SIZE, buildServer };
811

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

1115
function buildServer(context: GraphQLContext, plugins: Plugin[]) {
1216
const yoga = createYoga<GraphQLContext>({

src/services/actions-service/actions-service.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import {
2626
TracingState,
2727
extractTraceStateFromOptions,
2828
} from '../../tracing/tracer.js';
29-
import { throwActionStateError } from '../../errors/error.js';
29+
import {
30+
throwActionStateError,
31+
throwBlockRangeError,
32+
} from '../../errors/error.js';
33+
import { BLOCK_RANGE_SIZE } from '../../server/server.js';
3034

3135
export { ActionsService };
3236

@@ -97,7 +101,12 @@ class ActionsService implements IActionsService {
97101
tokenId ||= DEFAULT_TOKEN_ID;
98102
status ||= BlockStatusFilter.all;
99103
if (to && from && to < from) {
100-
throw new Error('to must be greater than from');
104+
throwBlockRangeError('to must be greater than from');
105+
}
106+
if (to && from && to - from > BLOCK_RANGE_SIZE) {
107+
throwBlockRangeError(
108+
`The block range is too large. The maximum range is ${BLOCK_RANGE_SIZE}`
109+
);
101110
}
102111

103112
return getActionsQuery(

src/services/events-service/events-service.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
TracingState,
2323
extractTraceStateFromOptions,
2424
} from '../../tracing/tracer.js';
25+
import { BLOCK_RANGE_SIZE } from '../../server/server.js';
26+
import { throwBlockRangeError } from '../../errors/error.js';
2527

2628
export { EventsService };
2729

@@ -67,7 +69,12 @@ class EventsService implements IEventsService {
6769
tokenId ||= DEFAULT_TOKEN_ID;
6870
status ||= BlockStatusFilter.all;
6971
if (to && from && to < from) {
70-
throw new Error('to must be greater than from');
72+
throwBlockRangeError('to must be greater than from');
73+
}
74+
if (to && from && to - from > BLOCK_RANGE_SIZE) {
75+
throwBlockRangeError(
76+
`The block range is too large. The maximum range is ${BLOCK_RANGE_SIZE}`
77+
);
7178
}
7279

7380
return getEventsQuery(

0 commit comments

Comments
 (0)