perf(ui): smaller initial page, slower idle polling
Photos grid was fetching per_page=500 on the very first request, which serialized hundreds of thumbnail requests behind a single sort+payload. Split into PER_PAGE_INITIAL=100 (one viewport, fast paint) and PER_PAGE_BACKGROUND=500 (subsequent prefetch pages, fewer round-trips). Idle polling for scan-status and worker-status was set to 10s / 15s respectively. With nothing queued the typical session was firing 4–6 status requests every minute through the single uvicorn event loop on top of everything else. Bumped both to 30s. While actively scanning / processing the 2s / 3s cadence is unchanged — that's where the user actually wants live updates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,20 +28,20 @@ export function ScanProgress() {
|
||||
queryKey: ['scan-status'],
|
||||
queryFn: () => library.scanStatus(),
|
||||
refetchInterval: (query) =>
|
||||
query.state.data?.is_scanning ? 2000 : 10000,
|
||||
query.state.data?.is_scanning ? 2000 : 30000,
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
const isScanning = scanStatus?.is_scanning ?? false
|
||||
|
||||
// Poll worker status to track vision queue activity.
|
||||
// Fast polling (3s) while processing, slow (15s) otherwise.
|
||||
// Fast polling (3s) while processing, slow (30s) otherwise.
|
||||
const { data: workerStatus } = useQuery<WorkerStatus>({
|
||||
queryKey: ['worker-status-progress'],
|
||||
queryFn: () => library.maintenance.workerStatus(),
|
||||
refetchInterval: (query) => {
|
||||
const q = totalQueued(query.state.data)
|
||||
return q > 0 ? 3000 : 15000
|
||||
return q > 0 ? 3000 : 30000
|
||||
},
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
@@ -81,17 +81,19 @@ export function usePhotosQuery() {
|
||||
// seeks directly to the next slice via an indexed range scan,
|
||||
// O(1) regardless of depth (no OFFSET skipping).
|
||||
//
|
||||
// MAX_PAGES is intentionally modest: 20 × 500 = 10 000 photos
|
||||
// covers almost every browsing session up-front without burning
|
||||
// through a 100k library on cold load. If a user scrolls past
|
||||
// that horizon we'll add an infinite-query trigger; for now the
|
||||
// cap keeps cold-load memory / network pressure sane.
|
||||
const PER_PAGE = 500
|
||||
// First page is small (~one viewport) so the grid paints fast;
|
||||
// background pages are larger so we still cover ~10k photos in
|
||||
// a few round-trips without burning through a 100k library on
|
||||
// cold load. If a user scrolls past that horizon we'll add an
|
||||
// infinite-query trigger; for now the cap keeps cold-load
|
||||
// memory / network pressure sane.
|
||||
const PER_PAGE_INITIAL = 100
|
||||
const PER_PAGE_BACKGROUND = 500
|
||||
const MAX_PAGES = 20
|
||||
const INTER_PAGE_DELAY_MS = 50
|
||||
|
||||
const first = await fetchCursorPage(
|
||||
{ per_page: PER_PAGE, ...filterParams },
|
||||
{ per_page: PER_PAGE_INITIAL, ...filterParams },
|
||||
signal,
|
||||
)
|
||||
const firstBatch = first.photos || []
|
||||
@@ -104,7 +106,7 @@ export function usePhotosQuery() {
|
||||
if (signal?.aborted) return
|
||||
try {
|
||||
const page = await fetchCursorPage(
|
||||
{ per_page: PER_PAGE, cursor: nextCursor, ...filterParams },
|
||||
{ per_page: PER_PAGE_BACKGROUND, cursor: nextCursor, ...filterParams },
|
||||
signal,
|
||||
)
|
||||
if (signal?.aborted) return
|
||||
@@ -114,7 +116,7 @@ export function usePhotosQuery() {
|
||||
['photos', filterParams],
|
||||
(prev) => (prev ? [...prev, ...more] : more)
|
||||
)
|
||||
if (!nextCursor || more.length < PER_PAGE) return
|
||||
if (!nextCursor || more.length < PER_PAGE_BACKGROUND) return
|
||||
// Yield a beat between pages so the main thread stays
|
||||
// responsive (thumbnail decode, scroll handling) while
|
||||
// we're back-filling in the background.
|
||||
|
||||
@@ -49,14 +49,14 @@ export function useScanActivity() {
|
||||
queryKey: ['scan-status'],
|
||||
queryFn: () => library.scanStatus(),
|
||||
refetchInterval: (query) =>
|
||||
query.state.data?.is_scanning ? 2000 : 10000,
|
||||
query.state.data?.is_scanning ? 2000 : 30000,
|
||||
})
|
||||
const { data: workerStatus } = useQuery<WorkerStatus>({
|
||||
queryKey: ['worker-status-progress'],
|
||||
queryFn: () => library.maintenance.workerStatus(),
|
||||
refetchInterval: (query) => {
|
||||
const data = query.state.data
|
||||
return pendingTasks(data) + activeTasks(data) > 0 ? 3000 : 15000
|
||||
return pendingTasks(data) + activeTasks(data) > 0 ? 3000 : 30000
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user