Performance Deep-Dive: Fixing Slow Transaction Loads from AgentOS
Performance Deep-Dive: Fixing Slow Transaction Loads from AgentOS
Relates to: v1.0.49 ·
src/lib/agentos/client.ts
The Problem
If you have been using the platform for a year or more, you may have noticed that the Transactions page can take 10–30 seconds to load. This article explains why, and what the fix looks like.
The root cause is in getTransactions() inside src/lib/agentos/client.ts. When this function runs, it:
- Fetches the full list of your historical statements from AgentOS — 1 HTTP request.
- For each statement, fires a separate HTTP request to fetch that statement's entries.
A landlord with 2 years of statements (24 months) ends up making 25 sequential HTTP requests every single time getTransactions() is called. Because these requests are sequential — each one waits for the previous to complete — latency compounds quickly.
Why Is This Called So Often?
The same getTransactions() function is invoked in two places:
- During bulk import, when syncing transactions from AgentOS into the local
agentosTransactionsdatabase table. - On every Transactions page load via the
listTransactionstRPC procedure — meaning even routine page navigation hits the live AgentOS API 25+ times.
The Fix
Two changes together resolve the issue:
1. Parallelize Statement Entry Fetches
Instead of awaiting each statement fetch one-by-one, use Promise.all() with a concurrency cap. A limit of 5 concurrent requests balances speed against rate-limit risk on the AgentOS API.
Using p-limit:
import pLimit from 'p-limit';
const limit = pLimit(5);
const allEntries = await Promise.all(
statements.map(statement =>
limit(() => fetchStatementEntries(statement.id))
)
);
Alternatively, a manual chunking approach works without an additional dependency:
const CHUNK_SIZE = 5;
const results = [];
for (let i = 0; i < statements.length; i += CHUNK_SIZE) {
const chunk = statements.slice(i, i + CHUNK_SIZE);
const chunkResults = await Promise.all(
chunk.map(s => fetchStatementEntries(s.id))
);
results.push(...chunkResults);
}
2. Serve listTransactions from the Local Database
Bulk import already populates the agentosTransactions table in the local database. The listTransactions tRPC procedure should read from this table rather than calling the live AgentOS API on each page load.
This means:
- The Transactions page loads in milliseconds (local DB query).
- The live AgentOS API is only called during explicit import/sync operations.
- Users always see a consistent, fast view of their imported transactions.
// Before: hits live AgentOS API on every page load
const transactions = await agentOSClient.getTransactions();
// After: reads from locally-imported table
const transactions = await db.agentosTransactions.findMany({
where: { landlordId: ctx.landlordId },
orderBy: { date: 'desc' },
});
Expected Improvement
| Metric | Before Fix | After Fix |
|---|---|---|
| Transactions page load time | 10–30 seconds | 2–5 seconds |
| HTTP calls per page load | 25+ sequential | 0 (DB only) |
| HTTP calls during bulk import | 25+ sequential | ~5 concurrent batches |
Summary
The core issue is a missing parallelisation strategy in getTransactions(), compounded by an architecture that calls the live API on every UI page load. The fix involves concurrency-limiting Promise.all() for the import path, and switching the listTransactions tRPC route to serve from the local agentosTransactions table for all routine reads.