From 1893b0054af7e103b3193d33c1324a5a899fe785 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Wed, 8 May 2019 10:14:00 +0900 Subject: [PATCH] Optimize the API to get transactions The previous query joins all rows of tables to calculate offset even though it needs only itemsPerPage rows. This patch splits the query to get transactions. The first query gets the transaction hash that the second query selects. It helps the second query not to scan all the table of the rows of included tables. --- src/models/logic/transaction.ts | 71 +++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/src/models/logic/transaction.ts b/src/models/logic/transaction.ts index 5729c48..dd0e885 100644 --- a/src/models/logic/transaction.ts +++ b/src/models/logic/transaction.ts @@ -1,10 +1,11 @@ +import * as assert from "assert"; import { SDK } from "codechain-sdk"; import { H160, H256, SignedTransaction } from "codechain-sdk/lib/core/classes"; import * as _ from "lodash"; import * as Sequelize from "sequelize"; import * as Exception from "../../exception"; import models from "../index"; -import { TransactionInstance } from "../transaction"; +import { TransactionAttribute, TransactionInstance } from "../transaction"; import { createAddressLog } from "./addressLog"; import { updateAssetScheme } from "./assetscheme"; import { createChangeAssetScheme } from "./changeAssetScheme"; @@ -425,6 +426,60 @@ export async function removeOutdatedPendings( } } +async function getTransactionHashes(params: { + address?: string | null; + addressFilter?: string[] | null; + assetType?: H160 | null; + page: number; + itemsPerPage: number; + type?: string[] | null; + tracker?: H256 | null; + blockNumber?: number | null; + blockHash?: H256 | null; + includePending?: boolean | null; + onlySuccessful?: boolean | null; +}): Promise { + const where: Sequelize.WhereOptions = {}; + if (params.type != null) { + where.type = { [Sequelize.Op.in]: params.type }; + } + if (params.tracker != null) { + where.tracker = params.tracker.value; + } + if (params.blockNumber != null) { + where.blockNumber = params.blockNumber; + } + if (params.blockHash != null) { + where.blockHash = params.blockHash.value; + } + if (params.onlySuccessful) { + where.errorHint = null; + } + if (params.includePending !== true) { + where.isPending = false; + } + const { page, itemsPerPage, address, addressFilter, assetType } = params; + try { + return await models.Transaction.findAll({ + subQuery: false, + attributes: ["hash"], + where, + order: [["blockNumber", "DESC"], ["transactionIndex", "DESC"]], + group: [ + "Transaction.hash", + "Transaction.blockNumber", + "Transaction.transactionIndex" + ], + limit: itemsPerPage, + offset: (page - 1) * itemsPerPage, + include: buildIncludeArray({ address, addressFilter, assetType }) + }).map(result => result.get("hash")); + } catch (err) { + console.error(err); + return []; + } +} + export async function getTransactions(params: { address?: string | null; addressFilter?: string[] | null; @@ -440,15 +495,23 @@ export async function getTransactions(params: { onlySuccessful?: boolean | null; confirmThreshold?: number | null; }) { - const { address, addressFilter, assetType, page, itemsPerPage } = params; + const { address, addressFilter, assetType, itemsPerPage } = params; try { + // TODO: Querying twice will waste IO bandwidth and take longer time as long as the response time + // Find a way to merge these queries. + const hashes = await getTransactionHashes(params); + assert( + hashes.length <= itemsPerPage, + `The number of hashes(${ + hashes.length + }) must not be greater than itemsPerPage(${itemsPerPage})` + ); return models.Transaction.findAll({ where: { + hash: hashes, ...buildQueryForTransactions(params) }, order: [["blockNumber", "DESC"], ["transactionIndex", "DESC"]], - limit: itemsPerPage, - offset: (page - 1) * itemsPerPage, include: [ ...fullIncludeArray, ...buildIncludeArray({ address, addressFilter, assetType })