Improve performance of account search

This commit is contained in:
Alex Gleason 2025-02-12 14:40:00 -06:00
parent 7d8e5e676c
commit 379953a8cb
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
2 changed files with 34 additions and 33 deletions

View file

@ -119,6 +119,7 @@ const accountSearchQuerySchema = z.object({
const accountSearchController: AppController = async (c) => { const accountSearchController: AppController = async (c) => {
const { signal } = c.req.raw; const { signal } = c.req.raw;
const { limit } = c.get('pagination'); const { limit } = c.get('pagination');
const kysely = await Storages.kysely(); const kysely = await Storages.kysely();
const viewerPubkey = await c.get('signer')?.getPublicKey(); const viewerPubkey = await c.get('signer')?.getPublicKey();
@ -136,27 +137,22 @@ const accountSearchController: AppController = async (c) => {
if (!event && lookup) { if (!event && lookup) {
const pubkey = await lookupPubkey(lookup); const pubkey = await lookupPubkey(lookup);
return c.json(pubkey ? [await accountFromPubkey(pubkey)] : []); return c.json(pubkey ? [accountFromPubkey(pubkey)] : []);
} }
const followedPubkeys: Set<string> = viewerPubkey ? await getFollowedPubkeys(viewerPubkey) : new Set(); const events: NostrEvent[] = [];
const pubkeys = Array.from(await getPubkeysBySearch(kysely, { q: query, limit, offset: 0, followedPubkeys }));
let events = event ? [event] : await store.query([{ kinds: [0], authors: pubkeys, limit }], { if (event) {
signal, events.push(event);
}); } else {
const following = viewerPubkey ? await getFollowedPubkeys(viewerPubkey) : new Set<string>();
if (!event) { const authors = [...await getPubkeysBySearch(kysely, { q: query, limit, offset: 0, following })];
events = pubkeys const profiles = await store.query([{ kinds: [0], authors, limit }], { signal });
.map((pubkey) => events.find((event) => event.pubkey === pubkey)) events.push(...profiles);
.filter((event) => !!event);
} }
const accounts = await hydrateEvents({ events, store, signal }).then(
(events) => const accounts = await hydrateEvents({ events, store, signal })
Promise.all( .then((events) => events.map((event) => renderAccount(event)));
events.map((event) => renderAccount(event)),
),
);
return c.json(accounts); return c.json(accounts);
}; };

View file

@ -5,30 +5,35 @@ import { DittoTables } from '@/db/DittoTables.ts';
/** Get pubkeys whose name and NIP-05 is similar to 'q' */ /** Get pubkeys whose name and NIP-05 is similar to 'q' */
export async function getPubkeysBySearch( export async function getPubkeysBySearch(
kysely: Kysely<DittoTables>, kysely: Kysely<DittoTables>,
opts: { q: string; limit: number; offset: number; followedPubkeys: Set<string> }, opts: { q: string; limit: number; offset: number; following: Set<string> },
): Promise<Set<string>> { ): Promise<Set<string>> {
const { q, limit, followedPubkeys, offset } = opts; const { q, limit, following, offset } = opts;
let query = kysely const pubkeys = new Set<string>();
const query = kysely
.selectFrom('author_stats') .selectFrom('author_stats')
.select((eb) => [ .select('pubkey')
'pubkey', .where('search', sql`%>`, q)
'search', .orderBy('followers_count desc')
eb.fn('word_similarity', [sql`${q}`, 'search']).as('sml'),
])
.where(() => sql`${q} <% search`)
.orderBy(['followers_count desc'])
.orderBy(['sml desc', 'search'])
.limit(limit) .limit(limit)
.offset(offset); .offset(offset);
const pubkeys = new Set((await query.execute()).map(({ pubkey }) => pubkey)); if (following.size) {
const authorsQuery = query.where('pubkey', 'in', [...following]);
if (followedPubkeys.size > 0) { for (const { pubkey } of await authorsQuery.execute()) {
query = query.where('pubkey', 'in', [...followedPubkeys]); pubkeys.add(pubkey);
}
} }
const followingPubkeys = new Set((await query.execute()).map(({ pubkey }) => pubkey)); if (pubkeys.size >= limit) {
return pubkeys;
}
return new Set(Array.from(followingPubkeys.union(pubkeys))); for (const { pubkey } of await query.execute()) {
pubkeys.add(pubkey);
}
return pubkeys;
} }