Skip to content

Commit 4ecb171

Browse files
majectyjoojis
authored andcommitted
Use an index to find rows in the next page in the BalanceHistory 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 BalanceHistory query. The BalanceHistory query will find a row using `firstEvaluatedKey` or `lastEvaluatedKey` and returns next `n` rows.
1 parent 2637cca commit 4ecb171

File tree

5 files changed

+129
-12
lines changed

5 files changed

+129
-12
lines changed

src/models/cccChanges.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const defaultAllReasons = [
2222
];
2323

2424
export interface CCCChangeAttribute {
25+
id?: string;
2526
address: string;
2627
change: string;
2728
blockNumber: number;

src/models/logic/cccChange.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { U64 } from "codechain-sdk/lib/core/classes";
22
import { Transaction } from "sequelize";
33
import * as Sequelize from "sequelize";
44
import * as Exception from "../../exception";
5-
import { CCCChangeInstance, defaultAllReasons, Reason } from "../cccChanges";
5+
import { cccChangesPagination } from "../../routers/pagination";
6+
import {
7+
CCCChangeAttribute,
8+
CCCChangeInstance,
9+
defaultAllReasons,
10+
Reason
11+
} from "../cccChanges";
612
import models from "../index";
713

814
async function createCCCChange(
@@ -210,23 +216,45 @@ export async function getByAddress(
210216
page: number;
211217
itemsPerPage: number;
212218
reasonFilter?: string[];
219+
firstEvaluatedKey?: number[] | null;
220+
lastEvaluatedKey?: number[] | null;
213221
}
214222
): Promise<CCCChangeInstance[]> {
215-
const { page, itemsPerPage, reasonFilter = defaultAllReasons } = option;
223+
const {
224+
page,
225+
itemsPerPage,
226+
reasonFilter = defaultAllReasons,
227+
firstEvaluatedKey,
228+
lastEvaluatedKey
229+
} = option;
216230
try {
231+
const whereCond: any[] = [
232+
{
233+
address,
234+
reason: {
235+
[Sequelize.Op.in]: reasonFilter
236+
}
237+
}
238+
];
239+
if (firstEvaluatedKey || lastEvaluatedKey) {
240+
whereCond.push(
241+
cccChangesPagination.byAccount.where({
242+
firstEvaluatedKey,
243+
lastEvaluatedKey
244+
})
245+
);
246+
}
217247
return await models.CCCChange.findAll({
218248
attributes: [
249+
"id",
219250
"address",
220251
"change",
221252
"blockNumber",
222253
"reason",
223254
"transactionHash"
224255
],
225256
where: {
226-
address,
227-
reason: {
228-
[Sequelize.Op.in]: reasonFilter
229-
}
257+
[Sequelize.Op.and]: whereCond
230258
},
231259
include: [
232260
{
@@ -236,15 +264,23 @@ export async function getByAddress(
236264
}
237265
],
238266
limit: itemsPerPage,
239-
offset: (page - 1) * itemsPerPage,
240-
order: [["blockNumber", "DESC"], ["id", "DESC"]]
267+
offset:
268+
firstEvaluatedKey || lastEvaluatedKey
269+
? 0
270+
: (page - 1) * itemsPerPage,
271+
order: [["blockNumber", "DESC"], ["id", "DESC"]],
272+
logging: console.log
241273
});
242274
} catch (err) {
243275
console.error(err);
244276
throw Exception.DBError();
245277
}
246278
}
247279

280+
export function createCCCChangesEvaluatedKey(cccChange: CCCChangeAttribute) {
281+
return JSON.stringify([cccChange.blockNumber, cccChange.id]);
282+
}
283+
248284
export async function getCountByAddress(
249285
address: string,
250286
option: {

src/routers/account.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { Router } from "express";
22
import { IndexerContext } from "../context";
33
import * as AccountModel from "../models/logic/account";
44
import * as CCCChangeModel from "../models/logic/cccChange";
5+
import { parseEvaluatedKey } from "../models/logic/utils/middleware";
6+
import { createPaginationResult } from "./pagination";
57
import {
8+
accountBalanceHistoryPaginationSchema,
69
paginationSchema,
710
platformAddressSchema,
811
reasonFilterSchema,
@@ -131,6 +134,16 @@ export function handle(_C: IndexerContext, router: Router) {
131134
* in: query
132135
* required: false
133136
* type: number
137+
* - name: firstEvaluatedKey
138+
* description: the evaulated key of the first item in the previous page. It will be used for the pagination
139+
* in: query
140+
* required: false
141+
* type: string
142+
* - name: lastEvaluatedKey
143+
* description: the evaulated key of the last item in the previous page. It will be used for the pagination
144+
* in: query
145+
* required: false
146+
* type: string
134147
* responses:
135148
* 200:
136149
* description: account
@@ -155,9 +168,14 @@ export function handle(_C: IndexerContext, router: Router) {
155168
*/
156169
router.get(
157170
"/account/:address/balance-history",
171+
parseEvaluatedKey,
158172
validate({
159173
params: { address: platformAddressSchema },
160-
query: { ...reasonFilterSchema, ...paginationSchema }
174+
query: {
175+
...reasonFilterSchema,
176+
...paginationSchema,
177+
...accountBalanceHistoryPaginationSchema
178+
}
161179
}),
162180
async (req, res, next) => {
163181
const reasonFilter = req.query.reasonFilter;
@@ -166,16 +184,37 @@ export function handle(_C: IndexerContext, router: Router) {
166184
const itemsPerPage = req.query.itemsPerPage
167185
? parseInt(req.query.itemsPerPage, 10)
168186
: 15;
187+
188+
const lastEvaluatedKey = req.query.lastEvaluatedKey;
189+
const firstEvaluatedKey = req.query.firstEvaluatedKey;
190+
169191
try {
170192
const accounts = await CCCChangeModel.getByAddress(address, {
171193
page,
172-
itemsPerPage,
194+
itemsPerPage: itemsPerPage + 1,
173195
reasonFilter:
174196
typeof reasonFilter === "string"
175197
? reasonFilter.split(",")
176-
: undefined
198+
: undefined,
199+
firstEvaluatedKey,
200+
lastEvaluatedKey
177201
});
178-
res.json(accounts.map(account => account.get({ plain: true })));
202+
203+
res.json(
204+
createPaginationResult({
205+
query: {
206+
firstEvaluatedKey,
207+
lastEvaluatedKey
208+
},
209+
rows: accounts.map(account =>
210+
account.get({ plain: true })
211+
),
212+
getEvaluatedKey:
213+
CCCChangeModel.createCCCChangesEvaluatedKey,
214+
itemsPerPage
215+
})
216+
);
217+
res.json();
179218
} catch (e) {
180219
next(e);
181220
}

src/routers/pagination.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,42 @@ export const addressLogPagination = {
335335
}
336336
};
337337

338+
export const cccChangesPagination = {
339+
byAccount: {
340+
forwardOrder: [["blockNumber", "DESC"], ["id", "DESC"]],
341+
reverseOrder: [["blockNumber", "ASC"], ["id", "ASC"]],
342+
orderby: (params: {
343+
firstEvaluatedKey?: number[] | null;
344+
lastEvaluatedKey?: number[] | null;
345+
}) => {
346+
const order = queryOrder(params);
347+
if (order === "forward") {
348+
return cccChangesPagination.byAccount.forwardOrder;
349+
} else if (order === "reverse") {
350+
return cccChangesPagination.byAccount.reverseOrder;
351+
}
352+
},
353+
where: (params: {
354+
firstEvaluatedKey?: number[] | null;
355+
lastEvaluatedKey?: number[] | null;
356+
}) => {
357+
const order = queryOrder(params);
358+
const { firstEvaluatedKey, lastEvaluatedKey } = params;
359+
if (order === "forward") {
360+
const [blockNumber, id] = lastEvaluatedKey!;
361+
return Sequelize.literal(
362+
`("CCCChange"."blockNumber", "id")<(${blockNumber}, ${id})`
363+
);
364+
} else if (order === "reverse") {
365+
const [blockNumber, id] = firstEvaluatedKey!;
366+
return Sequelize.literal(
367+
`("CCCChange"."blockNumber", "id")>(${blockNumber}, ${id})`
368+
);
369+
}
370+
}
371+
}
372+
};
373+
338374
export const pendingTxPagination = {
339375
forwardOrder: [["pendingTimestamp", "DESC"]],
340376
reverseOrder: [["pendingTimestamp", "ASC"]],

src/routers/validator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ export const snapshotPaginationSchema = {
116116
)
117117
};
118118

119+
export const accountBalanceHistoryPaginationSchema = {
120+
firstEvaluatedKey: Joi.array().items(Joi.number(), Joi.string()),
121+
lastEvaluatedKey: Joi.array().items(Joi.number(), Joi.string())
122+
};
123+
119124
export const txSchema = {
120125
address,
121126
assetType: assetTypeSchema,

0 commit comments

Comments
 (0)