# PERFORMANCE NOTES — arac-hasar-v2 Statik kod analizi ile cikarilmis darbogazlar ve **tahmini** kazanim listesi. Bu dosya hicbir benchmark calistirilmadan, sadece kaynak okuma uzerinden uretildi — sayilar gercek olculmedi, mantik tabanli tahminlerdir. Hedef metrikler README'den alindi: end-to-end < 8s / 4 foto, damage ~45ms, parts ~30ms, severity ~12ms / image. Hedef bandi su an SADECE GPU + warmup + 1 image varsaymina dayaniyor; bu dosyadaki darbogazlar duzeltilmezse 4-foto end-to-end realistik tahminim 12-25s arasinda. --- ## 1) Backend — `main.py`, `ml_service.py`, `worker.py`, `ws.py`, `database.py` ### 1.1 [KRITIK] Sync endpoint event-loop'u bloklar — async def icinde senkron `ml_pipeline.analyze` **Konum:** `services/backend/main.py:593-628` (`_process_sync`) ```python async def _process_sync(files, auth): for i, f in enumerate(files): content, url = await _store_upload(f, inspection_id, i) img = _decode_image(content, i) r = ml_pipeline.analyze(img) # <-- BLOKLAR! Senkron 100-500ms GPU isi ``` `ml_pipeline.analyze` icinde `_infer_lock` ve YOLO `predict()` cagrilari **senkron**; async route icinde dogrudan cagrilirsa uvicorn worker'inin event-loop'u tum yaglar boyunca bloke olur — baska istekler queue'lanir. `ml_service.py` icinde zaten `_analyze_one_async` -> `asyncio.to_thread` helper'i var ama `_process_sync` onu **kullanmiyor**, dogrudan blocking call yapiyor. Ayni dosyadaki `_decode_image` (cv2.imdecode) de senkron — kucuk ama bloklayici. **Tahmini etki:** 1 uvicorn worker'da concurrent 4 sync request -> her biri sirayla iclendigi icin p95 latency 4x artar. Fix sonrasi: aci p95 ayni ama diger endpoint'ler (history, status) bloklanmaz; throughput 1.5-2x. **Onerilen fix (kod degisikligi yok, sadece not):** ```python r = await asyncio.to_thread(ml_pipeline.analyze, img) ``` Hatta tum sync loop'u `asyncio.gather` ile parallelize edilebilir (asagiya bak). --- ### 1.2 [KRITIK] Sync mode coklu goruntu — seri islem, paralellestirilebilir **Konum:** `main.py:598-606` Sync moda 5 goruntuye kadar izin var (`max_images_sync`) ve hepsi sirayla isleniyor: upload (S3 await) -> decode -> analyze -> sonraki. Upload S3 ve decode CPU; ML GPU. Bunlari pipelining ile orterek wall-clock 30-40% kisaltilabilir. **Tahmini etki:** 5-foto sync mod 5*1.5s ~= 7.5s -> ~4.5-5.5s. Single-foto icin etki yok. **NOT:** GPU paylasimi `_infer_lock` ile serialize edildigi icin `analyze` calls'i gercekten paralel calismaz (tek GPU/lock). Ama upload+decode `gather` edilince ML adimindan **once** is yapilmis olur. --- ### 1.3 [KRITIK] ML pipeline: 3 model SERI calisiyor — README ile celiskili **Konum:** `services/ml/pipeline.py:534-554` (`analyze` icindeki timing bloku) ```python damages = self._detect_damages(image) # ~45ms parts = self._detect_parts(image) # ~30ms self._assign_parts_to_damages(...) # CPU, ~5-15ms (mask IoU) self._classify_severities(damages, image) # her hasar icin ~12ms (CNN) ``` README.md:153: *"All three models run in parallel per image"* — **YANLIS**. `_detect_damages` ve `_detect_parts` art arda cagriliyor; severity bunlardan sonra geliyor cunku `primary_part`'a ihtiyaci var. **Damage + Parts gercekten paralel olabilir** (severity'den onceki adim) cunku ikisi de input olarak sadece image aliyor. `concurrent.futures` veya CUDA streams ile paralel kosulursa: - Mevcut: damage(45) + parts(30) = 75ms model wall-clock - Paralel: max(45, 30) = 45ms -> **~30ms / image kazanc**, 4 foto icin **~120ms / inspection** (kucuk ama bedava). GPU bound oldugu icin tek GPU uzerinde gercek paralellik sinirli; ama Python GIL'i serbest birakildigi icin (ultralytics+torch C extension'da release eder) ThreadPoolExecutor ile sample-level overlap mumkun. CPU device'inda kazanc buyuk (~20-30ms). **Severity** her damage icin ayri CNN forward — `_classify_severities` icinde `for d in damages: severity.predict(...)` (line 481). Bu **N+1 GPU call**. Birden cok crop tek batch'te yapilirsa **N kez 12ms -> ~1*30ms** (batch inference). 5 hasarli foto icin: 5*12=60ms -> 25ms, ~35ms/image kazanc. **Tahmini etki:** Per-image 30-70ms; 4-foto inspection icin **~200-300ms** toplam. Hedef <8s bandinda kalmasi acisindan kritik. --- ### 1.4 [YUKSEK] ML warmup yapilmis ama yarim — sadece damage+parts **Konum:** `services/backend/ml_service.py:91-96` ```python dummy = np.zeros((settings.ml_imgsz, settings.ml_imgsz, 3), dtype=np.uint8) self._pipeline.analyze(dummy) ``` `analyze` cagrildigi icin tum pipeline asilgar (severity dahil), ama `pipeline.warmup()` (pipeline.py:312) icin ayri bir method var ki **severity modeli warm etmiyor** (sadece damage+parts predict). `analyze(dummy)` versiyon genis ama dummy resimde `damages` listesi bos olacagi icin **severity asla calismaz** -> ilk gercek isteğin severity adimı cold-start (CUDA kernel JIT, ~300-1000ms ekstra). **Tahmini etki:** Ilk gercek istek 0.3-1.0s daha yavas. Fix: warmup dummy icin sahte bir damage ekle veya `severity.predict` ayni anda cagir. --- ### 1.5 [KRITIK] Upload memory load — buyuk fotoda RAM patlar, streaming yok **Konum:** `main.py:495-501` ```python async def _store_upload(file, inspection_id, index): content = await file.read() # <-- TUM bytes RAM'e _validate_image_file(file, content, index) ... url = await upload_image(content, key, ...) ``` `file.read()` tum dosyayi RAM'e yukler. `max_image_size_mb=10` * 20 async images = **200MB / request** bellek. Concurrent 10 istekte 2GB. Render free tier'in 512MB-1GB RAM limitleri ile uyumsuz; OOM kill riski. **Fix:** `UploadFile.file` chunked iterator + S3 multipart upload, validasyon icin sadece ilk birkac KB (MIME magic bytes). **Tahmini etki:** Bellek pik 200MB -> 20-40MB; RAM olum spike'lari ortadan kalkar. Throughput dogrudan etkilenmez ama autoscale-OOM cycle dururlar. **Ek:** `storage.download_image` (storage.py:108-126) worker'da indirme yapinca yine memory load. Worker icin disk-stream + decode-on-fly daha guvenli. --- ### 1.6 [KRITIK] Database — iki paralel sistem, bağlanti pool kullanilmiyor **Konum:** `main.py:88-324` (psycopg2 raw SQL) + `database.py` (SQLAlchemy async asyncpg) `database.py` cok guzel async engine + pool (`pool_size=10, max_overflow=20, pool_pre_ping`) hazirlamis ama `main.py` **bunu kullanmiyor**! Her DB cagrisinda yeni `psycopg2.connect()` aciliyor: ```python def _pg_connect(): return _psycopg2.connect(settings.database_url, connect_timeout=3) ``` Her `save_inspection`, `update_inspection`, `get_inspection`, `list_inspections`, `count` cagrisi **3-handshake TCP + auth = ~15-50ms ekstra latency / call**. WS polling fallback (`ws.py:154-191`) saniyede 1 kere bu islemi yapiyor — 600s boyunca 600 ekstra connection setup, **inanilmaz savurgan**. **Tahmini etki (en buyuk single-fix):** - Her DB endpoint icin **15-50ms eklenen baglanti maliyeti** ortadan kalkar. - WS polling (Redis yoksa) 600 connection -> 1 persistent. - `GET /api/v1/inspect` (history) suanki tahmin: 100-200ms (count + list = 2 ayri baglanti). Pool'lu hali: 20-40ms. **~150ms -> 30ms kazanim, 5x.** Action: `main.py` icindeki repo'yu ORM session'a tasi (yorum yapilmis, henuz yapilmamis). pgbouncer + asyncpg tek baglanti ile bu darbogazi tamamen kapatir. --- ### 1.7 [YUKSEK] N+1 yok — ama list endpoint count + items 2 ayri query **Konum:** `main.py:716-743` (`list_inspections`) ```python raw_items = db.list(client_id=auth.client_id, limit=page_size, offset=offset) total = db.count(client_id=auth.client_id) if hasattr(db, "count") else len(raw_items) ``` Iki ayri DB roundtrip. Postgres'te `count(*) OVER ()` window function ile tek query'e indirilebilir. Pool olmayan ortamda her biri ayri TCP setup demek (yukaridaki 1.6'ya ek). In-memory fallback'te paterns gercek N+1 olmaz cunku `_MemoryStore.list/count` zaten dict tarama, ama Postgres'te `count(*)` filter index taramasi yapar. **Tahmini etki:** History endpoint 80-150ms -> 40-80ms. Index'lerin var oldugu zaten dogrulandi (`idx_inspections_client_created` line 108-110). --- ### 1.8 [ORTA] Celery worker — `asyncio.run` her task icin yeni event loop **Konum:** `worker.py:113-115` ```python from ml_service import run_inspection aggregated = asyncio.run(run_inspection(image_bytes, user_id=inspection_id)) ``` `asyncio.run` her task'ta yeni loop kurar/kapatir (~10-20ms setup). Worker sadece sync ML cagirdigi icin async tum kullanim aslinda gereksiz; ya da worker bazinda persistent loop kurulabilir. **Marjinal, dusuk oncelik.** --- ### 1.9 [ORTA] WebSocket polling fallback — DB Hammer **Konum:** `ws.py:154-191` Redis yoksa her saniyede `_get_inspection` cagrisi (yine pool'suz `psycopg2.connect`). 10 dakika boyunca 600 query. **Tahmini etki:** 1.6 pool fix'i ile zaten cozulur. Yine de Redis pub/sub aktif tutmak (`redis_url` set edili) zorunlu olmali — production'da fallback asla devreye girmemeli. --- ## 2) ML Pipeline — `services/ml/pipeline.py` ### 2.1 [KRITIK] (Yukarida 1.3) Damage + Parts seri; Severity per-damage loop ### 2.2 [ORTA] Image preprocess — resize cv2/PIL roundtrip **Konum:** `pipeline.py:89-175` (`load_image_bgr`, `_pil_to_bgr`) PIL'le decode -> numpy convert -> cv2 BGR. YOLO `predict` icine direkt numpy gonderiliyor ki ultralytics zaten 640x640'a resize ediyor (`imgsz=640`). Yani 4000x3000 telefon foto: 1. backend `_decode_image` cv2.imdecode -> RAM full-res numpy (~36MB / image) 2. pipeline `load_image_bgr` ayni isi tekrar yapiyor (PIL ile, sonra cv2) 3. YOLO icinde resize 640 Tek goruntu icin **2 kez decode** + tam cozumlu numpy bellekte. **Tahmini etki:** ~10-30ms / image preprocess kazanc; bellek 36MB -> 4MB (640x640). **Fix yonu:** Backend tarafinda decode'u atla (bytes pipeline'a ver) **veya** pipeline tarafinda ndarray geldiginde PIL'i atlamak icin `_pil_to_bgr`'i bypass et. Mobile zaten 1600px'e compress ediyor (UploadScreen.tsx:25-39), bu yuzden web/desktop tarafinda da client-side resize **once** yapilirsa upload bandwidth + memory'de 5-10x tasarruf. ### 2.3 [DUSUK] `cost_engine.estimate` for-loop — N hasar icin N call **Konum:** `pipeline.py:491-500`. Cost YAML lookup, hizli (microsec). Marjinal. ### 2.4 [ORTA] Visualization PNG encode — opsiyonel ama default kapali `generate_visuals=False` default; backend zaten cagirmıyor. Ama `output_formatter` icindeki polygon serialize Buyuk JSON uretebilir (mask'lar polygon-only, asagida bayrak). --- ## 3) Web — `apps/web` ### 3.1 [YUKSEK] Tum sayfalar Client Component — Next.js 15 RSC fayda yok **Konum:** `app/inspect/page.tsx:1`, `app/history/page.tsx:1` (`'use client'`) Her ikisi de top-level `'use client'`. History sayfasinin **listeyi server'da fetch edip statik HTML olarak gondermesi** mumkun (en azindan ilk render). Suanki kurguda: - Sayfa JS olarak indirilir - Client mount olur - useEffect tetiklenir - Fetch baslar -> spinner - Sonuc gelir Server Component yapilirsa: HTML zaten data ile gelir, hydration sadece filter/pagination icin gerekir. **LCP 200-600ms iyilesir** (kullanicinin internetine gore). ### 3.2 [DUSUK] Bundle — buyuk dep yok, lucide tree-shake aktif `package.json` ozellikle hafif: axios, lucide-react (`optimizePackageImports`), react-dropzone, zod, react-hook-form. **Lodash YOK** (iyi). `next.config.ts` zaten `optimizePackageImports: ['lucide-react', '@arac-hasar/ui']`. **Olcum:** Production build sonrasi bundle analyzer eklemek faydali olur ama mevcut konfigde alarm yok. ### 3.3 [DUSUK] `next/image` kullanilmiyor, raw `` ile lazy `history/page.tsx:240-245`: ```tsx ``` `next/image` olsa otomatik webp/avif + responsive srcset. Thumbnail'lar S3'ten geldigi icin `next/image` `remotePatterns` ile zaten konfigure edilmis (`next.config.ts:10-17`). Switch -> **~30-50% bandwidth tasarruf** thumbnail'larda. ### 3.4 [ORTA] Polling 2s interval — WS varken hala polling **Konum:** `lib/use-inspection-polling.ts:32` Backend WS `/api/v1/inspect/{id}/stream` var ama web'de kullanilmiyor. Sayfa acik kalirken her 2s'de `GET /api/v1/inspect/{id}`. 60s maxDuration -> 30 ekstra istek. WS'e gecirilse: 1 connection, anlik push. **Tahmini etki:** Sunucu yuku 30x azalir _processing donemi icin_. Kullanici UX'i ayni veya daha hizli (status anlik). Backend 1.6 fix'i olmazsa psycopg2 connection hammer azalir. --- ## 4) Mobile — `apps/mobile` ### 4.1 [TAMAM] Image compression upload oncesi yapiliyor **Konum:** `screens/UploadScreen.tsx:25-39, 89` ```typescript const MAX_WIDTH = 1600; const COMPRESS_QUALITY = 0.85; const compressed = await Promise.all(photos.map((u) => compress(u))); ``` Iyi. 1600px + JPEG 0.85 -> tipik 400-800KB / foto. 8 foto'da ~5MB. **Bayrak: OK**. NOT: `ImageManipulator.manipulateAsync` her foto icin ayri promise; `Promise.all` paralel ama mobile cihazda native module thread havuzu kucuk, gercek paralellik 2-4 esis arasi. Cok foto icin chunklamak (4'er) daha guvenli olabilir. (Marjinal) ### 4.2 [TAMAM] FlatList kullanilmis — ScrollView yok `HistoryScreen.tsx:99` ve `CameraScreen.tsx:161` `FlatList` ile virtualize. `UploadScreen.tsx:111` da `FlatList` (horizontal photo strip). **OK**. ### 4.3 [ORTA] CameraScreen `quality: 0.85, skipProcessing: false` `CameraScreen.tsx:59-62` `skipProcessing: false` -> Expo orientation/EXIF normalize ediyor (iyi). Ama `quality:0.85` + sonra ImageManipulator ile bir kez daha resize+compress = **iki re-encode**. Camera tarafinda direkt resize alternatif yok (Expo limit), ama quality 1.0 tut + manipulator ile tek encode yapmak biraz daha hizli olur. (Marjinal, ~50-100ms/foto) ### 4.4 [DUSUK] On-device QC (TFLite) henuz stub `onDeviceQC.ts:11` "TFLite entegrasyonu icin react-native-fast-tflite kurmak gerek". TFLite eklenirse upload oncesi reject ile sunucu yuku **40-60% azalabilir** (kullanici %30-40 hatali fotograf cekiyor varsayim). Bu performance degil ama cost/throughput acisindan kritik. --- ## 5) Hedef metrikler & gercekci tahmin README hedefi: **<8s end-to-end / 4 foto**, damage 45ms, parts 30ms, severity 12ms/img. Mevcut (statik tahmin, GPU + warmup yapilmis varsayim): - Per image model: damage(45) + parts(30) + match(10) + severity(N*12) + cost(2) ~= **100-150ms** - 4 foto seri (GPU lock): 4 * 125 = **500ms** ML toplam - Upload + S3 (4 foto * 1MB): 4 * 100-300ms = **400-1200ms** (HTTP + S3 put) - Backend orchestration + decode (4x): ~**200-400ms** - DB write/read: 50-200ms Theoretical optimum tum bottleneck'ler kapatildiginda: ~**2-3s end-to-end**. Mevcut darbogazlarla (1.5, 1.6, 1.7 acik) gercek tahminim: **8-15s** (Render free tier'da daha kotu, OOM riski ile birlikte). GPU yoksa (CPU) per-image **5-10x daha yavas**, hedef tutmaz; CPU ortamda batch + parallel kritik. --- ## 6) Tahmini Kazanim Toplami (oncelik sirasiyla) | # | Bulgu | Kazanim (per request) | Risk/Effort | |---|---|---|---| | 1.5 | Streaming upload | RAM 200MB -> 20MB | Orta effort, kod degisikligi | | 1.6 | DB pool kullan (ORM'e tasi) | History 150ms -> 30ms, WS polling DB hammer cozumu | Yuksek effort (ORM migrasyonu) | | 1.1 | Sync route `to_thread` | Event-loop bloklamayi cozer, throughput 1.5-2x | Dusuk effort, 1 satir | | 1.3 | Damage/Parts paralel + Severity batch | Per image 30-70ms, 4-foto 200ms+ | Orta effort, refactor | | 1.2 | Sync mode upload+ML pipelining | 5-foto 7.5s -> 5s | Orta effort | | 1.4 | Severity warmup eksigi | Cold-start -300-1000ms | Dusuk effort | | 3.1 | Web History RSC | LCP -200-600ms | Orta effort | | 3.3 | next/image | Thumbnail bandwidth -30-50% | Dusuk effort | | 3.4 | WS yerine polling | Server load -30x processing window | Orta effort | | 2.2 | Çift decode'u kaldir | Per image -10-30ms, RAM -32MB | Orta effort | --- *Bu notlar bir kez okuma ile uretildi; gercek profilleme (py-spy, scalene, Chrome DevTools, lighthouse, RUM) tum tahminleri 2-3x degistirebilir. Once 1.6 ve 1.5 fix'leri ile gerçek darbogazi olcun.*