AI tabanlı sistemler, özellikle üretim ortamında çalışan pipeline’lar, sürekli hata riski taşır. Ağ kesintileri, API yanıt süreleri, modelin geç yanıt vermesi, veri tutarsızlıkları veya dış servislerin anlık erişilemez hale gelmesi gibi durumlarla sıkça karşılaşıyorum. Bu tür durumlar, AI pipeline’ının güvenilirliğini doğrudan etkiler ve iş akışlarını aksatabilir.
Bu yazıda, AI pipeline’larında hataları yönetmek için kullandığım iki temel stratejiyi, yani retry (tekrar deneme) ve idempotency (tekrarlanabilirlik) mekanizmalarını anlatacağım. Bunları nasıl tasarladığımı, ne zaman ve nasıl uyguladığımı, ve gerçek dünya senaryolarında hangi dersleri çıkardığımı paylaşacağım. Amacım, bu yaklaşımlarla sistemlerimin daha dayanıklı ve öngörülebilir çalışmasını sağlamak.
AI Pipeline Hatalarının Doğası ve İlk Gözlemlerim
AI pipeline’ları genellikle birden fazla aşamadan oluşur: veri alımı, ön işleme, model çıkarımı, sonuçların depolanması veya başka bir sisteme aktarılması. Her aşama, kendine özgü hata potansiyelleri taşır. Özellikle dış bağımlılıklar ve dağıtık yapı, hata senaryolarını daha karmaşık hale getiriyor.
Geçen ay bir üretim firmasının ERP’sine entegre ettiğim AI tabanlı üretim planlama motorunda benzer bir durum yaşadım. Üretim verilerini dış bir API’den çekerken, her 1000 çağrıdan ortalama 15 tanesi ağ zaman aşımı nedeniyle veya API’nin geç yanıt vermesiyle başarısız oluyordu. Bu durum, planlama motorunun eksik veriyle çalışmasına ve yanlış üretim kararları alınmasına yol açıyordu. Sorunun kökeni API’nin kendisinde değil, anlık ağ dalgalanmalarındaydı.
Bu tür senaryolarda, hatanın kalıcı mı yoksa geçici mi olduğunu ayırt etmek önemlidir. Kalıcı hatalar (örneğin, geçersiz veri formatı veya yetkilendirme sorunları) için tekrar denemek anlamsızdır ve sadece kaynak israfına yol açar. Ancak geçici hatalar için retry mekanizmaları, sistemin kendi kendini iyileştirmesini sağlayarak operasyonel yükü azaltır. Benim deneyimimde, çoğu zaman karşılaştığım hataların %80’inden fazlası geçici nitelikte oluyor.
Retries (Tekrar Denemeler): Ne Zaman ve Nasıl Kullanmalı?
Retry mekanizmaları, geçici bir hatayla karşılaşıldığında bir işlemin belirli aralıklarla ve belirli sayıda tekrar denenmesini sağlar. Bu, özellikle uzak servis çağrıları, veritabanı bağlantıları veya ağ üzerinden yapılan işlemler için hayati öneme sahiptir. Ancak her yere gelişigüzel retry eklemek, sistemde istenmeyen yan etkilere yol açabilir.
Benim kullandığım en yaygın retry stratejisi “exponential backoff with jitter”dır. Bu yöntemde, başarısız denemeler arasında bekleme süresi katlanarak artırılır (exponential backoff) ve bu süreye rastgele bir gecikme (jitter) eklenir. Jitter, aynı anda başarısız olan birden fazla işlemin, aynı anda tekrar denememesini sağlayarak hedef serviste ani yüklenmeleri engeller.
import time
import random
import requests
def retry_with_backoff(func, retries=5, delay=1, backoff_factor=2, jitter=0.1):
"""
Belirli bir fonksiyonu exponential backoff ve jitter ile tekrar dener.
"""
for i in range(retries):
try:
return func()
except requests.exceptions.RequestException as e:
print(f"Deneme {i+1} başarısız oldu: {e}")
if i < retries - 1:
sleep_time = delay * (backoff_factor ** i)
sleep_time = sleep_time * (1 + random.uniform(-jitter, jitter))
print(f" {sleep_time:.2f} saniye bekliyor...")
time.sleep(sleep_time)
else:
print("Tüm denemeler başarısız oldu.")
raise
def call_ai_service():
"""
Örnek bir AI servis çağrısı. Bazen hata verir.
"""
response = requests.get("http://ai-service-endpoint.com/predict", timeout=2)
response.raise_for_status() # HTTP 4xx/5xx hataları için exception fırlatır
return response.json()
# Kullanım örneği
# try:
# result = retry_with_backoff(call_ai_service, retries=3, delay=0.5)
# print(f"Başarılı sonuç: {result}")
# except Exception as e:
# print(f"İşlem nihayetinde başarısız oldu: {e}")
Kendi yan ürünümün Android uygulamasında, arka uç servislerine yapılan çağrılarda genelde 3 deneme ve 2 saniyeye kadar exponential backoff kullanıyorum. Bu, mobil ağ koşullarındaki anlık dalgalanmalar için oldukça etkili oluyor ve kullanıcı deneyimini önemli ölçüde iyileştiriyor. Ancak burada dikkat edilmesi gereken bir trade-off var: retries, toplam işlem süresini uzatabilir ve hedef servise ek yük bindirebilir. Bu yüzden retry sayısını ve bekleme sürelerini doğru ayarlamak kritik. Çok fazla retry, kaynak israfına ve daha uzun gecikmelere yol açarken, çok az retry sistemin kırılgan olmasına neden olabilir.
Idempotency (Tekrarlanabilirlik): Neden Kritik ve Nasıl Sağlanır?
Retry mekanizmalarını uygularken gözden kaçırılmaması gereken en önemli konulardan biri idempotency’dir. Bir işlemi birden fazla kez tekrarladığımızda, sistemin durumu ilk kez uygulandığı zamankiyle aynı kalıyorsa, o işlem idempotenttir. Bu, dağıtık sistemlerde ve özellikle retry mekanizmalarının kullanıldığı yerlerde hayati öneme sahiptir. Aksi takdirde, aynı işlem birden fazla kez işlenerek veri tutarsızlıklarına veya istenmeyen yan etkilere yol açabilir.
Örneğin, bir üretim ERP’sinde bir siparişin durumunu güncelleyen bir AI servisi düşünelim. Eğer bu güncelleme işlemi sırasında bir ağ hatası meydana gelirse ve retry mekanizması devreye girerse, aynı güncelleme isteği sunucuya birden fazla kez ulaşabilir. Idempotency sağlanmazsa, sipariş durumu iki kez güncellenebilir veya başka yan etkiler oluşabilir.
Idempotency’yi sağlamanın birkaç yolu var:
- Benzersiz İstek Kimlikleri (Idempotency Keys): Her isteğe benzersiz bir kimlik (UUID gibi) eklenir. Sunucu, bu kimliği kullanarak aynı isteğin daha önce işlenip işlenmediğini kontrol eder. Eğer işlenmişse, aynı sonucu döndürür ve işlemi tekrar yapmaz.
- Koşullu Güncellemeler: Veritabanında güncelleme yaparken, sadece belirli bir koşul (örneğin, bir
versionalanı) sağlandığında işlemi gerçekleştirmek. - UPSERT (INSERT ON CONFLICT): Veritabanı seviyesinde, bir kayıt yoksa ekle, varsa güncelle. PostgreSQL’de
INSERT ... ON CONFLICT (id) DO UPDATE ...yapısı bunu sağlar.
Bir bankanın iç platformunda, işlem kayıtlarını tutarken her zaman bir transaction_id veya request_uuid kullanırdım. Bu sayede, ağ kesintisi sonrası bile aynı işlemi tekrar gönderdiğimizde, sistemin yalnızca bir kez işlediğinden emin oluyorduk. Bu, özellikle finansal işlemler gibi kritik alanlarda vazgeçilmez bir yaklaşım. Benim finansal hesaplayıcılarıma gelen request’lerde de benzer bir X-Request-ID başlığı kullanıyorum.
# FastAPI örneği
from fastapi import FastAPI, Header, HTTPException
from typing import Optional
from uuid import uuid4
app = FastAPI()
processed_requests = {} # Gerçekte bir veritabanı veya Redis kullanılmalı
@app.post("/process_ai_task")
async def process_ai_task(
data: dict,
x_idempotency_key: Optional[str] = Header(None)
):
if not x_idempotency_key:
raise HTTPException(status_code=400, detail="X-Idempotency-Key is required")
if x_idempotency_key in processed_requests:
# Daha önce işlenmiş, aynı sonucu döndür
print(f"Idempotency Key {x_idempotency_key} ile zaten işlenmiş, aynı sonucu döndürüyor.")
return processed_requests[x_idempotency_key]
# İşlemi gerçekleştir
print(f"Idempotency Key {x_idempotency_key} ile yeni bir işlem başlatılıyor.")
result = {"status": "processed", "data": data, "processed_id": str(uuid4())}
processed_requests[x_idempotency_key] = result
# Gerçek uygulamada, bu nokta işlem tamamlandıktan sonra kalıcı hale getirilmeli
# Örn: Veritabanına kaydet ve committed_transactions tablosuna idempotency_key ekle
return result
# Kullanım örneği (curl ile)
# curl -X POST "http://localhost:8000/process_ai_task" \
# -H "X-Idempotency-Key: some-unique-key-123" \
# -H "Content-Type: application/json" \
# -d '{"prompt": "Generate a summary"}'
Bu örnekte, processed_requests sözlüğü basit bir in-memory depolama görevi görüyor. Gerçek bir üretim ortamında bu, Redis gibi hızlı bir anahtar-değer deposu veya veritabanında özel bir tablo ile yönetilmelidir. Anahtarın yaşam süresi ve temizlik mekanizmaları da düşünülmelidir.
Retry ve Idempotency’yi Birlikte Tasarlamak
Retry ve idempotency, birbirini tamamlayan iki güçlü mekanizmadır. Retry, geçici hataların üstesinden gelerek işlemin tamamlanma şansını artırırken, idempotency, bu tekrar denemelerin sistem üzerinde istenmeyen çoğaltmalar yaratmamasını sağlar. Bu ikiliyi doğru bir şekilde tasarlamak, dağıtık AI pipeline’larının güvenilirliğini katlar.
Bir müşteri projesinde, üretim ERP’sinden gelen veri akışında, bir siparişin sevkiyat durumu değiştiğinde bunu AI modeline bildirmemiz gerekiyordu. Eğer bu bildirim sırasında ağ kesintisi gibi bir durum yaşanır ve retry yaparsak, idempotency olmadan model aynı siparişin sevkiyat durumunu iki kez işlemeye çalışabilirdi. Bu durum, yanlış üretim planlamasına veya gereksiz kaynak tahsisine yol açabilirdi. Bu tür senaryolar için genellikle “transaction outbox pattern” veya mesaj kuyrukları (Kafka, RabbitMQ) ile birlikte benzersiz mesaj kimlikleri kullanırım.
Bu pattern’da, bir işlem veritabanında gerçekleştirilirken, aynı transaction içinde bir “outbox” tablosuna da bir olay (event) yazılır. Transaction başarılı olduğunda, ayrı bir servis veya süreç bu outbox tablosunu dinleyerek olayları mesaj kuyruğuna gönderir. Eğer bir hata olursa ve işlem tekrar denenirse, outbox tablosundaki benzersiz olay kimlikleri (idempotency key olarak) sayesinde aynı olayın çift gönderilmesi engellenir. Bu yaklaşım, özellikle event-sourcing mimarilerinde veya mikroservisler arası iletişimde sıklıkla kullanılır.
-- Örnek bir outbox tablosu şeması (PostgreSQL)
CREATE TABLE outbox_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
aggregate_type VARCHAR(255) NOT NULL,
aggregate_id VARCHAR(255) NOT NULL,
event_type VARCHAR(255) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
processed_at TIMESTAMPTZ,
status VARCHAR(50) DEFAULT 'PENDING' -- PENDING, SENT, FAILED
);
-- Bir işlem ve outbox kaydının atomik olarak yazılması (pseudocode)
-- BEGIN TRANSACTION;
-- UPDATE orders SET status = 'SHIPPED' WHERE id = 'order-123';
-- INSERT INTO outbox_messages (aggregate_type, aggregate_id, event_type, payload)
-- VALUES ('Order', 'order-123', 'OrderShipped', '{"order_id": "order-123", "new_status": "SHIPPED"}');
-- COMMIT TRANSACTION;
Bu yapı, dağıtık sistemlerdeki transaction tutarlılığı sorunlarını büyük ölçüde hafifletir. Benim deneyimimde, bu pattern’ı uyguladığım sistemlerde, AI pipeline’larına gönderilen verilerin tutarlılığı ve işlem tekrarlanabilirliği önemli ölçüde arttı. dağıtık sistemlerde transaction yönetimi hakkında daha fazla bilgi edinmek isterseniz, bu konu üzerine daha önce yazdığım bir makaleye göz atabilirsiniz.
İzleme ve Alarm Mekanizmaları: Başarısızlıkları Tespit Etme
Retry ve idempotency mekanizmaları ne kadar sağlam olursa olsun, sistemdeki hataları ve bunların etkilerini izlemek vazgeçilmezdir. Pipeline’ın performansı, hata oranları, retry denemelerinin sayısı ve başarılı retry’ların oranı gibi metrikler, sistemin sağlığı hakkında değerli bilgiler sunar. Gözlemlenebilirlik (observability) olmadan, bu mekanizmaların ne kadar etkili çalıştığını veya nerede iyileştirmelere ihtiyaç duyulduğunu anlamak zordur.
Benim için izleme, sadece hataları değil, aynı zamanda sistemin normal çalışma koşullarından sapmalarını da tespit etmeyi içerir. Kullandığım bazı temel izleme metrikleri şunlar:
- Hata Oranı (Error Rate): Toplam işlem sayısı içinde başarısız olanların yüzdesi.
- Retry Sayısı (Retry Count): Bir işlemin kaç kez tekrar denendiği. Yüksek retry sayıları, arka planda sürekli bir geçici sorun olduğuna işaret edebilir.
- Gecikme (Latency): İşlemlerin tamamlanma süresi, özellikle %95 veya %99 persentil değerleri. Retry’lar latency’yi artıracağından, bunun beklenen sınırlar içinde olup olmadığını kontrol etmek gerekir.
- Başarılı Retry Oranı: Tekrar denemeler sonucunda başarılı olan işlemlerin oranı. Bu metrik, retry mekanizmasının ne kadar etkili çalıştığını gösterir.
Geçen yıl bir müşteri projesinde, AI tahmin servisinin latency değerlerinde %95 persentil 500ms’in üzerine çıktığında otomatik uyarı tetikleyecek metrikler kurmuştuk. Bu genellikle arka plandaki modelin kaynak sıkıntısı çektiğini veya dış bir bağımlılığın yavaşladığını gösterirdi. Bu tür metrikler, Prometheus gibi araçlarla toplanıp Grafana gibi panolarla görselleştirildiğinde, anormallikleri hızlıca fark edebiliyorum.
# Prometheus için örnek bir alert kuralı
groups:
- name: ai_pipeline_alerts
rules:
- alert: AIPipelineHighErrorRate
expr: sum(rate(ai_pipeline_errors_total[5m])) by (pipeline_name) / sum(rate(ai_pipeline_requests_total[5m])) by (pipeline_name) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "AI Pipeline {{ $labels.pipeline_name }} hata oranı çok yüksek"
description: "Son 5 dakikada {{ $labels.pipeline_name }} pipeline'ının hata oranı %5'in üzerine çıktı. Retry'lar yetersiz kalıyor olabilir."
- alert: AIPipelineExcessiveRetries
expr: sum(rate(ai_pipeline_retries_total[5m])) by (pipeline_name) / sum(rate(ai_pipeline_requests_total[5m])) by (pipeline_name) > 0.10
for: 10m
labels:
severity: info
annotations:
summary: "AI Pipeline {{ $labels.pipeline_name }} aşırı tekrar deneme yapıyor"
description: "Son 10 dakikada {{ $labels.pipeline_name }} pipeline'ının isteklerinin %10'undan fazlası retry edildi. Altta yatan bir sorun olabilir."
Loglama tarafında ise, her retry denemesini, neden başarısız olduğunu ve idempotency anahtarının kullanılıp kullanılmadığını ayrıntılı bir şekilde kaydederim. journald veya benzeri bir log yönetim sistemi kullanarak bu logları merkezi bir yerde toplamak ve anahtar kelimelerle arama yapabilmek, sorun giderme süreçlerini çok hızlandırıyor. Kendi VPS’imde, Redis’in OOM evictions’larını izlerken, journald kayıtlarındaki OOM-killed mesajlarını ve cgroup memory.high limitlerini yakalayan alarmlar kurmuştum. Bu, sistemin neden kararsızlaştığını anlamamı sağlamıştı.
Gerçek Dünya Senaryolarından Dersler ve Trade-off’lar
AI pipeline’larında retry ve idempotency uygularken edindiğim en önemli derslerden biri, her zaman “mükemmel” çözümü aramak yerine, “yeterince iyi” ve pragmatik bir yaklaşım benimsemek gerektiğidir. Her sistemin kendine özgü kısıtlamaları ve öncelikleri vardır.
Bir bankanın iç platformu için geliştirdiğim bir AI destekli dolandırıcılık tespit sisteminde, her saniyenin ve her işlemin kritik olduğunu biliyorduk. Bu yüzden, retry stratejilerini çok agresif tutmak yerine, daha çok idempotency’ye ve hızlı hata bildirimine odaklandık. Çünkü bir işlemin gecikmesi, potansiyel bir dolandırıcılık olayının kaçırılması anlamına gelebilirdi. Burada trade-off, yüksek güvenilirlik için kabul edilebilir bir gecikme süresi belirlemekti.
Kendi küçük projelerimde veya yan ürünlerimin backend’inde ise, bazen basit bir cronjob ile başarısız AI görevlerini tekrar tetiklemek, karmaşık bir event-sourcing mimarisi kurmaktan daha pratik olabiliyor. Örneğin, bir metin özetleme servisimde, eğer bir istek başarısız olursa, bir saat sonra otomatik olarak tekrar denenmesini sağlayan basit bir mekanizma yeterli oluyor. Buradaki trade-off, anlık tutarlılıktan biraz fedakarlık edip geliştirme hızına odaklanmaktı.
Sonuç olarak, retry ve idempotency mekanizmalarını tasarlarken şu soruları sormak önemlidir:
- Bu hata geçici mi, kalıcı mı?
- Tekrar denemek ne kadar gecikmeye yol açacak?
- İşlem idempotent mi? Değilse, nasıl idempotent hale getirebilirim?
- Bu mekanizmaların karmaşıklığı, sağladığı faydaya değer mi?
Bu soruların cevapları, her proje ve bağlam için farklılık gösterecektir. Ancak bu prensipleri aklımda tutarak, genellikle daha sağlam ve sürdürülebilir AI pipeline’ları inşa edebiliyorum.
Sonuç
AI pipeline’larında hata yönetimi, sistemlerin güvenilirliği ve sürekliliği için temel bir gerekliliktir. Retry ve idempotency, bu süreçte kullandığım en güçlü araçlardan ikisi. Retry mekanizmalarıyla geçici hataların üstesinden gelirken, idempotency ile işlemlerin tekrarlanabilirliğini sağlayarak veri tutarsızlıklarını önlüyorum.
Bu mekanizmaları tasarlarken, her zaman gerçek dünya senaryolarını, sistemin kritiklik seviyesini ve mevcut kaynakları göz önünde bulunduruyorum. Gözlemlenebilirlik araçlarıyla sistemin sağlığını sürekli takip etmek ve anormalliklere karşı alarm kurmak, bu stratejilerin etkinliğini artırıyor. Karmaşıklığı yönetmek ve doğru trade-off’ları yapmak, bu alandaki 20 yıllık deneyimimde edindiğim en değerli derslerden oldu.
Umarım bu rehber, kendi AI pipeline’larınızda hata yönetimi stratejileri geliştirirken size yol gösterir. Bir sonraki adım, AI modellerinin versiyonlama stratejileri ve roll-back süreçleri üzerine olacak.