Skip to content

Commit 474427e

Browse files
Park JuhyungPark Juhyung
authored andcommitted
Use an index to find rows in the next page in the UTXO 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 `lastEvaluatedKey` for the pagination in the UTXO query. The UTXO query will find a row using `lastEvaluatedKey` and returns next `n` rows.
1 parent 8b0c80b commit 474427e

File tree

4 files changed

+81
-9
lines changed

4 files changed

+81
-9
lines changed

src/models/logic/utils/middleware.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RequestHandler } from "express";
1+
import { NextFunction, Request, RequestHandler, Response } from "express";
22
import { SERVICE_UNAVAILABLE } from "http-status-codes";
33
import { IndexerContext } from "../../../context";
44

@@ -19,3 +19,18 @@ export function syncIfNeeded(context: IndexerContext): RequestHandler {
1919
next();
2020
};
2121
}
22+
23+
export function parseLastEvaluatedKey(
24+
req: Request,
25+
_: Response,
26+
next: NextFunction
27+
): any {
28+
try {
29+
if (req.query.lastEvaluatedKey) {
30+
req.query.lastEvaluatedKey = JSON.parse(req.query.lastEvaluatedKey);
31+
}
32+
next();
33+
} catch (e) {
34+
next(e);
35+
}
36+
}

src/models/logic/utxo.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,15 @@ async function getUTXOQuery(params: {
148148
shardId?: number | null;
149149
onlyConfirmed?: boolean | null;
150150
confirmThreshold?: number | null;
151+
lastEvaluatedKey?: number[] | null;
151152
}) {
152153
const {
153154
address,
154155
shardId,
155156
onlyConfirmed,
156157
confirmThreshold,
157-
assetType
158+
assetType,
159+
lastEvaluatedKey
158160
} = params;
159161
const query = [];
160162
if (address) {
@@ -197,6 +199,18 @@ async function getUTXOQuery(params: {
197199
usedTransactionHash: null
198200
});
199201
}
202+
203+
if (lastEvaluatedKey) {
204+
const lastBlockNumber = lastEvaluatedKey[0];
205+
const lastTransactionIndex = lastEvaluatedKey[1];
206+
const lastTransactionOutputIndex = lastEvaluatedKey[2];
207+
query.push(
208+
Sequelize.literal(
209+
`("UTXO"."blockNumber", "UTXO"."transactionIndex", "UTXO"."transactionOutputIndex")<(${lastBlockNumber}, ${lastTransactionIndex}, ${lastTransactionOutputIndex})`
210+
)
211+
);
212+
}
213+
200214
return query;
201215
}
202216

