From 7167553afeffae2d504f324574dc333450d097e9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 10 Dec 2023 13:53:51 -0600 Subject: [PATCH] stats: switch to a system based on diff tuples --- src/stats.ts | 64 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/src/stats.ts b/src/stats.ts index edd7479..2496ed6 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -1,29 +1,59 @@ import { db, type EventStatsRow, type PubkeyStatsRow } from '@/db.ts'; -import { Event } from '@/deps.ts'; +import { Event, findReplyTag } from '@/deps.ts'; type PubkeyStat = keyof Omit; type EventStat = keyof Omit; +type PubkeyStatDiff = ['pubkey_stats', pubkey: string, stat: PubkeyStat, diff: number]; +type EventStatDiff = ['event_stats', eventId: string, stat: EventStat, diff: number]; +type StatDiff = PubkeyStatDiff | EventStatDiff; + /** Store stats for the event in LMDB. */ function updateStats(event: Event) { - return updateStatsQuery(event)?.execute(); + const statDiffs = getStatsDiff(event); + if (!statDiffs.length) return; + + const pubkeyDiffs = statDiffs.filter(([table]) => table === 'pubkey_stats') as PubkeyStatDiff[]; + const eventDiffs = statDiffs.filter(([table]) => table === 'event_stats') as EventStatDiff[]; + + return db.transaction().execute(() => { + return Promise.all([ + pubkeyStatsQuery(pubkeyDiffs).execute(), + eventStatsQuery(eventDiffs).execute(), + ]); + }); } -function updateStatsQuery(event: Event) { - const firstE = findFirstTag(event, 'e'); +/** Calculate stats changes ahead of time so we can build an efficient query. */ +function getStatsDiff(event: Event): StatDiff[] { + const statDiffs: StatDiff[] = []; + + const firstE = event.tags.find(([name]) => name === 'e')?.[1]; + const replyTag = findReplyTag(event as Event<1>); switch (event.kind) { case 1: - return incrementPubkeyStatsQuery([event.pubkey], 'notes_count', 1); + statDiffs.push(['pubkey_stats', event.pubkey, 'notes_count', 1]); + if (replyTag && replyTag[1]) { + statDiffs.push(['event_stats', replyTag[1], 'replies_count', 1]); + } + break; case 6: - return firstE ? incrementEventStatsQuery([firstE], 'reposts_count', 1) : undefined; + if (firstE) { + statDiffs.push(['event_stats', firstE, 'reposts_count', 1]); + } + break; case 7: - return firstE ? incrementEventStatsQuery([firstE], 'reactions_count', 1) : undefined; + if (firstE) { + statDiffs.push(['event_stats', firstE, 'reactions_count', 1]); + } } + + return statDiffs; } -function incrementPubkeyStatsQuery(pubkeys: string[], stat: PubkeyStat, diff: number) { - const values: PubkeyStatsRow[] = pubkeys.map((pubkey) => { +function pubkeyStatsQuery(diffs: PubkeyStatDiff[]) { + const values: PubkeyStatsRow[] = diffs.map(([_, pubkey, stat, diff]) => { const row: PubkeyStatsRow = { pubkey, followers_count: 0, @@ -40,13 +70,15 @@ function incrementPubkeyStatsQuery(pubkeys: string[], stat: PubkeyStat, diff: nu oc .column('pubkey') .doUpdateSet((eb) => ({ - [stat]: eb(stat, '+', diff), + followers_count: eb('followers_count', '+', eb.ref('excluded.followers_count')), + following_count: eb('following_count', '+', eb.ref('excluded.following_count')), + notes_count: eb('notes_count', '+', eb.ref('excluded.notes_count')), })) ); } -function incrementEventStatsQuery(eventIds: string[], stat: EventStat, diff: number) { - const values: EventStatsRow[] = eventIds.map((event_id) => { +function eventStatsQuery(diffs: EventStatDiff[]) { + const values: EventStatsRow[] = diffs.map(([_, event_id, stat, diff]) => { const row: EventStatsRow = { event_id, replies_count: 0, @@ -63,13 +95,11 @@ function incrementEventStatsQuery(eventIds: string[], stat: EventStat, diff: num oc .column('event_id') .doUpdateSet((eb) => ({ - [stat]: eb(stat, '+', diff), + replies_count: eb('replies_count', '+', eb.ref('excluded.replies_count')), + reposts_count: eb('reposts_count', '+', eb.ref('excluded.reposts_count')), + reactions_count: eb('reactions_count', '+', eb.ref('excluded.reactions_count')), })) ); } -function findFirstTag({ tags }: Event, name: string): string | undefined { - return tags.find(([n]) => n === name)?.[1]; -} - export { updateStats };