From 1df2e61f78faafb88d7a6bd56ae16f90a784ebfd Mon Sep 17 00:00:00 2001 From: Silberengel Date: Fri, 3 Oct 2025 20:33:56 +0200 Subject: [PATCH] make relays more efficient --- src/components/QuoteList/index.tsx | 8 +++++--- src/components/ReplyNoteList/index.tsx | 10 ++++++---- src/constants.ts | 16 +++++++++++++++ src/services/client.service.ts | 27 ++++++++++++++++---------- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/components/QuoteList/index.tsx b/src/components/QuoteList/index.tsx index e732a3b..e3e192f 100644 --- a/src/components/QuoteList/index.tsx +++ b/src/components/QuoteList/index.tsx @@ -1,4 +1,4 @@ -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { FAST_READ_RELAY_URLS, ExtendedKind } from '@/constants' import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event' import { useNostr } from '@/providers/NostrProvider' import { useUserTrust } from '@/providers/UserTrustProvider' @@ -32,14 +32,16 @@ export default function QuoteList({ event, className }: { event: Event; classNam const relayList = await client.fetchRelayList(event.pubkey) // Include user's mailbox relays for better quote discovery const userRelays = userRelayList?.read || [] - const relayUrls = relayList.read.concat(userRelays).concat(BIG_RELAY_URLS) + const relayUrls = Array.from(new Set(relayList.read.concat(userRelays).concat(FAST_READ_RELAY_URLS))) const seenOn = client.getSeenEventRelayUrls(event.id) relayUrls.unshift(...seenOn) + // Deduplicate the final list including seenOn relays + const finalRelayUrls = Array.from(new Set(relayUrls)) const { closer, timelineKey } = await client.subscribeTimeline( [ { - urls: relayUrls, + urls: finalRelayUrls, filter: { '#q': [ isReplaceableEvent(event.kind) ? getReplaceableCoordinateFromEvent(event) : event.id diff --git a/src/components/ReplyNoteList/index.tsx b/src/components/ReplyNoteList/index.tsx index 9197f9e..2d5bc0f 100644 --- a/src/components/ReplyNoteList/index.tsx +++ b/src/components/ReplyNoteList/index.tsx @@ -1,4 +1,4 @@ -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { FAST_READ_RELAY_URLS, ExtendedKind } from '@/constants' import { getParentETag, getReplaceableCoordinateFromEvent, @@ -145,7 +145,7 @@ export default function ReplyNoteList({ index, event }: { index?: number; event: ) // Include user's mailbox relays for better reply discovery const userRelays = userRelayList?.read || [] - const relayUrls = relayList.read.concat(userRelays).concat(BIG_RELAY_URLS) + const relayUrls = Array.from(new Set(relayList.read.concat(userRelays).concat(FAST_READ_RELAY_URLS))) const seenOn = rootInfo.type === 'E' ? client.getSeenEventRelayUrls(rootInfo.id) @@ -153,6 +153,8 @@ export default function ReplyNoteList({ index, event }: { index?: number; event: ? client.getSeenEventRelayUrls(rootInfo.eventId) : [] relayUrls.unshift(...seenOn) + // Deduplicate the final list including seenOn relays + const finalRelayUrls = Array.from(new Set(relayUrls)) const filters: (Omit & { limit: number @@ -184,7 +186,7 @@ export default function ReplyNoteList({ index, event }: { index?: number; event: } ) if (rootInfo.relay) { - relayUrls.push(rootInfo.relay) + finalRelayUrls.push(rootInfo.relay) } } else { filters.push({ @@ -195,7 +197,7 @@ export default function ReplyNoteList({ index, event }: { index?: number; event: } const { closer, timelineKey } = await client.subscribeTimeline( filters.map((filter) => ({ - urls: relayUrls.slice(0, 5), + urls: finalRelayUrls.slice(0, 5), filter })), { diff --git a/src/constants.ts b/src/constants.ts index 3fe6a18..0cfbbdb 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -67,6 +67,22 @@ export const BIG_RELAY_URLS = [ 'wss://nostr21.com' ] +// Optimized relay list for read operations (includes aggregator) +export const FAST_READ_RELAY_URLS = [ + 'wss://theforest.nostr1.com', + 'wss://orly-relay.imwald.eu', + 'wss://nostr.wine', + 'wss://aggr.nostr.land' +] + +// Optimized relay list for write operations (no aggregator since it's read-only) +export const FAST_WRITE_RELAY_URLS = [ + 'wss://nostr.wine', + 'wss://nostr.land', + 'wss://freelay.sovbit.host/', + 'wss://thecitadel.nostr1.com/' +] + export const SEARCHABLE_RELAY_URLS = ['wss://relay.nostr.band/', 'wss://freelay.sovbit.host/', 'wss://relay.damus.io/', 'wss://search.nos.today/', 'wss://aggr.nostr.land', 'wss://purplepag.es', 'wss://profiles.nostr1.com'] // Combined relay URLs for profile fetching - includes both BIG_RELAY_URLS and SEARCHABLE_RELAY_URLS diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 94dc5b3..fb9ba3e 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -1,4 +1,4 @@ -import { BIG_RELAY_URLS, ExtendedKind, PROFILE_FETCH_RELAY_URLS } from '@/constants' +import { BIG_RELAY_URLS, ExtendedKind, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS, PROFILE_FETCH_RELAY_URLS } from '@/constants' import { compareEvents, getReplaceableCoordinate, @@ -131,20 +131,20 @@ class ClientService extends EventTarget { } const relayList = await this.fetchRelayList(event.pubkey) - relays = (relayList?.write.slice(0, 10) ?? []).concat( + relays = (relayList?.write.slice(0, 6) ?? []).concat( Array.from(new Set(_additionalRelayUrls)) ?? [] ) } if (!relays.length) { - relays.push(...BIG_RELAY_URLS) + relays.push(...FAST_WRITE_RELAY_URLS) } return relays } async publishEvent(relayUrls: string[], event: NEvent) { - const uniqueRelayUrls = Array.from(new Set(relayUrls)) + const uniqueRelayUrls = this.optimizeRelaySelection(Array.from(new Set(relayUrls))) await new Promise((resolve, reject) => { let successCount = 0 let finishedCount = 0 @@ -154,7 +154,7 @@ class ClientService extends EventTarget { // eslint-disable-next-line @typescript-eslint/no-this-alias const that = this const relay = await this.pool.ensureRelay(url) - relay.publishTimeout = 10_000 // 10s + relay.publishTimeout = 8_000 // 8s return relay .publish(event) .then(() => { @@ -367,7 +367,7 @@ class ClientService extends EventTarget { onAllClose?: (reasons: string[]) => void } ) { - const relays = Array.from(new Set(urls)) + const relays = this.optimizeRelaySelection(Array.from(new Set(urls))) const filters = Array.isArray(filter) ? filter : [filter] // eslint-disable-next-line @typescript-eslint/no-this-alias @@ -386,7 +386,7 @@ class ClientService extends EventTarget { async function startSub() { startedCount++ - const relay = await that.pool.ensureRelay(url, { connectionTimeout: 5000 }).catch(() => { + const relay = await that.pool.ensureRelay(url, { connectionTimeout: 3000 }).catch(() => { return undefined }) // cannot connect to relay @@ -465,7 +465,7 @@ class ClientService extends EventTarget { } return }, - eoseTimeout: 10_000 // 10s + eoseTimeout: 8_000 // 8s }) } }) @@ -657,7 +657,7 @@ class ClientService extends EventTarget { } getSeenEventRelayUrls(eventId: string) { - return this.getSeenEventRelays(eventId).map((relay) => relay.url) + return Array.from(new Set(this.getSeenEventRelays(eventId).map((relay) => relay.url))) } getEventHints(eventId: string) { @@ -875,7 +875,7 @@ class ClientService extends EventTarget { } private async fetchEventsFromBigRelays(ids: readonly string[]) { - const events = await this.query(PROFILE_FETCH_RELAY_URLS, { + const events = await this.query(FAST_READ_RELAY_URLS, { ids: Array.from(new Set(ids)), limit: ids.length }) @@ -1340,6 +1340,13 @@ class ClientService extends EventTarget { return await this.replaceableEventDataLoader.loadMany(params) } + // ================= Performance Optimization ================= + + private optimizeRelaySelection(relays: string[]): string[] { + // Limit to 4 relays for better performance + return relays.slice(0, 4) + } + // ================= Utils ================= async generateSubRequestsForPubkeys(pubkeys: string[], myPubkey?: string | null) {