@@ -206,6 +220,7 @@ export async function getUTXO(params: {
206220
shardId?: number | null;
207221
page?: number | null;
208222
itemsPerPage?: number | null;
223+
lastEvaluatedKey?: number[] | null;
209224
onlyConfirmed?: boolean | null;
210225
confirmThreshold?: number | null;
211226
}) {
@@ -215,6 +230,7 @@ export async function getUTXO(params: {
215230
shardId,
216231
page = 1,
217232
itemsPerPage = 15,
233+
lastEvaluatedKey,
218234
onlyConfirmed = false,
219235
confirmThreshold = 0
220236
} = params;
@@ -223,7 +239,8 @@ export async function getUTXO(params: {
223239
assetType,
224240
shardId,
225241
onlyConfirmed,
226-
confirmThreshold
242+
confirmThreshold,
243+
lastEvaluatedKey
227244
});
228245
let includeArray: any = [
229246
{
@@ -269,7 +286,7 @@ export async function getUTXO(params: {
269286
["transactionOutputIndex", "DESC"]
270287
],
271288
limit: itemsPerPage!,
272-
offset: (page! - 1) * itemsPerPage!,
289+
offset: lastEvaluatedKey ? 0 : (page! - 1) * itemsPerPage!,
273290
include: includeArray
274291
});
275292
} catch (err) {

src/routers/asset.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ import * as Exception from "../exception";
66
import * as AssetImageModel from "../models/logic/assetimage";
77
import * as AssetSchemeModel from "../models/logic/assetscheme";
88
import * as BlockModel from "../models/logic/block";
9-
import { syncIfNeeded } from "../models/logic/utils/middleware";
9+
import {
10+
parseLastEvaluatedKey,
11+
syncIfNeeded
12+
} from "../models/logic/utils/middleware";
1013
import * as UTXOModel from "../models/logic/utxo";
1114
import {
1215
assetTypeSchema,
1316
paginationSchema,
1417
snapshotSchema,
18+
utxoPaginationSchema,
1519
utxoSchema,
1620
validate
1721
} from "./validator";
@@ -84,6 +88,11 @@ export function handle(context: IndexerContext, router: Router) {
8488
* in: query
8589
* required: false
8690
* type: number
91+
* - name: lastEvaluatedKey
92+
* description: the evaulated key of the last item in the previous page. It will be used for the pagination
93+
* in: query
94+
* required: false
95+
* type: string
8796
* - name: itemsPerPage
8897
* description: items per page for the pagination (default 15)
8998
* in: query
@@ -114,10 +123,12 @@ export function handle(context: IndexerContext, router: Router) {
114123
*/
115124
router.get(
116125
"/utxo",
126+
parseLastEvaluatedKey,
117127
validate({
118128
query: {
119129
...utxoSchema,
120-
...paginationSchema
130+
...paginationSchema,
131+
...utxoPaginationSchema
121132
}
122133
}),
123134
syncIfNeeded(context),
@@ -128,11 +139,15 @@ export function handle(context: IndexerContext, router: Router) {
128139
req.query.shardId && parseInt(req.query.shardId, 10);
129140
const page = req.query.page && parseInt(req.query.page, 10);
130141
const itemsPerPage =
131-
req.query.itemsPerPage && parseInt(req.query.itemsPerPage, 10);
142+
(req.query.itemsPerPage &&
143+
parseInt(req.query.itemsPerPage, 10)) ||
144+
15;
132145
const onlyConfirmed = req.query.onlyConfirmed;
133146
const confirmThreshold =
134147
req.query.confirmThreshold &&
135148
parseInt(req.query.confirmThreshold, 10);
149+
const lastEvaluatedKey = req.query.lastEvaluatedKey;
150+
136151
let assetType;
137152
try {
138153
if (assetTypeString) {
@@ -143,12 +158,29 @@ export function handle(context: IndexerContext, router: Router) {
143158
assetType,
144159
shardId,
145160
page,
146-
itemsPerPage,
161+
itemsPerPage: itemsPerPage + 1,
162+
lastEvaluatedKey,
147163
onlyConfirmed,
148164
confirmThreshold
149165
});
150166
const utxo = utxoInsts.map(inst => inst.get({ plain: true }));
151-
res.json(utxo);
167+
const hasNextPage = utxo.length === itemsPerPage + 1;
168+
if (hasNextPage) {
169+
utxo.pop();
170+
}
171+
const lastItem = utxo[utxo.length - 1];
172+
173+
res.json({
174+
data: utxo,
175+
lastEvaluatedKey: lastItem
176+
? JSON.stringify([
177+
lastItem.blockNumber,
178+
lastItem.transactionIndex,
179+
lastItem.transactionOutputIndex
180+
])
181+
: null,
182+
hasNextPage
183+
});
152184
} catch (e) {
153185
next(e);
154186
}

src/routers/validator.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ export const paginationSchema = {
6969
.max(100)
7070
};
7171

72+
export const utxoPaginationSchema = {
73+
lastEvaluatedKey: Joi.array().items(
74+
Joi.number(),
75+
Joi.number(),
76+
Joi.number()
77+
)
78+
};
79+
7280
export const txSchema = {
7381
address,
7482
assetType: assetTypeSchema,

0 commit comments

Comments
 (0)