İçindekiler
Bir veri ortamında zamanlanmış işler kontrolden çıkmaya başladığında ilk refleks genelde aynıdır: “Bütün işleri tek bir arayüzde görelim.” Oysa zamanlanmış işlerin en tehlikeli hali hata vermeleri değil, sessizce durmaları ya da yanlış veri üretmeleridir.
Farklı sunuculardaki cron job’ları, Windows Task Scheduler’da duran script’ler ve veritabanı içindeki zamanlayıcılar bir süre sonra bir görünürlük sorunu değil, bir fark edilebilirlik krizi yaratır. Script çalışır, çıkış kodu sıfırdır; ama içindeki adımlardan biri hata vermiştir ve kimse bunu görmemiştir. Bir işin çalışmadığını üç gün sonra gelen bir kullanıcı şikayetiyle öğreniyorsanız, sorun aracın kendisinde değil, operasyonel kurgunun eksikliğindedir. Bir işin çalıştığını bilmek kolaydır; çalışmadığını bilmek zordur.
Bu yazıda Prefect’i üç soru üzerinden ele alıyorum: Prefect nedir, ne değildir ve üretimde nasıl kurgulanmalı? Arada Prefect’i bir entegrasyon katmanı olarak ve FastMCP ile yapay zeka ajanlarına açılan bir yürütme motoru olarak nasıl konumlandırabileceğinizi anlatacak, Airflow ve Dagster’la da karşılaştıracağım. Odağım özellikle kısıtlı kaynakla çalışan küçük ekipler için sürdürülebilir bir kurgu. Yazının sonunda da bu yapıyı bir yapay zeka kodlama asistanına kurdurmak için hazır bir prompt var.
Prefect nedir?
Prefect, Python için açık kaynaklı (Apache 2.0), dinamik ve dayanıklı (durable) bir iş akışı orkestrasyon aracı. Yaptığı iş özünde şu: Sıradan Python fonksiyonlarınızı izlenebilir, yeniden denenebilir, zamanlanabilir ve elle tetiklenebilir işlere dönüştürür. Bunu yaparken sizden yeni bir dil ya da bir DAG tanımı istemez. Bir fonksiyonun başına @flow, içindeki adımların başına @task koymanız yeterli:
from prefect import flow, get_run_logger, task
@task(retries=2, retry_delay_seconds=30)
def kayitlari_cek() -> list[dict]:
...
@flow
def gunluk_rapor():
kayitlar = kayitlari_cek()
get_run_logger().info("%s kayıt işlendi", len(kayitlar))
Döngüler, koşullar ve hata yakalama normal Python’dur. Prefect her çalışmanın durumunu, süresini ve logunu kaydeder; arayüzde gösterir; gerektiğinde yeniden dener.
Bu yazıyı yazarken güncel sürüm 3.8 serisi (Eylül 2026 itibarıyla 3.8.6). Prefect 3, Python 3.10 ile 3.14 arasını destekliyor. Prefect 2 hala bakım sürümleri alıyor, ama yeni bir projeye 2 ile başlamak için bir sebep yok.
Gündemde: Prefect, Dagster’ı satın alıyor
Temmuz 2026’da Prefect, orkestrasyon dünyasındaki en yakın rakibi Dagster’ın arkasındaki şirket Dagster Labs’i satın alacağını duyurdu. Bu bir birleşme değil; ürünler de birleşmiyor. Prefect’in duyurusunda “Dagster ve Dagster+ kalıcı” deniyor. Dagster’ın müşterilere yönelik açıklamasında da Dagster’ı Prefect’e katma planı olmadığı, kimsenin taşınmaya zorlanmayacağı ve açık kaynak lisansının aynen süreceği yazıyor. Yazıyı yazdığım tarihte anlaşmanın tamamlandığına dair bir duyuru görmedim.
Prefect’in üç ürünü bundan sonra nasıl konumlandırdığı tek cümlede özetleniyor: “Dagster sonucu tanımlar, Prefect işi yürütür, FastMCP erişimi yönetir.” Bu cümle iki aracın zihinsel modeli arasındaki farkı da çok iyi anlatıyor; aşağıdaki karşılaştırmada buna döneceğim. Şirketin son dönemdeki diğer hamleleri de aynı yöne bakıyor: Python’da MCP sunucusu yazmak için yaygın olarak kullanılan FastMCP ve MCP sunucularını barındırıp yöneten Prefect Horizon. Prefect açıkça yapay zeka ajanlarının orkestrasyonuna konumlanıyor; FastMCP’yi aşağıda ayrı bir bölümde ele alıyorum.
Bu iki araçtan birini kullanan biri için bugün değişen bir şey yok. Ama “hangisini seçmeliyim?” sorusu artık iki rakip ürün arasında değil, aynı çatı altındaki iki farklı felsefe arasında bir seçim.
Temel kavramlar
Prefect’in kapsamı ilk bakışta göründüğünden geniş. Aşağıdaki liste açık kaynak sürümü esas alıyor; sadece Prefect Cloud’da olanları [Cloud] diye işaretledim.
İş akışı yazmak
- Flow ve task: Takip edilen iş birimleri. Bir flow başka bir flow’u çağırırsa o çağrı alt çalışma (subflow) olur.
- Eşzamanlılık:
.submit()ve.map()ile task’lar paralel çalışır. Varsayılan task runner thread havuzu; process havuzu, Dask ve Ray da kullanılabilir. - Async:
async defflow ve task’lar doğrudan desteklenir. - Arka plan task’ları: Bir task
.delay()ile kuyruğa atılıp ayrı bir task worker’ı tarafından çalıştırılabilir. Örneğin bir web uygulamasından iş tetiklemek için.
Çalıştırmak ve zamanlamak
- Deployment: Bir flow’un sunucu tarafından bilinen, parametreleri ve zamanlaması olan hali. Üç yolla oluşturulur:
flow.serve()(kendi makinenizde uzun süre çalışan bir süreç),flow.deploy()(çalışmaları bir work pool’a gönderir) veprefect.yamldosyası. - Zamanlamalar: Cron, interval ve RRule; IANA saat dilimleriyle.
- Work pool ve worker: Çalışmanın nerede koşacağını belirler: yerel süreç, Docker, Kubernetes, ECS, Cloud Run ve benzerleri. Worker’lar yapılacak işi Prefect sunucusuna kendileri sorar; bu yüzden worker’ın çalıştığı makineye dışarıdan bağlantı açmanız gerekmez. [Cloud] Worker gerektirmeyen serverless “push” havuzları ve kodunuzu Prefect’in kendi altyapısında çalıştıran Prefect Managed.
- Altyapı dekoratörleri:
@docker,@kubernetesgibi dekoratörlerle bir flow, deployment oluşturmadan uzak bir altyapıya gönderilebilir (3.7 ile kararlı hale geldi).
Dayanıklılık
- Retry: Flow ve task seviyesinde
retriesveretry_delay_seconds. - Durumlar ve hook’lar:
on_failure,on_completion,on_runninggibi durum değişikliği kancaları;on_crashedveon_cancellationsadece flow’larda. - Sonuçlar ve önbellek: Task sonuçları varsayılan olarak saklanmaz; açarsanız saklanır ve hazır önbellek politikalarıyla (girdiye göre, kaynak koda göre,
NO_CACHEvb.) tekrar kullanılabilir. - Transaction’lar: Birbirine bağlı adımları
on_commit/on_rollbackkancalarıyla gruplar. Veritabanı transaction’ı değildir; geri alma adımını sizin yazmanız gerekir. - Eşzamanlılık ve hız sınırları: Global sınırlar, tag bazlı task sınırları ve deployment, work pool ve kuyruk seviyesinde sınırlar. Aynı kaynağa aynı anda kaç işin dokunacağını buradan yönetirsiniz.
- Etkileşimli akışlar: Bir flow duraklatılıp bir insandan girdi bekleyebilir.
Olaylar ve otomasyon
- Event, automation ve trigger: Her durum değişikliği bir olaydır. Otomasyonlar bu olaylara, metriklere ya da beklenen bir olayın hiç gelmemesine tepki verebilir. Sonuncusu, “bu iş sabah dokuza kadar bitmediyse haber ver” gibi kurallar için çok değerli.
- [Cloud] Dışarıdan gelen HTTP isteklerini olaya çeviren webhook’lar, SLA tanımları ve otomasyonlardan e-posta gönderme ya da olay (incident) açma.
Yapılandırma ve gözlem
- Block, variable ve secret: Tipli ayar ve kimlik bilgisi nesneleri (örneğin
Secretblock’u) ve JSON değişkenler. - Loglama ve artifact:
get_run_logger()ile çalışmaya bağlı loglar; arayüzde görünen Markdown, tablo, bağlantı ve ilerleme çıktıları. - Arayüz: 3.8 ile yenilenen arayüz, kendi sunucusunu kuranlar için de varsayılan oldu.
- [Cloud]
@materializeile veri varlıkları (asset) ve aralarındaki soy (lineage) ilişkisi.
Nerede çalışır?
- Kendi sunucunuz: Prefect server açık kaynak. Varsayılan veritabanı SQLite ve tek sunucu için yeterli; üretimde PostgreSQL öneriliyor. Birden fazla sunucu çalıştırmak için PostgreSQL ve Redis gerekiyor.
- Prefect Cloud: Barındırılan sürüm. Ücretsiz Hobby planı 2 kullanıcı, 5 deployment ve 7 günlük çalışma geçmişi veriyor. Rol bazlı yetki, SSO ve denetim kaydı gibi yönetim özellikleri sadece Cloud’da.
Prefect ne değildir?
Bir aracı iyi kullanmanın yarısı, ondan ne beklememeniz gerektiğini bilmektir.
Cilalı bir cron değildir. Cron’un yaptığı her şeyi yapar; ama bir orkestratörün asıl değeri, işler yolundayken zamanlama yapması değil, işler ters gittiğinde durumu kaydetmesi, bağımlılıkları yönetmesi ve alarm üretebilmesidir. İşler olaylarla, otomasyonlarla ya da başka bir flow’un bitişiyle de tetiklenebilir. Prefect’i sadece cron yerine kullanmak kolaydır. Zor olan, onlarca işin birbirini bilerek çalıştığı, hata olduğunda kimin haberdar olacağının belli olduğu bir platform kurmaktır. Prefect bunun araçlarını verir, ama o platformu sizin yerinize kurmaz.
Bir veri işleme motoru değildir. Veriniz Prefect sunucusunun içinden akmaz. Prefect sizin kodunuzu çalıştırır ve izler; kod ve veri sizin altyapınızda kalır, sunucuya sadece durumlar ve loglar gider. Ağır hesaplama gerekiyorsa işi Dask ya da Ray gibi bir motora devredersiniz. Prefect, Spark’ın ya da SQL motorunuzun yerini almaz.
Bir veri ambarı ya da ELT aracı değildir. dbt modellerinizi, stored procedure’lerinizi, SQL’inizi tetikler; onların yerine geçmez. İş kuralları hala verinin yaşadığı yerde yaşamalı.
Bir izleme garantisi değildir. Prefect size kodunuzun ne söylediğini gösterir; kodunuz “başarılı” diyorsa ekran yeşildir. Şöyle bir kurgu düşünün: Bir veritabanının yedeklerini başka bir sunucuya kopyalayan bir script ve onu kontrol eden bir flow var. Flow; log dosyası okunabiliyor mu, son çalışma taze mi, çıkış kodu başarılı mı diye bakıyor. Yedeklerin yazıldığı bağlantı koparsa script eski dosyaları kopyalamaya devam edip “başarıyla tamamlandı” diyebilir ve flow haftalarca yeşil kalır. Süreç çalışır, sonuç yoktur. “İş başarıyla tamamlandı” bir sonuç değil, bir süreç ifadesidir.
Arayüzü bir gözlem deposu değildir. Prefect arayüzü şu an ne olduğunu mükemmel gösterir. Ama geçen ay ne olduğunu sormaya başladığınızda loglar bir metin akışı olarak kalır: Bu iş geçen ay kaç kayıt işledi? Bu adım ne zaman yavaşladı? Ücretsiz Cloud planında çalışma geçmişi zaten 7 gün tutuluyor. Geçmişe dair sorulara cevap istiyorsanız, o veriyi kendiniz sorgulanabilir bir yere yazmalısınız.
İdempotent olmayan kodu sihirli biçimde düzelten bir araç değildir. Kendi içinde tekrarlanabilir tasarlanmamış bir sorguya ya da script’e refleksle retries=1 koymak, aynı verinin iki kez işlenmesine yol açar. Prefect’in transaction özelliği de veritabanı transaction’ı değildir; geri alma adımını sizin yazmanız gerekir. Bu konuya aşağıda ayrıntılı döneceğim, çünkü en pahalı hatalar tam olarak burada yapılıyor.
Prefect aynı zamanda bir entegrasyon motorudur
Prefect’i yalnızca veritabanları arasında veri taşıyan bir zamanlayıcı olarak görmek eksik kalır. Farklı sistemler, mikroservisler ve güvenlik bölgeleri arasında çalışan bir entegrasyon katmanı olarak da kurgulanabilir. Özellikle dış dünyayla konuşan sistemler ile kurum içindeki hassas altyapı arasında bir tampon katman, yani bir tür DMZ (Demilitarized Zone) olarak konumlandırılabilir:

