perf: reserve history in separate object store

This commit is contained in:
Florian Dold 2020-05-11 21:47:35 +05:30
parent 277a513a8f
commit 857a2b9dca
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 83 additions and 19 deletions

View File

@ -7,7 +7,7 @@ import { openDatabase, Database, Store, Index } from "./util/query";
* with each major change. When incrementing the major version, * with each major change. When incrementing the major version,
* the wallet should import data from the previous version. * the wallet should import data from the previous version.
*/ */
const TALER_DB_NAME = "taler-walletdb-v2"; const TALER_DB_NAME = "taler-walletdb-v3";
/** /**
* Current database minor version, should be incremented * Current database minor version, should be incremented

View File

@ -145,7 +145,7 @@ export async function getBalances(
): Promise<WalletBalance> { ): Promise<WalletBalance> {
logger.trace("starting to compute balance"); logger.trace("starting to compute balance");
return await ws.db.runWithReadTransaction( const wbal = await ws.db.runWithReadTransaction(
[ [
Stores.coins, Stores.coins,
Stores.refreshGroups, Stores.refreshGroups,
@ -157,4 +157,8 @@ export async function getBalances(
return getBalancesInsideTransaction(ws, tx); return getBalancesInsideTransaction(ws, tx);
}, },
); );
logger.trace("finished computing wallet balance");
return wbal;
} }

View File

@ -172,6 +172,7 @@ export async function getHistory(
Stores.purchases, Stores.purchases,
Stores.refreshGroups, Stores.refreshGroups,
Stores.reserves, Stores.reserves,
Stores.reserveHistory,
Stores.tips, Stores.tips,
Stores.withdrawalGroups, Stores.withdrawalGroups,
Stores.payEvents, Stores.payEvents,
@ -384,8 +385,12 @@ export async function getHistory(
type: ReserveType.Manual, type: ReserveType.Manual,
}; };
} }
const hist = await tx.get(Stores.reserveHistory, reserve.reservePub);
if (!hist) {
throw Error("inconsistent database");
}
const s = summarizeReserveHistory( const s = summarizeReserveHistory(
reserve.reserveTransactions, hist.reserveTransactions,
reserve.currency, reserve.currency,
); );
history.push({ history.push({

View File

@ -33,8 +33,8 @@ import {
updateRetryInfoTimeout, updateRetryInfoTimeout,
ReserveUpdatedEventRecord, ReserveUpdatedEventRecord,
WalletReserveHistoryItemType, WalletReserveHistoryItemType,
PlanchetRecord,
WithdrawalSourceType, WithdrawalSourceType,
ReserveHistoryRecord,
} from "../types/dbTypes"; } from "../types/dbTypes";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
import { Amounts } from "../util/amounts"; import { Amounts } from "../util/amounts";
@ -114,11 +114,15 @@ export async function createReserve(
lastSuccessfulStatusQuery: undefined, lastSuccessfulStatusQuery: undefined,
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastError: undefined, lastError: undefined,
reserveTransactions: [],
currency: req.amount.currency, currency: req.amount.currency,
}; };
reserveRecord.reserveTransactions.push({ const reserveHistoryRecord: ReserveHistoryRecord = {
reservePub: keypair.pub,
reserveTransactions: [],
};
reserveHistoryRecord.reserveTransactions.push({
type: WalletReserveHistoryItemType.Credit, type: WalletReserveHistoryItemType.Credit,
expectedAmount: req.amount, expectedAmount: req.amount,
}); });
@ -161,7 +165,12 @@ export async function createReserve(
const cr: CurrencyRecord = currencyRecord; const cr: CurrencyRecord = currencyRecord;
const resp = await ws.db.runWithWriteTransaction( const resp = await ws.db.runWithWriteTransaction(
[Stores.currencies, Stores.reserves, Stores.bankWithdrawUris], [
Stores.currencies,
Stores.reserves,
Stores.reserveHistory,
Stores.bankWithdrawUris,
],
async (tx) => { async (tx) => {
// Check if we have already created a reserve for that bankWithdrawStatusUrl // Check if we have already created a reserve for that bankWithdrawStatusUrl
if (reserveRecord.bankWithdrawStatusUrl) { if (reserveRecord.bankWithdrawStatusUrl) {
@ -188,6 +197,7 @@ export async function createReserve(
} }
await tx.put(Stores.currencies, cr); await tx.put(Stores.currencies, cr);
await tx.put(Stores.reserves, reserveRecord); await tx.put(Stores.reserves, reserveRecord);
await tx.put(Stores.reserveHistory, reserveHistoryRecord);
const r: CreateReserveResponse = { const r: CreateReserveResponse = {
exchange: canonExchange, exchange: canonExchange,
reservePub: keypair.pub, reservePub: keypair.pub,
@ -462,7 +472,7 @@ async function updateReserve(
const balance = Amounts.parseOrThrow(reserveInfo.balance); const balance = Amounts.parseOrThrow(reserveInfo.balance);
const currency = balance.currency; const currency = balance.currency;
await ws.db.runWithWriteTransaction( await ws.db.runWithWriteTransaction(
[Stores.reserves, Stores.reserveUpdatedEvents], [Stores.reserves, Stores.reserveUpdatedEvents, Stores.reserveHistory],
async (tx) => { async (tx) => {
const r = await tx.get(Stores.reserves, reservePub); const r = await tx.get(Stores.reserves, reservePub);
if (!r) { if (!r) {
@ -472,14 +482,19 @@ async function updateReserve(
return; return;
} }
const hist = await tx.get(Stores.reserveHistory, reservePub);
if (!hist) {
throw Error("inconsistent database");
}
const newHistoryTransactions = reserveInfo.history.slice( const newHistoryTransactions = reserveInfo.history.slice(
r.reserveTransactions.length, hist.reserveTransactions.length,
); );
const reserveUpdateId = encodeCrock(getRandomBytes(32)); const reserveUpdateId = encodeCrock(getRandomBytes(32));
const reconciled = reconcileReserveHistory( const reconciled = reconcileReserveHistory(
r.reserveTransactions, hist.reserveTransactions,
reserveInfo.history, reserveInfo.history,
); );
@ -514,9 +529,10 @@ async function updateReserve(
r.retryInfo = initRetryInfo(false); r.retryInfo = initRetryInfo(false);
} }
r.lastSuccessfulStatusQuery = getTimestampNow(); r.lastSuccessfulStatusQuery = getTimestampNow();
r.reserveTransactions = reconciled.updatedLocalHistory; hist.reserveTransactions = reconciled.updatedLocalHistory;
r.lastError = undefined; r.lastError = undefined;
await tx.put(Stores.reserves, r); await tx.put(Stores.reserves, r);
await tx.put(Stores.reserveHistory, hist);
}, },
); );
ws.notify({ type: NotificationType.ReserveUpdated }); ws.notify({ type: NotificationType.ReserveUpdated });
@ -602,17 +618,29 @@ async function depleteReserve(
ws: InternalWalletState, ws: InternalWalletState,
reservePub: string, reservePub: string,
): Promise<void> { ): Promise<void> {
const reserve = await ws.db.get(Stores.reserves, reservePub); let reserve: ReserveRecord | undefined;
let hist: ReserveHistoryRecord | undefined;
await ws.db.runWithReadTransaction(
[Stores.reserves, Stores.reserveHistory],
async (tx) => {
reserve = await tx.get(Stores.reserves, reservePub);
hist = await tx.get(Stores.reserveHistory, reservePub);
},
);
if (!reserve) { if (!reserve) {
return; return;
} }
if (!hist) {
throw Error("inconsistent database");
}
if (reserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) { if (reserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
return; return;
} }
logger.trace(`depleting reserve ${reservePub}`); logger.trace(`depleting reserve ${reservePub}`);
const summary = summarizeReserveHistory( const summary = summarizeReserveHistory(
reserve.reserveTransactions, hist.reserveTransactions,
reserve.currency, reserve.currency,
); );
@ -674,7 +702,12 @@ async function depleteReserve(
}; };
const success = await ws.db.runWithWriteTransaction( const success = await ws.db.runWithWriteTransaction(
[Stores.withdrawalGroups, Stores.reserves, Stores.planchets], [
Stores.withdrawalGroups,
Stores.reserves,
Stores.reserveHistory,
Stores.planchets,
],
async (tx) => { async (tx) => {
const newReserve = await tx.get(Stores.reserves, reservePub); const newReserve = await tx.get(Stores.reserves, reservePub);
if (!newReserve) { if (!newReserve) {
@ -683,8 +716,12 @@ async function depleteReserve(
if (newReserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) { if (newReserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
return false; return false;
} }
const newHist = await tx.get(Stores.reserveHistory, reservePub);
if (!newHist) {
throw Error("inconsistent database");
}
const newSummary = summarizeReserveHistory( const newSummary = summarizeReserveHistory(
newReserve.reserveTransactions, newHist.reserveTransactions,
newReserve.currency, newReserve.currency,
); );
if ( if (
@ -703,7 +740,7 @@ async function depleteReserve(
const sd = denomsForWithdraw.selectedDenoms[i]; const sd = denomsForWithdraw.selectedDenoms[i];
for (let j = 0; j < sd.count; j++) { for (let j = 0; j < sd.count; j++) {
const amt = Amounts.add(sd.denom.value, sd.denom.feeWithdraw).amount; const amt = Amounts.add(sd.denom.value, sd.denom.feeWithdraw).amount;
newReserve.reserveTransactions.push({ newHist.reserveTransactions.push({
type: WalletReserveHistoryItemType.Withdraw, type: WalletReserveHistoryItemType.Withdraw,
expectedAmount: amt, expectedAmount: amt,
}); });
@ -712,6 +749,7 @@ async function depleteReserve(
newReserve.reserveStatus = ReserveRecordStatus.DORMANT; newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
newReserve.retryInfo = initRetryInfo(false); newReserve.retryInfo = initRetryInfo(false);
await tx.put(Stores.reserves, newReserve); await tx.put(Stores.reserves, newReserve);
await tx.put(Stores.reserveHistory, newHist);
await tx.put(Stores.withdrawalGroups, withdrawalRecord); await tx.put(Stores.withdrawalGroups, withdrawalRecord);
return true; return true;
}, },

View File

@ -564,7 +564,7 @@ async function processWithdrawGroupImpl(
// Withdraw coins in batches. // Withdraw coins in batches.
// The batch size is relatively large // The batch size is relatively large
await processInBatches(genWork(), 50); await processInBatches(genWork(), 10);
} }
export async function getExchangeWithdrawalInfo( export async function getExchangeWithdrawalInfo(

View File

@ -210,6 +210,11 @@ export type WalletReserveHistoryItem =
| WalletReserveHistoryRecoupItem | WalletReserveHistoryRecoupItem
| WalletReserveHistoryClosingItem; | WalletReserveHistoryClosingItem;
export interface ReserveHistoryRecord {
reservePub: string;
reserveTransactions: WalletReserveHistoryItem[];
}
/** /**
* A reserve record as stored in the wallet's database. * A reserve record as stored in the wallet's database.
*/ */
@ -295,8 +300,6 @@ export interface ReserveRecord {
* (either talking to the bank or the exchange). * (either talking to the bank or the exchange).
*/ */
lastError: OperationError | undefined; lastError: OperationError | undefined;
reserveTransactions: WalletReserveHistoryItem[];
} }
/** /**
@ -1639,6 +1642,12 @@ export namespace Stores {
} }
} }
class ReserveHistoryStore extends Store<ReserveHistoryRecord> {
constructor() {
super("reserveHistory", { keyPath: "reservePub" });
}
}
class TipsStore extends Store<TipRecord> { class TipsStore extends Store<TipRecord> {
constructor() { constructor() {
super("tips", { keyPath: "tipId" }); super("tips", { keyPath: "tipId" });
@ -1725,6 +1734,7 @@ export namespace Stores {
keyPath: "recoupGroupId", keyPath: "recoupGroupId",
}); });
export const reserves = new ReservesStore(); export const reserves = new ReservesStore();
export const reserveHistory = new ReserveHistoryStore();
export const purchases = new PurchasesStore(); export const purchases = new PurchasesStore();
export const tips = new TipsStore(); export const tips = new TipsStore();
export const senderWires = new SenderWiresStore(); export const senderWires = new SenderWiresStore();

View File

@ -177,6 +177,7 @@ class WalletBalanceView extends React.Component<any, any> {
private gotError = false; private gotError = false;
private canceler: (() => void) | undefined = undefined; private canceler: (() => void) | undefined = undefined;
private unmount = false; private unmount = false;
private updateBalanceRunning = false;
componentWillMount(): void { componentWillMount(): void {
this.canceler = wxApi.onUpdateNotification(() => this.updateBalance()); this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
@ -192,6 +193,10 @@ class WalletBalanceView extends React.Component<any, any> {
} }
async updateBalance(): Promise<void> { async updateBalance(): Promise<void> {
if (this.updateBalanceRunning) {
return;
}
this.updateBalanceRunning = true;
let balance: WalletBalance; let balance: WalletBalance;
try { try {
balance = await wxApi.getBalance(); balance = await wxApi.getBalance();
@ -203,6 +208,8 @@ class WalletBalanceView extends React.Component<any, any> {
console.error("could not retrieve balances", e); console.error("could not retrieve balances", e);
this.setState({}); this.setState({});
return; return;
} finally {
this.updateBalanceRunning = false;
} }
if (this.unmount) { if (this.unmount) {
return; return;