Browse Source

optimize usage of cache

master
Silberengel 1 month ago
parent
commit
d24a9150be
  1. 4
      public/healthz.json
  2. 4
      src/lib/components/relay/RelayInfo.svelte
  3. 15
      src/lib/modules/discussions/DiscussionList.svelte
  4. 9
      src/lib/modules/feed/FeedPage.svelte
  5. 100
      src/lib/services/nostr/nostr-client.ts

4
public/healthz.json

@ -2,7 +2,7 @@
"status": "ok", "status": "ok",
"service": "aitherboard", "service": "aitherboard",
"version": "0.2.0", "version": "0.2.0",
"buildTime": "2026-02-06T13:19:22.159Z", "buildTime": "2026-02-06T16:11:30.844Z",
"gitCommit": "unknown", "gitCommit": "unknown",
"timestamp": 1770383962159 "timestamp": 1770394290844
} }

4
src/lib/components/relay/RelayInfo.svelte

@ -176,7 +176,7 @@
const events = await nostrClient.fetchEvents( const events = await nostrClient.fetchEvents(
[{ kinds: [1], limit: 1 }], [{ kinds: [1], limit: 1 }],
[relayUrl], [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 // This is just a connectivity check, not a real count
// Real event count would require a COUNT query which not all relays support // Real event count would require a COUNT query which not all relays support
@ -194,7 +194,7 @@
const favoriteRelayEvents = await nostrClient.fetchEvents( const favoriteRelayEvents = await nostrClient.fetchEvents(
[{ kinds: [KIND.FAVORITE_RELAYS], limit: 100 }], [{ kinds: [KIND.FAVORITE_RELAYS], limit: 100 }],
relayManager.getProfileReadRelays(), 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}`); console.debug(`[RelayInfo] Fetched ${favoriteRelayEvents.length} favorite relay events for ${relayUrl}`);

15
src/lib/modules/discussions/DiscussionList.svelte

@ -185,8 +185,7 @@
[{ kinds: [KIND.DISCUSSION_THREAD], since, limit: config.feedLimit }], [{ kinds: [KIND.DISCUSSION_THREAD], since, limit: config.feedLimit }],
threadRelays, threadRelays,
{ {
relayFirst: true, // Query relays first with timeout useCache: 'relay-first', // Query relays first with timeout, fill from cache if relay query returns nothing
useCache: true, // Fill from cache if relay query returns nothing
cacheResults: true, // Cache the results cacheResults: true, // Cache the results
timeout: config.standardTimeout, timeout: config.standardTimeout,
onUpdate: async (updatedEvents) => { onUpdate: async (updatedEvents) => {
@ -249,7 +248,7 @@
const deletionFetchPromise = nostrClient.fetchEvents( const deletionFetchPromise = nostrClient.fetchEvents(
[{ kinds: [KIND.EVENT_DELETION], '#e': reactionIds, limit: config.feedLimit }], [{ kinds: [KIND.EVENT_DELETION], '#e': reactionIds, limit: config.feedLimit }],
reactionRelays, reactionRelays,
{ relayFirst: true, useCache: true, cacheResults: true, timeout: config.standardTimeout } { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout }
); );
activeFetchPromises.add(deletionFetchPromise); activeFetchPromises.add(deletionFetchPromise);
const deletionEvents = await deletionFetchPromise; const deletionEvents = await deletionFetchPromise;
@ -306,8 +305,7 @@
[{ kinds: [KIND.REACTION], '#e': threadIds, limit: config.feedLimit }], [{ kinds: [KIND.REACTION], '#e': threadIds, limit: config.feedLimit }],
reactionRelays, reactionRelays,
{ {
relayFirst: true, useCache: 'relay-first',
useCache: true,
cacheResults: true, cacheResults: true,
timeout: config.standardTimeout, timeout: config.standardTimeout,
onUpdate: handleReactionUpdate onUpdate: handleReactionUpdate
@ -326,8 +324,7 @@
[{ kinds: [KIND.REACTION], '#E': threadIds, limit: config.feedLimit }], [{ kinds: [KIND.REACTION], '#E': threadIds, limit: config.feedLimit }],
reactionRelays, reactionRelays,
{ {
relayFirst: true, useCache: 'relay-first',
useCache: true,
cacheResults: true, cacheResults: true,
timeout: config.standardTimeout, timeout: config.standardTimeout,
onUpdate: handleReactionUpdate onUpdate: handleReactionUpdate
@ -363,7 +360,7 @@
const zapFetchPromise = nostrClient.fetchEvents( const zapFetchPromise = nostrClient.fetchEvents(
[{ kinds: [KIND.ZAP_RECEIPT], '#e': threadIds, limit: config.feedLimit }], [{ kinds: [KIND.ZAP_RECEIPT], '#e': threadIds, limit: config.feedLimit }],
zapRelays, zapRelays,
{ relayFirst: true, useCache: true, cacheResults: true, timeout: config.standardTimeout } { useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout }
); );
activeFetchPromises.add(zapFetchPromise); activeFetchPromises.add(zapFetchPromise);
const allZapReceipts = await zapFetchPromise; const allZapReceipts = await zapFetchPromise;
@ -389,7 +386,7 @@
const commentsFetchPromise = nostrClient.fetchEvents( const commentsFetchPromise = nostrClient.fetchEvents(
[{ kinds: [KIND.COMMENT], '#E': threadIds, '#K': ['11'], limit: config.feedLimit }], [{ kinds: [KIND.COMMENT], '#E': threadIds, '#K': ['11'], limit: config.feedLimit }],
commentRelays, 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); activeFetchPromises.add(commentsFetchPromise);
const allComments = await commentsFetchPromise; const allComments = await commentsFetchPromise;

9
src/lib/modules/feed/FeedPage.svelte

@ -112,9 +112,8 @@
filters, filters,
relays, relays,
{ {
relayFirst: true, useCache: 'relay-first',
useCache: true, cacheResults: true,
cacheResults: true,
timeout: config.standardTimeout timeout: config.standardTimeout
} }
); );
@ -191,13 +190,11 @@
filters, filters,
relays, relays,
singleRelay ? { singleRelay ? {
relayFirst: true,
useCache: false, useCache: false,
cacheResults: false, cacheResults: false,
timeout: config.singleRelayTimeout timeout: config.singleRelayTimeout
} : { } : {
relayFirst: true, useCache: 'relay-first',
useCache: true,
cacheResults: true, cacheResults: true,
timeout: config.standardTimeout timeout: config.standardTimeout
} }

100
src/lib/services/nostr/nostr-client.ts

@ -19,11 +19,10 @@ export interface PublishOptions {
} }
interface FetchOptions { 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; cacheResults?: boolean;
onUpdate?: (events: NostrEvent[]) => void; onUpdate?: (events: NostrEvent[]) => void;
timeout?: number; 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) 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") caller?: string; // Optional caller identifier for logging (e.g., "topics/[name]/+page.svelte")
} }
@ -1361,8 +1360,25 @@ class NostrClient {
relays: string[], relays: string[],
options: FetchOptions = {} options: FetchOptions = {}
): Promise<NostrEvent[]> { ): Promise<NostrEvent[]> {
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(); 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 // Create a key for this fetch to prevent duplicates
const fetchKey = JSON.stringify({ const fetchKey = JSON.stringify({
@ -1447,13 +1463,56 @@ class NostrClient {
// Mark this fetch as pending // Mark this fetch as pending
this.emptyResultCache.set(emptyCacheKey, { cachedAt: Date.now(), pending: true }); 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 // Create the main fetch promise
{ const relayTimeout = timeout || 10000; // Default 10s timeout
// Fetching events from relays const fetchPromise = (async () => {
const relayTimeout = timeout || 10000; // Default 10s timeout // Check cache first if strategy is 'cache-first' (default behavior)
const fetchPromise = (async () => { // This provides instant page loads from cache, then enhances with fresh relay data
// For single relay queries, ensure connection is established and authenticated first if (isCacheFirst) {
if (relays.length === 1) { 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]; const relayUrl = relays[0];
if (!this.relays.has(relayUrl)) { if (!this.relays.has(relayUrl)) {
// Try to connect first // Try to connect first
@ -1553,7 +1612,7 @@ class NostrClient {
if (relayEvents.length > 0) { if (relayEvents.length > 0) {
// Start cache enhancement in background, but wait for it before logging // Start cache enhancement in background, but wait for it before logging
const cacheEnhancementPromise = (async () => { const cacheEnhancementPromise = (async () => {
if (useCache && onUpdate) { if (shouldUseCache && onUpdate) {
try { try {
const cachedEvents = await this.getCachedEvents(filters); const cachedEvents = await this.getCachedEvents(filters);
if (cachedEvents.length > 0) { if (cachedEvents.length > 0) {
@ -1593,9 +1652,9 @@ class NostrClient {
return relayEvents; 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 // IMPORTANT: In single-relay mode, useCache should be false to avoid showing events from other relays
if (useCache) { if (shouldUseCache) {
try { try {
const cachedEvents = await this.getCachedEvents(filters); const cachedEvents = await this.getCachedEvents(filters);
if (cachedEvents.length > 0) { if (cachedEvents.length > 0) {
@ -1647,14 +1706,13 @@ class NostrClient {
} }
return finalEvents; return finalEvents;
})(); })();
this.activeFetches.set(fetchKey, fetchPromise); this.activeFetches.set(fetchKey, fetchPromise);
fetchPromise.finally(() => { fetchPromise.finally(() => {
this.activeFetches.delete(fetchKey); this.activeFetches.delete(fetchKey);
}); });
return fetchPromise; return fetchPromise;
}
} }
private async fetchFromRelays( private async fetchFromRelays(

Loading…
Cancel
Save