Prefect; dış API’ler, iç sistemler, veritabanları, raporlama ve bildirim kanalları arasında orkestratör olarak. Görsel: Gemini ile üretildi.
[ Dış dünya / X API ] ──▶ ( Prefect flow: doğrulama ve kontrol ) ──▶ [ İç sistem / Y API ]
Bu kurgunun gerekçeleri şunlar:
- Doğrudan erişimi engellemek. Dış dünyadaki bir X API’sinin kurum içindeki bir Y API’sine ya da veritabanına doğrudan erişmesi ciddi bir güvenlik riskidir. Prefect flow’u iki sistem arasında izole bir köprü olur: X, Y’yi hiç görmez.
- Doğrulama geçidi (validation gate). X’ten çekilen ham veri doğrudan Y’ye basılmaz. Önce flow içinde doğrulanır: Pydantic ile tip kontrolü, boş alan kontrolü, sınır değer testleri.
- Hatanın içeri sızmaması. X’ten bozuk ya da uyumsuz bir kayıt gelirse, hata iç sisteme ulaşmadan Prefect katmanında yakalanır. Kayıt loglanır, karantinaya alınır ve ilgili ekibe bildirim gider.
- Kimlik bilgilerinin izolasyonu. X ve Y’nin token’ları ve erişim anahtarları kodda ya da sunucuda açıkta durmaz, Secret block’larda tutulur. X’in anahtarı Y’yi, Y’nin anahtarı X’i hiç görmez.
Kabaca şöyle görünür:
import pydantic
from decimal import Decimal
from typing import Literal
from prefect import flow, task
from prefect.cache_policies import NO_CACHE
class GelenKayit(pydantic.BaseModel):
kayit_id: int = pydantic.Field(gt=0)
tutar: Decimal = pydantic.Field(ge=0, le=1_000_000)
para_birimi: Literal["TRY", "USD", "EUR"]
@task(cache_policy=NO_CACHE)
def dogrula(ham_kayitlar: list[dict]) -> tuple[list[GelenKayit], list[dict]]:
gecerli, karantina = [], []
for ham in ham_kayitlar:
try:
gecerli.append(GelenKayit.model_validate(ham))
except pydantic.ValidationError as e:
karantina.append({"kayit": ham, "hata": str(e)})
return gecerli, karantina
@flow(retries=0)
def x_den_y_ye():
ham = x_api_cek() # X'in token'ı kendi Secret block'unda
gecerli, karantina = dogrula(ham)
if karantina:
karantinaya_yaz(karantina) # iç sisteme değil, karantina tablosuna
alarm_gonder(len(karantina))
y_api_gonder(gecerli) # içeri sadece doğrulanmış kayıtlar girer
Bu kurgunun güvenliği Prefect’ten kendiliğinden gelmez; tasarımdan gelir. Üç şeye dikkat edin:
- Veri worker’ın içinden geçer, sunucunun değil. Tampon katmanın asıl yeri, işi çalıştıran worker’dır. Worker’ı hem X’e hem Y’ye erişebilen ayrı bir ağ bölümüne koyun. Worker’lar işi sunucuya kendileri sorduğu için o makineye dışarıdan bağlantı açmak gerekmez.
- Y’ye yazma adımını idempotent yapın. Örneğin kayıt kimliğine göre upsert kullanın. Aşağıdaki retry bölümünde anlatacağım çarpan etkisi burada da geçerli.
- Her geçişi loglayın. Kaç kayıt geldi, kaçı geçti, kaçı karantinaya alındı: Bu sayılar yapılandırılmış log olarak kaydedilirse, iki sistem arasında denetlenebilir bir iz oluşur.
Böylece iki sistem birbirine doğrudan bağımlı olmaz. Arada her geçişin kaydı tutulan, retry’ı bilinçli kurgulanmış ve veri kalitesini kontrol eden bir entegrasyon kalkanı durur.
Prefect ve FastMCP: iş akışlarını yapay zeka ajanlarına açmak
Prefect’in satın alma duyurusundaki üçlünün son parçası FastMCP’ydi: “Dagster sonucu tanımlar, Prefect işi yürütür, FastMCP erişimi yönetir.” Bu bölüm, o “erişim” kısmının pratikte ne anlama geldiğini anlatıyor.
MCP nedir?
Model Context Protocol (MCP), yapay zeka asistanlarının dış sistemlere, araçlara ve verilere standart bir yoldan bağlanması için açık bir protokol. Anthropic tarafından Kasım 2024’te duyuruldu; Aralık 2025’te Linux Foundation bünyesindeki Agentic AI Foundation’a devredildi. Güncel spesifikasyon sürümü 2026-07-28.
Modeli basit: Bir MCP sunucusu araçlar (tools), kaynaklar (resources) ve hazır istemler (prompts) sunar. Claude ya da Cursor gibi uygulamaların içindeki MCP istemcisi bunları keşfeder ve modelin isteğiyle çağırır. Sunucu yerelde bir süreç olarak (stdio) ya da HTTP üzerinden (Streamable HTTP) çalışabilir. Bir kez MCP sunucusu yazdığınızda, onu MCP destekleyen her asistana bağlayabilirsiniz.
FastMCP nedir?
FastMCP, Python’da MCP sunucusu ve istemcisi yazmanın en pratik yolu. Prefect’in kurucusu Jeremiah Lowin tarafından başlatıldı; ilk sürümü resmi MCP Python SDK’sına dahil edildi, sonraki sürümleri Prefect bünyesinde ayrı bir proje olarak gelişti. Apache 2.0 lisanslı; güncel sürüm 4.0.10 (Eylül 2026). 4.0 ile MCP’nin 2026-07-28 sürümüne geçti; belgelerine göre çoğu sunucu kod değişikliği olmadan yükseltilebiliyor. FastMCP’nin kendi belgelerine göre günde bir milyondan fazla indiriliyor ve MCP sunucularının yaklaşık yüzde 70’i bir FastMCP sürümüyle çalışıyor; bunlar üreticinin kendi rakamları.
Sunduğu başlıca şeyler:
- Dekoratörle araç tanımı:
@mcp.toolile bir Python fonksiyonu araca dönüşür; parametre tipleri ve docstring’den şema otomatik üretilir. Kaynaklar için@mcp.resource, hazır istemler için@mcp.promptvar. - Kimlik doğrulama: JWT doğrulama ve GitHub, Google, Azure gibi sağlayıcılar için OAuth.
- Birleştirme ve vekil sunucu: Birden fazla sunucuyu tek bir uçta toplamak ya da uzak bir sunucuyu vekil olarak sunmak.
- OpenAPI ve FastAPI’den üretim: Var olan bir API’den MCP sunucusu türetmek. Belgeler bunu prototip için öneriyor; modeller, özenle seçilmiş dar araçlarla daha iyi çalışıyor.
- Bellek içi test istemcisi:
Client(mcp)ile sunucuyu ağ ya da ayrı bir süreç olmadan test etmek. - Kurulum komutları:
fastmcp install claude-codeya dafastmcp install cursorile sunucuyu doğrudan bir asistana eklemek.
Barındırma tarafında Prefect Horizon var. MCP sunucularını yönetilen olarak barındırıyor (Deploy), kurum içindeki MCP yeteneklerini tek bir katalogda topluyor (Registry) ve araç seviyesinde rol bazlı erişim kontrolü ile denetim kaydı sunan bir geçit sağlıyor (Gateway). Kişisel kullanım için ücretsiz bir planı var.
Prefect’i yapay zeka ajanlarına açmak
Bir yapay zeka ajanına Prefect’le ilgili iki farklı türde erişim verebilirsiniz.
1. Gözlem: Prefect’in resmi MCP sunucusu. PrefectHQ’nun yayımladığı prefect-mcp-server, bir asistanın Prefect ortamınızı incelemesini sağlar: flow’lar, deployment’lar, çalışmalar, loglar, work pool’lar ve otomasyonlar. Bilerek salt okunur tasarlanmış; hiçbir çalışmayı tetikleyemez, hiçbir şeyi değiştiremez. “Dün gece hangi flow’lar başarısız oldu, loglarında ne yazıyor?” gibi sorular için ideal. Claude Code’a eklemek tek satır; PREFECT_API_URL ortam değişkeninin (Cloud kullanıyorsanız PREFECT_API_KEY’in de) tanımlı olması yeterli:
claude mcp add prefect -- uvx --from prefect-mcp prefect-mcp-server
2. Eylem: kendi FastMCP sunucunuz. Ajanın bir işi başlatabilmesini istiyorsanız, bunu geniş bir yetkiyle değil, dar ve bilinçli seçilmiş araçlarla yapın. Aşağıdaki sunucu ajana iki araç açıyor; biri sadece okuyor, diğeri tek bir deployment’ı tetikliyor:
from fastmcp import FastMCP
from prefect.client.orchestration import get_client
from prefect.client.schemas.filters import (
FlowRunFilter,
FlowRunFilterState,
FlowRunFilterStateType,
)
from prefect.client.schemas.objects import StateType
from prefect.client.schemas.sorting import FlowRunSort
from prefect.deployments import run_deployment
mcp = FastMCP("prefect-araclari")
@mcp.tool(annotations={"readOnlyHint": True})
async def son_hatalar(adet: int = 10) -> list[dict]:
"""Son başarısız flow çalışmalarını listeler. Hiçbir şeyi değiştirmez."""
async with get_client() as client:
runs = await client.read_flow_runs(
flow_run_filter=FlowRunFilter(
state=FlowRunFilterState(
type=FlowRunFilterStateType(any_=[StateType.FAILED])
)
),
sort=FlowRunSort.EXPECTED_START_TIME_DESC,
limit=min(adet, 50),
)
return [{"calisma": r.name, "zaman": str(r.expected_start_time)} for r in runs]
@mcp.tool(annotations={"destructiveHint": False, "idempotentHint": True})
async def rapor_calistir(tarih: str) -> str:
"""Günlük raporu verilen tarih (YYYY-AA-GG) için başlatır."""
run = await run_deployment(
name="gunluk-rapor/gunluk-rapor",
parameters={"tarih": tarih},
idempotency_key=f"gunluk-rapor:{tarih}", # aynı tarih için tek çalışma
timeout=0, # sadece başlat; bitmesini bekleme
)
return f"Çalışma başlatıldı: {run.id}"
if __name__ == "__main__":
mcp.run()
Bu küçük sunucu, önceki bölümlerdeki ilkelerin aynısını yapay zeka ajanlarına uyguluyor:
- Ajan, entegrasyon katmanındaki “dış dünya”nın bir başka örneğidir. Veritabanı şifrelerini, Prefect’in yönetim yetkisini ya da genel bir “komut çalıştır” aracını görmez; sadece sizin açtığınız dar kapıları görür. Asıl iş, doğrulama, loglama ve retry kurallarıyla birlikte yine Prefect flow’unun içinde yapılır.
- Okuma ve eylem araçlarını ayırın. Araç işaretleri (
readOnlyHint,destructiveHint,idempotentHint) istemciye aracın ne yaptığını söyler. MCP spesifikasyonuna göre istemci bir aracı çağırmadan önce kullanıcıdan açık onay almalı ve bir insan her zaman çağrıyı reddedebilmelidir. Ama bu işaretler bir güvenlik mekanizması değil, ipucudur; güvenlik aracın kapsamını dar tutmaktan gelir. - Eylem araçlarını idempotent yapın. Ajanlar aynı isteği tekrar gönderebilir.
run_deployment’a verilenidempotency_keysayesinde aynı tarih için ikinci çağrı yeni bir çalışma başlatmaz, mevcut olanı döndürür. Aşağıdaki retry bölümünde anlattığım çarpan etkisi burada da geçerli. - Bekleme, tetikle.
timeout=0ile araç işi başlatıp hemen döner; ajan durumu sonra okuma aracıyla sorgular. Uzun süren bir işi araç çağrısının içinde beklemek zaman aşımlarına ve gereksiz tekrarlara yol açar.
Sunucuyu Claude Code’a eklemek için:
fastmcp install claude-code sunucu.py --name prefect-araclari --with prefect \
--env PREFECT_API_URL=http://localhost:4200/api
Cursor için komuttaki claude-code yerine cursor yazmanız yeterli.
Airflow, Dagster, Prefect: hangisi ne zaman?
Orkestratör seçimi teknik bir gösteriş değil; gece üçte sistemi kimin ve nasıl ayakta tutacağıyla ilgili operasyonel bir karardır. Üç araç da iş akışı orkestrasyonu yapar ama dünyaya farklı yerlerden bakar. Farkı anlamanın en kısa yolu, her birinin sizden ilk olarak neyi tanımlamanızı istediğine bakmaktır: Airflow görevleri, Dagster veri varlıklarını, Prefect ise sıradan Python kodunu ister.
Airflow: önce görevler ve DAG
Apache Airflow bu alanın en eski ve en yaygın aracı. Güncel sürüm 3.3.2 (Eylül 2026); Airflow 2’nin desteği Nisan 2026’da sona erdi. Airflow’da işinizi görevlerden ve aralarındaki bağımlılıklardan oluşan bir DAG olarak tanımlarsınız. TaskFlow API ile görevleri Python fonksiyonu olarak yazabilirsiniz, ama düşünme biçimi yine önceden tanımlanmış bir DAG’dir. Var olan script’leri taşımak genellikle onları DAG ve operator yapısına göre yeniden düzenlemek demektir.
Airflow 3 aracı ciddi biçimde yeniledi:
- Görevler bir Task SDK üzerinden çalışıyor; mimari istemci-sunucu modeline geçti.
- DAG sürümleme: Bir çalışma, başladığı DAG sürümüyle bitiyor.
- Eskiden “dataset” denen kavramın adı asset oldu ve dış sistemlerden gelen olaylarla tetiklenebiliyor.
- Yeni bir React arayüzü geldi; geçmişe dönük çalıştırmalar (backfill) arayüzden başlatılabiliyor.
- Görevleri uzak makinelerde çalıştırmak için Edge Executor eklendi.
Sonraki sürümler bunlara insan onayı bekleyen adımları, süre aşımı uyarılarını, çoklu ekip desteğini ve Java ile Go için Task SDK’yı ekledi.
Airflow’un en güçlü yanı ekosistemi: yüzü aşkın resmi provider paketi, en kalabalık topluluk ve üç büyük yönetilen hizmet (Astronomer Astro, AWS MWAA ve Google Cloud Composer). Bedeli ise bir tür bakım vergisi: En küçük kurulum bile bir scheduler, bir DAG processor, bir API sunucusu ve bir metadata veritabanı ister; ertelenmiş görev kullanıyorsanız buna triggerer da eklenir. Bunları kendi sunucunuzda kararlı tutmak, küçük bir ekip için ayrı bir iş yüküdür.
Dagster: önce veri varlıkları
Dagster’da önce var olması gereken veriyi tanımlarsınız: bir tablo, bir veri seti, bir makine öğrenmesi modeli. Dagster bu varlıkları üretmek için neyin ne zaman çalışması gerektiğini kendisi planlar. Güncel sürüm 1.13.24 (Eylül 2026).
Modelin merkezinde varlıklar ve aralarındaki soy (lineage) ilişkisi var. Bunun üzerine kurulan başlıca özellikler şunlar:
- Varlıklara bağlı veri kalitesi ve tazelik kontrolleri (asset check’ler).
- Zamana ya da değerlere göre bölümlenmiş varlıklar ve bunlar üzerinde geçmişe dönük çalıştırmalar.
- Kod yazmadan koşul tanımlanan bildirimsel otomasyon.
- dbt projelerinde sütun seviyesinde soy takibi.
Yeni projeler için önerilen yol, 1.12 ile kararlı hale gelen Components yapısı ve dg komut satırı aracı. Kendi sunucunuzda çalıştırmak için bir web sunucusu, zamanlama ve sensörleri yürüten bir daemon, kod konumu (code location) sunucuları ve bir depolama katmanı gerekiyor. Barındırılan sürüm Dagster+ ücretli; 30 günlük deneme dışında kalıcı bir ücretsiz planı yok.
Prefect: önce Python ve yürütme
Prefect’te önce işi yazarsınız: sıradan Python fonksiyonları, sıradan if ve for döngüleri. Prefect bu kodun yürütülmesini izlenebilir, yeniden denenebilir ve zamanlanabilir hale getirir. Var olan bir fonksiyona @flow eklemek çoğu zaman yeterlidir. En küçük kurulum tek bir prefect server start (varsayılan olarak SQLite ile) ve işleri çalıştıracak bir worker ya da .serve() sürecidir. Olaylar ve otomasyonlar açık kaynak sürümde de var; barındırılan Prefect Cloud’un ücretsiz bir planı bulunuyor. Zayıf yanları da belli: Varlık ve soy takibi sadece Cloud’da; hazır entegrasyon sayısı da Airflow’un provider ekosisteminden küçük.
Yan yana
| Airflow | Dagster | Prefect | |
|---|---|---|---|
| Temel birim | Görev (DAG) | Veri varlığı | Python fonksiyonu |
| Dinamik akış | Dynamic task mapping | Dinamik bölümler, DynamicOut | Doğal Python, .map() |
| Yerel test | dag.test(), metadata veritabanı gerekir | Varlığı doğrudan çağırmak, pytest | .fn() ya da test harness |
| Tetikleme | Cron, timetable, varlık olayları | Schedule, sensör, bildirimsel otomasyon | Cron, interval, RRule, olay ve otomasyonlar |
| Soy takibi | Asset’ler, OpenLineage | Modelin merkezinde | Sadece Cloud’da |
| En küçük kurulum | 4 süreç ve veritabanı | Web sunucusu, daemon, kod konumu | Tek sunucu ve worker |
| Kendi sunucunuzda işletme yükü | Yüksek | Orta | Düşük |
| Ekosistem | ~107 provider paketi | ~90 entegrasyon | Ana repoda 18 entegrasyon paketi |
| GitHub yıldızı | ~47 bin | ~16 bin | ~24 bin |
| Barındırılan seçenek | Astro, MWAA, Composer | Dagster+ (ücretli) | Prefect Cloud (ücretsiz plan var) |
Sürüm ve sayılar 26 Eylül 2026 itibarıyla. “İşletme yükü” satırı benim değerlendirmem; yönetilen hizmetler bu yükün büyük kısmını üstlenir.
Hangisini ne zaman seçmeli?
- Airflow, ekibiniz Airflow’u zaten biliyorsa, çok sayıda hazır bağlantıya ihtiyacınız varsa ya da bulut sağlayıcınızın yönetilen hizmetini kullanmak istiyorsanız mantıklı. Düzenli çalışan klasik ETL hatları için hala en güvenli seçim.
- Dagster, işiniz tablolar, modeller ve dbt projeleri gibi veri varlıklarını güncel tutmaksa öne çıkar. “Bu tablo neden eski, neye bağlı?” sorusuna cevap arıyorsanız ve veri kalitesi kontrolleri sizin için birinci sınıf bir kavramsa, Dagster’ın modeli size daha doğal gelir.
- Prefect, işleriniz heterojense daha rahat oturur: API çağırmak, sistemler arasında veri köprüsü kurmak, rapor göndermek, başka sistemleri izlemek, bir kuyruğu beslemek ya da yapay zeka ajanlarını çalıştırmak. Bunlar veri varlığı değil, eylemdir. Var olan Python script’lerinizi az değişiklikle orkestre etmek ve küçük bir ekiple en az işletme yüküyle başlamak istiyorsanız da Prefect iyi bir başlangıç noktası.
Satın alma duyurusundan sonra Dagster ve Prefect rakipten çok birbirini tamamlayan araçlar olarak konumlanıyor. dagster-prefect entegrasyonu önizleme olarak yayınlandı; bir Dagster varlığından Prefect deployment’ı tetiklemek artık mümkün. Varlığı Dagster’da tanımlayıp işi Prefect’e yürüttüren karma kurguları ileride daha sık göreceğiz gibi görünüyor.
Son bir not: Bu araçları karşılaştıran yazıların çoğu bir üreticiye ait. Prefect artık Dagster’ın da sahibi olduğu için iki aracın resmi sayfalarındaki karşılaştırmaları bağımsız bir görüş olarak okumamak gerekir.
Nasıl kurgulanmalı?
Prefect’i projeye dahil etmek kolaydır. Zor olan, onlarca zamanlanmış işi sorunsuz ayakta tutacak bir yapı kurmaktır. Prefect size @flow ve @task verir; bağlantıların nereden geleceğini, logun nereye yazılacağını, hata olunca kimin haberdar olacağını ve ayarların nerede duracağını söylemez. Bu sözleşmeyi siz kurarsınız. Kurmazsanız her flow onu kendi bildiği gibi kurar ve beşinci flow’da aynı kırk satırı beşinci kez yazıyor olursunuz.
Aşağıdaki ilkeler, küçük bir ekibin, hatta tek bir kişinin işletebileceği ölçekten başlayıp büyüyebilen bir yapı için. Her birinin arkasında Prefect projelerinde sık yapılan bir hata var.
1. Dört katman, tek yönlü bağımlılık
Flow’ları ayrı ayrı yazmaya başladığınızda, kısa sürede her dosyanın başının aynı olduğunu görürsünüz: bağlantı kurma, cursor açma, hata yakalama, mail gönderme, log yazma. Farklı olan tek şey ortadaki sorgudur. Sonra mail sunucusunun ayarı değişir; beş dosyayı güncellersiniz, altıncıyı unutursunuz.
Bunun ilacı dört katman ve tek bir kural:
core/ → services/ → models/ → flows/
altyapı yatay servis sözleşmeler iş mantığı
core/: Bağlantıların tek giriş kapısı ve kendi hata hiyerarşiniz. İçinde hiçbir iş kuralı ya da sorgu olmamalı.services/: Her flow’un ihtiyaç duyduğu ama hiçbirine ait olmayan şeyler: mail servisi ve logger.models/: Her flow’un kabul ettiği ayarlar ve döndürdüğü sonuç tipleri. Yani flow’un sözleşmesi.flows/: Sadece iş mantığı. Bir flow dosyası bir tarif olmalı; mutfağın altyapısını ve bağlantı detaylarını taşımamalı.
Kural: Bağımlılık oku asla geriye dönmez. core, services’ı bilmez; services hiçbir flow’u bilmez. Bu kural tek bir soruyu cevaplar: “Bu kod nereye gitmeli?” Kendinden sonraki katmanı bilmesi gerekiyorsa yanlış yerdedir.
Bir uyarı: Bu katmanları baştan tasarlamaya çalışmayın, tekrar eden koddan çıkarın. Önce flow’ları düz yazın, aynı kodu üçüncü kez yazarken durun ve onu ortak bir yere taşıyın. Önceden alınması gereken tek karar bağımlılığın yönü; o da bedava.
2. Bağlantılar için tek giriş kapısı
Birden fazla sisteme bağlanan onlarca iş varsa, şifreler nerede durmalı? Kodda durmaz. .env dosyası sunucuda, yedeklerde ve container’da dolaşır. Ayrı bir vault ise küçük bir ekip için işletilecek yeni bir sistem demek. Prefect’in Secret block’u iyi bir orta yol: zaten işlettiğiniz sistemin parçası, arayüzden düzenlenebiliyor ve bir bağlantının tüm bilgileri JSON olarak tek bir block’ta durabiliyor.
Block verileri kendi sunucunuzda da şifreli saklanır. Ama bir ayrıntıya dikkat: Şifreleme anahtarını vermezseniz Prefect onu kendisi üretir ve aynı veritabanında saklar; veritabanına erişen biri şifreleri çözebilir. Anahtarı PREFECT_SERVER_ENCRYPTION_KEY ortam değişkeniyle veritabanının dışında tutun.
Kritik karar şu: Hiçbir flow bu block’ları doğrudan okumamalı. Tek bir sınıf okumalı:
class DatabaseBlocks:
"""Tüm veritabanı ve servis bağlantılarının tek giriş kapısı."""
@classmethod
def get_mssql(cls, block_name: str = "kaynak-db") -> pymssql.Connection:
try:
config = cls._load_secret_json(block_name)
return pymssql.connect(
server=config["host"],
port=config.get("port", 1433),
user=config["username"],
password=config["password"],
database=config["database"],
)
except ConfigurationError:
raise # "block yok" hatasını veritabanı hatası gibi gösterme
except Exception as e:
raise DatabaseError(f"Bağlantı hatası: {e}") from e
Bu sınıftaki kararlar önemli:
- Block adı bir parametre. Yeni bir veritabanı eklemek yeni bir block oluşturmak demek olmalı; koda tek satır dokunulmamalı. Yeni bir örnek eklemek kod değişikliği gerektiriyorsa soyutlama yanlış yerdedir.
- “Block bulunamadı” ile “veritabanı yanıt vermiyor” ayrı hatalar olmalı. Birincisi kurulum hatasıdır, ikincisi altyapı olayı; farklı insanları ilgilendirirler.
- Bağlantı stratejisi kütüphaneye göre seçilir. Kendi bağlantı havuzu olmayan bir sürücüde (örneğin
pymssql) her çağrıda yeni bağlantı açıp işi bitincefinallyiçinde kapatın; uzun yaşayan bir bağlantı ağ koptuğunda sessizce ölü kalır.MongoClientgibi kendi havuzunu yöneten istemcilerde ise süreç boyunca tek bir örnek (singleton) kullanın. Singleton bir moda değil, kütüphanenin davranışına verilen cevaptır. - Deploy öncesi doğrulama. Gerekli block’ların hepsinin yerinde olup olmadığını iki saniyede söyleyen bir
verify_all()fonksiyonu, flow’un yarısında patlamasını beklemekten çok daha ucuzdur.
Repo genelinde pymssql.connect(...), MongoClient(...) ve smtplib.SMTP(...) gibi doğrudan bağlantıları yasaklayın. Bu yasak denetlenebilir de: grep -rn "pymssql.connect" flows/ her zaman boş dönmeli.
Bir uyarı daha: Block’ların yedeğini alırsanız, o yedek dosyası bütün şifreleri açık metin olarak içerebilir. Gizli bilgiyi doğru yerde saklamak, yedeğini yanlış yerde bırakmayı engellemez. Güvenlik, en zayıf kopyanın güvenliğidir.
3. Ayarlar: gizlilik ile değişkenlik farklı eksenlerdir
İlk refleks genelde her ayarı Secret block’a koymaktır. Sonuç: “Bu rapor kime gidiyor?” sorusunu cevaplamak için arayüzü açıp JSON aramak gerekir; kodu okuyan biri cevabı koddan bulamaz. Oysa bir şey sık değişiyor diye gizli değildir.
Ayarları üç katmanda tutun:
- A. Düz dict: Dosyanın tepesinde, insanın okuması için. İş kuralları, eşikler, mail alıcıları, açma/kapama bayrakları. Kod incelemesinde görünmeleri bir avantajdır.
- B. Pydantic sınıfı: Dict’e bağlı; tip güvenliği ve doğrulama sağlar.
max_rows="elli"yazılırsa flow başlamadan patlar, geçersiz bir e-posta adresi daha deploy aşamasında yakalanır. Prefect de bu sınıfı deployment parametresi olarak arayüzde form halinde gösterir. - C. Opsiyonel block: Sadece gerçekten hassas ya da ortama özgü değerler için: token’lar, API adresleri.
Hangi ayarın nereye gideceğini tek bir soru belirler: “Bu değeri bir ekran görüntüsünde paylaşabilir miyim?” Evet ise kodda dursun.
Bu katmanda sessiz bir tuzak da var. Pydantic düz liste varsayılanlarını her örnek için kendisi kopyalar; ama varsayılanı default_factory ile dosyanın tepesindeki sözlükten alıyorsanız kopyalama sorumluluğu sizindir:
emails: list[str] = Field(default_factory=lambda: CONFIG["emails"]) # ❌ her örnek aynı listeyi alır
emails: list[str] = Field(default_factory=lambda: CONFIG["emails"].copy()) # ✅ her örneğe kendi kopyası
İlk satırda bir çalışmanın listeye eklediği alıcı, ayar sözlüğünün kendisine yazılır ve aynı süreçteki sonraki çalışmalarda da görünür. Uzun süre çalışan bir worker’da bu, saatler sonra “bu alıcı nereden geldi?” diye ortaya çıkan hayalet bir hataya dönüşür.
Bu katmandan doğan en verimli desen parametrik flow’dur. Aynı işi farklı parametrelerle yapan dört ayrı dosya yerine tek bir flow, bir ayar sözlüğü ve dört deployment. Yeni bir varyant eklemek, sözlüğe bir blok ve deployment listesine bir blok eklemek demektir; test edilmiş flow koduna dokunulmaz. Sınırı da belli: Varyantlar arasındaki fark veri değil davranış olmaya başladıysa, yani kodda if flow_name == "x" belirdiyse, artık iki ayrı flow’unuz vardır.
4. Standart flow iskeleti
Her flow aynı iskelete uymalı. Bir flow dosyasını okuyan kişi, altyapı hakkında hiçbir şey bilmeden ne yapıldığını anlayabilmeli:
@task(name="veri-cek", retries=2, retry_delay_seconds=30, cache_policy=NO_CACHE)
def veri_cek(logger: FlowLogger, db_block: str = "kaynak-db") -> list[dict]:
task_logger = logger.for_task("veri-cek")
with task_logger.operation("veri_cekme"): # süreyi kendiliğinden ölçer
conn = None
try:
conn = DatabaseBlocks.get_mssql(block_name=db_block)
cursor = conn.cursor(as_dict=True)
cursor.execute("SELECT ...")
return cursor.fetchall()
except Exception as e:
raise DatabaseError(f"Kaynak sorgusu başarısız: {e}") from e
finally:
if conn is not None:
conn.close() # bağlantı havuzu yok; kapatmak zorunlu
@flow(name="ornek-rapor", retries=0)
def ornek_rapor(config: Optional[OrnekConfig] = None) -> OrnekResult:
run_id = str(flow_run.get_id()) if flow_run.get_id() else "local-run"
logger = FlowLogger(flow_name="ornek-rapor", flow_run_id=run_id)
config = config or OrnekConfig()
result = OrnekResult(started_at=datetime.now()) # hata olsa da bir sonuç olsun
try:
kayitlar = veri_cek(logger=logger)
result.total = len(kayitlar)
# ... iş mantığı ve rapor maili ...
return result
except Exception as e:
logger.error("Flow hatası", error=e)
if config.alert_emails:
try:
MailService().send_error_alert(to=config.alert_emails, ...)
except Exception:
pass # alarm gidemese bile asıl hatayı gizleme
raise # flow kırmızı görünmeli
if __name__ == "__main__":
print(ornek_rapor().model_dump_json(indent=2))
Önemli detaylar:
cache_policy=NO_CACHE. Prefect 3 her task için girdilerinden bir önbellek anahtarı üretmeye çalışır. Task’a logger, veritabanı bağlantısı ya da istemci gibi serileştirilemeyen bir nesne verirseniz bu hesap başarısız olur ve Prefect her çalışmada uzun birERRORlogu basar. Task yine çalışır, ama loglar sahte hatalarla dolar. Dış dünyaya dokunan ya da bu tür nesneler alan task’larda önbelleği açıkça kapatın; yan etkisi olan bir işin sonucunu önbelleklemek zaten istemeyeceğiniz bir şeydir.flow_run.get_id()bir flow çalışmasının dışında boş döner. Örneğin fonksiyonu test için.fn()ile doğrudan çağırdığınızda."local-run"yedeği olmazsa loglar kimliksiz kalır ve birbirine karışır.- En sondaki
raise. Hatayı yakalayıp loga yazmak yetmez, flow başarısız görünmeli. Hatayı yutup “başarılı” dönmek, bu yazının başındaki sessiz duruşun ta kendisidir. - Hatayı sararken
from e. Kendi hata sınıfınıza sarmak, altta ne olduğunu gizlemek anlamına gelmemeli;from eorijinal hatayı zincirde korur. - Alarm gönderimi kendi
trybloğunda. Mail sunucusu da çökmüş olabilir; alarmın başarısız olması asıl hatayı gizlememeli. if __name__ == "__main__": Her flow tek başına çalıştırılabilmeli; worker’a ya da container’a dokunmadanpython -m flows.ornek_raporile denenebilmeli. Hata ayıklamada en çok zaman kazandıran üç satır budur.
5. Log bir veri kümesidir
Bir orkestrasyon platformu birkaç ay çalıştıktan sonra gelen sorular hep aynı türdendir: Bu iş geçen ay toplam kaç kayıt işledi? Bu adım ne zaman yavaşlamaya başladı? Şu hata bu ay kaçıncı kez oluyor? print() ile loglanan bir platformda bu soruların cevabı yoktur, çünkü print çıktısı sorgulanamaz ve veri hiç toplanmamıştır.
Bu yüzden repo genelinde print() yerine yapılandırılmış, çift hedefli bir logger kullanın. Tek bir çağrı iki yere birden yazsın:
logger.info("Kayıtlar tarandı", kayit_sayisi=156, kaynak="kaynak-a")
İnsan okusun diye Prefect arayüzüne düz bir satır, makine sorgulasın diye log deposuna yapılandırılmış bir JSON kaydı gider: flow_name, task_name, flow_run_id, details ve duration_ms alanlarıyla. “Son 90 günde günlük kayıt sayısı” artık tek bir sorgudur. Adımları saran bir operation(...) bloğu her adımın süresini kendiliğinden ölçer; kimse elle zaman tutmaz.
İki kural var. Log deposuna yazılamazsa iş yine sürmeli, çünkü loglama işin kendisinden daha kritik olamaz. Ölçüm bloğu da hatayı kaydetmeli ama yutmamalı.
Log deposunun hangi veritabanı olduğu ikincil: PostgreSQL ve JSONB, MongoDB gibi bir doküman veritabanı ya da ekibinizin zaten işlettiği herhangi bir sorgulanabilir depo olabilir. Doğru cevap bir marka değil, sorgulanabilir bir yer. En tehlikeli durum “flow yeşil, veri yanlış” halidir: Her gün 150 kayıt işleyen bir iş bir gün 12 kayıt işlemişse, bunu ancak sayıları kaydediyorsanız görürsünüz.
6. Retry bir güvenlik ağı değil, bir çarpandır
Retry’ı her flow’a refleksle koymak çok yaygın bir hatadır. Bir stored procedure çağıran flow düşünün. Ağ kısa bir süre kesilir; prosedür çalışır ve biter, ama sonuç dönerken bağlantı kopar. Prefect açısından iş başarısızdır; retry devreye girer ve prosedür ikinci kez çalışır. Aynı satırlar iki kez işlenmiş olur.
Retry, işin idempotent olduğunu, yani iki kez çalışınca sonucun değişmediğini varsayar. Bu varsayım yoksa retry bir veri bozma mekanizmasıdır. Kurallar şöyle olmalı:
- Geçici hatalarla kalıcı hataları ayırın.
ConfigurationErrorveValidationErrortekrar denenmez; 30 saniye sonra block yine yoktur, veri yine bozuktur.DatabaseErrorveMailErrorgibi altyapı hataları geçici olabilir, denenebilir. - İdempotent olmayan iş yapan flow’da
retries=0. Bu bir eksiklik değil, bir belgelemedir: Kodu okuyana “bu iş iki kez çalışmamalı” der. - Retry’ı güvenli kılmanın yollarını bilin: tam yenileme (truncate + insert), anahtara göre
MERGE, yüksek su seviyesi (id > MAX(id)) ve kuyruk işlerinde satırları tek bir atomik ifadeyle sahiplenmek.
Prefect’in transaction özelliği de bu konuda sizi kurtarmaz: Geri alma adımını yine sizin yazmanız gerekir. En sağlıklı varsayılan şu: Her yerde retries=0 olsun, idempotentlik kanıtlanınca retry eklensin. Güvenli varsayılan, güvenli olmayan varsayılandan her zaman iyidir.
7. Deployment’lar kodda dursun
Deployment’ları arayüzden ya da tek tek CLI komutlarıyla oluşturmak kayıt dışı kalır; hangi komutun çalıştırıldığını kimse hatırlamaz. Deployment’ları sürüm kontrolündeki bir dosyada tanımlayın. Python tercih ediyorsanız bir deploy.py:
from prefect import Flow
from prefect.schedules import Cron
Flow.from_source(
source="/app",
entrypoint="flows/veri_kalite.py:veri_kalite_raporu",
).deploy(
name="veri-kalite-gunluk",
work_pool_name="varsayilan-havuz",
schedule=Cron("0 8 * * *", timezone="Europe/Istanbul"),
tags=["veri-kalite", "gunluk"],
)
Bütün deployment’lar tek bir dosyadaysa o dosya platformun zamanlama haritası olur: “Sabah sekizde ne çalışıyor?” sorusu tek ekranda cevaplanır, yeni bir iş eklerken aynı saatte başka ne olduğunu görürsünüz. Deployment sayısı yüzlere yaklaştığında modüler tanımlara ya da prefect.yaml’a geçmek gerekir.
Bu katmanda üç tuzak var:
- Saat dilimi. Düz bir
cron="0 8 * * *"metni UTC olarak yorumlanır; yerel saat UTC+3 ise iş 11’de çalışır. Daha sinsi olanı gün kaymasıdır: UTC salı 22:00, yerel saatle çarşamba 01:00 demek; “salı raporu” çarşamba çıkar. Hesabı kafadan yapmayın; yukarıdaki gibiCron(..., timezone=...)ile saat dilimini açıkça verin. - Eski deployment’lar. Prefect gelecekteki zamanlanmış çalışmaları önceden oluşturur. Bir deployment’ın zamanlamasını değiştirdiğinizde henüz başlamamış çalışmaları siler ve yenilerini oluşturur; ama deployment’ı farklı bir adla yeniden oluşturursanız eskisi zamanlamasıyla birlikte yerinde durur ve iş iki kez çalışır. Bir deployment’ı yeniden adlandırdıktan sonra eskisini silmeyi ve arayüzde bekleyen çalışmalara bir kez bakmayı alışkanlık haline getirin.
- Kodun dağıtımı. Kodu worker’a volume olarak bağlamak hızlıdır: Deploy etmek dosyayı güncellemekten ibarettir, image yeniden derlenmez. Bedeli yeniden üretilebilirliktir; “hangi kod sürümü çalışıyor?” sorusunun cevabı image etiketinde değil, dosya sistemindedir. Tek worker’lı küçük bir kurulumda bu takas kabul edilebilir; birden fazla worker’da kodu sürüm etiketli bir image’a koymak gerekir.
8. Süreci değil, sonucu ölçün
“Ne değildir” bölümündeki yedek örneğini hatırlayın. Bunun panzehiri, başka sistemlerin sonucunu doğrudan ölçen ayrı bir flow ailesidir; onlara nöbetçi flow diyorum. Yedekleme işinin çalışıp çalışmadığını değil, veritabanının kendi kaydına bakarak yedeğin gerçekten var olup olmadığını ve ne kadar taze olduğunu sorarlar. Nöbetçilerin iki kuralı olmalı:
- Erişim hatası da bir ihlaldir. Kontrol sorgusu patlarsa “sorun yok” değil, “bilmiyorum” raporlanmalı. Bilgi eksikliği iyi haber sayılmaz.
- Her alarmdan önce şu soruyu sorun: “Bu ayda kaç kez ötecek?” Cevap “her gün” ise o bir alarm değil, bir rapordur. Her çalışmada uyarı üreten bir izleme flow’una iki hafta sonra kimse bakmaz; gerçek uyarı da o gürültünün içinde kaybolur. Alarm yorgunluğu teknik değil, insani bir sorundur; ama mühendislikle çözülmesi gerekir.
Prefect’in “beklenen bir olay gelmezse tetiklen” türündeki otomasyonları da aynı işe yarar: “Bu iş sabah dokuza kadar tamamlanmadıysa haber ver” kuralı, işin hiç başlamadığı durumu da yakalar.
Ölçeğe göre ne değişir?
Yukarıdaki ilkeler ölçekten bağımsız, ama uygulamaları değişir. Küçük başlayıp büyüyen bir kurulum için kabaca şöyle:
| Konu | Küçük başlarken | Büyüdükçe |
|---|---|---|
| Sunucu veritabanı | SQLite | PostgreSQL (çoklu sunucuda + Redis) |
| Worker | Tek process worker | Birden fazla work pool, Docker ya da Kubernetes |
| Deployment tanımı | Tek deploy.py | Modüler tanımlar ya da prefect.yaml |
| Kod dağıtımı | Volume ile bağlanan repo | Sürüm etiketli image |
| Test | Flow’ları yerelde çalıştırmak | Otomatik test ve CI |
| Gizli bilgi | Secret block | Ayrı bir gizli bilgi yöneticisi |
Bonus: Bu yapıyı bir yapay zeka asistanına kurdurmak
Yapay zeka kodlama asistanları, kuralları ne kadar net verirseniz o kadar tutarlı kod üretir. “Bana bir Prefect projesi kur” demek yerine sözleşmeyi baştan vermek gerekir.
Aşağıdaki prompt’u boş bir repoda Claude Code, Cursor ya da benzeri bir kodlama asistanına verebilirsiniz. Köşeli parantezli yerleri kendi ortamınıza göre doldurun:
Sen kıdemli bir veri platformu mimarı ve deneyimli bir Python/Prefect
geliştiricisisin. Prefect 3 ile, küçük bir ekibin işletebileceği, katmanlı bir
iş orkestrasyonu reposu kuracağız. Aşağıdaki kurallara harfiyen uy; bir kuralı
esnetmen gerekirse önce bana sor.
## Bağlam
- Kaynak sistemler: [ör. 2 adet MSSQL veritabanı, 1 REST API]
- Log deposu: [ör. PostgreSQL (JSONB) veya MongoDB]
- Çalışma ortamı: tek Docker host, self-hosted Prefect server, tek process worker
- Yerel saat dilimi: [ör. Europe/Istanbul]
## Mimari kurallar
1. Klasörler: core/ → services/ → models/ → flows/. Bağımlılık tek yönlüdür;
bir katman kendinden sonrakini asla import etmez.
2. core/blocks.py: Tüm bağlantıların tek giriş kapısı olan DatabaseBlocks
sınıfı. Bağlantı bilgileri Prefect Secret block'larında JSON olarak durur.
Block adı parametredir; yeni bir kaynak eklemek kod değişikliği
gerektirmemelidir. Kendi havuzu olmayan sürücülerde (ör. pymssql) her
çağrıda yeni bağlantı aç ve finally içinde kapat; kendi havuzunu yöneten
istemcilerde (ör. MongoClient) singleton kullan. Gerekli tüm block'ları
kontrol eden bir verify_all() fonksiyonu yaz.
3. core/exceptions.py: BaseFlowError altında ConfigurationError, DatabaseError,
MailError, ValidationError ve TaskError. Sararken her zaman `raise ... from e`.
4. services/logging.py: Çift hedefli FlowLogger. Hem Prefect loguna hem log
deposuna yapılandırılmış kayıt yazar; süre ölçen bir operation() context
manager'ı ve for_task() metodu vardır. Log deposuna yazılamazsa flow durmaz.
5. services/mail.py: Singleton MailService. send() ve send_error_alert()
metotları olur; ekleri bellekte (bytes) taşır; her gönderimde flow_name ve
flow_run_id zorunludur.
6. models/config.py: Her flow için dosyanın tepesinde okunabilir bir dict ve ona
bağlı bir Pydantic sınıfı. Liste varsayılanları default_factory ile ve .copy()
yapılarak alınır. Sadece gizli veya ortama özgü değerler block'tan gelir.
7. models/results.py: FlowResult tabanlı sonuç sınıfları; her flow toplam,
başarılı ve başarısız sayılarını, başlangıç ve bitiş zamanını döndürür.
## Flow kuralları
- Flow dosyası sadece iş mantığını anlatır; bağlantı dizesi, SMTP ayarı gibi
altyapı detayı içermez.
- Dış dünyaya dokunan ya da logger, bağlantı gibi serileştirilemeyen nesneler
alan task'larda cache_policy=NO_CACHE kullan.
- Varsayılan retries=0. Retry'ı sadece idempotent olduğu açık olan task'lara ekle
ve nedenini yorumla yaz. ConfigurationError ve ValidationError retry edilmez.
- Hata olursa: logla, sonuç nesnesini doldur, alarm listesine mail at (mail
hatası asıl hatayı gizlemesin), sonra mutlaka raise et. Hatayı yutup başarılı
dönmek yasaktır.
- flow_run.get_id() bir flow çalışması dışında (ör. .fn() ile çağrıda) None
döner; "local-run" yedeğini kullan.
- Her flow dosyası `if __name__ == "__main__":` ile tek başına çalıştırılabilir.
- Yasaklar: flows/ içinde doğrudan veritabanı veya SMTP bağlantısı (pymssql.connect,
MongoClient, smtplib), print() ile loglama, kendi retry decorator'ın. Bunları
grep ile kontrol eden küçük bir scripts/check_rules.sh yaz.
## Deployment kuralları
- Tüm deployment'lar tek bir deploy.py içinde Python koduyla tanımlanır.
- Zamanlamalar prefect.schedules.Cron ile ve Bağlam'daki saat dilimi açıkça
verilerek yazılır, ör. Cron("0 8 * * *", timezone="Europe/Istanbul").
- Aynı işi farklı parametrelerle yapan varyantlar için tek flow, bir ayar sözlüğü
ve birden fazla deployment kullan.
- Prefect server'ın PREFECT_SERVER_ENCRYPTION_KEY değişkenini veritabanının
dışında tutacak şekilde docker-compose örneği hazırla.
## MCP (isteğe bağlı)
- mcp_server.py: FastMCP ile yapay zeka ajanlarına açılan araçlar. Sadece
okuyan araçlar readOnlyHint ile işaretlenir. Eylem araçları yalnızca
run_deployment ile mevcut bir deployment'ı tetikler, idempotency_key kullanır
ve timeout=0 ile hemen döner. MCP sunucusu veritabanına ya da block'lara
doğrudan erişmez.
## İlk görev
1. Klasör yapısını kur; requirements.txt, Dockerfile ve docker-compose.yml'ı ekle.
2. "ornek-rapor" adında üretime hazır tek bir örnek flow yaz: kaynaktan kayıt
çeksin, sayıyı logla, özet maili göndersin.
3. README.md'ye "yeni flow ekleme kontrol listesi"ni yaz: ayar → sonuç tipi →
flow → yerel test → deploy.py → elle bir kez çalıştır → log deposunda doğrula.
4. Her adımdan sonra ne yaptığını kısaca özetle; varsayım yaptığın her yeri
açıkça belirt.
Bu prompt’un asıl işi kod yazdırmak değil, kural koymak. Asistan her yeni flow’da aynı kuralları uyguladığında, altı ay sonra repoya bakan kişi, siz de dahil, nereye dokunacağını bilir.
Özet
- Prefect Python fonksiyonlarını izlenebilir, yeniden denenebilir ve zamanlanabilir işlere dönüştüren, kurulumu ve işletmesi hafif bir orkestratördür. Küçük ve orta ölçekli ekipler için güçlü bir başlangıç noktasıdır.
- Cilalı bir cron, bir veri işleme motoru ya da bir izleme garantisi değildir; idempotent olmayan kodu da düzeltmez.
- Sistemler arasında bir entegrasyon ve tampon katman olarak kurgulanabilir; güvenliği Prefect’ten değil, tasarımdan gelir.
- FastMCP ile flow’larınızı yapay zeka ajanlarına dar, idempotent ve denetlenebilir araçlar olarak açabilirsiniz; sadece gözlem için Prefect’in salt okunur resmi MCP sunucusu var.
- Airflow, Dagster ve Prefect farklı sorulara cevap verir: görev mi, veri varlığı mı, yoksa eylem mi?
- Asıl başarı araçtan değil, flow’ların etrafına kurulan disiplinden gelir: tek yönlü katmanlar, tek bağlantı kapısı, ayrışmış ayarlar, çift hedefli loglar, bilinçli retry, kodda duran deployment’lar ve sonucu ölçen nöbetçiler.
İyi bir veri platformunun kalitesi üzerinde kaç iş çalıştığıyla değil, yeni bir iş eklenirken ne kadar az yeni kod yazıldığıyla ölçülür.
Bir orkestratörü değerlendirirken “ne kadar güçlü?” diye değil, “bir iş sessizce durduğunda bunu ne zaman fark ederim?” diye sorun. Bu yazıdaki ilkelerin her birini ileride ayrı yazılarda daha ayrıntılı açmayı düşünüyorum.
Kaynaklar
- Prefect acquires Dagster (Prefect, 13 Temmuz 2026) ve Dagster customer FAQ (Dagster)
- Prefect 3.8 sürüm notları, Schedules, Automations, Transactions ve Caching (Prefect belgeleri)
- How Prefect works ve Pricing (Prefect)
- Block şifrelemesi: encryption.py (Prefect kaynak kodu)
- MCP spesifikasyonu (2026-07-28) ve MCP’nin Agentic AI Foundation’a katılması (Model Context Protocol)
- FastMCP belgeleri, FastMCP 4.0 sürüm notları, Prefect MCP sunucusu ve Prefect Horizon duyurusu
- Airflow 3.0 duyurusu, Airflow mimarisi ve desteklenen sürümler (Apache Airflow)
- Dagster kavramları, açık kaynak dağıtım mimarisi ve Pricing (Dagster)