perf(feed): rewrite v_feed, bound feed_delta, server-time SSE cursor (H6,H7,M9)

H6: migration 006 replaces v_feed's double LEFT JOIN + COUNT(DISTINCT) (which
materialized a likes×comments Cartesian per upload) with correlated scalar
subqueries that each use their own index. Output columns are unchanged, so
feed + hashtag-filtered paths both benefit.

H7: feed_delta now applies the feed rate limit (keyed per user), caps results
at 200 rows, and clamps how far back a client `since` may reach (7 days). When
clamped or capped it returns reload_required=true; the SSE client turns that
into a full feed reload instead of streaming the whole gallery through the view
on every tab refocus.

M9: the SSE reconnect cursor is now advanced only from server timestamps (an
upload's created_at, seeded from the feed and updated on new-upload events and
delta responses), never the client clock — so a skewed phone clock can't drop
events missed while backgrounded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-27 15:58:35 +02:00
parent ae6c496f94
commit cf428725b9
6 changed files with 133 additions and 8 deletions

View File

@@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { getToken, getUserId } from '$lib/auth';
import { api } from '$lib/api';
import { connectSse, disconnectSse, onSseEvent } from '$lib/sse';
import { connectSse, disconnectSse, onSseEvent, setLastEventTime } from '$lib/sse';
import { onMount, onDestroy } from 'svelte';
import FeedGrid from '$lib/components/FeedGrid.svelte';
import FeedListCard from '$lib/components/FeedListCard.svelte';
@@ -200,6 +200,9 @@
}),
onSseEvent('like-update', () => loadFeed(true)),
onSseEvent('new-comment', () => loadFeed(true)),
// Delta was clamped/capped server-side (H7) — do a full reload instead
// of trusting a partial set.
onSseEvent('feed-reload', () => loadFeed(true)),
// Synthetic event from the SSE client after a foreground reconnect — merge
// any uploads + deletions we missed while the tab was hidden.
onSseEvent('feed-delta', (data) => {
@@ -243,6 +246,10 @@
const res = await api.get<FeedResponse>(`/feed?${params}`);
uploads = res.uploads;
nextCursor = res.next_cursor;
// Seed the SSE reconnect cursor from the newest server timestamp so the
// delta on the next reconnect is based on server time, not the client
// clock (M9).
if (uploads.length) setLastEventTime(uploads[0].created_at);
} catch (e) {
// Initial / user-triggered refresh is worth surfacing — background SSE refetches are noisier and silenced below.
if (!refresh) toastError(e);