Skip to content

Commit e911289

Browse files
Park JuhyungPark Juhyung
authored andcommitted
Use an index to find rows in the next page in the PendingTx 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 PendingTx query. The PendingTx query will find a row using `firstEvaluatedKey` or `lastEvaluatedKey` and returns next `n` rows.
1 parent 5c38bcb commit e911289

File tree

5 files changed

+285
-78
lines changed

5 files changed

+285
-78
lines changed

src/models/logic/transaction.ts

Lines changed: 145 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import * as _ from "lodash";
1010
import * as Sequelize from "sequelize";
1111
import { Transaction } from "sequelize";
1212
import * as Exception from "../../exception";
13-
import { txPagination } from "../../routers/pagination";
13+
import {
14+
addressLogPagination,
15+
pendingTxPagination,
16+
txPagination
17+
} from "../../routers/pagination";
18+
import { AddressLogType } from "../addressLog";
1419
import models from "../index";
1520
import { TransactionAttribute, TransactionInstance } from "../transaction";
1621
import { createAddressLog } from "./addressLog";
@@ -245,39 +250,156 @@ export async function applyTransaction(
245250

246251
export async function getPendingTransactions(params: {
247252
address?: string | null;
248-
assetType?: string | null;
249-
type?: string[] | null;
250253
page: number;
251254
itemsPerPage: number;
255+
firstEvaluatedKey: [number, number] | null;
256+
lastEvaluatedKey: [number, number] | null;
252257
}) {
253-
const { address, assetType, type, page, itemsPerPage } = params;
254-
const query = {
255-
isPending: true,
256-
...(type == null
257-
? {}
258-
: {
259-
[Sequelize.Op.in]: type
260-
})
261-
};
258+
const { address } = params;
259+
260+
if (address) {
261+
return getPendingTransactionsByAddress({
262+
...params,
263+
...{
264+
address
265+
}
266+
});
267+
} else {
268+
return getAnyPendingTransactions(params);
269+
}
270+
}
271+
272+
async function getAnyPendingTransactions(params: {
273+
page: number;
274+
itemsPerPage: number;
275+
firstEvaluatedKey: [number, number] | null;
276+
lastEvaluatedKey: [number, number] | null;
277+
}) {
278+
const { page, itemsPerPage, firstEvaluatedKey, lastEvaluatedKey } = params;
279+
const query: any[] = [
280+
{
281+
isPending: true
282+
}
283+
];
284+
285+
if (firstEvaluatedKey || lastEvaluatedKey) {
286+
query.push(
287+
pendingTxPagination.where({
288+
firstEvaluatedKey,
289+
lastEvaluatedKey
290+
})
291+
);
292+
}
293+
262294
try {
263295
return models.Transaction.findAll({
264296
where: {
265-
...query
297+
[Sequelize.Op.and]: query
266298
},
267-
order: [["pendingTimestamp", "DESC"]],
299+
order: pendingTxPagination.orderby({
300+
firstEvaluatedKey,
301+
lastEvaluatedKey
302+
}),
268303
limit: itemsPerPage,
269-
offset: (page - 1) * itemsPerPage,
270-
include: [
271-
...fullIncludeArray,
272-
...buildIncludeArray({ address, assetType })
273-
]
304+
offset:
305+
firstEvaluatedKey || lastEvaluatedKey
306+
? 0
307+
: (page - 1) * itemsPerPage,
308+
include: [...fullIncludeArray]
309+
});
310+
} catch (err) {
311+
console.error(err);
312+
throw Exception.DBError();
313+
}
314+
}
315+
316+
async function getPendingTransactionsByAddress(params: {
317+
address: string;
318+
page: number;
319+
itemsPerPage: number;
320+
firstEvaluatedKey: [number, number] | null;
321+
lastEvaluatedKey: [number, number] | null;
322+
}) {
323+
const { itemsPerPage, firstEvaluatedKey, lastEvaluatedKey } = params;
324+
325+
const hashes = await getPendingHashesByPlatformAddress(params);
326+
assert(
327+
hashes.length <= itemsPerPage,
328+
`The number of hashes(${
329+
hashes.length
330+
}) must not be greater than itemsPerPage(${itemsPerPage})`
331+
);
332+
try {
333+
return models.Transaction.findAll({
334+
where: {
335+
hash: hashes
336+
},
337+
order: addressLogPagination.bySeq.orderby({
338+
firstEvaluatedKey,
339+
lastEvaluatedKey
340+
}),
341+
limit: itemsPerPage,
342+
include: [...fullIncludeArray]
274343
});
275344
} catch (err) {
276345
console.error(err);
277346
throw Exception.DBError();
278347
}
279348
}
280349

350+
async function getPendingHashesByPlatformAddress(params: {
351+
address: string;
352+
page: number;
353+
itemsPerPage: number;
354+
firstEvaluatedKey: [number, number] | null;
355+
lastEvaluatedKey: [number, number] | null;
356+
}): Promise<string[]> {
357+
const {
358+
address,
359+
page,
360+
itemsPerPage,
361+
firstEvaluatedKey,
362+
lastEvaluatedKey
363+
} = params;
364+
365+
const addressLogType: AddressLogType = "TransactionSigner";
366+
const whereCond: any[] = [
367+
{
368+
address,
369+
isPending: true,
370+
type: addressLogType
371+
}
372+
];
373+
if (firstEvaluatedKey || lastEvaluatedKey) {
374+
whereCond.push(
375+
addressLogPagination.bySeq.where({
376+
firstEvaluatedKey,
377+
lastEvaluatedKey
378+
})
379+
);
380+
}
381+
try {
382+
return models.AddressLog.findAll({
383+
attributes: ["transactionHash"],
384+
where: {
385+
[Sequelize.Op.and]: whereCond
386+
},
387+
order: addressLogPagination.bySeq.orderby({
388+
firstEvaluatedKey,
389+
lastEvaluatedKey
390+
}),
391+
limit: itemsPerPage,
392+
offset:
393+
firstEvaluatedKey || lastEvaluatedKey
394+
? 0
395+
: (page - 1) * itemsPerPage
396+
}).map(r => r.get("transactionHash"));
397+
} catch (err) {
398+
console.error(err);
399+
throw Exception.DBError();
400+
}
401+
}
402+
281403
export async function getAllPendingTransactionHashes() {
282404
try {
283405
return models.Transaction.findAll({
@@ -760,37 +882,6 @@ export async function getSuccessfulByTracker(
760882
}
761883
}
762884

763-
function buildIncludeArray(params: {
764-
address?: string | null;
765-
assetType?: string | null;
766-
}): Sequelize.IncludeOptions[] {
767-
const { address, assetType } = params;
768-
return [
769-
...(address == null
770-
? []
771-
: [
772-
{
773-
model: models.AddressLog,
774-
as: "addressLogs",
775-
attributes: [],
776-
where: { address },
777-
required: true
778-
}
779-
]),
780-
...(assetType == null
781-
? []
782-
: [
783-
{
784-
model: models.AssetTypeLog,
785-
as: "assetTypeLogs",
786-
attributes: [],
787-
where: { assetType },
788-
required: true
789-
}
790-
])
791-
];
792-
}
793-
794885
export async function getRegularKeyOwnerByPublicKey(
795886
pubkey: string,
796887
options: { transaction?: Transaction; blockNumber?: number }
@@ -826,3 +917,7 @@ export async function getRegularKeyOwnerByPublicKey(
826917
export function createTxEvaluatedKey(tx: TransactionAttribute) {
827918
return JSON.stringify([tx.blockNumber, tx.transactionIndex]);
828919
}
920+
921+
export function createPendingTxEvaluatedKey(tx: TransactionAttribute) {
922+
return JSON.stringify([tx.pendingTimestamp]);
923+
}

src/routers/pagination.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,81 @@ export const txPagination = {
256256
}
257257
}
258258
};
259+
260+
export const addressLogPagination = {
261+
bySeq: {
262+
forwardOrder: [["seq", "ASC"]],
263+
reverseOrder: [["seq", "DESC"]],
264+
orderby: (params: {
265+
firstEvaluatedKey?: number[] | null;
266+
lastEvaluatedKey?: number[] | null;
267+
}) => {
268+
const order = queryOrder(params);
269+
if (order === "forward") {
270+
return addressLogPagination.bySeq.forwardOrder;
271+
} else if (order === "reverse") {
272+
return addressLogPagination.bySeq.reverseOrder;
273+
}
274+
},
275+
where: (params: {
276+
firstEvaluatedKey?: number[] | null;
277+
lastEvaluatedKey?: number[] | null;
278+
}) => {
279+
const order = queryOrder(params);
280+
const { firstEvaluatedKey, lastEvaluatedKey } = params;
281+
if (order === "forward") {
282+
const [seq] = lastEvaluatedKey!;
283+
return {
284+
seq: {
285+
[Sequelize.Op.gt]: seq
286+
}
287+
};
288+
} else if (order === "reverse") {
289+
const [seq] = firstEvaluatedKey!;
290+
return {
291+
seq: {
292+
[Sequelize.Op.lt]: seq
293+
}
294+
};
295+
}
296+
}
297+
}
298+
};
299+
300+
export const pendingTxPagination = {
301+
forwardOrder: [["pendingTimestamp", "DESC"]],
302+
reverseOrder: [["pendingTimestamp", "ASC"]],
303+
orderby: (params: {
304+
firstEvaluatedKey?: number[] | null;
305+
lastEvaluatedKey?: number[] | null;
306+
}) => {
307+
const order = queryOrder(params);
308+
if (order === "forward") {
309+
return pendingTxPagination.forwardOrder;
310+
} else if (order === "reverse") {
311+
return pendingTxPagination.reverseOrder;
312+
}
313+
},
314+
where: (params: {
315+
firstEvaluatedKey?: number[] | null;
316+
lastEvaluatedKey?: number[] | null;
317+
}) => {
318+
const order = queryOrder(params);
319+
const { firstEvaluatedKey, lastEvaluatedKey } = params;
320+
if (order === "forward") {
321+
const [pendingTimestamp] = lastEvaluatedKey!;
322+
return {
323+
pendingTimestamp: {
324+
[Sequelize.Op.lt]: pendingTimestamp
325+
}
326+
};
327+
} else if (order === "reverse") {
328+
const [pendingTimestamp] = firstEvaluatedKey!;
329+
return {
330+
pendingTimestamp: {
331+
[Sequelize.Op.gt]: pendingTimestamp
332+
}
333+
};
334+
}
335+
}
336+
};

0 commit comments

Comments
 (0)