Skip to content

Commit f50a76e

Browse files
committed
prune tx by count
1 parent b964bd0 commit f50a76e

File tree

6 files changed

+24
-51
lines changed

6 files changed

+24
-51
lines changed

src/db/transactions/db.ts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class TransactionDB {
128128
*/
129129
static bulkDelete = async (queueIds: string[]) => {
130130
if (queueIds.length === 0) {
131-
return [];
131+
return 0;
132132
}
133133

134134
const keys = queueIds.map(this.transactionDetailsKey);
@@ -176,38 +176,26 @@ export class TransactionDB {
176176
};
177177

178178
/**
179-
* Deletes transactions between a time range.
180-
* @param from Date?
181-
* @param to Date?
179+
* Prunes transaction details and lists, keeping the latest `keep` amount/
180+
* @param keep number - The max recent transactions to not prune.
181+
* @returns number - The number of transactions pruned.
182182
*/
183-
static pruneTransactionLists = async (args: { from?: Date; to?: Date }) => {
184-
const { from, to } = args;
185-
const min = from ? toSeconds(from) : 0;
186-
const max = to ? toSeconds(to) : "+inf";
183+
static pruneTransactionDetailsAndLists = async (keep: number) => {
184+
// Delete up to `keep - 1` index, inclusive.
185+
const stop = -keep - 1;
186+
187+
const queueIds = await redis.zrange(this.queuedTransactionsKey, 0, stop);
188+
const numPruned = await this.bulkDelete(queueIds);
187189

188-
// Delete per-status sorted sets.
189190
await redis
190191
.pipeline()
191-
.zremrangebyscore(this.queuedTransactionsKey, min, max)
192-
.zremrangebyscore(this.minedTransactionsKey, min, max)
193-
.zremrangebyscore(this.cancelledTransactionsKey, min, max)
194-
.zremrangebyscore(this.erroredTransactionsKey, min, max)
192+
.zremrangebyrank(this.queuedTransactionsKey, 0, stop)
193+
.zremrangebyrank(this.minedTransactionsKey, 0, stop)
194+
.zremrangebyrank(this.cancelledTransactionsKey, 0, stop)
195+
.zremrangebyrank(this.erroredTransactionsKey, 0, stop)
195196
.exec();
196-
};
197197

198-
/**
199-
* Prunes transaction details after `keep` transactions.
200-
* Example: `keep=100` prunes all transaction details except the most recent 100.
201-
* @param keep number - The count of recent transactions to not prune. All older transactions are pruned.
202-
* @returns number - The number of transaction details pruned.
203-
*/
204-
static pruneTransactionDetails = async (keep: number) => {
205-
const queueIds = await redis.zrange(
206-
this.queuedTransactionsKey,
207-
0,
208-
-keep - 1,
209-
);
210-
return await this.bulkDelete(queueIds);
198+
return numPruned;
211199
};
212200
}
213201

src/server/routes/backend-wallet/getTransactions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Static, Type } from "@sinclair/typebox";
22
import { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { TransactionDB } from "../../../db/transactions/db";
5+
import { env } from "../../../utils/env";
56
import { normalizeAddress } from "../../../utils/primitiveTypes";
67
import { standardResponseSchema } from "../../schemas/sharedApiSchemas";
78
import {
@@ -42,11 +43,10 @@ export async function getAllTransactions(fastify: FastifyInstance) {
4243
const chainId = await getChainIdFromChain(chain);
4344
const walletAddress = normalizeAddress(_walletAddress);
4445

45-
// @TODO: This query is not optimized. Cap the results to the most recent 10k total transactions for performance reasons.
4646
const { transactions } = await TransactionDB.getTransactionListByStatus({
4747
status: "queued",
4848
page: 1,
49-
limit: 10_000,
49+
limit: env.TRANSACTION_HISTORY_COUNT,
5050
});
5151
const filtered = transactions.filter(
5252
(t) => t.chainId === chainId && t.from === walletAddress,

src/server/schemas/wallet/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const walletHeaderSchema = Type.Object({
88
}),
99
"x-idempotency-key": Type.Optional(
1010
Type.String({
11-
description: `Multiple transactions submitted with the same idempotency key will not send a new transaction for ${env.PRUNE_TRANSACTIONS} day(s).`,
11+
description: `Transactions submitted with the same idempotency key will be de-duplicated. Only the last ${env.TRANSACTION_HISTORY_COUNT} transactions are compared.`,
1212
}),
1313
),
1414
});

src/utils/env.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,9 @@ export const env = createEnv({
6565
// to avoid running out of memory. If this limit is reached, keys are evicted
6666
// with a "allkeys-lru" policy (removes least recently used keys).
6767
REDIS_MAXMEMORY: z.string().default("900mb"),
68-
PRUNE_TRANSACTIONS: z
69-
.union([
70-
z.literal("true").transform(() => 7),
71-
z.literal("false").transform(() => 0),
72-
z.coerce.number().int(),
73-
])
74-
.default(7),
75-
// PRUNE_TRANSACTIONS_KEEP_COUNT defines the max transaction details to keep.
68+
// TRANSACTION_HISTORY_COUNT defines the max transaction details to keep.
7669
// In testing, storing about 400k consumes 1GB memory.
77-
PRUNE_TRANSACTIONS_KEEP_COUNT: z.coerce.number().default(400_000),
70+
TRANSACTION_HISTORY_COUNT: z.coerce.number().default(400_000),
7871
CLIENT_ANALYTICS_URL: z
7972
.union([UrlSchema, z.literal("")])
8073
.default("https://c.thirdweb.com/event"),
@@ -109,8 +102,7 @@ export const env = createEnv({
109102
ENABLE_HTTPS: process.env.ENABLE_HTTPS,
110103
HTTPS_PASSPHRASE: process.env.HTTPS_PASSPHRASE,
111104
TRUST_PROXY: process.env.TRUST_PROXY,
112-
PRUNE_TRANSACTIONS: process.env.PRUNE_TRANSACTIONS,
113-
PRUNE_TRANSACTIONS_KEEP_COUNT: process.env.PRUNE_TRANSACTIONS_KEEP_COUNT,
105+
TRANSACTION_HISTORY_COUNT: process.env.TRANSACTION_HISTORY_COUNT,
114106
CLIENT_ANALYTICS_URL: process.env.CLIENT_ANALYTICS_URL,
115107
SDK_BATCH_TIME_LIMIT: process.env.SDK_BATCH_TIME_LIMIT,
116108
SDK_BATCH_SIZE_LIMIT: process.env.SDK_BATCH_SIZE_LIMIT,

src/worker/queues/queues.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const defaultJobOptions: JobsOptions = {
1010
count: 10_000,
1111
},
1212
// Purge failed jobs.
13-
// These limits should be high to debug Redis instances.
13+
// These limits should be sufficiently high to debug/retry.
1414
removeOnFail: {
1515
age: 30 * 24 * 60 * 60,
1616
count: 100_000,

src/worker/tasks/pruneTransactionsWorker.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,8 @@ import { PRUNE_TRANSACTIONS_QUEUE_NAME } from "../queues/pruneTransactionsQueue"
66
import { logWorkerExceptions } from "../queues/queues";
77

88
const handler: Processor<any, void, string> = async (job: Job<string>) => {
9-
// Purge transactions up to `PRUNE_TRANSACTIONS` days ago.
10-
const to = new Date();
11-
to.setDate(to.getDate() - env.PRUNE_TRANSACTIONS);
12-
await TransactionDB.pruneTransactionLists({ to });
13-
job.log(`Pruned transaction lists to ${to.toLocaleString()}.`);
14-
15-
// Prune transactions DB to the most recent `PRUNE_TRANSACTIONS_COUNT`.
16-
const numPruned = await TransactionDB.pruneTransactionDetails(
17-
env.PRUNE_TRANSACTIONS_KEEP_COUNT,
9+
const numPruned = await TransactionDB.pruneTransactionDetailsAndLists(
10+
env.TRANSACTION_HISTORY_COUNT,
1811
);
1912
job.log(`Pruned ${numPruned} transaction details.`);
2013
};

0 commit comments

Comments
 (0)