From 03a4c75e3ec0dce98d03681fe42b90e72eededb7 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 12 Apr 2026 21:32:05 +0200 Subject: [PATCH] fix: reduce db pool size to prevent connection exhaustion, sort grouped views by count Pool was 20+10 overflow per engine; uvicorn --reload leaked pools until Postgres hit max_connections=100. Reduced to 5+5 with pool_pre_ping. Grouped views (tags, people, colors, rated) now sort cards largest-first. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/config.py | 3 ++- backend/app/database.py | 2 ++ frontend/src/components/colors/ColorsView.tsx | 1 + frontend/src/components/people/PeopleView.tsx | 8 ++++++-- frontend/src/components/rated/RatedView.tsx | 3 ++- frontend/src/components/tags/TagsView.tsx | 5 ++++- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/backend/app/config.py b/backend/app/config.py index 5d5b866..025bd6f 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -26,7 +26,8 @@ class PerformanceSettings(BaseModel): """Performance tuning settings""" max_concurrent_thumbnails: int = 10 cache_ttl: int = 3600 - db_pool_size: int = 20 + db_pool_size: int = 5 + db_pool_max_overflow: int = 5 db_pool_recycle: int = 3600 class EmbedderSettings(BaseModel): diff --git a/backend/app/database.py b/backend/app/database.py index a6b5b9b..32211a8 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -73,7 +73,9 @@ else: settings.database_url, echo=False, pool_size=settings.performance.db_pool_size, + max_overflow=settings.performance.db_pool_max_overflow, pool_recycle=settings.performance.db_pool_recycle, + pool_pre_ping=True, ) # Create async session factory diff --git a/frontend/src/components/colors/ColorsView.tsx b/frontend/src/components/colors/ColorsView.tsx index 23f009d..f20c831 100644 --- a/frontend/src/components/colors/ColorsView.tsx +++ b/frontend/src/components/colors/ColorsView.tsx @@ -62,6 +62,7 @@ export function ColorsView() { representative: uncolored[0], }) } + result.sort((a, b) => b.count - a.count) return result }, [allPhotos]) diff --git a/frontend/src/components/people/PeopleView.tsx b/frontend/src/components/people/PeopleView.tsx index 81b9f91..8d15256 100644 --- a/frontend/src/components/people/PeopleView.tsx +++ b/frontend/src/components/people/PeopleView.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react' +import { useState, useCallback, useMemo } from 'react' import { Users, Pencil, Check, X, Loader2, ArrowLeft } from 'lucide-react' import clsx from 'clsx' import { useMutation, useQueryClient } from '@tanstack/react-query' @@ -19,7 +19,11 @@ import { toast } from '../ToastContainer' * 2. Detail view showing a person's photos in the full Timeline — Esc to go back */ export function PeopleView() { - const { data: clusters = [], isLoading } = useTagsQuery('face_cluster') + const { data: rawClusters = [], isLoading } = useTagsQuery('face_cluster') + const clusters = useMemo( + () => [...rawClusters].sort((a, b) => b.photo_count - a.photo_count), + [rawClusters] + ) const queryClient = useQueryClient() const setTagIds = useFilterStore((s) => s.setTagIds) diff --git a/frontend/src/components/rated/RatedView.tsx b/frontend/src/components/rated/RatedView.tsx index d62b34d..09d4561 100644 --- a/frontend/src/components/rated/RatedView.tsx +++ b/frontend/src/components/rated/RatedView.tsx @@ -37,7 +37,7 @@ export function RatedView() { } } - // Highest rating first + // Sort by count, biggest first const result: RatingGroup[] = [] for (let r = 5; r >= 1; r--) { const photos = buckets.get(r) ?? [] @@ -49,6 +49,7 @@ export function RatedView() { representative: photos[0], }) } + result.sort((a, b) => b.count - a.count) return result }, [allPhotos]) diff --git a/frontend/src/components/tags/TagsView.tsx b/frontend/src/components/tags/TagsView.tsx index 8eaba16..28f15f3 100644 --- a/frontend/src/components/tags/TagsView.tsx +++ b/frontend/src/components/tags/TagsView.tsx @@ -19,7 +19,10 @@ export function TagsView() { // Exclude face_cluster tags (those live in PeopleView) const tags = useMemo( - () => allTags.filter((t) => t.kind !== 'face_cluster'), + () => + allTags + .filter((t) => t.kind !== 'face_cluster') + .sort((a, b) => b.photo_count - a.photo_count), [allTags] )