Skip to content

Commit 2b2a983

Browse files
committed
fix: long time to load block height using cursor id
1 parent 74cd471 commit 2b2a983

File tree

2 files changed

+149
-9
lines changed

2 files changed

+149
-9
lines changed

indexer/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ Docker Compose provides a way to run the entire indexer stack with a single comm
105105
To start all services:
106106

107107
```bash
108-
yarn dev
108+
$ yarn compose:up
109+
$ yarn compose:down
109110
```
110111

111112
**NOTE:** Using the image on with the composer require the database `DB_USERNAME` to default to `postgres`.

indexer/src/kadena-server/repository/infra/repository/block-db-repository.ts

Lines changed: 147 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ export default class BlockDbRepository implements BlockRepository {
121121
* supporting cursor-based pagination and chain filtering for efficient
122122
* navigation through large result sets.
123123
*
124+
* Uses keyset pagination with compound cursors (height:id) for better performance
125+
* when navigating through large datasets.
126+
*
124127
* @param params - Object containing height range and pagination parameters
125128
* @returns Promise resolving to page info and block edges
126129
*/
@@ -145,14 +148,140 @@ export default class BlockDbRepository implements BlockRepository {
145148
const queryParams: (string | number | string[])[] = [limit, startHeight];
146149
let conditions = '';
147150

148-
if (before) {
149-
queryParams.push(before);
150-
conditions += `\nAND b.id > $${queryParams.length}`;
151+
// Handle "after" cursor (for forward pagination) - decode to get both height and id
152+
let afterHeight, afterId;
153+
if (after) {
154+
try {
155+
// Try to parse as compound cursor format (height:id)
156+
const [height, id] = after.split(':');
157+
afterHeight = parseInt(height, 10);
158+
afterId = parseInt(id, 10);
159+
160+
// Check if values are valid numbers
161+
if (!isNaN(afterHeight) && !isNaN(afterId)) {
162+
// For forward pagination with "after", we want records where:
163+
// (height < afterHeight) OR (height = afterHeight AND id < afterId)
164+
queryParams.push(afterHeight, afterId);
165+
conditions += `\nAND (b.height < $${queryParams.length - 1} OR (b.height = $${queryParams.length - 1} AND b.id < $${queryParams.length}))`;
166+
console.info(
167+
`[INFO][QUERY][OPTIMIZATION] Using optimized query with compound cursor: height=${afterHeight}, id=${afterId}`,
168+
);
169+
} else {
170+
// Old cursor format - look up the height for this ID to optimize the query
171+
const idValue = parseInt(after, 10);
172+
if (!isNaN(idValue)) {
173+
console.info(
174+
`[INFO][QUERY][OPTIMIZATION] Converting old cursor format (id=${idValue}) to optimized format`,
175+
);
176+
177+
// Get the height for this ID with a fast lookup
178+
const heightQuery = `SELECT height FROM "Blocks" WHERE id = $1 LIMIT 1`;
179+
const { rows } = await rootPgPool.query(heightQuery, [idValue]);
180+
181+
if (rows.length > 0) {
182+
// We found the height, now use the optimized approach
183+
afterHeight = rows[0].height;
184+
afterId = idValue;
185+
186+
// Use the optimized condition
187+
queryParams.push(afterHeight, afterId);
188+
conditions += `\nAND (b.height < $${queryParams.length - 1} OR (b.height = $${queryParams.length - 1} AND b.id < $${queryParams.length}))`;
189+
console.info(
190+
`[INFO][QUERY][OPTIMIZATION] Converted to optimized query: height=${afterHeight}, id=${afterId}`,
191+
);
192+
} else {
193+
// Fallback if we couldn't find the height
194+
queryParams.push(after);
195+
conditions += `\nAND b.id < $${queryParams.length}`;
196+
console.info(
197+
`[INFO][QUERY][OPTIMIZATION] Could not find height for id=${idValue}, using fallback approach`,
198+
);
199+
}
200+
} else {
201+
// Fallback for compatibility with old cursor format
202+
queryParams.push(after);
203+
conditions += `\nAND b.id < $${queryParams.length}`;
204+
console.info(
205+
`[INFO][QUERY][OPTIMIZATION] Using fallback approach with cursor=${after}`,
206+
);
207+
}
208+
}
209+
} catch (e: unknown) {
210+
// Fallback for compatibility with old cursor format
211+
queryParams.push(after);
212+
conditions += `\nAND b.id < $${queryParams.length}`;
213+
console.info(
214+
`[INFO][QUERY][OPTIMIZATION] Error parsing cursor, using fallback approach: ${e instanceof Error ? e.message : String(e)}`,
215+
);
216+
}
151217
}
152218

153-
if (after) {
154-
queryParams.push(after);
155-
conditions += `\nAND b.id < $${queryParams.length}`;
219+
// Handle "before" cursor (for backward pagination) - decode to get both height and id
220+
let beforeHeight, beforeId;
221+
if (before) {
222+
try {
223+
// Try to parse as compound cursor format (height:id)
224+
const [height, id] = before.split(':');
225+
beforeHeight = parseInt(height, 10);
226+
beforeId = parseInt(id, 10);
227+
228+
// Check if values are valid numbers
229+
if (!isNaN(beforeHeight) && !isNaN(beforeId)) {
230+
// For backward pagination with "before", we want records where:
231+
// (height > beforeHeight) OR (height = beforeHeight AND id > beforeId)
232+
queryParams.push(beforeHeight, beforeId);
233+
conditions += `\nAND (b.height > $${queryParams.length - 1} OR (b.height = $${queryParams.length - 1} AND b.id > $${queryParams.length}))`;
234+
console.info(
235+
`[INFO][QUERY][OPTIMIZATION] Using optimized query with compound cursor: height=${beforeHeight}, id=${beforeId}`,
236+
);
237+
} else {
238+
// Old cursor format - look up the height for this ID to optimize the query
239+
const idValue = parseInt(before, 10);
240+
if (!isNaN(idValue)) {
241+
console.info(
242+
`[INFO][QUERY][OPTIMIZATION] Converting old cursor format (id=${idValue}) to optimized format`,
243+
);
244+
245+
// Get the height for this ID with a fast lookup
246+
const heightQuery = `SELECT height FROM "Blocks" WHERE id = $1 LIMIT 1`;
247+
const { rows } = await rootPgPool.query(heightQuery, [idValue]);
248+
249+
if (rows.length > 0) {
250+
// We found the height, now use the optimized approach
251+
beforeHeight = rows[0].height;
252+
beforeId = idValue;
253+
254+
// Use the optimized condition
255+
queryParams.push(beforeHeight, beforeId);
256+
conditions += `\nAND (b.height > $${queryParams.length - 1} OR (b.height = $${queryParams.length - 1} AND b.id > $${queryParams.length}))`;
257+
console.info(
258+
`[INFO][QUERY][OPTIMIZATION] Converted to optimized query: height=${beforeHeight}, id=${beforeId}`,
259+
);
260+
} else {
261+
// Fallback if we couldn't find the height
262+
queryParams.push(before);
263+
conditions += `\nAND b.id > $${queryParams.length}`;
264+
console.info(
265+
`[INFO][QUERY][OPTIMIZATION] Could not find height for id=${idValue}, using fallback approach`,
266+
);
267+
}
268+
} else {
269+
// Fallback for compatibility with old cursor format
270+
queryParams.push(before);
271+
conditions += `\nAND b.id > $${queryParams.length}`;
272+
console.info(
273+
`[INFO][QUERY][OPTIMIZATION] Using fallback approach with cursor=${before}`,
274+
);
275+
}
276+
}
277+
} catch (e: unknown) {
278+
// Fallback for compatibility with old cursor format
279+
queryParams.push(before);
280+
conditions += `\nAND b.id > $${queryParams.length}`;
281+
console.info(
282+
`[INFO][QUERY][OPTIMIZATION] Error parsing cursor, using fallback approach: ${e instanceof Error ? e.message : String(e)}`,
283+
);
284+
}
156285
}
157286

158287
if (chainIds?.length) {
@@ -165,6 +294,9 @@ export default class BlockDbRepository implements BlockRepository {
165294
conditions += `\nAND b."height" <= $${queryParams.length}`;
166295
}
167296

297+
// Record the start time to measure performance
298+
const startTime = Date.now();
299+
168300
const query = `
169301
SELECT b.id,
170302
b.hash,
@@ -182,14 +314,21 @@ export default class BlockDbRepository implements BlockRepository {
182314
FROM "Blocks" b
183315
WHERE b.height >= $2
184316
${conditions}
185-
ORDER BY b.height ${order}
317+
ORDER BY b.height ${order}, b.id ${order}
186318
LIMIT $1;
187319
`;
188320

189321
const { rows: blockRows } = await rootPgPool.query(query, queryParams);
190322

323+
// Log the query performance
324+
const endTime = Date.now();
325+
console.info(
326+
`[INFO][QUERY][PERFORMANCE] Block query executed in ${endTime - startTime}ms, returned ${blockRows.length} rows`,
327+
);
328+
329+
// Create edges with a compound cursor containing both height and id
191330
const edges = blockRows.map(row => ({
192-
cursor: row.id.toString(),
331+
cursor: `${row.height}:${row.id}`, // Compound cursor for better pagination
193332
node: blockValidator.validate(row),
194333
}));
195334

0 commit comments

Comments
 (0)