9.3 KiB
Performance & Architektur Report (Pounce)
Stand (Codebase): d08ca33fe3c88b3b2d716f0bdf22b71f989a5eb9
Datum: 2025-12-12
Scope: frontend/ (Next.js 14 App Router) + backend/ (FastAPI + async SQLAlchemy + APScheduler) + DB + Docker/Deploy.
Status (umgesetzt)
- ✅ Phase 0: Scheduler split, Market-Feed bounded paging, Health cache-first, PriceTracker N+1 fix (
2e8ff50) - ✅ Phase 1: DB migrations (indexes + optional columns), persisted
pounce_score, Admin N+1 removal, Radar summary endpoint (ee4266d) - ✅ Phase 2: Redis + ARQ worker scaffolding, Prometheus metrics (
/metrics), load test scaffolding, Docker hardening (5d23d34)
Executive Summary (die 5 größten Hebel)
-
Scheduler aus dem API-Prozess herauslösen
Aktuell startet der Scheduler inbackend/app/main.pyim App-Lifespan. Bei mehreren Uvicorn/Gunicorn-Workern laufen Jobs mehrfach parallel → doppelte Scrapes/Checks, DB-Last, E-Mail-Spam, inkonsistente Zustände. -
Market Feed Endpoint (
/api/v1/auctions/feed) DB-seitig paginieren/sortieren
backend/app/api/auctions.pylädt derzeit alle aktiven Auktionen + alle aktiven Listings in Python, berechnet Score, sortiert, und paginiert erst am Ende. Das skaliert schlecht sobald ihr > ein paar hundert Auktionen habt. -
Price Tracker N+1 eliminieren
backend/app/services/price_tracker.py::detect_price_changes()macht aktuell: distinct(tld, registrar) → pro Paar query(limit 2). Das ist ein klassischer N+1 und wird bei 800+ TLDs schnell sehr langsam. -
Health-Cache wirklich nutzen
Es gibtDomainHealthCache, und der Scheduler schreibt Status/Score. AberGET /domains/{id}/healthmacht immer einen Live-Check (domain_health.pymit HTTP/DNS/SSL). Für UI/Performance besser: default cached, live nur “Refresh”. -
Valuation im Request-Path reduzieren (Auctions)
backend/app/api/auctions.pyberechnet pro Auction im Response optional valuation, undvaluation_servicefragt pro Domain auch DB-Daten ab (TLD cost). Das ist pro Request potenziell sehr teuer.
Messwerte (Frontend Build)
Aus frontend/ → npm run build (Next.js 14.0.4):
- First Load JS (shared): ~81.9 kB
- Größte Pages (First Load):
/terminal/watchlist: ~120 kB/terminal/radar: ~120 kB/terminal/intel/[tld]: ~115 kB/terminal/market: ~113 kB
- Warnings: Einige Routen “deopted into client-side rendering” (z.B.
/terminal/radar,/terminal/listing,/unsubscribe,/terminal/welcome). Das ist nicht zwingend schlimm, aber ein Hinweis: dort wird kein echtes SSR/Static genutzt.
Interpretation: Das Frontend ist vom Bundle her bereits recht schlank. Die größten Performance-Risiken liegen aktuell eher im Backend (Queries, Jobs, N+1, Caching).
Backend – konkrete Hotspots & Fixes
1) Scheduler: Architektur & Skalierung
Ist-Zustand
backend/app/main.py:start_scheduler()imlifespan()→ Scheduler läuft im selben Prozess wie die API.backend/app/scheduler.py: viele Jobs (Domain Checks, Health Checks, TLD Scrape, Auction Scrape, Cleanup, Sniper Matching).
Probleme
- Multi-worker Deployment (Gunicorn/Uvicorn) → Scheduler läuft pro Worker → Job-Duplikate.
- Jobs sind teils sequentiell (Domain Checks), teils N+1 (Health Cache, Digests, Sniper Matching).
Empfehlung (Best Practice)
- Scheduler als separater Service/Container laufen lassen (z.B. eigener Docker Service
scheduler, oder systemd/cron job, oder Celery Worker + Beat). - Wenn Scheduler im selben Code bleiben soll: Leader-Lock (Redis/DB advisory lock) einbauen, sodass nur ein Prozess Jobs ausführt.
2) Market Feed (backend/app/api/auctions.py::get_market_feed)
Ist-Zustand
- Holt Listings und Auktionen ohne DB-Limit/Offset, baut
itemsin Python, sortiert in Python, paginiert erst am Ende.
Warum das weh tut
- Bei z.B. 10’000 aktiven Auktionen ist jeder Request an
/feedein “Full table scan + Python sort + JSON build”.
Fix-Strategie
- Score persistieren:
pounce_scoreinDomainAuctionundDomainListingspeichern/aktualisieren (beim Scrape bzw. beim Listing Create/Update).
Dann kann man DB-seitigWHERE pounce_score >= :min_scoreundORDER BY pounce_score DESCmachen. - DB-Pagination:
LIMIT/OFFSETin SQL, nicht in Python. - Filter DB-seitig:
keyword,tld,price range,ending_withinin SQL. - Response caching: Für public feed (oder häufige Filterkombos) Redis TTL 15–60s.
3) Auction Search (backend/app/api/auctions.py::search_auctions)
Ist-Zustand
- Nach Query werden Auktionen in Python gefiltert (Vanity Filter) und dann pro Auction in einer Schleife
valuation_service.estimate_value(...)aufgerufen.
Probleme
- Valuation kann DB-Queries pro Item auslösen (TLD cost avg), und läuft seriell.
Fix-Strategie
- Valuations vorberechnen (Background Job) und in einer Tabelle/Spalte cachen.
- Alternativ: Valuation nur für Top-N (z.B. 20) berechnen und für den Rest weglassen.
- TLD-Cost als in-memory cache (LRU/TTL) oder einmal pro Request prefetchen.
4) Price Tracker (backend/app/services/price_tracker.py)
Ist-Zustand
- N+1 Queries: distinct(tld, registrar) → pro Paar 1 Query für die letzten 2 Preise.
Fix-Strategie
- SQL Window Function (Postgres & SQLite können das):
ROW_NUMBER() OVER (PARTITION BY tld, registrar ORDER BY recorded_at DESC)- dann self-join oder
LAG()für vorherigen Preis.
- Zusätzlich DB-Index:
tld_prices(tld, registrar, recorded_at DESC)
5) Domain Health (backend/app/services/domain_health.py + backend/app/api/domains.py)
Ist-Zustand
- Live Health Check macht pro Request echte DNS/HTTP/SSL Checks.
- Scheduler schreibt
DomainHealthCache, aber Endpoint nutzt ihn nicht.
Fix-Strategie
- Neue Endpoints:
GET /domains/health-cache→ cached health für alle Domains eines Users (1 Request für UI)POST /domains/{id}/health/refresh→ live refresh (asynchron, job queue)
DomainHealthCacheauch mitdns_data/http_data/ssl_databefüllen (ist im Model vorgesehen).
Datenbank – Indexing & Query Patterns
Empfohlene Indizes (High Impact)
- Domain Checks
domain_checks(domain_id, checked_at DESC)für/domains/{id}/history
- TLD Prices
tld_prices(tld, registrar, recorded_at DESC)für “latest two prices” und history queries
- Health Cache
domain_health_cache(domain_id)(unique ist vorhanden), optionalchecked_at
Query-Patterns (Quick Wins)
-
In
backend/app/api/domains.py::add_domain()wird aktuelllen(current_user.domains)genutzt → lädt potenziell viele Rows.
Besser:SELECT COUNT(*) FROM domains WHERE user_id = .... -
Admin “Users list”: vermeidet N+1 (Subscription + Domain Count pro User) →
JOIN+GROUP BY.
Frontend – Verbesserungen (gezielt, nicht “blind refactor”)
1) Reduziere API-Calls pro Screen (Dashboard/Watchlist)
Aktuell holen manche Screens mehrere Endpoints und rechnen Stats client-side:
/terminal/radar: holt u.a. Auctions undGET /listings/mynur um Stats zu zählen.
Empfehlung
- Ein Endpoint:
GET /dashboard/summary(counts + small previews) → 1 Request statt 3–5.
2) Tabellen/Listen skalieren
- Für sehr große Listen (Market Feed / TLDs / Admin Users) mittelfristig:
- Pagination + “infinite scroll”
- ggf. Virtualisierung (
react-window) falls 1000+ Rows.
3) Kleine Code-Health Fixes (auch Performance)
- Achtung bei
.sort()auf State-Arrays:.sort()mutiert. Immer vorher kopieren ([...arr].sort(...)), sonst entstehen subtile Bugs und unnötige Re-Renders.
Deployment/Infra – “Production grade” Performance
Backend
- Gunicorn + Uvicorn Workers (oder Uvicorn
--workers) ist gut für CPU/IO – aber nur wenn Scheduler separat läuft. - DB Pooling:
create_async_engine(..., pool_size=..., max_overflow=...)für Postgres (nicht bei SQLite). - slowapi: in Production Redis storage verwenden (sonst pro Worker eigener limiter state).
Frontend
- Dockerfile erwartet
.next/standalone. Dafür infrontend/next.config.jsoutput: 'standalone'aktivieren (oder Dockerfile anpassen).
Priorisierte Roadmap
Phase 0 (0–1 Tag, Quick Wins)
- Scheduler entkoppeln ODER Leader-Lock einbauen
/auctions/feed: DB-limit + offset + order_by (keine full scans)PriceTracker.detect_price_changes: Window-Query statt N+1- Cached Health Endpoint für Watchlist
Phase 1 (1–2 Wochen)
- Precompute
pounce_score+ valuations (Background Jobs), persistieren & cachen - Admin N+1 entfernen (Users list)
- DB Indizes ergänzen (DomainCheck, TLDPrice)
- “Dashboard summary” Endpoint + Frontend umstellen
Phase 2 (2–6 Wochen)
- Background-Job System (Celery/RQ/Dramatiq) + Redis
- Observability: Request timing, DB query timing, Prometheus metrics, tracing
- Load testing + Performance budgets (API p95, page LCP/TTFB)
Mess-/Monitoring Plan (damit wir nicht im Dunkeln optimieren)
- Backend
- Log: Request duration + endpoint + status
- DB: slow query logging / EXPLAIN ANALYZE (prod-like)
- Metrics: p50/p95 latency pro endpoint, queue depth, job runtime
- Frontend
- Core Web Vitals Tracking (ist bereits angelegt in
frontend/src/lib/analytics.ts) - “API Timing” (TTFB + payload size) für Market/Watchlist
- Core Web Vitals Tracking (ist bereits angelegt in