Merge branch 'tag-perf' into 'main'

EventsDB: query tags by converting to ids filter

See merge request soapbox-pub/ditto!425
This commit is contained in:
Alex Gleason 2024-07-18 03:33:03 +00:00
commit bf9b816e0b

View file

@ -7,6 +7,7 @@ import { nip27 } from 'nostr-tools';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import { DittoTables } from '@/db/DittoTables.ts'; import { DittoTables } from '@/db/DittoTables.ts';
import { normalizeFilters } from '@/filter.ts';
import { dbEventCounter, dbQueryCounter } from '@/metrics.ts'; import { dbEventCounter, dbQueryCounter } from '@/metrics.ts';
import { RelayError } from '@/RelayError.ts'; import { RelayError } from '@/RelayError.ts';
import { purifyEvent } from '@/storages/hydrate.ts'; import { purifyEvent } from '@/storages/hydrate.ts';
@ -250,7 +251,7 @@ class EventsDB implements NStore {
/** Converts filters to more performant, simpler filters that are better for SQLite. */ /** Converts filters to more performant, simpler filters that are better for SQLite. */
async expandFilters(filters: NostrFilter[]): Promise<NostrFilter[]> { async expandFilters(filters: NostrFilter[]): Promise<NostrFilter[]> {
filters = structuredClone(filters); filters = normalizeFilters(structuredClone(filters));
for (const filter of filters) { for (const filter of filters) {
if (filter.search) { if (filter.search) {
@ -287,10 +288,42 @@ class EventsDB implements NStore {
// If this results in an empty kinds array, NDatabase will remove the filter before querying and return no results. // If this results in an empty kinds array, NDatabase will remove the filter before querying and return no results.
filter.kinds = filter.kinds.filter((kind) => !NKinds.ephemeral(kind)); filter.kinds = filter.kinds.filter((kind) => !NKinds.ephemeral(kind));
} }
// Convert tag filters into `ids` filter for performance reasons.
const tagEntries = Object.entries(filter).filter(EventsDB.isTagEntry);
if (tagEntries.length) {
const tagIds: string[] = await tagEntries
.map(([key, value]) =>
this.kysely
.selectFrom('nostr_tags')
.select('event_id')
.distinct()
.where('name', '=', key.replace(/^#/, ''))
.where('value', 'in', value)
)
.reduce((result, query) => result.intersect(query))
.execute()
.then((rows) => rows.map(({ event_id }) => event_id));
if (tagIds.length) {
filter.ids = filter.ids ?? [];
filter.ids.push(...tagIds);
}
for (const [key] of tagEntries) {
delete filter[key];
}
}
} }
return filters; return filters;
} }
/** Check if the object entry is a valid tag filter. */
private static isTagEntry(entry: [string, unknown]): entry is [`#${string}`, string[]] {
const [key, value] = entry;
return key.startsWith('#') && Array.isArray(value);
}
} }
export { EventsDB }; export { EventsDB };