fix: move blocking file I/O to threads to prevent event loop stall

Ne yaptık:
- json.dump/json.load ve db.commit çağrılarını asyncio.to_thread() ile sardık
- 5 ayrı blocking I/O noktası: kategori scraping yazma, kategori okuma (sosyal kanıt için), rapor JSON yazma, DB kaydetme, sosyal kanıt JSON yazma
- _write_json, _read_json, _db_save helper fonksiyonları eklendi

Neden yaptık:
- Büyük raporlarda (15K+ ürün) sosyal kanıt tamamlandıktan sonra rapor kaydetme aşamasında senkron I/O event loop'u blokluyordu
- Backend health check fail oluyordu, container "unhealthy" durumuna düşüyordu
- "Tamamlandı" modalı frontend'e ulaşamıyordu çünkü SSE stream donuyordu

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
furkanyigit34
2026-03-27 18:01:40 +03:00
parent dc1f8fcfb2
commit 1baaa6fbce

View File

@@ -1469,6 +1469,20 @@ async def create_report(
# Generate unique task ID # Generate unique task ID
task_id = str(uuid.uuid4()) task_id = str(uuid.uuid4())
# Helper functions for non-blocking I/O
def _write_json(path, data):
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def _read_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def _db_save(db, report):
db.add(report)
db.commit()
db.refresh(report)
# Stream progress with SSE # Stream progress with SSE
async def progress_stream(): async def progress_stream():
"""Generator that yields real-time progress events""" """Generator that yields real-time progress events"""
@@ -1528,8 +1542,7 @@ async def create_report(
"products": products "products": products
} }
with open(filename, 'w', encoding='utf-8') as f: await asyncio.to_thread(_write_json, filename, data)
json.dump(data, f, ensure_ascii=False, indent=2)
results["successful"] += 1 results["successful"] += 1
results["total_products"] += len(products) results["total_products"] += len(products)
@@ -1586,8 +1599,7 @@ async def create_report(
if detail["success"] and detail["file_path"]: if detail["success"] and detail["file_path"]:
category_name = detail.get("category_name", "Bilinmeyen Kategori") category_name = detail.get("category_name", "Bilinmeyen Kategori")
try: try:
with open(detail["file_path"], 'r', encoding='utf-8') as f: cat_data = await asyncio.to_thread(_read_json, detail["file_path"])
cat_data = json.load(f)
products = cat_data.get("products", []) products = cat_data.get("products", [])
# print(f"🔍 DEBUG: {detail['file_path']} dosyasından {len(products)} ürün bulundu") # print(f"🔍 DEBUG: {detail['file_path']} dosyasından {len(products)} ürün bulundu")
for product in products: for product in products:
@@ -1710,8 +1722,7 @@ async def create_report(
"details": results["details"] "details": results["details"]
} }
with open(json_filename, 'w', encoding='utf-8') as f: await asyncio.to_thread(_write_json, json_filename, combined_data)
json.dump(combined_data, f, ensure_ascii=False, indent=2)
# Save to database # Save to database
yield f"data: {json_module.dumps({'type': 'info', 'message': '💾 Veritabanına kaydediliyor...', 'progress': 95})}\n\n" yield f"data: {json_module.dumps({'type': 'info', 'message': '💾 Veritabanına kaydediliyor...', 'progress': 95})}\n\n"
@@ -1727,9 +1738,7 @@ async def create_report(
created_at=datetime.now() created_at=datetime.now()
) )
db.add(new_report) await asyncio.to_thread(_db_save, db, new_report)
db.commit()
db.refresh(new_report)
# Save social proof data to persistent cache # Save social proof data to persistent cache
# print(f"\n🔍 DEBUG: Sosyal kanıt kaydetme bölümü - social_proof_data uzunluğu: {len(social_proof_data)}") # print(f"\n🔍 DEBUG: Sosyal kanıt kaydetme bölümü - social_proof_data uzunluğu: {len(social_proof_data)}")
@@ -1752,8 +1761,7 @@ async def create_report(
# print(f"✅ DEBUG: Sosyal kanıt dosyası kaydediliyor: {social_file}") # print(f"✅ DEBUG: Sosyal kanıt dosyası kaydediliyor: {social_file}")
# print(f"🔍 DEBUG: Toplam metrikler: {social_output['total']}") # print(f"🔍 DEBUG: Toplam metrikler: {social_output['total']}")
with open(social_file, 'w', encoding='utf-8') as f: await asyncio.to_thread(_write_json, social_file, social_output)
json.dump(social_output, f, ensure_ascii=False, indent=2)
# print(f"✅ DEBUG: Sosyal kanıt dosyası başarıyla kaydedildi") # print(f"✅ DEBUG: Sosyal kanıt dosyası başarıyla kaydedildi")
else: else:
pass pass