Skip to content

Commit d788959

Browse files
Park Juhyungjoojis
authored andcommitted
Use an index to find rows in the next page in the AggsUTXO query
The indexer was using SQL's `skip` for the pagination. The DB should scan the number of skipped rows, to get the page result. For the performance enhancement, we concluded that change the pagination method. Finding a particular row using an index and get the following `n` rows is much faster than using `skip`. This commit uses `firstEvaluatedKey` and `lastEvaluatedKey` for the pagination in the AggsUTXO query. The AggsUTXO query will find a row using `firstEvaluatedKey` or `lastEvaluatedKey` and returns next `n` rows.
1 parent 367e7ab commit d788959

6 files changed

Lines changed: 234 additions & 24 deletions

File tree

src/models/logic/aggsUTXO.ts

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,30 @@ import { H160 } from "codechain-primitives";
22
import * as Sequelize from "sequelize";
33
import models from "..";
44
import * as Exception from "../../exception";
5+
import { aggsUTXOPagination } from "../../routers/pagination";
56

67
export async function getByAddress(params: {
78
address: string;
89
assetType?: H160 | null;
910
page?: number | null;
1011
itemsPerPage?: number | null;
12+
firstEvaluatedKey?: [number] | null;
13+
lastEvaluatedKey?: [number] | null;
1114
}) {
12-
const { address, assetType, page = 1, itemsPerPage = 15 } = params;
15+
const {
16+
address,
17+
assetType,
18+
page = 1,
19+
itemsPerPage = 15,
20+
firstEvaluatedKey,
21+
lastEvaluatedKey
22+
} = params;
1323
const query: any = getAggsUTXOQuery({
1424
address,
15-
assetType
25+
assetType,
26+
order: "assetType",
27+
firstEvaluatedKey,
28+
lastEvaluatedKey
1629
});
1730

1831
const includeArray: any = [
@@ -27,9 +40,15 @@ export async function getByAddress(params: {
2740
where: {
2841
[Sequelize.Op.and]: query
2942
},
30-
order: [["address", "DESC"], ["assetType", "DESC"]],
43+
order: aggsUTXOPagination.byAssetType.orderby({
44+
firstEvaluatedKey,
45+
lastEvaluatedKey
46+
}),
3147
limit: itemsPerPage!,
32-
offset: (page! - 1) * itemsPerPage!,
48+
offset:
49+
firstEvaluatedKey || lastEvaluatedKey
50+
? 0
51+
: (page! - 1) * itemsPerPage!,
3352
include: includeArray
3453
});
3554
} catch (err) {
@@ -43,11 +62,23 @@ export async function getByAssetType(params: {
4362
address?: string | null;
4463
page?: number | null;
4564
itemsPerPage?: number | null;
65+
firstEvaluatedKey?: [number] | null;
66+
lastEvaluatedKey?: [number] | null;
4667
}) {
47-
const { address, assetType, page = 1, itemsPerPage = 15 } = params;
68+
const {
69+
address,
70+
assetType,
71+
page = 1,
72+
itemsPerPage = 15,
73+
firstEvaluatedKey,
74+
lastEvaluatedKey
75+
} = params;
4876
const query: any = getAggsUTXOQuery({
4977
address,
50-
assetType
78+
assetType,
79+
order: "address",
80+
firstEvaluatedKey,
81+
lastEvaluatedKey
5182
});
5283

5384
const includeArray: any = [
@@ -62,9 +93,15 @@ export async function getByAssetType(params: {
6293
where: {
6394
[Sequelize.Op.and]: query
6495
},
65-
order: [["assetType", "DESC"], ["address", "DESC"]],
96+
order: aggsUTXOPagination.byAddress.orderby({
97+
firstEvaluatedKey,
98+
lastEvaluatedKey
99+
}),
66100
limit: itemsPerPage!,
67-
offset: (page! - 1) * itemsPerPage!,
101+
offset:
102+
firstEvaluatedKey || lastEvaluatedKey
103+
? 0
104+
: (page! - 1) * itemsPerPage!,
68105
include: includeArray
69106
});
70107
} catch (err) {
@@ -76,8 +113,17 @@ export async function getByAssetType(params: {
76113
function getAggsUTXOQuery(params: {
77114
address?: string | null;
78115
assetType?: H160 | null;
116+
order: "assetType" | "address";
117+
firstEvaluatedKey?: [number] | null;
118+
lastEvaluatedKey?: [number] | null;
79119
}) {
80-
const { address, assetType } = params;
120+
const {
121+
order,
122+
address,
123+
assetType,
124+
firstEvaluatedKey,
125+
lastEvaluatedKey
126+
} = params;
81127
const query = [];
82128
if (address) {
83129
query.push({
@@ -90,5 +136,23 @@ function getAggsUTXOQuery(params: {
90136
});
91137
}
92138

139+
if (firstEvaluatedKey || lastEvaluatedKey) {
140+
if (order === "assetType") {
141+
query.push(
142+
aggsUTXOPagination.byAssetType.where({
143+
firstEvaluatedKey,
144+
lastEvaluatedKey
145+
})
146+
);
147+
} else if (order === "address") {
148+
query.push(
149+
aggsUTXOPagination.byAddress.where({
150+
firstEvaluatedKey,
151+
lastEvaluatedKey
152+
})
153+
);
154+
}
155+
}
156+
93157
return query;
94158
}

src/models/logic/utxo.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as Sequelize from "sequelize";
33
import models from "..";
44
import * as Exception from "../../exception";
55
import { utxoPagination } from "../../routers/pagination";
6+
import { AggsUTXOAttribute } from "../aggsUTXO";
67
import { AssetSchemeAttribute } from "../assetscheme";
78
import { TransactionInstance } from "../transaction";
89
import { AssetTransferOutput } from "../transferAsset";
@@ -309,31 +310,55 @@ export function createUTXOEvaluatedKey(utxo: UTXOAttribute): string {
309310
]);
310311
}
311312

313+
export function createAggsUTXOEvaluatedKey(
314+
aggs: AggsUTXOAttribute,
315+
order: "assetType" | "address"
316+
): string {
317+
return JSON.stringify([
318+
order === "assetType" ? aggs.assetType : aggs.address
319+
]);
320+
}
321+
312322
export async function getAggsUTXO(params: {
323+
order: "assetType" | "address";
313324
address?: string | null;
314325
assetType?: H160 | null;
315326
shardId?: number | null;
316327
page?: number | null;
317328
itemsPerPage?: number | null;
329+
firstEvaluatedKey?: [number] | null;
330+
lastEvaluatedKey?: [number] | null;
318331
onlyConfirmed?: boolean | null;
319332
confirmThreshold?: number | null;
320333
}) {
321-
const { address, assetType, page = 1, itemsPerPage = 15 } = params;
334+
const {
335+
order,
336+
address,
337+
assetType,
338+
page = 1,
339+
itemsPerPage = 15,
340+
firstEvaluatedKey,
341+
lastEvaluatedKey
342+
} = params;
322343

323344
try {
324-
if (address) {
345+
if (order === "assetType") {
325346
return await AggsUTXOModel.getByAddress({
326-
address,
347+
address: address!,
327348
assetType,
328349
page,
329-
itemsPerPage
350+
itemsPerPage,
351+
firstEvaluatedKey,
352+
lastEvaluatedKey
330353
});
331-
} else if (assetType) {
354+
} else if (order === "address") {
332355
return await AggsUTXOModel.getByAssetType({
333356
address,
334-
assetType,
357+
assetType: assetType!,
335358
page,
336-
itemsPerPage
359+
itemsPerPage,
360+
firstEvaluatedKey,
361+
lastEvaluatedKey
337362
});
338363
} else {
339364
throw new Error("address == aggs == null");

src/routers/asset.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import * as UTXOModel from "../models/logic/utxo";
1414
import { createPaginationResult } from "./pagination";
1515
import {
16+
aggsUTXOPaginationSchema,
1617
assetTypeSchema,
1718
paginationSchema,
1819
snapshotSchema,
@@ -312,6 +313,16 @@ export function handle(context: IndexerContext, router: Router) {
312313
* in: query
313314
* required: false
314315
* type: number
316+
* - name: firstEvaluatedKey
317+
* description: the evaulated key of the first item in the previous page. It will be used for the pagination
318+
* in: query
319+
* required: false
320+
* type: string
321+
* - name: lastEvaluatedKey
322+
* description: the evaulated key of the last item in the previous page. It will be used for the pagination
323+
* in: query
324+
* required: false
325+
* type: string
315326
* - name: onlyConfirmed
316327
* description: returns only confirmed component
317328
* in: query
@@ -336,10 +347,12 @@ export function handle(context: IndexerContext, router: Router) {
336347
*/
337348
router.get(
338349
"/aggs-utxo",
350+
parseEvaluatedKey,
339351
validate({
340352
query: {
341353
...utxoSchema,
342-
...paginationSchema
354+
...paginationSchema,
355+
...aggsUTXOPaginationSchema
343356
}
344357
}),
345358
syncIfNeeded(context),
@@ -350,27 +363,53 @@ export function handle(context: IndexerContext, router: Router) {
350363
req.query.shardId && parseInt(req.query.shardId, 10);
351364
const page = req.query.page && parseInt(req.query.page, 10);
352365
const itemsPerPage =
353-
req.query.itemsPerPage && parseInt(req.query.itemsPerPage, 10);
366+
(req.query.itemsPerPage &&
367+
parseInt(req.query.itemsPerPage, 10)) ||
368+
15;
354369
const onlyConfirmed = req.query.onlyConfirmed;
355370
const confirmThreshold =
356371
req.query.confirmThreshold &&
357372
parseInt(req.query.confirmThreshold, 10);
373+
const firstEvaluatedKey = req.query.firstEvaluatedKey;
374+
const lastEvaluatedKey = req.query.lastEvaluatedKey;
358375
let assetType;
376+
let order: "assetType" | "address";
377+
if (address) {
378+
order = "assetType";
379+
} else {
380+
order = "address";
381+
}
382+
359383
try {
360384
if (assetTypeString) {
361385
assetType = new H160(assetTypeString);
362386
}
363387
const aggsInst = await UTXOModel.getAggsUTXO({
388+
order,
364389
address,
365390
assetType,
366391
shardId,
367392
page,
368-
itemsPerPage,
393+
itemsPerPage: itemsPerPage + 1,
394+
firstEvaluatedKey,
395+
lastEvaluatedKey,
369396
onlyConfirmed,
370397
confirmThreshold
371398
});
372-
const utxo = aggsInst.map(inst => inst.get({ plain: true }));
373-
res.json(utxo);
399+
const aggs = aggsInst.map(inst => inst.get({ plain: true }));
400+
401+
res.json(
402+
createPaginationResult({
403+
query: {
404+
firstEvaluatedKey,
405+
lastEvaluatedKey
406+
},
407+
rows: aggs,
408+
getEvaluatedKey: row =>
409+
UTXOModel.createAggsUTXOEvaluatedKey(row, order),
410+
itemsPerPage
411+
})
412+
);
374413
} catch (e) {
375414
next(e);
376415
}

src/routers/pagination.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,80 @@ export const blockPagination = {
143143
}
144144
}
145145
};
146+
147+
export const aggsUTXOPagination = {
148+
byAssetType: {
149+
forwardOrder: [["assetType", "DESC"]],
150+
reverseOrder: [["assetType", "ASC"]],
151+
orderby: (params: {
152+
firstEvaluatedKey?: number[] | null;
153+
lastEvaluatedKey?: number[] | null;
154+
}) => {
155+
const order = queryOrder(params);
156+
if (order === "forward") {
157+
return aggsUTXOPagination.byAssetType.forwardOrder;
158+
} else if (order === "reverse") {
159+
return aggsUTXOPagination.byAssetType.reverseOrder;
160+
}
161+
},
162+
where: (params: {
163+
firstEvaluatedKey?: number[] | null;
164+
lastEvaluatedKey?: number[] | null;
165+
}) => {
166+
const order = queryOrder(params);
167+
const { firstEvaluatedKey, lastEvaluatedKey } = params;
168+
if (order === "forward") {
169+
const assetType = lastEvaluatedKey![0];
170+
return {
171+
assetType: {
172+
[Sequelize.Op.lt]: assetType
173+
}
174+
};
175+
} else if (order === "reverse") {
176+
const assetType = firstEvaluatedKey![0];
177+
return {
178+
assetType: {
179+
[Sequelize.Op.gt]: assetType
180+
}
181+
};
182+
}
183+
}
184+
},
185+
byAddress: {
186+
forwardOrder: [["address", "DESC"]],
187+
reverseOrder: [["address", "ASC"]],
188+
orderby: (params: {
189+
firstEvaluatedKey?: number[] | null;
190+
lastEvaluatedKey?: number[] | null;
191+
}) => {
192+
const order = queryOrder(params);
193+
if (order === "forward") {
194+
return aggsUTXOPagination.byAddress.forwardOrder;
195+
} else if (order === "reverse") {
196+
return aggsUTXOPagination.byAddress.reverseOrder;
197+
}
198+
},
199+
where: (params: {
200+
firstEvaluatedKey?: number[] | null;
201+
lastEvaluatedKey?: number[] | null;
202+
}) => {
203+
const order = queryOrder(params);
204+
const { firstEvaluatedKey, lastEvaluatedKey } = params;
205+
if (order === "forward") {
206+
const address = lastEvaluatedKey![0];
207+
return {
208+
address: {
209+
[Sequelize.Op.lt]: address
210+
}
211+
};
212+
} else if (order === "reverse") {
213+
const address = firstEvaluatedKey![0];
214+
return {
215+
address: {
216+
[Sequelize.Op.gt]: address
217+
}
218+
};
219+
}
220+
}
221+
}
222+
};

src/routers/validator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export const blockPaginationSchema = {
8888
firstEvaluatedKey: blockEvaluationKey
8989
};
9090

91+
export const aggsUTXOPaginationSchema = {
92+
firstEvaluatedKey: Joi.array().items(Joi.string()),
93+
lastEvaluatedKey: Joi.array().items(Joi.string())
94+
};
95+
9196
export const txSchema = {
9297
address,
9398
assetType: assetTypeSchema,

0 commit comments

Comments
 (0)