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,
* 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

View File

@ -145,7 +145,7 @@ export async function getBalances(
): Promise<WalletBalance> {
logger.trace("starting to compute balance");
return await ws.db.runWithReadTransaction(
const wbal = await ws.db.runWithReadTransaction(
[
Stores.coins,
Stores.refreshGroups,
@ -157,4 +157,8 @@ export async function getBalances(
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.refreshGroups,
Stores.reserves,
Stores.reserveHistory,
Stores.tips,
Stores.withdrawalGroups,
Stores.payEvents,
@ -384,8 +385,12 @@ export async function getHistory(
type: ReserveType.Manual,
};
}
const hist = await tx.get(Stores.reserveHistory, reserve.reservePub);
if (!hist) {
throw Error("inconsistent database");
}
const s = summarizeReserveHistory(
reserve.reserveTransactions,
hist.reserveTransactions,
reserve.currency,
);
history.push({

View File

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

View File

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

View File

@ -210,6 +210,11 @@ export type WalletReserveHistoryItem =
| WalletReserveHistoryRecoupItem
| WalletReserveHistoryClosingItem;
export interface ReserveHistoryRecord {
reservePub: string;
reserveTransactions: WalletReserveHistoryItem[];
}
/**
* 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).
*/
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> {
constructor() {
super("tips", { keyPath: "tipId" });
@ -1725,6 +1734,7 @@ export namespace Stores {
keyPath: "recoupGroupId",
});
export const reserves = new ReservesStore();
export const reserveHistory = new ReserveHistoryStore();
export const purchases = new PurchasesStore();
export const tips = new TipsStore();
export const senderWires = new SenderWiresStore();

View File

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