From 75b6bbd1e607138bfc1081cfc05c24cf0d84e77b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 03:09:14 +0000 Subject: [PATCH] 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 <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/storage/dmDb.js | 41 ++++++++++++++++++++++++++---------- tests/benchmark-dm-db.mjs | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 tests/benchmark-dm-db.mjs diff --git a/js/storage/dmDb.js b/js/storage/dmDb.js index 2b41ff2e..eb11e0fa 100644 --- a/js/storage/dmDb.js +++ b/js/storage/dmDb.js @@ -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); diff --git a/tests/benchmark-dm-db.mjs b/tests/benchmark-dm-db.mjs new file mode 100644 index 00000000..1700ad6e --- /dev/null +++ b/tests/benchmark-dm-db.mjs @@ -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);