2026-04-06
Frontend fiyatları görmeden COMPLETED diyen pipeline
Bir bug vardı: frontend search job'ın durumunu polling ile kontrol ediyordu. COMPLETED görünce fiyatları çekiyordu — ama fiyatlar boş geliyordu. Sayfa yenilersen fiyatlar görünüyordu. Klasik race condition.
Sorun
Eski akış şuydu:
storeResult() → raw HTML kaydet → markCompleted() → job COMPLETED
→ [ASYNC] parse event → fiyatları DB'ye yaz
markCompleted() senkron çalışıyordu, storeResult() içinde. Fiyat parsing ise async event listener'da. Yani:
- Raw HTML kaydedildi
- Job COMPLETED oldu (commit)
- Frontend COMPLETED gördü, fiyatları sorguladı
- Fiyat parsing henüz bitmedi → boş sonuç
Adım 2 ve 4 arasında bir yarış var. Frontend hızlıysa (ki genelde öyle), fiyatlar henüz yazılmamış oluyor.
Çözüm
markCompleted()'ı storeResult()'tan çıkardım. Fiyat parsing'in içine taşıdım:
storeResult() → raw HTML kaydet → RawResultReceivedEvent
→ [ASYNC, REQUIRES_NEW] HotelPriceParsingService
→ parseAndStore() → fiyatlar DB'ye yazıldı
→ markCompleted() → job COMPLETED
→ [COMMIT] fiyatlar + status aynı transaction'da
Artık markCompleted() ancak fiyatlar parse edilip DB'ye yazıldıktan sonra çalışıyor. Ve aynı transaction içinde — ya ikisi birden commit olur, ya hiçbiri.
REQUIRES_NEW detayı
Parsing listener'ı @Transactional(propagation = REQUIRES_NEW) ile çalışıyor. Neden? Çünkü:
storeResult()kendi transaction'ını commit etmeli (raw HTML kaydedilsin)@TransactionalEventListener(phase = AFTER_COMMIT)kullanıyorum — event ancak commit'ten sonra tetikleniyor- Parsing kendi yeni transaction'ında çalışıp hem fiyatları hem COMPLETED status'ü birlikte commit ediyor
Bu sayede raw HTML kaydı ve fiyat kaydı birbirinden bağımsız transaction'larda ama fiyatlar + status atomik.
Terminal status guard
Bir de şunu ekledim: markCompleted() çağrılmadan önce job'ın zaten terminal durumda olup olmadığını kontrol ediyorum.
if (job.getStatus().isTerminal()) {
log.warn("Job {} already in terminal state: {}", job.getId(), job.getStatus());
return;
}
Neden? Timeout service arada job'ı FAILED yapabiliyor. Parsing yavaş kaldıysa, timeout gelip FAILED yaptı, sonra parsing bitti ve COMPLETED yapmaya çalışıyor — FAILED'dan COMPLETED'a geçiş mantıksız. Guard bunu engelliyor.
Ne öğrendim
Event-driven pipeline'larda "status değişikliği nerede olmalı" sorusu kritik. Status, yan etkiler tamamlanmadan değişirse consumer (frontend, başka servis) tutarsız veri görür.
Kural basit: status değişikliği, o status'ün vadettiği veri ile aynı transaction'da olmalı. COMPLETED diyorsan, fiyatların da orada olması lazım.