perf: implement IndexedDB connection pooling in dmDb.js

Refactor `js/storage/dmDb.js` to use a singleton connection pattern for IndexedDB.
Previously, the database was opened and closed for every single operation,
leading to significant overhead.

Changes:
- Introduced `dbInstance` and `dbPromise` to cache the database connection.
- Refactored `openDmDb` to reuse the existing connection or pending promise.
- Added `onversionchange` handler to safely close the connection if an upgrade is requested.
- Removed all manual `db.close()` calls from exported functions.
- Added a benchmark script in `tests/benchmark-dm-db.mjs` to verify the improvement.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-08 03:09:14 +00:00
1 parent 84485e5607
commit 75b6bbd1e6
2 files changed
+74 -11

No files matched your search

+30 -11
View File
@@ -100,8 +100,18 @@ function applyMigrations(db, oldVersion, newVersion, transaction) {
}
}
let dbInstance = null;
let dbPromise = null;
function openDmDb() {
return new Promise((resolve, reject) => {
if (dbInstance) {
return Promise.resolve(dbInstance);
}
if (dbPromise) {
return dbPromise;
}
dbPromise = new Promise((resolve, reject) => {
if (!isIndexedDbAvailable()) {
resolve(null);
return;
@@ -118,10 +128,25 @@ function openDmDb() {
applyMigrations(db, oldVersion, newVersion, upgradeTransaction);
};
request.onsuccess = () => resolve(request.result);
request.onerror = () =>
request.onsuccess = () => {
dbInstance = request.result;
dbPromise = null;
dbInstance.onversionchange = () => {
dbInstance.close();
dbInstance = null;
};
resolve(dbInstance);
};
request.onerror = () => {
dbPromise = null;
reject(request.error || new Error("Failed to open DM database."));
};
});
return dbPromise;
}
function sanitizeTimestamp(value) {
@@ -264,7 +289,6 @@ export async function writeConversationMetadata(conversation) {
const tx = db.transaction(STORES.CONVERSATIONS, "readwrite");
tx.objectStore(STORES.CONVERSATIONS).put(normalized);
await finalizeTransaction(tx);
db.close();
return normalized;
} catch (error) {
userLogger.warn("[dmDb] Failed to write conversation metadata", error);
@@ -296,7 +320,6 @@ export async function writeMessages(messages) {
}
await finalizeTransaction(tx);
db.close();
return normalized;
} catch (error) {
userLogger.warn("[dmDb] Failed to persist messages", error);
@@ -319,7 +342,6 @@ export async function getConversation(conversationId) {
const store = tx.objectStore(STORES.CONVERSATIONS);
const result = await runRequest(store.get(conversationId.trim()));
await finalizeTransaction(tx);
db.close();
return result || null;
} catch (error) {
userLogger.warn("[dmDb] Failed to read conversation metadata", error);
@@ -369,7 +391,7 @@ export async function listMessagesByConversation(conversationId, options = {}) {
userLogger.warn("[dmDb] Failed to list messages", request.error);
resolve(results);
};
}).finally(() => db.close());
});
} catch (error) {
userLogger.warn("[dmDb] Failed to list messages", error);
return [];
@@ -429,7 +451,6 @@ export async function updateConversationFromMessage(message, options = {}) {
store.put(next);
await finalizeTransaction(tx);
db.close();
return next;
} catch (error) {
userLogger.warn("[dmDb] Failed to update conversation metadata", error);
@@ -478,7 +499,6 @@ export async function updateConversationOpenState(
store.put(next);
await finalizeTransaction(tx);
db.close();
return next;
} catch (error) {
userLogger.warn("[dmDb] Failed to update conversation open state", error);
@@ -512,7 +532,7 @@ export async function listConversations() {
userLogger.warn("[dmDb] Failed to list conversations", request.error);
resolve(results);
};
}).finally(() => db.close());
});
} catch (error) {
userLogger.warn("[dmDb] Failed to list conversations", error);
return [];
@@ -542,7 +562,6 @@ export async function clearDmDb() {
}
await finalizeTransaction(tx);
db.close();
return true;
} catch (error) {
devLogger.warn("[dmDb] Failed to clear DM database", error);
+44
View File
@@ -0,0 +1,44 @@
import { writeMessages } from "../js/storage/dmDb.js";
import { indexedDB, IDBKeyRange } from "fake-indexeddb";
if (!globalThis.indexedDB) {
globalThis.indexedDB = indexedDB;
globalThis.IDBKeyRange = IDBKeyRange;
}
const ITERATIONS = 500;
async function benchmark() {
console.log(`Starting benchmark with ${ITERATIONS} iterations...`);
// Warm up
await writeMessages({
id: `warmup`,
conversationId: "bench-conv",
senderPubkey: "sender",
receiverPubkey: "receiver",
createdAt: Date.now(),
kind: 4,
content: "Warmup",
});
const start = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
await writeMessages({
id: `msg-${i}`,
conversationId: "bench-conv",
senderPubkey: "sender",
receiverPubkey: "receiver",
createdAt: Date.now(),
kind: 4,
content: "Bench",
});
}
const end = performance.now();
console.log(`Total time for ${ITERATIONS} writes: ${(end - start).toFixed(2)}ms`);
console.log(`Average time per write: ${((end - start) / ITERATIONS).toFixed(2)}ms`);
}
benchmark().catch(console.error);