From d24a9150be1c890be9fba7a6dd6d41baf75465d4 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Fri, 6 Feb 2026 17:11:50 +0100 Subject: [PATCH] optimize usage of cache --- public/healthz.json | 4 +- src/lib/components/relay/RelayInfo.svelte | 4 +- .../modules/discussions/DiscussionList.svelte | 15 ++- src/lib/modules/feed/FeedPage.svelte | 9 +- src/lib/services/nostr/nostr-client.ts | 100 ++++++++++++++---- 5 files changed, 92 insertions(+), 40 deletions(-) diff --git a/public/healthz.json b/public/healthz.json index 5b449ab..ff85e69 100644 --- a/public/healthz.json +++ b/public/healthz.json @@ -2,7 +2,7 @@ "status": "ok", "service": "aitherboard", "version": "0.2.0", - "buildTime": "2026-02-06T13:19:22.159Z", + "buildTime": "2026-02-06T16:11:30.844Z", "gitCommit": "unknown", - "timestamp": 1770383962159 + "timestamp": 1770394290844 } \ No newline at end of file diff --git a/src/lib/components/relay/RelayInfo.svelte b/src/lib/components/relay/RelayInfo.svelte index 07a60bb..86cdf0d 100644 --- a/src/lib/components/relay/RelayInfo.svelte +++ b/src/lib/components/relay/RelayInfo.svelte @@ -176,7 +176,7 @@ const events = await nostrClient.fetchEvents( [{ kinds: [1], limit: 1 }], [relayUrl], - { relayFirst: true, useCache: true, cacheResults: false, timeout: 3000 } + { useCache: 'relay-first', cacheResults: false, timeout: 3000 } ); // This is just a connectivity check, not a real count // Real event count would require a COUNT query which not all relays support @@ -194,7 +194,7 @@ const favoriteRelayEvents = await nostrClient.fetchEvents( [{ kinds: [KIND.FAVORITE_RELAYS], limit: 100 }], relayManager.getProfileReadRelays(), - { relayFirst: true, useCache: true, cacheResults: true, timeout: 3000 } + { useCache: 'relay-first', cacheResults: true, timeout: 3000 } ); console.debug(`[RelayInfo] Fetched ${favoriteRelayEvents.length} favorite relay events for ${relayUrl}`); diff --git a/src/lib/modules/discussions/DiscussionList.svelte b/src/lib/modules/discussions/DiscussionList.svelte index 5c44bd7..4834234 100644 --- a/src/lib/modules/discussions/DiscussionList.svelte +++ b/src/lib/modules/discussions/DiscussionList.svelte @@ -185,8 +185,7 @@ [{ kinds: [KIND.DISCUSSION_THREAD], since, limit: config.feedLimit }], threadRelays, { - relayFirst: true, // Query relays first with timeout - useCache: true, // Fill from cache if relay query returns nothing + useCache: 'relay-first', // Query relays first with timeout, fill from cache if relay query returns nothing cacheResults: true, // Cache the results timeout: config.standardTimeout, onUpdate: async (updatedEvents) => { @@ -249,7 +248,7 @@ const deletionFetchPromise = nostrClient.fetchEvents( [{ kinds: [KIND.EVENT_DELETION], '#e': reactionIds, limit: config.feedLimit }], reactionRelays, - { relayFirst: true, useCache: true, cacheResults: true, timeout: config.standardTimeout } + { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout } ); activeFetchPromises.add(deletionFetchPromise); const deletionEvents = await deletionFetchPromise; @@ -306,8 +305,7 @@ [{ kinds: [KIND.REACTION], '#e': threadIds, limit: config.feedLimit }], reactionRelays, { - relayFirst: true, - useCache: true, + useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout, onUpdate: handleReactionUpdate @@ -326,8 +324,7 @@ [{ kinds: [KIND.REACTION], '#E': threadIds, limit: config.feedLimit }], reactionRelays, { - relayFirst: true, - useCache: true, + useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout, onUpdate: handleReactionUpdate @@ -363,7 +360,7 @@ const zapFetchPromise = nostrClient.fetchEvents( [{ kinds: [KIND.ZAP_RECEIPT], '#e': threadIds, limit: config.feedLimit }], zapRelays, - { relayFirst: true, useCache: true, cacheResults: true, timeout: config.standardTimeout } + { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout } ); activeFetchPromises.add(zapFetchPromise); const allZapReceipts = await zapFetchPromise; @@ -389,7 +386,7 @@ const commentsFetchPromise = nostrClient.fetchEvents( [{ kinds: [KIND.COMMENT], '#E': threadIds, '#K': ['11'], limit: config.feedLimit }], commentRelays, - { relayFirst: true, useCache: true, cacheResults: true, timeout: config.standardTimeout, priority: 'low' } + { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout, priority: 'low' } ); activeFetchPromises.add(commentsFetchPromise); const allComments = await commentsFetchPromise; diff --git a/src/lib/modules/feed/FeedPage.svelte b/src/lib/modules/feed/FeedPage.svelte index 78fe91d..f36016e 100644 --- a/src/lib/modules/feed/FeedPage.svelte +++ b/src/lib/modules/feed/FeedPage.svelte @@ -112,9 +112,8 @@ filters, relays, { - relayFirst: true, - useCache: true, - cacheResults: true, + useCache: 'relay-first', + cacheResults: true, timeout: config.standardTimeout } ); @@ -191,13 +190,11 @@ filters, relays, singleRelay ? { - relayFirst: true, useCache: false, cacheResults: false, timeout: config.singleRelayTimeout } : { - relayFirst: true, - useCache: true, + useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout } diff --git a/src/lib/services/nostr/nostr-client.ts b/src/lib/services/nostr/nostr-client.ts index 63920a2..aee2ae8 100644 --- a/src/lib/services/nostr/nostr-client.ts +++ b/src/lib/services/nostr/nostr-client.ts @@ -19,11 +19,10 @@ export interface PublishOptions { } interface FetchOptions { - useCache?: boolean; + useCache?: boolean | 'cache-first' | 'relay-first'; // Cache strategy: true/'cache-first' = check cache first (default), 'relay-first' = query relays first then cache fallback, false = no cache cacheResults?: boolean; onUpdate?: (events: NostrEvent[]) => void; timeout?: number; - relayFirst?: boolean; // If true, query relays first with timeout, then fill from cache priority?: 'high' | 'medium' | 'low'; // Priority level: high for critical UI (comments), low for background (reactions, profiles) caller?: string; // Optional caller identifier for logging (e.g., "topics/[name]/+page.svelte") } @@ -1361,8 +1360,25 @@ class NostrClient { relays: string[], options: FetchOptions = {} ): Promise { - const { useCache = true, cacheResults = true, onUpdate, timeout = 10000, relayFirst = false, priority = 'medium', caller: providedCaller } = options; + const { cacheResults = true, onUpdate, timeout = 10000, priority = 'medium', caller: providedCaller } = options; const caller = providedCaller || this.getCallerInfo(); + + // Normalize useCache to a string strategy + let useCacheValue: 'cache-first' | 'relay-first' | false; + const useCacheOption = options.useCache ?? true; + + // Normalize boolean to string for consistency + if (useCacheOption === true) { + useCacheValue = 'cache-first'; + } else if (useCacheOption === false) { + useCacheValue = false; + } else { + useCacheValue = useCacheOption; // Already a string + } + + // Determine if we should use cache at all + const shouldUseCache = useCacheValue !== false; + const isCacheFirst = useCacheValue === 'cache-first'; // Create a key for this fetch to prevent duplicates const fetchKey = JSON.stringify({ @@ -1447,13 +1463,56 @@ class NostrClient { // Mark this fetch as pending this.emptyResultCache.set(emptyCacheKey, { cachedAt: Date.now(), pending: true }); - // Always use relay-first mode: query relays first with timeout, then fill from cache if needed - { - // Fetching events from relays - const relayTimeout = timeout || 10000; // Default 10s timeout - const fetchPromise = (async () => { - // For single relay queries, ensure connection is established and authenticated first - if (relays.length === 1) { + // Create the main fetch promise + const relayTimeout = timeout || 10000; // Default 10s timeout + const fetchPromise = (async () => { + // Check cache first if strategy is 'cache-first' (default behavior) + // This provides instant page loads from cache, then enhances with fresh relay data + if (isCacheFirst) { + try { + const cachedEvents = await this.getCachedEvents(filters); + if (cachedEvents.length > 0) { + // Return cached data immediately for fast page loads + console.log(`[nostr-client] Fetch complete: ${cachedEvents.length} from cache (instant), fetching from relays in background [${filterDesc}] from [${relayDesc}]`); + + // Fetch from relays in background to enhance/update results + // Don't await this - let it run in background + this.fetchFromRelays(filters, relays, { + cacheResults: cacheResults, + onUpdate: (freshEvents) => { + // Merge fresh events with cached events via onUpdate callback + if (onUpdate) { + onUpdate(freshEvents); + } + }, + timeout: relayTimeout, + priority: options.priority + }).then((freshEvents) => { + // Log when background fetch completes + if (freshEvents.length > 0) { + console.log(`[nostr-client] Background fetch complete: ${freshEvents.length} fresh events from relays [${filterDesc}] from [${relayDesc}]`); + } + }).catch(() => { + // Silently fail - background fetch is optional + }); + + // Clear pending flag since we got results + const currentEntry = this.emptyResultCache.get(emptyCacheKey); + if (currentEntry?.pending) { + this.emptyResultCache.delete(emptyCacheKey); + } + + return cachedEvents; + } + } catch (error) { + console.debug('[nostr-client] Error querying cache first, falling back to relays:', error); + // Continue to relay fetch below + } + } + + // Relay-first mode: query relays first with timeout, then fill from cache if needed + // For single relay queries, ensure connection is established and authenticated first + if (relays.length === 1) { const relayUrl = relays[0]; if (!this.relays.has(relayUrl)) { // Try to connect first @@ -1553,7 +1612,7 @@ class NostrClient { if (relayEvents.length > 0) { // Start cache enhancement in background, but wait for it before logging const cacheEnhancementPromise = (async () => { - if (useCache && onUpdate) { + if (shouldUseCache && onUpdate) { try { const cachedEvents = await this.getCachedEvents(filters); if (cachedEvents.length > 0) { @@ -1593,9 +1652,9 @@ class NostrClient { return relayEvents; } - // If no results from relays, try to fill from cache (only if useCache is true) + // If no results from relays, try to fill from cache (only if cache is enabled) // IMPORTANT: In single-relay mode, useCache should be false to avoid showing events from other relays - if (useCache) { + if (shouldUseCache) { try { const cachedEvents = await this.getCachedEvents(filters); if (cachedEvents.length > 0) { @@ -1647,14 +1706,13 @@ class NostrClient { } return finalEvents; - })(); - - this.activeFetches.set(fetchKey, fetchPromise); - fetchPromise.finally(() => { - this.activeFetches.delete(fetchKey); - }); - return fetchPromise; - } + })(); + + this.activeFetches.set(fetchKey, fetchPromise); + fetchPromise.finally(() => { + this.activeFetches.delete(fetchKey); + }); + return fetchPromise; } private async fetchFromRelays(