Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2026-06-08 - [Avoid Re-Querying Static DB Data if Cache Exists]
**Learning:** In applications using frequent polling (like a live traffic dashboard), direct database queries for static or slow-changing data (like model catalogs/prices) can cause unnecessary DB load. The application already maintained an in-memory cache for models `getCachedPoolModels()`, but `getTraffic` was directly querying the DB using `prisma.cachedModel.findMany` instead of utilizing the cache.
**Action:** Always check if a caching layer or service already exists for lookup/reference data before writing direct database queries, especially on high-frequency endpoints.

## 2026-06-10 - [Avoid N+1 queries in mapping collections]
**Learning:** In applications mapping over records (`allAccounts.map`) and awaiting queries inside (`await store.getLocalKeys(req.user.id, account.id)`), an N+1 query vulnerability exists which leads to excessive database load when the collection size is large.
**Action:** Always batch related data queries prior to mapping over collections by retrieving data with an `IN` clause and aggregating them into a Map object that can be queried continuously in memory.
8 changes: 7 additions & 1 deletion server/controllers/PoolController.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class PoolController extends BaseController {
try {
const allAccounts = await store.getAllAccountsWithKeys(req.user.id);

// ⚡ Bolt: Fetch all local keys for all accounts in one query instead of N queries
const localKeysMap = await store.getLocalKeysForAccounts(
req.user.id,
allAccounts.map((a) => a.id)
);

const accountResults = await Promise.allSettled(
allAccounts.map(async (account) => {
throwIfAborted(requestAbort.signal);
Expand Down Expand Up @@ -87,7 +93,7 @@ class PoolController extends BaseController {
}

// Get local DB records to merge isPooled + hasKeyString
const localKeys = await store.getLocalKeys(req.user.id, account.id);
const localKeys = localKeysMap.get(account.id) || [];
const localMap = new Map(localKeys.map((k) => [k.hash, k]));

const enrichedKeys = liveKeys.map((k) => {
Expand Down
29 changes: 29 additions & 0 deletions server/services/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,35 @@ export async function getLocalKeys(userId, accountId) {
});
}

export async function getLocalKeysForAccounts(userId, accountIds) {
if (!accountIds || accountIds.length === 0) return new Map();

const keys = await prisma.key.findMany({
where: { accountId: { in: accountIds }, account: { userId } },
});

const map = new Map();
for (const accountId of accountIds) {
map.set(accountId, []);
}

for (const keyRecord of keys) {
let key = null;
if (keyRecord.key) {
try {
key = decrypt(keyRecord.key) || null;
} catch (err) {
logger.warn(`[STORE] Failed to decrypt local key hash=${keyRecord.hash}: ${err.message}`);
key = null;
}
}
const enriched = { ...keyRecord, key };
map.get(keyRecord.accountId).push(enriched);
}

return map;
}

export async function registerKeyString(userId, hash, rawKeyString) {
const normalizedKey = rawKeyString?.trim();
if (!normalizedKey) {
Expand Down
Loading