<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>sade.dev</title><description>Backend, sistem tasarımı, PostgreSQL, Redis, Laravel ve AI-destekli mühendislik üzerine Türkçe notlar ve geliştirici araçları.</description><link>https://sade.dev/tr/</link><language>tr-TR</language><atom:link href="https://sade.dev/tr/rss.xml" rel="self" type="application/rss+xml"/><item><title>İndeks Yönetimi: Eksik Yavaşlatır, Fazlası Yazmayı Öldürür</title><link>https://sade.dev/tr/notes/indeks-yonetimi/</link><guid isPermaLink="true">https://sade.dev/tr/notes/indeks-yonetimi/</guid><description>Eksik indeks sorguyu yavaşlatır, gereğinden fazla indeks her yazma işlemini pahalılaştırır. Doğru dengeyi tahminle değil ölçümle kurmak.</description><pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;İki karşıt ekip gördüm. Biri tabloya hiç indeks koymamıştı — her sorgu bir sequential scan, her liste sayfası saniyeler sürüyordu. Diğeri tam tersini yapmıştı: “ne olur ne olmaz” diye her kolona bir indeks açmıştı ve şimdi her &lt;code&gt;INSERT&lt;/code&gt; ağır aksak ilerliyordu.&lt;/p&gt;
&lt;p&gt;İkisi de aynı yanılgının iki ucu: indeksi ücretsiz bir hız kaynağı sanmak.&lt;/p&gt;
&lt;h2 id=&quot;i̇ndeks-ücretsiz-hız-değildir&quot;&gt;İndeks ücretsiz hız değildir&lt;/h2&gt;
&lt;p&gt;Bir indeks okumayı hızlandırır, çünkü veritabanı tüm tabloyu taramak yerine sıralı bir yapıya bakar. Ama o sıralı yapı kendi kendine güncel kalmaz: tablodaki bir &lt;code&gt;INSERT&lt;/code&gt;, o tablonun &lt;strong&gt;her indeksine&lt;/strong&gt; bir girdi yazar; bir &lt;code&gt;UPDATE&lt;/code&gt; de indeksli bir kolona dokunduğu anda aynısını yapar. İstisnalar var — indeksli hiçbir kolonu değiştirmeyen HOT güncellemesi indekslere dokunmaz, kısmi indeks yüklemi dışında kalan satırlar için güncellenmez, &lt;code&gt;DELETE&lt;/code&gt; ise indeks girdilerini temizlemeyi &lt;code&gt;VACUUM&lt;/code&gt;’a bırakır — ama bunlar istisna, varsayılan değil.&lt;/p&gt;
&lt;p&gt;Yani her indeks bir takastır: okuma hızını, yazma maliyetiyle satın alırsınız. Beş indeksli bir tabloya tek satır eklemek, altı yapıyı birden güncellemektir. İndeks “bedava okuma hızı” değil, “yazmayla ödenmiş okuma hızı”dır.&lt;/p&gt;
&lt;h2 id=&quot;eksik-indeks-tahmin-değil-ölçüm&quot;&gt;Eksik indeks: tahmin değil, ölçüm&lt;/h2&gt;
&lt;p&gt;Eksik indeksi sorgu planından bulun, tahminden değil:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;EXPLAIN ANALYZE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; status&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pending&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Çıktıda büyük bir tabloda &lt;code&gt;Seq Scan&lt;/code&gt; görüyorsanız ve sorgu satırların küçük bir kısmını döndürüyorsa, muhtemelen bir indeks eksiktir. &lt;code&gt;pg_stat_user_tables&lt;/code&gt; tablosunda &lt;code&gt;seq_scan&lt;/code&gt; sayısı yüksek olan büyük tabloları tarayın — bunlar adaylarınızdır.&lt;/p&gt;
&lt;p&gt;İndeksi gerçek, yavaş bir sorgu için ekleyin. “İleride lazım olabilir” diye eklenen indeks, &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;spekülatif genelliğin&lt;/a&gt; veritabanı katmanındaki hâlidir.&lt;/p&gt;
&lt;h2 id=&quot;fazla-indeks-kullanılmayanı-bul&quot;&gt;Fazla indeks: kullanılmayanı bul&lt;/h2&gt;
&lt;p&gt;Ters yönde, hiç kullanılmayan indeksler her yazmayı boş yere pahalılaştırır. Onları da ölçümle bulun:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; indexrelname, idx_scan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pg_stat_user_indexes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; idx_scan &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;idx_scan = 0&lt;/code&gt; olan bir indeks hiç okuma hızlandırmıyor ama her yazmaya maliyet ekliyor demektir. (Primary key ve unique kısıtların indekslerini elbette koruyun.) Bu, &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;veri yoğunluklu sistemlerin&lt;/a&gt; yazma kırılma noktasında sayılan ilk temizliktir.&lt;/p&gt;
&lt;h2 id=&quot;doğru-indeksi-seçmek&quot;&gt;Doğru indeksi seçmek&lt;/h2&gt;
&lt;p&gt;İndeks “var/yok”tan ibaret değildir; doğru türü ve şekli seçmek önemlidir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bileşik indekste kolon sırası.&lt;/strong&gt; &lt;code&gt;(a, b)&lt;/code&gt; indeksi, &lt;code&gt;a&lt;/code&gt;’da eşitlik + &lt;code&gt;b&lt;/code&gt;’de aralık sorgusuna hizmet eder; &lt;code&gt;b&lt;/code&gt;’de tek başına yapılan bir arama da bu indeksi kullanabilir ama taranan indeks bölümünü daraltmaz — PostgreSQL 18 öncesinde indeksin tamamı taranır, 18’den itibaren skip scan optimizasyonu bunu daraltır. Eşitlikle filtrelenen kolonu başa, aralıkla filtreleneni sona koyun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kısmi indeks (partial).&lt;/strong&gt; Sorgu hep aynı alt kümeye bakıyorsa indeksi de o alt kümeyle sınırlayın: &lt;code&gt;CREATE INDEX ... WHERE status = &amp;#39;active&amp;#39;&lt;/code&gt;. Daha küçük indeks, daha ucuz bakım.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Covering indeks.&lt;/strong&gt; &lt;code&gt;INCLUDE&lt;/code&gt; ile sık okunan kolonları indekse ekleyip veritabanının tabloya hiç gitmemesini sağlayabilirsiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B-tree dışı türler.&lt;/strong&gt; Eşitlik/sıralama için B-tree; &lt;code&gt;jsonb&lt;/code&gt; ve tam metin için GIN; saf sıralı, append ağırlıklı veri için küçük ve ucuz BRIN.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;duplikat-ve-örtüşen-indeksler&quot;&gt;Duplikat ve örtüşen indeksler&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;(a)&lt;/code&gt; üzerindeki bir indeks, zaten &lt;code&gt;(a, b)&lt;/code&gt; indeksiniz varsa gereksizdir — bileşik indeks &lt;code&gt;a&lt;/code&gt; ile başlayan sorgulara da hizmet eder. Bu tür örtüşmeleri periyodik olarak temizleyin; her biri sessiz bir yazma vergisidir.&lt;/p&gt;
&lt;h2 id=&quot;denge-sorgu-planıyla-kurulur&quot;&gt;Denge sorgu planıyla kurulur&lt;/h2&gt;
&lt;p&gt;Tek kural: indeksi ne tahminle ekleyin ne tahminle silin. Bir indeks, gerçek bir sorgu planı onu istediği için eklenir; bir indeks, istatistikler onu kimsenin kullanmadığını gösterdiği için silinir. Eksik ile fazla arasındaki denge, hislerle değil, &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; ve &lt;code&gt;pg_stat_user_indexes&lt;/code&gt; ile kurulur.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;İndeks yönetimi, “daha çok indeks daha iyi” sanısından kurtulmakla başlar. Her indeks bir okuma kazancı ve bir yazma maliyetidir; iyi yönetim, bu ikisini ölçerek dengede tutmaktır.&lt;/p&gt;
&lt;p&gt;Eksik indeks sorguyu yavaşlatır; fazlası ise tüm tabloyu. İkisinden de ölçüm korur.&lt;/p&gt;</content:encoded><category>postgresql</category><category>database</category><category>performance</category><category>indexing</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Canlıda Sıfır Kesintiyle Şema Değiştirmek</title><link>https://sade.dev/tr/notes/zero-downtime-veritabani-gocleri/</link><guid isPermaLink="true">https://sade.dev/tr/notes/zero-downtime-veritabani-gocleri/</guid><description>Production&apos;da kesinti yaratmadan migration almak: geri uyumlu adımlar, çok aşamalı kolon değişiklikleri ve tablo kilitlenmesini önleme.</description><pubDate>Sat, 29 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir deploy, 50 milyon satırlık bir tabloda tek bir &lt;code&gt;ALTER TABLE&lt;/code&gt; çalıştırdı ve tabloyu dört dakika kilitledi. Dört dakika boyunca o tabloya değen her istek bekledi; site fiilen çöktü. Migration’ın kendisi doğruydu — sorun, onun tek adımda yapılmasıydı.&lt;/p&gt;
&lt;p&gt;Tehlikeli olan şema değişikliği değildir. Tehlikeli olan, şema değişikliğini tek seferde, geri uyumluluğu düşünmeden yapmaktır.&lt;/p&gt;
&lt;h2 id=&quot;locku-kim-alır&quot;&gt;Lock’u kim alır&lt;/h2&gt;
&lt;p&gt;Her şema değişikliği aynı maliyette değildir. Modern PostgreSQL’de sabit &lt;code&gt;DEFAULT&lt;/code&gt; ile kolon eklemek bir metadata işlemidir — hızlıdır. Asıl tehlike, tabloyu uzun süre kilitleyen işlemlerdedir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CREATE INDEX&lt;/code&gt; — &lt;code&gt;CONCURRENTLY&lt;/code&gt; olmadan tabloyu yazmaya kapatır.&lt;/li&gt;
&lt;li&gt;Tabloyu yeniden yazan &lt;code&gt;ALTER COLUMN ... TYPE&lt;/code&gt; değişiklikleri.&lt;/li&gt;
&lt;li&gt;Tüm tabloyu tarayan &lt;code&gt;NOT NULL&lt;/code&gt;, &lt;code&gt;CHECK&lt;/code&gt; veya foreign key eklemeleri.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu işlemler güçlü bir lock alır; &lt;code&gt;ACCESS EXCLUSIVE&lt;/code&gt; alanlar — tablo yeniden yazımı ya da &lt;code&gt;NOT NULL&lt;/code&gt;/&lt;code&gt;CHECK&lt;/code&gt; eklemesi — tabloya değen her sorguyu, okuma dahil, kuyruğa sokar. Geri kalanı yalnızca yazmaları bloklar. Tablo büyükse kuyruk uzar.&lt;/p&gt;
&lt;h2 id=&quot;tehlikeli-olan-tek-adımdır&quot;&gt;Tehlikeli olan tek adımdır&lt;/h2&gt;
&lt;p&gt;Çözüm migration yapmamak değil; her tehlikeli migration’ı, her biri tek başına güvenli ve geri uyumlu olan küçük adımlara bölmektir. Bunun adı &lt;strong&gt;expand-contract&lt;/strong&gt; (genişlet–daralt) kalıbıdır.&lt;/p&gt;
&lt;p&gt;Bir kolonu yeniden adlandırmak istediğinizi düşünün. Tek adımda &lt;code&gt;RENAME COLUMN&lt;/code&gt;, eski kolonu okuyan çalışan kodu anında kırar. Bunun yerine üç deploy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Genişlet.&lt;/strong&gt; Yeni kolonu ekle. Kod hem eskiye hem yeniye yazsın, hâlâ eskiden okusun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geçiş.&lt;/strong&gt; Eski veriyi yeni kolona batch’ler hâlinde taşı. Kod artık yeniden okusun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Daralt.&lt;/strong&gt; Eski kolonu sil.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Her adım, hem bir önceki sürümdeki kod hem de yeni kod ile çalışır. Hiçbir an, çalışan kod ile şema uyumsuz değildir.&lt;/p&gt;
&lt;h2 id=&quot;güvenli-reçeteler&quot;&gt;Güvenli reçeteler&lt;/h2&gt;
&lt;p&gt;Sık yapılan değişikliklerin kesintisiz hâlleri:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yeni &lt;code&gt;NOT NULL&lt;/code&gt; kolon.&lt;/strong&gt; Tek adımda &lt;code&gt;NOT NULL&lt;/code&gt; eklemek tabloyu tarar. Bölün:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- 1. Önce nullable ekle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; COLUMN &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- 2. Mevcut satırları batch&amp;#39;ler hâlinde doldur (uygulama tarafında)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- 3. Kısıtı önce doğrulamadan ekle, sonra ayrı adımda doğrula&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; orders &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; CONSTRAINT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; orders_status_not_null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    CHECK&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; IS NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;NOT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; VALID;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; orders VALIDATE &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;CONSTRAINT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; orders_status_not_null;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;NOT VALID&lt;/code&gt; ile eklenen kısıt yeni satırlar için hemen geçerlidir ama mevcut satırları taramaz; &lt;code&gt;VALIDATE&lt;/code&gt; ise tabloyu yalnızca &lt;code&gt;SHARE UPDATE EXCLUSIVE&lt;/code&gt; lock ile tarar — yazmaları engellemez.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;İndeks.&lt;/strong&gt; Her zaman &lt;code&gt;CONCURRENTLY&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; CONCURRENTLY&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; idx_orders_status &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; orders (&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Foreign key.&lt;/strong&gt; Aynı iki adımlı kalıp: önce &lt;code&gt;ADD CONSTRAINT ... NOT VALID&lt;/code&gt;, sonra &lt;code&gt;VALIDATE CONSTRAINT&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;lock_timeout&lt;/code&gt;.&lt;/strong&gt; Bir migration, uzun süren bir sorgunun arkasında lock için beklerken kendisi de arkasındaki her şeyi bloklar. Bunu önlemek için migration oturumuna kısa bir &lt;code&gt;lock_timeout&lt;/code&gt; verin — lock hemen alınamıyorsa migration beklemek yerine başarısız olsun, siz tekrar deneyin.&lt;/p&gt;
&lt;h2 id=&quot;kod-ve-şema-birlikte-uyumlu-olmalı&quot;&gt;Kod ve şema birlikte uyumlu olmalı&lt;/h2&gt;
&lt;p&gt;Expand-contract’ın özü tek bir kuralda: her ara adımda, hem &lt;strong&gt;çalışmakta olan eski kod&lt;/strong&gt; hem de &lt;strong&gt;yeni kod&lt;/strong&gt; mevcut şema ile çalışabilmeli.&lt;/p&gt;
&lt;p&gt;Veritabanı şeması, &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;ileride lazım olur&lt;/a&gt; yazısında “tek yönlü kapı” dediğim şeydir — geri almak pahalıdır. Bu yüzden değişikliği büyük ve geri dönülmez tek bir adım olarak değil, her biri ayrı ayrı geri alınabilir küçük adımlar olarak tasarlayın.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Sıfır kesintili migration bir araç değil, bir disiplindir: her şema değişikliğini, çalışan kodun hiç fark etmeyeceği kadar küçük adımlara bölmek.&lt;/p&gt;
&lt;p&gt;Tehlikeli olan değişikliğin kendisi değil, onu tek nefeste yapmaktır.&lt;/p&gt;</content:encoded><category>postgresql</category><category>database</category><category>migrations</category><category>production</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Read/Write Splitting: Okuma ve Yazma Yükünü Ayırmak</title><link>https://sade.dev/tr/notes/read-write-splitting/</link><guid isPermaLink="true">https://sade.dev/tr/notes/read-write-splitting/</guid><description>Veritabanını büyütmeden önce: okuma trafiğini replica&apos;lara taşımak, replication lag&apos;in yarattığı tuzaklar ve ne zaman gerçekten gerektiği.</description><pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Primary veritabanının CPU’su sürekli tavandaydı. İlginç olan şuydu: yazma oranı düşüktü. Yükün neredeyse tamamı okumaydı — rapor sayfaları, listeleme endpoint’leri, arama. Tek bir primary, kendisine hiç ihtiyaç duymayan bir sürü okumayı taşımaya çalışıyordu.&lt;/p&gt;
&lt;p&gt;Okuma ve yazma yükünü ayırmak — read/write splitting — bu tablonun bilinen çözümü. Ama doğru zamanda ve doğru tuzakların farkında uygulanmazsa, çözdüğünden çok problem getirir.&lt;/p&gt;
&lt;h2 id=&quot;çoğu-yük-okuma-ağırlıklıdır&quot;&gt;Çoğu yük okuma ağırlıklıdır&lt;/h2&gt;
&lt;p&gt;Trafiğin ne kadarının okuma olduğu iş yüküne bağlıdır: standart OLTP benchmark’larının ölçümünde TPC-E %90,69 okumadır, TPC-C ise %65,71’de kalır — yani kendi oranınızı varsaymak yerine ölçün. Her sipariş bir kez yazılır, ama onlarca kez okunur: listede, detayda, raporda, panelde. Bu asimetri, read/write splitting’i cazip kılan şeydir — çünkü ölçeklemeniz gereken taraf bellidir.&lt;/p&gt;
&lt;h2 id=&quot;önce-bu-gerçekten-bir-kapasite-sorunu-mu&quot;&gt;Önce: bu gerçekten bir kapasite sorunu mu?&lt;/h2&gt;
&lt;p&gt;Replica eklemeden önce durun. Primary’nin dolu olması her zaman “kapasite bitti” demek değildir. Çoğu zaman tek bir eksik indeks, primary’yi olduğundan kat kat meşgul gösterir.&lt;/p&gt;
&lt;p&gt;Bir replica eklemek — yeni bir sunucu, replication kurulumu, lag izleme — aslında bir &lt;code&gt;CREATE INDEX&lt;/code&gt;’in çözeceği bir problemi pahalıya satın almak olabilir. &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;Veri yoğunluklu sistemlerin kırılma noktaları&lt;/a&gt; yazısındaki sıra nettir: önce indeks disiplini, sonra replica. Bu sırayı atlamayın.&lt;/p&gt;
&lt;p&gt;Sorgularınızı &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; ile ölçün. Primary, doğru indekslenmiş sorgular altında gerçekten doyuyorsa — işte o zaman replica.&lt;/p&gt;
&lt;h2 id=&quot;replica-okumayı-ölçekler-yazmayı-değil&quot;&gt;Replica okumayı ölçekler, yazmayı değil&lt;/h2&gt;
&lt;p&gt;Net olalım: bir read replica, yazma kapasitenize &lt;strong&gt;hiçbir şey&lt;/strong&gt; katmaz. Aynı yazmalar her replica’da yeniden oynanır. Replica okuma yükü problemini çözer; yazma yükü probleminiz varsa replica yanlış araçtır.&lt;/p&gt;
&lt;h2 id=&quot;laravelde-kurulum&quot;&gt;Laravel’de kurulum&lt;/h2&gt;
&lt;p&gt;Laravel, okuma/yazma bağlantı ayrımını doğal destekler:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;// config/database.php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pgsql&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;driver&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pgsql&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;read&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;   =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;10.0.0.2&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]],&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;   // replica&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;  =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;10.0.0.1&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]],&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;   // primary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;sticky&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;    // ...ortak ayarlar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SELECT&lt;/code&gt;’ler replica’ya, &lt;code&gt;INSERT/UPDATE/DELETE&lt;/code&gt;’ler primary’ye gider. Replica’nın kendi bağlantı havuzu olur — primary’ninkinden ayrı; &lt;a href=&quot;/tr/notes/pgbouncer-auth-query/&quot;&gt;pgBouncer&lt;/a&gt; kullanıyorsanız ikisi ayrı havuzdur.&lt;/p&gt;
&lt;h2 id=&quot;replication-lag-asıl-fatura&quot;&gt;Replication lag: asıl fatura&lt;/h2&gt;
&lt;p&gt;Replica, primary’nin birkaç milisaniye — yük altında birkaç saniye — gerisindedir. Bu gecikme, read/write splitting’in gerçek bedelidir ve adı &lt;strong&gt;read-after-write&lt;/strong&gt; problemidir.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sticky =&amp;gt; true&lt;/code&gt; bunu kısmen çözer: bir istek içinde yazma yaptıysanız, aynı istekteki sonraki okumalar primary’ye gider. Ama &lt;code&gt;sticky&lt;/code&gt; yalnızca &lt;strong&gt;tek bir istek&lt;/strong&gt; sınırında çalışır.&lt;/p&gt;
&lt;p&gt;Sınırın dışı hâlâ açıktır: kullanıcı profilini günceller (istek 1, primary’ye yazıldı), sonraki sayfaya geçer (istek 2, replica’dan okur) ve henüz replica’da oynanmamış eski profilini görür. Kullanıcı kendi yazdığı veriyi bayat görür. Bu bir bug gibi görünür ama aslında mimarinin kabul ettiği bir ödündür — bilerek kabul edilmesi gereken bir ödün.&lt;/p&gt;
&lt;h2 id=&quot;sorgu-sınıflandırma-disiplini&quot;&gt;Sorgu sınıflandırma disiplini&lt;/h2&gt;
&lt;p&gt;Read/write splitting’i kurmak, her okumanın şu soruya cevap vermesini gerektirir: &lt;strong&gt;bu sorgu bayat veri okuyabilir mi?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Replica’dan okunabilir:&lt;/strong&gt; listeler, raporlar, arama sonuçları, paneller. Birkaç saniye gecikme önemsiz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Primary’den okunmalı:&lt;/strong&gt; hesap bakiyesi, stok adedi, yetki kontrolü, bir yazma kararının dayandığı her okuma.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu sınıflandırma artık mimarinin bir parçasıdır ve dokümante edilmesi gerekir. Yeni gelen bir geliştiricinin “bu sorgu nereden okumalı” sorusuna bakacak bir yeri olmalı — yoksa sınıflandırma sessizce çürür.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-gerçekten-gerekir&quot;&gt;Ne zaman gerçekten gerekir&lt;/h2&gt;
&lt;p&gt;Read/write splitting şu üç koşul birlikte sağlandığında doğru hamledir:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;İndeks disiplini tamamlanmış; sorgular doğru indeksli ve hâlâ primary doyuyor.&lt;/li&gt;
&lt;li&gt;Yük ölçülmüş biçimde okuma ağırlıklı.&lt;/li&gt;
&lt;li&gt;Bayat-okuma sınıflandırmasını yapıp dokümante edecek disiplin var.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bunlardan biri eksikse, replica’yı ertelemek daha ucuzdur.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Read/write splitting, dikey büyümeden ucuz bir ölçek hamlesidir — ama bedava değildir. Bedeli replication lag’dir ve onu görmezden gelmek, kullanıcıya kendi verisini eski göstermekle ödenir.&lt;/p&gt;
&lt;p&gt;Okumayı ayırmadan önce, hangi okumanın bayatlığa dayanabileceğini bilin.&lt;/p&gt;</content:encoded><category>postgresql</category><category>database</category><category>scaling</category><category>performance</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Redis Koymadan Önce: Doğru Cache Stratejisi</title><link>https://sade.dev/tr/notes/dogru-cache-stratejisi/</link><guid isPermaLink="true">https://sade.dev/tr/notes/dogru-cache-stratejisi/</guid><description>Cache eklemeden önce sorulması gereken sorular, cache invalidation&apos;ın neden zor olduğu ve TTL ile tutarlılık arasındaki seçim.</description><pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir ekip “site yavaş” diye Redis cache ekledi. Bir hafta sonra destek kuyruğu doldu: “Fiyatı güncelledim ama eski fiyat görünüyor.” Cache yavaşlığı çözmüştü — ve sessizce yeni bir problem yaratmıştı.&lt;/p&gt;
&lt;p&gt;Cache eklemek bedava bir performans kazancı değildir. Tazeliği hıza takas etmektir; bu takası bilerek yapmadıysanız, fatura bir bug olarak geri gelir.&lt;/p&gt;
&lt;h2 id=&quot;cache-bir-tutarlılık-ödünüdür&quot;&gt;Cache bir tutarlılık ödünüdür&lt;/h2&gt;
&lt;p&gt;Bir değeri cache’lediğiniz an şunu kabul etmiş olursunuz: birileri, bir süreliğine, eski veriyi görebilir. Cache hiçbir zaman “daha hızlı veritabanı” değildir; “kabul edilmiş bayatlık karşılığında hız”dır.&lt;/p&gt;
&lt;p&gt;Bu yüzden ilk soru teknik değil: &lt;strong&gt;bu veri ne kadar bayat olabilir?&lt;/strong&gt; Cevabı “hiç” olan bir veri — bir hesap bakiyesi, bir stok adedi — cache’lenmeden önce iki kez düşünülmelidir.&lt;/p&gt;
&lt;h2 id=&quot;önce-sorulacak-sorular&quot;&gt;Önce sorulacak sorular&lt;/h2&gt;
&lt;p&gt;Redis’i açmadan önce:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bu gerçekten bir okuma darboğazı mı?&lt;/strong&gt; “Site yavaş” bir hipotezdir, ölçüm değil. Yavaşlığın kaynağını görmeden cache eklemek, bilinmeyen bir şeyin üstünü örtmektir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yavaşlık aslında eksik bir indeks mi?&lt;/strong&gt; Cache, kötü bir sorgunun üstünü örter ama onu çözmez. Bir &lt;code&gt;CREATE INDEX&lt;/code&gt;’in çözeceği şeyi cache’le gizlemek, problemi taşımaktır — &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;veri yoğunluklu sistemlerdeki&lt;/a&gt; yanlış kırılma noktası tam olarak budur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bu veri ne kadar bayat kalabilir?&lt;/strong&gt; Cevap, hangi stratejiyi seçeceğinizi belirler.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;invalidation-neden-zor&quot;&gt;Invalidation neden zor&lt;/h2&gt;
&lt;p&gt;Yazılımın iki zor probleminden biri olarak anılır cache invalidation — ve abartı değil. Cache’i koymak kolaydır; onu doğru anda &lt;strong&gt;temizlemek&lt;/strong&gt; zordur.&lt;/p&gt;
&lt;p&gt;Çünkü bir cache değeri, onu etkileyen her yazma yolunda temizlenmelidir. Ürün fiyatı yalnızca “ürünü düzenle” ekranından değil; toplu fiyat güncellemesinden, bir indirim işinden, bir admin script’inden de değişir. Bu yolların &lt;strong&gt;hepsini&lt;/strong&gt; bulup cache’i temizlemek — işte zor olan bu. Bir tanesini kaçırırsanız, yazının başındaki destek talebini alırsınız.&lt;/p&gt;
&lt;h2 id=&quot;i̇ki-strateji&quot;&gt;İki strateji&lt;/h2&gt;
&lt;p&gt;Pratikte iki yol var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TTL tabanlı.&lt;/strong&gt; Değere bir ömür biçersiniz — 60 saniye, 5 dakika. Süre dolunca cache kendini tazeler. Basit, sağlam, her yazma yolunu bilmeyi gerektirmez. Karşılığında: TTL süresi kadar bayatlık kabul edersiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Açık invalidation.&lt;/strong&gt; Veri değiştiğinde cache’i elle silersiniz. Kesin ve tazedir — ama yalnızca her yazma yolunu eksiksiz yakalarsanız.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Varsayılanım TTL’dir. Sıkıcı, öngörülebilir ve unutulan bir yazma yolu yüzünden sessizce bozulmaz. Açık invalidation’a yalnızca bayatlık gerçekten kabul edilemezken ve yazma yollarının sayısı sınırlı ve bilinirken geçerim. Çoğu zaman ikisi birlikte kullanılır: kısa bir TTL bir güvenlik ağı olarak, açık invalidation hız için.&lt;/p&gt;
&lt;h2 id=&quot;cache-yazmayı-azaltmaz&quot;&gt;Cache yazmayı azaltmaz&lt;/h2&gt;
&lt;p&gt;Sık yapılan bir hata: yazma yükü altındaki bir sistemi cache ekleyerek kurtarmaya çalışmak. Cache &lt;strong&gt;okuma&lt;/strong&gt; yükünü azaltır; yazma yüküne hiçbir etkisi yoktur — aksine, invalidation’ın kendisi ek yazma işidir. Probleminiz yazma tarafındaysa, cache yanlış araçtır.&lt;/p&gt;
&lt;h2 id=&quot;pratik-kalıplar&quot;&gt;Pratik kalıplar&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cache-aside.&lt;/strong&gt; Oku: önce cache’e bak, yoksa veritabanından al ve cache’e yaz. En yaygın ve en anlaşılır kalıp.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stampede koruması.&lt;/strong&gt; Popüler bir anahtarın TTL’i dolduğu an yüzlerce istek aynı anda veritabanına gider. Cache miss’te kısa bir lock ile yalnızca bir isteğin veritabanına gitmesini sağlayın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Anahtar disiplini.&lt;/strong&gt; Cache anahtarlarını namespace’leyin — &lt;a href=&quot;/tr/notes/shared-redis-namespace-izolasyonu/&quot;&gt;shared Redis namespace izolasyonu&lt;/a&gt; notundaki kalıp, cache anahtarları için de geçerlidir.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;p&gt;Cache, yavaş bir sistemin çözümü değil; ölçülmüş bir okuma darboğazının, bilinçli bir tutarlılık ödünüyle hafifletilmesidir. Invalidation planı olmadan eklenen cache, hızlandırdığından fazlasını borç olarak geri alır.&lt;/p&gt;
&lt;p&gt;Cache koymadan önce sorun: bu veri ne kadar eski olabilir — ve onu kim temizleyecek?&lt;/p&gt;</content:encoded><category>caching</category><category>redis</category><category>performance</category><category>production</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>NoSQL Tuzağı: &quot;Şeması Yok&quot; Diye Başlamak</title><link>https://sade.dev/tr/journal/nosql-tuzagi/</link><guid isPermaLink="true">https://sade.dev/tr/journal/nosql-tuzagi/</guid><description>Şemasız başlamanın verdiği rahatlıkla NoSQL&apos;e geçen projelerin er geç çarptığı tutarlılık ve veri bütünlüğü krizleri.</description><pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;“Şeması yok, hızlı ilerleriz” diyerek MongoDB seçen bir projeyi iki yıl sonra gördüm. Her okuma savunmacıydı: &lt;code&gt;data?.user?.address?.city ?? null&lt;/code&gt;. Sebep basitti — hiçbir belge bir diğeriyle tam olarak aynı şekle sahip değildi. Şema kaybolmamıştı; sadece veritabanından çıkıp her okuma kodu satırına dağılmıştı.&lt;/p&gt;
&lt;h2 id=&quot;şemasız-bir-yanılgı&quot;&gt;”Şemasız” bir yanılgı&lt;/h2&gt;
&lt;p&gt;Şemasız veri diye bir şey yoktur. Verinin her zaman bir şekli vardır: bir kullanıcının e-postası, bir siparişin kalemleri, bir faturanın tarihi. Soru, şekil olup olmadığı değil; o şeklin &lt;strong&gt;nerede tanımlandığı&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;İki seçenek var. Şema ya veritabanında durur ve veritabanı tarafından zorlanır; ya da uygulama kodunda durur ve herkesin ona uyduğu varsayılır. “Şemasız” demek, ikincisini seçmektir — şemayı silmek değil, onu zorlanmayan bir yere taşımak.&lt;/p&gt;
&lt;h2 id=&quot;şema-nereye-taşınır&quot;&gt;Şema nereye taşınır&lt;/h2&gt;
&lt;p&gt;Veritabanı şemayı zorlamayı bıraktığında, o iş ortadan kalkmaz; şu yerlere dağılır:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Her okumaya.&lt;/strong&gt; Kod, gelen belgenin beklenen alanlara sahip olduğunu artık varsayamaz; her alanı savunmacı biçimde kontrol eder.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Her yazmaya.&lt;/strong&gt; Doğru şekli yazdığınızın garantisi yoktur; yalnızca umut vardır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dağınık doğrulama koduna.&lt;/strong&gt; Veritabanının tek satırda yaptığı &lt;code&gt;NOT NULL&lt;/code&gt;, tip kontrolü, foreign key — hepsi elle yazılmış kontrollere dönüşür.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ekibin kafasına.&lt;/strong&gt; “O alan bazen string, bazen array” bilgisi, dokümante edilmemiş bir kabile bilgisi olur.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Şema bürokrasi değildir; veritabanının sizin yerinize ücretsiz yaptığı doğrulamadır. Onu reddetmek faturayı silmek değil, başka birine kesmektir.&lt;/p&gt;
&lt;h2 id=&quot;çarpılan-krizler&quot;&gt;Çarpılan krizler&lt;/h2&gt;
&lt;p&gt;“Şemasız” rahatlığıyla başlayan projeler er ya da geç şunlara çarpar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Veri drift’i.&lt;/strong&gt; Zamanla belgenin on farklı sürümü aynı koleksiyonda yaşar. 2023’te yazılan kayıt, 2026’da yazılanla aynı şekilde değildir ve hiçbir migration bunu zorlamamıştır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Referans bütünlüğü yok.&lt;/strong&gt; Var olmayan bir kullanıcıya işaret eden bir sipariş, veritabanı tarafından engellenmez. Yetim kayıtlar sessizce birikir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migration bir uygulama işine dönüşür.&lt;/strong&gt; İlişkisel dünyada tek bir &lt;code&gt;ALTER TABLE&lt;/code&gt; ile yapılan şey, burada milyonlarca belgeyi tek tek gezen, batch’lenmesi gereken bir script olur.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunlar küçük projede görünmez; tam da projenin büyüdüğü, ekibin değiştiği, kimsenin “o alan neden bazen yok” sorusunu cevaplayamadığı anda patlar.&lt;/p&gt;
&lt;h2 id=&quot;nosqlin-gerçekten-doğru-olduğu-yerler&quot;&gt;NoSQL’in gerçekten doğru olduğu yerler&lt;/h2&gt;
&lt;p&gt;Bu, NoSQL’e karşı bir yazı değil. NoSQL’in gerçek ve haklı kullanımları var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tek bir primary’nin yazma kapasitesini aşan, gerçekten yatay ölçek gerektiren yükler — &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;veri yoğunluklu sistemlerdeki&lt;/a&gt; en zor kırılma noktası.&lt;/li&gt;
&lt;li&gt;Doğası gereği gerçekten heterojen belgeler.&lt;/li&gt;
&lt;li&gt;Belirli erişim desenleri — saf key-value, wide-column, graph.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tuzak NoSQL değil; NoSQL’i bu gerçek güçlerinden biri için değil, sadece “şema istemiyorum” diye seçmektir. Doğru araç, yanlış gerekçe.&lt;/p&gt;
&lt;h2 id=&quot;esneklik-istiyorsanız-postgresql-zaten-veriyor&quot;&gt;Esneklik istiyorsanız PostgreSQL zaten veriyor&lt;/h2&gt;
&lt;p&gt;“Bazı alanlar esnek olsun” ihtiyacı gerçekse, bunun için tüm bütünlüğü feda etmeniz gerekmez. PostgreSQL’in &lt;code&gt;jsonb&lt;/code&gt; sütunu, ilişkisel tablonun bütünlüğü içinde esnek bir belge alanı verir — &lt;a href=&quot;/tr/journal/postgresql-her-seye-yeter-mi/&quot;&gt;PostgreSQL her şeye yeter mi&lt;/a&gt; yazısında değindiğim gibi. Sabit olan alanlar şema olarak kalır, gerçekten değişken kısım &lt;code&gt;jsonb&lt;/code&gt;’de durur. İkisinin en iyisi, hem de tek sistemde.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Şema bir yük değil, bir kalkandır. Onu veritabanından kaldırdığınızda kaybolmaz — yalnızca sizi koruduğu yerden çıkıp, sizi koruyamayacağı yere taşınır.&lt;/p&gt;
&lt;p&gt;“Şemasız” diye bir şey yoktur; yalnızca “şemayı kim zorluyor” sorusu vardır.&lt;/p&gt;</content:encoded><category>nosql</category><category>database</category><category>architecture</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>PostgreSQL Her Şeye Yeter mi?</title><link>https://sade.dev/tr/journal/postgresql-her-seye-yeter-mi/</link><guid isPermaLink="true">https://sade.dev/tr/journal/postgresql-her-seye-yeter-mi/</guid><description>Ayrı arama motoru, kuyruk veya doküman DB&apos;si eklemeden önce: PostgreSQL&apos;in tek başına nereye kadar yettiği ve sınırının nerede başladığı.</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir mimari planında, daha tek kullanıcı yokken şu liste vardı: kalıcı veri için PostgreSQL, arama için Elasticsearch, cache için Redis, esnek belgeler için MongoDB, kuyruk için RabbitMQ. Beş ayrı veri sistemi — beşinin de yedeği, monitoring’i, sürüm yükseltmesi ve kendine özgü hata modu.&lt;/p&gt;
&lt;p&gt;O listenin büyük kısmını, uzun bir süre boyunca, tek başına PostgreSQL taşır. Soru “PostgreSQL bunu yapabilir mi” değil; “bu beş sistemin operasyon yükünü gerçekten satın aldınız mı”.&lt;/p&gt;
&lt;h2 id=&quot;postgresqlin-az-bilinen-genişliği&quot;&gt;PostgreSQL’in az bilinen genişliği&lt;/h2&gt;
&lt;p&gt;PostgreSQL’i sadece bir tablo-satır deposu sanmak, yeteneklerinin küçük bir kısmını kullanmaktır. Sık sık ayrı bir sistem getirten ihtiyaçların çoğu zaten içinde:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tam metin arama.&lt;/strong&gt; &lt;code&gt;tsvector&lt;/code&gt;, &lt;code&gt;tsquery&lt;/code&gt; ve GIN index ile gövdeli bir arama. Çoğu uygulamanın “ürün ara”, “yazı ara” ihtiyacını dilsel sapma, ağırlıklandırma ve sıralama dahil karşılar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Doküman saklama.&lt;/strong&gt; &lt;code&gt;jsonb&lt;/code&gt; sütunu, GIN index’iyle birlikte sorgulanabilir bir belge deposudur. Şema esnekliği isteyen alanları, ilişkisel tablonun içinde tutabilirsiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kuyruk.&lt;/strong&gt; &lt;code&gt;SELECT ... FOR UPDATE SKIP LOCKED&lt;/code&gt; ve &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;, orta hacimli bir iş kuyruğunu ayrı bir broker olmadan çalıştırır. Laravel’in &lt;code&gt;database&lt;/code&gt; queue driver’ı bunun &lt;code&gt;SKIP LOCKED&lt;/code&gt; tarafını kullanır — worker’ları &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt; sinyali beklemek yerine kuyruk tablosunu belirli aralıklarla yoklar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analitik sorgular.&lt;/strong&gt; Window function’lar, CTE’ler, materialized view’lar — ayrı bir analitik veritabanına gitmeden ciddi raporlama.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coğrafi veri.&lt;/strong&gt; PostGIS eklentisiyle konum sorguları.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu özellikler “var ama kullanılmıyor” değil; production’da güvenilir biçimde çalışan, olgun yeteneklerdir.&lt;/p&gt;
&lt;h2 id=&quot;tek-sistemin-sessiz-getirisi&quot;&gt;Tek sistemin sessiz getirisi&lt;/h2&gt;
&lt;p&gt;Her yeni veri sistemi yalnızca bir kutu değil; bir bakım yükümlülüğüdür. Tek sistemde kalmanın getirisi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tek yedek hattı.&lt;/strong&gt; Bir tane backup/restore prosedürü, bir tane kurtarma tatbikatı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek izleme hedefi.&lt;/strong&gt; Öğrenilecek bir metrik seti, kurulacak bir alarm düzeni.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Çapraz tutarlılık.&lt;/strong&gt; En önemlisi bu. Arama indeksiniz ile veriniz aynı veritabanındaysa aralarında &lt;strong&gt;drift olamaz&lt;/strong&gt; — ikisi aynı transaction’da güncellenir. Ayrı bir Elasticsearch ise eninde sonunda veriyle senkronizasyonu bozulur ve onu düzeltmek ayrı bir iş kalemine dönüşür.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek zihinsel model.&lt;/strong&gt; Ekip tek bir sistemin tuhaflıklarını öğrenir, beş sistemin değil.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu, &lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;boring architecture&lt;/a&gt; yazısındaki innovation token mantığının veri katmanındaki hâlidir: her yeni sistem bir token ve token sınırlı.&lt;/p&gt;
&lt;h2 id=&quot;sınır-nerede-başlar&quot;&gt;Sınır nerede başlar&lt;/h2&gt;
&lt;p&gt;PostgreSQL her şeye yetmez — dürüst sınırları var ve onları görmezden gelmek de bir hata olur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arama.&lt;/strong&gt; Yazım hatası toleransı, gelişmiş alaka düzeyi ayarı, faceted search ve çok dilli analiz ciddi ölçekte gerektiğinde, adanmış bir arama motoru gerçekten kazandırır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kuyruk.&lt;/strong&gt; Çok yüksek throughput, karmaşık yönlendirme ya da çok tüketicili fan-out gerektiğinde gerçek bir message broker doğru araçtır. PostgreSQL kuyruğu orta hacimde rahat, uç hacimde değil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yazma throughput’u.&lt;/strong&gt; Tek primary’nin fsync kapasitesini aşan sürekli yazma — bu, &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;veri yoğunluklu sistemlerdeki&lt;/a&gt; en zor kırılma noktası ve gerçek bir sınır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu sınırlardan birine çarptığınızda ek sistem getirmek doğru karardır. Çarpmadan getirmek ise yalnızca bir tahmin.&lt;/p&gt;
&lt;h2 id=&quot;karar-önce-postgresql-sonra-ölçüm&quot;&gt;Karar: önce PostgreSQL, sonra ölçüm&lt;/h2&gt;
&lt;p&gt;Kural sade: yeni bir projede, PostgreSQL’in yapabildiği her şeyi PostgreSQL’e yaptırın. Arama, kuyruk, cache benzeri ihtiyaçların ilk durağı o olsun. Ayrı bir sistem, ancak &lt;strong&gt;ölçülmüş&lt;/strong&gt; bir sınır aşıldığında gelsin — bir histen, bir blog yazısından ya da CV kaygısından değil.&lt;/p&gt;
&lt;p&gt;Çoğu ekip PostgreSQL’in belki %20’sini kullanır, sonra “yetmiyor” der. Yetmeyen çoğu zaman PostgreSQL değil, onu tanıma çabasıdır.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;PostgreSQL her şeye yetmez — ama sandığınızdan çok daha fazlasına yeter. İkinci bir veri sistemi getirmeden önce, birincisinin gerçekten sonuna gelip gelmediğinizi sorun.&lt;/p&gt;
&lt;p&gt;Önce PostgreSQL; gerisi ölçümle gelsin.&lt;/p&gt;</content:encoded><category>postgresql</category><category>database</category><category>architecture</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Bir Sistemin &quot;Gereğinden Karmaşık&quot; Olduğunu Gösteren Sinyaller</title><link>https://sade.dev/tr/journal/gereginden-karmasik-sistem-sinyalleri/</link><guid isPermaLink="true">https://sade.dev/tr/journal/gereginden-karmasik-sistem-sinyalleri/</guid><description>Kodun veya mimarinin gereksiz karmaşıklaştığını ele veren somut kırmızı çizgiler — ve sadeleştirmeye nereden başlanacağı.</description><pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ekibe yeni katılan bir mühendis ilk haftasının sonunda sordu: “Tek bir alan eklemek neden iki gün sürüyor?” Soru masumdu, teşhis netti. Kimse o sistemi bir gün oturup “karmaşıklaştıralım” diye kararlaştırmamıştı. Karmaşıklık öylece birikmişti.&lt;/p&gt;
&lt;p&gt;Sorun da burada: karmaşıklık tek bir kararla gelmez, fark edilmeden birikir. Onu geri almak için önce onu &lt;strong&gt;görebilmek&lt;/strong&gt; gerekir.&lt;/p&gt;
&lt;h2 id=&quot;karmaşıklık-nasıl-birikir&quot;&gt;Karmaşıklık nasıl birikir&lt;/h2&gt;
&lt;p&gt;Hiçbir karmaşık sistem, karmaşık olmaya karar vermez. Her adım tek başına makul görünür: “şimdilik bir interface açalım”, “bu duruma bir flag koyalım”, “araya bir katman daha”. Her biri küçük, her biri savunulabilir.&lt;/p&gt;
&lt;p&gt;Toplamları savunulabilir değil. Karmaşıklık bir olay değil, bir birikim — ve birikim, adı konmadığı sürece görünmez. Bu yüzden sadeleştirme bir his ile başlayamaz; somut sinyallerle başlar.&lt;/p&gt;
&lt;h2 id=&quot;somut-sinyaller&quot;&gt;Somut sinyaller&lt;/h2&gt;
&lt;p&gt;Bir sistemin gereğinden karmaşık olduğunu söyleyen, tartışmaya kapalı kırmızı çizgiler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tek bir özelliği anlamak için çok sayıda dosya açıyorsunuz.&lt;/strong&gt; Bir isteğin nereye gittiğini görmek altı dosya ve dört dolaylılık gerektiriyorsa, problem sizde değil, yapıdadır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek satırlık bir değişiklik bir düzine dosyaya dokunuyor.&lt;/strong&gt; Bir alan eklemek için migration, model, DTO, mapper, interface, factory, test fixture… Değişimin maliyeti, değişimin büyüklüğüyle orantılı değil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek implementasyonlu interface’ler.&lt;/strong&gt; “İhtimale karşı” açılmış, asla ikinci implementasyonu gelmemiş soyutlamalar. Bu, &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;spekülatif genelliğin&lt;/a&gt; kod tabanında bıraktığı tortudur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Onboarding haftalar sürüyor.&lt;/strong&gt; Olgun ve sade bir sistemde yeni mühendis ilk günlerde üretkendir. Haftalar sürüyorsa, kod kendini anlatmıyordur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kimse sistemi tahtaya çizemiyor.&lt;/strong&gt; “Şu kutu şuraya bağlı” diye anlatılamayan bir mimari, anlaşılmıyor demektir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test kurulumu testin kendisinden uzun.&lt;/strong&gt; Bir davranışı test etmek için kurulması gereken on mock, o davranışın çok fazla şeye bağlı olduğunun itirafıdır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“Oraya dokunma, kırılır.”&lt;/strong&gt; Bir kod bölgesi dokunulmaz ilan edilmişse, o bölge zaten kırıktır — sadece henüz patlamamıştır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kimsenin kullanmadığı config, flag ve seçenekler.&lt;/strong&gt; Açılıp unutulmuş her seçenek, kodu her okuyanın hesaba katması gereken bir daldır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bir sistemde bu sinyallerden ikisi-üçü varsa, tartışma “karmaşık mı” değil, “nereden başlayacağız”dır.&lt;/p&gt;
&lt;h2 id=&quot;sadeleştirmeye-nereden-başlanır&quot;&gt;Sadeleştirmeye nereden başlanır&lt;/h2&gt;
&lt;p&gt;Sadeleştirme bir yeniden yazım değildir; çoğu zaman bir &lt;strong&gt;çıkarma&lt;/strong&gt; işlemidir. Sırayla:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ölü kodu sil.&lt;/strong&gt; Kullanılmayan flag, çağrılmayan method, ulaşılmayan branch. En düşük riskli, en yüksek getirili adım — silinen kodun bug’ı olmaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek implementasyonlu interface’leri çökert.&lt;/strong&gt; Soyutlama gerçek bir ikinci kullanım kazanana kadar somut sınıfı doğrudan kullanın. Dikiş gerçekten gerektiğinde — &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;ihtiyaç kanıtlandığında&lt;/a&gt; — birkaç saatlik bir refactor’la geri gelir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Erken dolaylılığı satır içine al.&lt;/strong&gt; Tek yerden çağrılan, sadece argümanı bir başka fonksiyona geçiren katmanları kaldırın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sınırları netleştir.&lt;/strong&gt; Geriye kalan karmaşıklığı &lt;a href=&quot;/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/&quot;&gt;modüler monolitteki&lt;/a&gt; gibi net sınırların arkasına toplayın — böylece bir dahaki birikim erken görünür.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Her adımdan sonra durun ve ölçün. Sadeleştirme de aşırıya kaçabilir; amaç “en az kod” değil, “en az gereksiz kod”.&lt;/p&gt;
&lt;h2 id=&quot;karmaşıklık-her-zaman-kötü-değil&quot;&gt;Karmaşıklık her zaman kötü değil&lt;/h2&gt;
&lt;p&gt;Önemli bir ayrım: karmaşıklığın iki türü var. Fred Brooks’un kavramlarıyla — &lt;strong&gt;özsel (essential)&lt;/strong&gt; ve &lt;strong&gt;tesadüfi (accidental)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Özsel karmaşıklık domain’in kendisinden gelir. Vergi hesabı karmaşıktır, çünkü vergi mevzuatı karmaşıktır; bunu silemezsiniz, sadece dürüstçe modelleyebilirsiniz. Tesadüfi karmaşıklık ise sizin eklediğinizdir: gereksiz katman, erken soyutlama, unutulmuş flag.&lt;/p&gt;
&lt;p&gt;Sadeleştirmenin hedefi tesadüfi olanı temizlemektir — özsel olanı değil. Bir sistemi “çok basit” yapıp domain’in gerçek karmaşıklığını görmezden gelmek de bir hatadır; o karmaşıklık silinmez, sadece yanlış yere — genelde çağıran kodun içine — taşınır.&lt;/p&gt;
&lt;p&gt;İyi mimari, domain ne kadar karmaşıksa o kadar karmaşıktır; daha fazlası değil.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Karmaşıklık fark edilmeden birikir; bu yüzden onu fark edilir kılan sinyalleri ezbere bilmek bir mühendislik becerisidir. Sinyali adlandıramıyorsanız, sadeleştirmeye de başlayamazsınız.&lt;/p&gt;
&lt;p&gt;Bir sistemi basit tutmak, onu basit kurmaktan daha çok emek ister — ve o emeğe değer.&lt;/p&gt;</content:encoded><category>architecture</category><category>simplicity</category><category>decisions</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Dış Servis Entegrasyonlarında Circuit Breaker</title><link>https://sade.dev/tr/notes/circuit-breaker-dis-servis-entegrasyonu/</link><guid isPermaLink="true">https://sade.dev/tr/notes/circuit-breaker-dis-servis-entegrasyonu/</guid><description>Kritik bir sistemi dış API&apos;lere bağlarken: timeout, retry ve circuit breaker ile bir servisin çöküşünün tüm sistemi düşürmesini engellemek.</description><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir SMS sağlayıcısı yavaşladı — çökmedi bile, sadece yanıt süresi 30 saniyeye çıktı. Yarım saat içinde bütün uygulama yanıt veremez hâle geldi; SMS ile hiç ilgisi olmayan endpoint’ler bile zaman aşımına uğruyordu. Tek bir yavaş dış servis, koca sistemi birlikte aşağı çekmişti.&lt;/p&gt;
&lt;p&gt;Bir dış servis er ya da geç çöker. Soru o değil. Soru şu: sizin sisteminiz onunla birlikte mi çökecek?&lt;/p&gt;
&lt;h2 id=&quot;zincirleme-çöküş-nasıl-olur&quot;&gt;Zincirleme çöküş nasıl olur&lt;/h2&gt;
&lt;p&gt;Mantık basit ve acımasız. Yavaş bir dış çağrı, onu yapan worker’ı — ya da PHP-FPM process’ini — kendine kilitler. Çağrı 30 saniye sürerse, o process 30 saniye boyunca başka hiçbir isteğe bakamaz.&lt;/p&gt;
&lt;p&gt;İstekler birikir, process havuzu dolar. Havuz dolunca — SMS ile hiç ilgisi olmayanlar dâhil — yeni istekler de bekler. Sağlıklı endpoint’leriniz, hasta bir bağımlılık yüzünden ölür. Buna &lt;strong&gt;cascading failure&lt;/strong&gt; denir ve neredeyse her zaman bir yavaşlamayla başlar.&lt;/p&gt;
&lt;h2 id=&quot;i̇lk-savunma-timeout&quot;&gt;İlk savunma: timeout&lt;/h2&gt;
&lt;p&gt;İlk ve en ucuz savunma, her dış çağrıya &lt;strong&gt;açık ve kısa&lt;/strong&gt; bir timeout vermektir. Varsayılan timeout’lar — çoğu HTTP istemcisinde 30 saniye veya daha fazla — production için kabul edilemez.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; Http&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;        // toplam süre&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    -&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;connectTimeout&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;             // bağlantı kurma süresi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    -&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;https://sms-provider.example/send&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;/tr/notes/laravel-queue-production-yavaslama/&quot;&gt;Laravel queue yazısındaki&lt;/a&gt; kural burada da geçerli: varsayılan 30 saniye değil, 5. Bir process’in bir bağımlılığı ne kadar süre bekleyeceğini şansa bırakmayın — bu sayı bir karar olmalı.&lt;/p&gt;
&lt;h2 id=&quot;i̇kinci-savunma-retry--ama-dikkatli&quot;&gt;İkinci savunma: retry — ama dikkatli&lt;/h2&gt;
&lt;p&gt;Geçici hatalar için retry mantıklıdır, ama körlemesine retry zarar verir. Çöken bir servisi hemen yeniden denemek ona daha çok yük bindirir — buna &lt;strong&gt;retry storm&lt;/strong&gt; denir; servisi tam ayağa kalkamadan tekrar devirir.&lt;/p&gt;
&lt;p&gt;Retry yapıyorsanız iki şey şart: exponential backoff (her denemede artan bekleme) ve jitter (rastgele sapma — tüm client’lar aynı anda denemesin).&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;Http&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;retry&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;attempt&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;    // exponential backoff (200, 400, 800 ms) ve üstüne jitter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; **&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;attempt&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; random_int&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;},&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;exception&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;exception&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; instanceof&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; ConnectionException&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;})&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Retry sayısını düşük tutun. Üç denemeden sonra hâlâ hata varsa, sorun geçici değildir; denemeye devam etmek yalnızca kendi process’inizi daha uzun bağlar.&lt;/p&gt;
&lt;h2 id=&quot;üçüncü-savunma-circuit-breaker&quot;&gt;Üçüncü savunma: circuit breaker&lt;/h2&gt;
&lt;p&gt;Timeout tek bir çağrıyı sınırlar; circuit breaker &lt;strong&gt;çağrı yapmayı bırakmaya&lt;/strong&gt; karar verir. Mantığı bir elektrik sigortasından gelir ve üç durumu vardır:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Closed&lt;/strong&gt; — her şey normal, çağrılar geçer. Hatalar sayılır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open&lt;/strong&gt; — son N çağrının çoğu başarısız oldu; devre açılır. Artık dış servise hiç gidilmez, çağrı &lt;strong&gt;anında&lt;/strong&gt; hata döner.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Half-open&lt;/strong&gt; — bir süre sonra devre tek bir deneme çağrısına izin verir. Başarılıysa &lt;code&gt;closed&lt;/code&gt;’a döner, değilse tekrar &lt;code&gt;open&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kazanç şu: dış servis hastayken siz onu beklemezsiniz. 5 saniyelik timeout bile process başına 5 saniyedir; &lt;code&gt;open&lt;/code&gt; durumda bu süre 0’a iner. Fail-fast, yavaş-fail’den iyidir.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;// Kavramsal akış — eşik, sayaç ve zamanlayıcıyı olgun bir pakete bırakın.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;breaker&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;isOpen&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;sms&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;queueForLater&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;   // devre açık: hiç deneme yok&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; Http&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;smsUrl&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;breaker&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;recordSuccess&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;sms&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; catch&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; (\&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;Throwable&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;breaker&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;recordFailure&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;sms&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;          // eşik aşılırsa devre açılır&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;queueForLater&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu mantığı sıfırdan yazmak yerine olgun bir paket kullanın — durum saymayı, eşiği ve zamanlayıcıyı doğru kurmak göründüğünden inceliklidir.&lt;/p&gt;
&lt;h2 id=&quot;fallback-devre-açıkken-ne-yapılır&quot;&gt;Fallback: devre açıkken ne yapılır&lt;/h2&gt;
&lt;p&gt;Circuit breaker’ın değeri, &lt;code&gt;open&lt;/code&gt; durumda ne yaptığınızla ortaya çıkar. “Hata döndür” en zayıf seçenektir. Daha iyileri:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kuyruğa al, sonra gönder.&lt;/strong&gt; SMS hemen gitmek zorunda değilse, devre kapanınca işlenmek üzere &lt;a href=&quot;/tr/notes/senkron-mu-asenkron-mu/&quot;&gt;kuyruğa&lt;/a&gt; bırakın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bayat ama geçerli veriyi sun.&lt;/strong&gt; Bir döviz kuru servisi çökmüşse, son bilinen kuru göstermek hiç göstermemekten iyidir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Özelliği zarifçe kıs.&lt;/strong&gt; “Öneriler şu an yüklenemiyor” mesajı, koca sayfayı çökertmekten iyidir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hangi fallback’in doğru olduğu işe bağlıdır — ama “fallback yok” bir cevap değildir.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-bu-kadarına-gerek-yok&quot;&gt;Ne zaman bu kadarına gerek yok?&lt;/h2&gt;
&lt;p&gt;Her dış çağrı circuit breaker istemez. Timeout &lt;strong&gt;her zaman&lt;/strong&gt; şart. Retry, yalnızca işlem idempotent ise. Circuit breaker ise asıl olarak çağrı sık ve servis kullanıcı yolundayken değer katar — günde birkaç kez çağrılan bir entegrasyon için timeout ve makul bir retry çoğu zaman yeter.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Dış servis çökmesi bir “ihtimal” değil, bir “ne zaman” sorusudur. Onunla birlikte çökmek ise bir tasarım tercihidir — ve değiştirilebilir bir tercih.&lt;/p&gt;
&lt;p&gt;Sisteminizi, en zayıf bağımlılığınız kadar kırılgan bırakmayın.&lt;/p&gt;</content:encoded><category>resilience</category><category>architecture</category><category>api</category><category>production</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Serverless&apos;a Geçiş Kararı: Cold-Start ve Vendor Lock-in</title><link>https://sade.dev/tr/notes/serverless-gecis-karari/</link><guid isPermaLink="true">https://sade.dev/tr/notes/serverless-gecis-karari/</guid><description>FaaS&apos;a geçmeden önce: cold-start gecikmesinin gerçek maliyeti, vendor lock-in riski ve serverless&apos;ın hangi iş yüklerinde kazandırdığı.</description><pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir ekip yeni servisini “serverless first” kararıyla AWS Lambda üzerine kurdu. Oysa trafik sabit ve öngörülebilirdi — gün boyu aşağı yukarı aynı. Ay sonunda fatura, aynı işi yapan tek bir VPS’in birkaç katı çıktı; üstelik p95 latency de daha kötüydü.&lt;/p&gt;
&lt;p&gt;Serverless yanlış bir teknoloji değil. Yanlış iş yüküne uygulanmış bir teknolojiydi.&lt;/p&gt;
&lt;h2 id=&quot;serverless-ne-satın-alır&quot;&gt;Serverless ne satın alır&lt;/h2&gt;
&lt;p&gt;FaaS’ın (Function as a Service) gerçek getirileri var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sunucu yönetimi yok.&lt;/strong&gt; Yama, ölçekleme, kapasite planı sağlayıcıda.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sıfıra inen ölçek.&lt;/strong&gt; İstek yoksa çalışan hiçbir şey yok, ödenen hiçbir şey yok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kullanım başına ödeme.&lt;/strong&gt; Atıl kapasite için para ödemezsiniz.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu üçü, doğru iş yükünde gerçekten kazandırır. Mesele, o iş yükünü doğru teşhis etmek.&lt;/p&gt;
&lt;h2 id=&quot;cold-startın-gerçek-maliyeti&quot;&gt;Cold-start’ın gerçek maliyeti&lt;/h2&gt;
&lt;p&gt;Bir fonksiyon bir süredir çağrılmadıysa, sağlayıcı onu sıfırdan ayağa kaldırır: runtime yüklenir, bağımlılıklar init olur, kod çalışır. Bu &lt;strong&gt;cold-start&lt;/strong&gt;, çalışan bir process’e göre 100 ms’nin altından saniyelere kadar ek gecikmedir.&lt;/p&gt;
&lt;p&gt;Önemli ayrım: cold-start, düşük trafikli bir endpoint için &lt;strong&gt;istisna değil kural&lt;/strong&gt;dır. Saatte birkaç çağrı alan bir fonksiyonda neredeyse her istek cold’dur. “Ölçeğe göre ödeme” cazip gelir, ama sıfıra yakın trafikte ödediğiniz şey, kullanıcının her seferinde gördüğü gecikmedir. Latency’ye duyarlı bir kullanıcı yolunda bu doğrudan bir UX maliyetidir.&lt;/p&gt;
&lt;h2 id=&quot;vendor-lock-in&quot;&gt;Vendor lock-in&lt;/h2&gt;
&lt;p&gt;Lock-in yalnızca runtime değildir; asıl bağ etrafındaki yapıştırıcıdadır. Bir serverless uygulaması zamanla şunlara da bağlanır: sağlayıcının event kaynakları, kimlik/yetki modeli (IAM), yönetilen kuyruğu, API gateway’i, dağıtım araçları.&lt;/p&gt;
&lt;p&gt;Tek bir fonksiyonu taşımak kolaydır. Ama gerçek bir sistemde “serverless’tan çıkmak” çoğu zaman bir taşıma değil, bir yeniden yazımdır. Bu, &lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;boring architecture&lt;/a&gt; yazısındaki innovation token mantığıyla doğrudan ilgili: tek bir karar gibi görünen şey, aslında geri alınması pahalı bir bağdır.&lt;/p&gt;
&lt;h2 id=&quot;hangi-iş-yükünde-kazandırır&quot;&gt;Hangi iş yükünde kazandırır&lt;/h2&gt;
&lt;p&gt;Serverless, yükün &lt;strong&gt;değişken ve seyrek&lt;/strong&gt; olduğu yerde parlar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zamanlanmış işler — gecelik raporlar, cron benzeri görevler.&lt;/li&gt;
&lt;li&gt;Webhook alıcıları — ne zaman, ne sıklıkta geleceği belirsiz.&lt;/li&gt;
&lt;li&gt;Düşük hacimli iç araçlar — günde birkaç kez kullanılan paneller.&lt;/li&gt;
&lt;li&gt;Ani patlamalı (bursty) batch işleri — saatte bir, kısa süreliğine yüksek paralellik.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu yüklerde sıfıra inen ölçek gerçek para tasarrufudur ve cold-start çoğu zaman önemsizdir — kimse bir gece raporunun 800 ms geç başlamasını dert etmez.&lt;/p&gt;
&lt;h2 id=&quot;hangi-iş-yükünde-kaybettirir&quot;&gt;Hangi iş yükünde kaybettirir&lt;/h2&gt;
&lt;p&gt;Bunun tersi de net:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sabit, sürekli trafik.&lt;/strong&gt; Her zaman çalışan bir API’de “kullanım başına ödeme”, “her saniye ödeme”ye dönüşür — ve genelde sürekli çalışan bir sunucudan pahalıdır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency’ye duyarlı yollar.&lt;/strong&gt; Cold-start, kullanıcının beklediği bir istekte kabul edilemez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uzun süren işler.&lt;/strong&gt; FaaS’ın çalışma süresi sınırları vardır; uzun işler ya bölünür ya da uymaz.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu profildeki bir iş yükü için sıkıcı bir VPS — &lt;a href=&quot;/tr/systems/tek-vps-coklu-proje-mimarisi/&quot;&gt;tek VPS üzerinde çoklu proje&lt;/a&gt; yazısındaki kurulum gibi — hem daha ucuz hem daha öngörülebilirdir. Sabit yükün maliyeti, sabit bir sunucuyla eşleşir.&lt;/p&gt;
&lt;h2 id=&quot;laravel-ve-serverless&quot;&gt;Laravel ve serverless&lt;/h2&gt;
&lt;p&gt;Laravel tarafında Laravel Vapor, uygulamayı Lambda üzerinde çalıştırmayı mümkün kılar ve iyi de çalışır. Ama çalışması, doğru karar olduğu anlamına gelmez: Vapor’la birlikte cold-start’ı ve AWS bağını da miras alırsınız.&lt;/p&gt;
&lt;p&gt;Karar yine iş yüküdür. Trafiği patlamalı, seyrek ya da öngörülemez bir Laravel uygulaması için Vapor mantıklı. Sürekli ve latency’ye duyarlı bir uygulama için, klasik bir sunucu hâlâ daha sade ve ucuz cevaptır.&lt;/p&gt;
&lt;h2 id=&quot;karışık-kullanın&quot;&gt;Karışık kullanın&lt;/h2&gt;
&lt;p&gt;Bu, ikili bir seçim değil. Sürekli yükü taşıyan sıkıcı bir sunucu + yalnızca seyrek/patlamalı işleri (webhook, gece raporu) serverless’a vermek çoğu zaman en dürüst dağılımdır. Her iş yükünü, kendi maliyet profiline ait olduğu yere koyun.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Serverless bir mimari modası değil, belirli bir iş yükü profili için bir araçtır. O profil sizde varsa kazandırır; yoksa cold-start ve lock-in olarak geri ödenir.&lt;/p&gt;
&lt;p&gt;Önce yükünüzü ölçün; araç sonra gelir.&lt;/p&gt;</content:encoded><category>serverless</category><category>architecture</category><category>cloud</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>BFF Pattern: Mobil ve Web İçin Ayrı API Katmanı</title><link>https://sade.dev/tr/notes/bff-backend-for-frontend/</link><guid isPermaLink="true">https://sade.dev/tr/notes/bff-backend-for-frontend/</guid><description>Mobil ve web için ayrı API katmanları: tek API&apos;yi her istemciye uydurmanın gizli maliyeti ve BFF&apos;in ne zaman gerçekten gerektiği.</description><pubDate>Sat, 04 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir mobil uygulamanın tek bir ana ekranı çizmek için API’ye dokuz ayrı istek attığını gördüm. API aslında web için tasarlanmıştı; mobil, web’in böldüğü veriyi parça parça toplayıp ekranda birleştiriyordu. Her açılış dokuz round-trip — üstelik mobil ağ koşullarında.&lt;/p&gt;
&lt;p&gt;Bu, “tek API herkese yeter” varsayımının faturasıydı. BFF — Backend for Frontend — tam da bu faturayı kesmek için var.&lt;/p&gt;
&lt;h2 id=&quot;bff-nedir&quot;&gt;BFF nedir&lt;/h2&gt;
&lt;p&gt;BFF, her istemci türü için ince ve ona özel bir API katmanıdır. Web’in kendi BFF’i, mobilin kendi BFF’i olur. Her biri arkadaki asıl servisleri/domain’i çağırır, ama yanıtı kendi istemcisinin ekranına göre şekillendirir.&lt;/p&gt;
&lt;p&gt;Asıl iş mantığı BFF’te değildir. BFF sadece bir &lt;strong&gt;uyarlama katmanı&lt;/strong&gt;: agregasyon yapar, gereksiz alanı kırpar, istemcinin tek ekranda ihtiyaç duyduğu şeyi tek yanıtta toplar.&lt;/p&gt;
&lt;h2 id=&quot;tek-apiyi-herkese-uydurmanın-gizli-maliyeti&quot;&gt;Tek API’yi herkese uydurmanın gizli maliyeti&lt;/h2&gt;
&lt;p&gt;Tek bir genel API, zamanla iki istemciyi de tam memnun edemez:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Over-fetching.&lt;/strong&gt; Mobil, 40 alanlı bir kullanıcı nesnesi indirir, 4’ünü kullanır. Geri kalanı boşa giden bant genişliği ve pil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chatty akış.&lt;/strong&gt; Web’in normal bulduğu “önce listeyi çek, sonra her satır için detay çek” akışı, mobil ağda dokuz round-trip’e dönüşür.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Koşullu alanlar.&lt;/strong&gt; Yanıt &lt;code&gt;if mobile then ... else ...&lt;/code&gt; ile dallanmaya başlar. Tek endpoint, iki istemcinin gereksinimini aynı anda taşımaya çalışır ve ikisinde de orta karar bir iş çıkarır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versiyonlama düğümü.&lt;/strong&gt; Web için yapılması gereken bir değişiklik, mobilin yayınlanmış sürümünü kırma riski taşır. İki istemci tek kontrata kilitlenir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu maliyetler tek tek küçük görünür; toplamı bir mimari problemdir.&lt;/p&gt;
&lt;h2 id=&quot;bff-ne-zaman-gerekli&quot;&gt;BFF ne zaman gerekli&lt;/h2&gt;
&lt;p&gt;BFF, istemcilerin ihtiyacı &lt;strong&gt;gerçekten&lt;/strong&gt; ayrıştığında gerekçelidir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mobil ile web belirgin biçimde farklı ekranlara, farklı veri şekillerine ihtiyaç duyuyor.&lt;/li&gt;
&lt;li&gt;Mobil ağ ve pil kısıtları, round-trip sayısını ve payload boyutunu birinci sınıf bir kaygı yapıyor.&lt;/li&gt;
&lt;li&gt;İstemciler farklı tempoda yayınlanıyor — mobil sürümü mağaza onayına takılıyken web her gün deploy ediliyor; tek kontrat ikisini birbirine kilitliyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu farklar varsa, her istemciye kendi BFF’ini vermek, tek API’yi sürekli ikisine birden uydurmaya çalışmaktan ucuzdur.&lt;/p&gt;
&lt;h2 id=&quot;bff-ne-zaman-gereksiz&quot;&gt;BFF ne zaman gereksiz&lt;/h2&gt;
&lt;p&gt;Tek bir istemciniz varsa BFF, çözdüğünden fazla katman ekler — sadece fazladan bir sıçrama.&lt;/p&gt;
&lt;p&gt;İki istemciniz var ama ikisi de neredeyse aynı veriyi aynı şekilde istiyorsa, yine gereksiz. BFF’i haklı çıkaran şey istemci sayısı değil, istemciler &lt;strong&gt;arasındaki farktır&lt;/strong&gt;. Fark yoksa ayrı katman da olmamalı — bu, &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;spekülatif genelliğin&lt;/a&gt; API katmanındaki hâli olur.&lt;/p&gt;
&lt;h2 id=&quot;bff-mikroservis-değildir&quot;&gt;BFF mikroservis değildir&lt;/h2&gt;
&lt;p&gt;Sık karışan nokta: BFF eklemek sizi dağıtık mimariye sokmaz. BFF bir sunum/agregasyon katmanıdır, bir domain servisi değil. İçine iş kuralı koymak, kuralı istemci sayısı kadar kopyalamak demektir.&lt;/p&gt;
&lt;p&gt;BFF’in mikroservisle tek ilişkisi şu: ikisi de “ayrı bir deployable’a değer mi?” sorusunu sorar. Cevap çoğu zaman “hayır” — &lt;a href=&quot;/tr/journal/mikroservise-ne-zaman-gecerim/&quot;&gt;mikroservise geçiş kararındaki&lt;/a&gt; ölçülmüş-sinyal eşiği burada da geçerli.&lt;/p&gt;
&lt;h2 id=&quot;hafif-başlayın&quot;&gt;Hafif başlayın&lt;/h2&gt;
&lt;p&gt;“BFF” demek, ayrı bir sunucu kurmak demek zorunda değil. Aynı monolit içinde:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;İstemci başına bir route namespace — &lt;code&gt;routes/web-api.php&lt;/code&gt;, &lt;code&gt;routes/mobile-api.php&lt;/code&gt; — her biri kendi controller’larıyla, kendi yanıt şekliyle.&lt;/li&gt;
&lt;li&gt;Ya da tek bir GraphQL katmanı: her istemci ihtiyacı kadarını sorgular, over-fetching kendiliğinden çözülür.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Asıl fikir bir kutu değil, bir sınırdır: istemciye özel uyarlamayı tek bir yerde toplayın, domain’i ondan temiz tutun. Ayrı bir deployable’a ancak &lt;a href=&quot;/tr/journal/mikroservise-ne-zaman-gecerim/&quot;&gt;ölçülmüş bir sinyal&lt;/a&gt; zorladığında geçin.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;BFF, “her istemci aynı API’yle idare etsin” inadının panzehiridir — ama yalnızca istemciler gerçekten ayrıştığında. Fark yoksa katman da olmamalı.&lt;/p&gt;
&lt;p&gt;Katmanı ekleyen şey istemci sayısı değil, aralarındaki mesafedir.&lt;/p&gt;</content:encoded><category>architecture</category><category>api</category><category>bff</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Senkron mu, Asenkron mu? HTTP ile Kuyruk Arasındaki Sınır</title><link>https://sade.dev/tr/notes/senkron-mu-asenkron-mu/</link><guid isPermaLink="true">https://sade.dev/tr/notes/senkron-mu-asenkron-mu/</guid><description>Bir işi HTTP isteğiyle anında mı yapmalı, kuyruğa mı atmalı? Yanıt süresi ve hata toleransı üzerinden karar çizgisi.</description><pubDate>Sat, 27 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir kayıt formu sekiz saniye sürüyordu. İstek içinde sırayla şunlar oluyordu: kullanıcı kaydediliyor, hoş geldin e-postası gönderiliyor, bir PDF üretiliyor, bir CRM’e kayıt işleniyor. Kullanıcı bu sekiz saniye boyunca dönen bir spinner’a bakıyordu — oysa onu ilgilendiren tek şey, kaydının oluşup oluşmadığıydı.&lt;/p&gt;
&lt;p&gt;Bir işi HTTP isteği içinde anında mı yapmalı, kuyruğa mı atmalı? Çizgi göründüğünden net.&lt;/p&gt;
&lt;h2 id=&quot;karar-çizgisi-kullanıcı-sonucu-bekliyor-mu&quot;&gt;Karar çizgisi: kullanıcı sonucu bekliyor mu?&lt;/h2&gt;
&lt;p&gt;Tek bir soru kararın çoğunu verir: &lt;strong&gt;kullanıcı bu işin sonucunu görmek zorunda mı?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cevap “evet” ise iş senkron kalır — istek içinde, yanıt dönmeden biter.&lt;/li&gt;
&lt;li&gt;Cevap “hayır” ise iş kuyruğa gider — istek “kabul edildi” der ve döner.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yukarıdaki formda kullanıcı sadece “kaydım oluştu mu” cevabını bekliyor. E-posta, PDF, CRM — hiçbirinin sonucunu o ekranda görmesi gerekmiyor. Üçü de yanlış tarafta duruyordu.&lt;/p&gt;
&lt;h2 id=&quot;senkron-kalması-gerekenler&quot;&gt;Senkron kalması gerekenler&lt;/h2&gt;
&lt;p&gt;Kullanıcının sonucunu doğrudan gördüğü işler istek içinde kalır:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Girdi doğrulama — hata anında dönmeli.&lt;/li&gt;
&lt;li&gt;Asıl kaynağın yaratılması — kullanıcı oluşturma, sipariş oluşturma.&lt;/li&gt;
&lt;li&gt;Ödeme &lt;strong&gt;yetkilendirmesinin sonucu&lt;/strong&gt; — kart geçti mi geçmedi mi, kullanıcı bunu o an bilmeli.&lt;/li&gt;
&lt;li&gt;Kullanıcının bir sonraki adımda göreceği veri.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunları kuyruğa atmak, kullanıcıya “sonra bak” demektir — ve çoğu zaman kötü bir deneyimdir.&lt;/p&gt;
&lt;h2 id=&quot;kuyruğa-gitmesi-gerekenler&quot;&gt;Kuyruğa gitmesi gerekenler&lt;/h2&gt;
&lt;p&gt;Kullanıcının sonucunu o an görmesi gerekmeyen her iş asenkrondur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;E-posta ve bildirim.&lt;/li&gt;
&lt;li&gt;PDF ve rapor üretimi.&lt;/li&gt;
&lt;li&gt;Webhook gönderimi.&lt;/li&gt;
&lt;li&gt;Üçüncü parti senkronizasyonu — CRM, analitik, arama indeksi.&lt;/li&gt;
&lt;li&gt;Görsel işleme, dışa aktarma.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunlar için doğru yanıt &lt;code&gt;202 Accepted&lt;/code&gt;’dir: “isteğini aldım, işliyorum.” Kullanıcı beklemez, sistem işi kendi temposunda yapar.&lt;/p&gt;
&lt;h2 id=&quot;çizgiyi-yanıt-süresi-keskinleştirir&quot;&gt;Çizgiyi yanıt süresi keskinleştirir&lt;/h2&gt;
&lt;p&gt;Her endpoint için bir p95 bütçeniz olmalı — diyelim 300 ms. Bir adım bu bütçeyi tek başına yiyorsa ve kullanıcı o adımın sonucunu görmek zorunda değilse, o adım kuyruğa gider.&lt;/p&gt;
&lt;p&gt;Senkron zincir, en yavaş halkası kadar hızlıdır. PDF üretimi 4 saniye sürüyorsa, kullanıcı oluşturma da 4 saniye sürer — PDF’in yanıtla hiçbir ilgisi olmasa bile.&lt;/p&gt;
&lt;h2 id=&quot;hata-toleransı-asıl-ayrım&quot;&gt;Hata toleransı: asıl ayrım&lt;/h2&gt;
&lt;p&gt;Asıl mesele hız değil, hatadır. Senkron ve asenkron, başarısızlığı bambaşka karşılar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Senkron hata&lt;/strong&gt; = kullanıcı bir hata ekranı görür. İş kaybolur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asenkron hata&lt;/strong&gt; = job retry edilir, gerekirse DLQ’ya düşer, &lt;a href=&quot;/tr/notes/laravel-queue-production-yavaslama/&quot;&gt;kuyruk&lt;/a&gt; sonra işler.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Buradan tek bir kural çıkar: &lt;strong&gt;kararsız bir üçüncü parti asla senkron yolda durmamalı.&lt;/strong&gt; O CRM çağrısı istek içindeyse, CRM her çöktüğünde sizin kayıt formunuz da çöker. Kuyruğa alınmış bir çağrı ise, CRM döndüğünde retry kendiliğinden tamamlanır. Üçüncü partinin uptime’ı sizin uptime’ınız olmaktan çıkar.&lt;/p&gt;
&lt;h2 id=&quot;202-ve-durum-sorgusu-kalıbı&quot;&gt;202 ve durum sorgusu kalıbı&lt;/h2&gt;
&lt;p&gt;İş kuyruğa gidince kullanıcıya bir takip yolu bırakın:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;POST /exports          → 202 Accepted  { &amp;quot;id&amp;quot;: &amp;quot;exp_1a2b&amp;quot;, &amp;quot;status&amp;quot;: &amp;quot;processing&amp;quot; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET  /exports/exp_1a2b → 200 OK        { &amp;quot;status&amp;quot;: &amp;quot;done&amp;quot;, &amp;quot;url&amp;quot;: &amp;quot;...&amp;quot; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kullanıcı sonucu beklemiyordur ama bazen merak eder. Bir durum endpoint’i ya da hazır olunca gönderilen bir bildirim, bu boşluğu kapatır.&lt;/p&gt;
&lt;h2 id=&quot;sınır-nerede-kalkar&quot;&gt;Sınır nerede kalkar?&lt;/h2&gt;
&lt;p&gt;Bazı işler ikisinin arasındadır: kullanıcı sonucu görmek ister ama iş de yavaştır. Burada üçüncü bir yol var — işi senkron başlatın, ilk anlamlı sonucu hemen döndürün, gerisini kuyruğa devredin. Sipariş kaydını anında oluşturun (senkron), faturayı arkada üretin (asenkron).&lt;/p&gt;
&lt;p&gt;Çizgiyi her iş için ayrı çizin; “her şey kuyruğa” da “her şey senkron” kadar yanlıştır.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Senkron mu asenkron mu sorusu bir performans sorusu değil, bir “kim bekliyor” sorusudur. Kullanıcının beklemediği hiçbir iş, onun isteğini bekletmemeli.&lt;/p&gt;
&lt;p&gt;İsteği yalnızca, cevabını gerçekten istediği işle meşgul edin.&lt;/p&gt;</content:encoded><category>architecture</category><category>queue</category><category>async</category><category>api</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Transactional Outbox: Dual-Write, At-Least-Once ve İdempotent Tüketim</title><link>https://sade.dev/tr/systems/transactional-outbox-dual-write-ve-idempotent-tuketim/</link><guid isPermaLink="true">https://sade.dev/tr/systems/transactional-outbox-dual-write-ve-idempotent-tuketim/</guid><description>Veritabanına yazıp event&apos;i kuyruğa düşürememe: dual-write&apos;ı outbox&apos;la kapatmak, doğan at-least-once tekrarını idempotent tüketimle bastırmak ve alanı şifrelemek.</description><pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Belirti küçüktü: günde bir-iki fatura veritabanında duruyor ama consumer onları hiç görmüyordu. Kullanıcı “kestim” diyor, karşı tarafta hiçbir iz yok. Hep gece, hep bir deploy ya da network dalgalanması anında. Tabloyu netleştirince sebep ortaya çıktı — veritabanına &lt;code&gt;INSERT&lt;/code&gt; ile kuyruğa &lt;code&gt;publish&lt;/code&gt; iki ayrı sistemdi ve aralarında hiçbir garanti yoktu.&lt;/p&gt;
&lt;p&gt;Bu yazı o boşluğu kapatan &lt;strong&gt;transactional outbox&lt;/strong&gt; kalıbının, kapatınca açılan &lt;strong&gt;at-least-once&lt;/strong&gt; tekrarının ve son olarak event’in içinden geçen kişisel veriyi &lt;strong&gt;alan seviyesinde&lt;/strong&gt; şifrelemenin sistem tarafı. Karar zincirinin günlük tarafını &lt;a href=&quot;https://muhammetsafak.com.tr/blog/outbox-idempotent-tuketim-ve-alan-sifrelemesi&quot;&gt;muhammetsafak.com.tr’de ayrı yazdım&lt;/a&gt;; burada kalıbın production’da dayanıklı olması için verilmesi gereken kararları topluyorum.&lt;/p&gt;
&lt;h2 id=&quot;dual-write-bir-transactionı-iki-sisteme-yayamazsınız&quot;&gt;Dual-write: bir transaction’ı iki sisteme yayamazsınız&lt;/h2&gt;
&lt;p&gt;Sorunun özü tek cümle: yerel veritabanı transaction’ınız RabbitMQ’yu kapsamaz. Klasik kod &lt;em&gt;çoğu zaman&lt;/em&gt; çalıştığı için tehlikeli:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ()&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;                 // 1) MySQL&amp;#39;e yaz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;publishToRabbit&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt; // 2) kuyruğa publish&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İki ayrı arıza modu var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;save()&lt;/code&gt; başarılı, &lt;code&gt;publish&lt;/code&gt; network hatasıyla düşer → DB’de fatura var, event yok. &lt;strong&gt;Kayıp event.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publish&lt;/code&gt; başarılı, ardından transaction başka sebeple &lt;code&gt;rollback&lt;/code&gt; olur → event gitti, DB’de karşılığı yok. &lt;strong&gt;Hayalet event.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;DB::transaction&lt;/code&gt; kurtarmaz, çünkü commit/rollback yalnızca MySQL tarafını sarar; broker o transaction’ın parçası değildir. İki sistemi tek atomik adımda tutmanın pratik yolu, yazmayı &lt;strong&gt;tek bir sisteme&lt;/strong&gt; indirmek.&lt;/p&gt;
&lt;h2 id=&quot;outbox-önce-tek-sisteme-yaz-sonra-ayrı-bir-süreç-yayımlasın&quot;&gt;Outbox: önce tek sisteme yaz, sonra ayrı bir süreç yayımlasın&lt;/h2&gt;
&lt;p&gt;Kalıbın fikri sade: mesajı kuyruğa doğrudan basmak yerine, &lt;strong&gt;aynı transaction içinde&lt;/strong&gt; bir &lt;code&gt;outbox&lt;/code&gt; tablosuna satır olarak yaz. Fatura ile event aynı commit’te ya birlikte var olur ya da birlikte yok olur — dual-write tek write’a iner.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; outbox&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    id            &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;BINARY&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;)   &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,        &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- event id = idempotency anahtarı&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    aggregate_id  &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;BIGINT&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;       NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,        &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- ordering ve partition anahtarı&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    topic         &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;120&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    payload       &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;         NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    status&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;        ENUM(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pending&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;published&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pending&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    created_at    &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    published_at  &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;DATETIME&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (id),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    KEY&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; idx_dispatch (&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;, created_at)        &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- relay taraması bu indeksten gider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yazma artık iş kodunun derdi değil:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ()&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;    Outbox&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;invoice.issued&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kuyruğa basmayı ayrı bir &lt;strong&gt;relay&lt;/strong&gt; yapar: &lt;code&gt;pending&lt;/code&gt; satırları okur, RabbitMQ’ya &lt;code&gt;publish&lt;/code&gt; eder, başarınca &lt;code&gt;published&lt;/code&gt; damgalar. Burada kritik gerçek şu: relay “publish ettim ama &lt;code&gt;published&lt;/code&gt; damgasını vuramadan çöktüm” durumuna düşebilir. Yani outbox dual-write’ı çözerken size bedava bir garanti &lt;strong&gt;vermez&lt;/strong&gt; — &lt;strong&gt;at-least-once&lt;/strong&gt; verir. Mesaj kaybolmaz ama tekrar edebilir. Geri kalan her şey bu gerçeğin etrafına dizilir.&lt;/p&gt;
&lt;h2 id=&quot;relayi-nasıl-beslersiniz-polling-mi-cdc-mi&quot;&gt;Relay’i nasıl beslersiniz: polling mi, CDC mi?&lt;/h2&gt;
&lt;p&gt;Relay &lt;code&gt;pending&lt;/code&gt; satırları iki yoldan görebilir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Polling&lt;/strong&gt; — relay tabloyu periyodik tarar:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; id, topic, payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; outbox&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; status&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pending&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; created_at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basit, her veritabanında çalışır, operasyonel yükü düşük. Bedeli iki şey: tarama aralığı kadar &lt;strong&gt;gecikme&lt;/strong&gt; (1 sn’lik poll = ~1 sn’lik kuyruk gecikmesi) ve boşta dönen sorgu. &lt;code&gt;idx_dispatch&lt;/code&gt; indeksi olmadan bu tarama tablo büyüdükçe pahalılaşır; indeksle bile poll sıklığı ile DB yükü arasında pazarlık yaparsınız. Küçük-orta hacimde polling &lt;strong&gt;doğru&lt;/strong&gt; cevap — gecikmeyi kabul edilebilir tutacak kadar sık, DB’yi yormayacak kadar seyrek (pratikte 200 ms–1 sn) ayarlayın.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CDC&lt;/strong&gt; (change data capture) — relay tabloyu değil, veritabanının &lt;strong&gt;WAL/binlog&lt;/strong&gt;’unu dinler (Debezium tipik araç). &lt;code&gt;outbox&lt;/code&gt;’a düşen her &lt;code&gt;INSERT&lt;/code&gt; neredeyse anında bir event’e döner; polling gecikmesi ve boşa tarama ortadan kalkar. Bedeli operasyonel: binlog’a erişim, bir connector süreci, ek bir hareketli parça. CDC’yi gecikme gerçekten önemliyse (saniye altı) ya da hacim polling’i zorluyorsa açarım; aksi halde polling’in sadeliğini bir avantaj sayarım.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kural: gecikme bütçeniz ile operasyonel bütçeniz çatışır. CDC gecikmeyi satın alır, karşılığında bir altyapı parçası ödetir. Önce polling’le başlayın; CDC’ye ölçülmüş bir sebep çıkınca geçin.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;çoklu-relay-skip-locked-ile-çakışmasız-dağıtım&quot;&gt;Çoklu relay: &lt;code&gt;SKIP LOCKED&lt;/code&gt; ile çakışmasız dağıtım&lt;/h2&gt;
&lt;p&gt;Tek relay bir bottleneck ve tek arıza noktasıdır. Birden çok relay aynı tabloyu tarayınca yeni bir risk doğar: ikisi aynı satırı kapıp aynı mesajı iki kez yayımlar. Naif çözüm tabloyu kilitlemektir — ki bu paralelliği öldürür.&lt;/p&gt;
&lt;p&gt;Doğru araç &lt;code&gt;SELECT ... FOR UPDATE SKIP LOCKED&lt;/code&gt;. Her relay bir batch satırı &lt;strong&gt;kilitleyerek&lt;/strong&gt; claim eder; başka bir relay’in kilitlediği satırları sıraya girip beklemek yerine &lt;strong&gt;atlar&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  SELECT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; id, topic, payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; outbox&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  WHERE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; status&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pending&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; created_at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  LIMIT&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  FOR&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; UPDATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; SKIP&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; LOCKED;     &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- başka relay&amp;#39;in tuttuğu satırları atla, bekleme&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;  -- bu batch&amp;#39;i publish et, sonra:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  UPDATE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; outbox &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; status&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;published&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;, published_at &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; NOW&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  WHERE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;IN&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (...);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt; olmadan relay’ler aynı satırlar için kuyruğa girer ve paralellik fiilen seri yürür. &lt;code&gt;SKIP LOCKED&lt;/code&gt; ile her relay disjoint bir küme çeker, hepsi birden ilerler. PostgreSQL ve MySQL 8+ bunu destekler.&lt;/p&gt;
&lt;p&gt;Bir incelik: &lt;code&gt;publish&lt;/code&gt; ile &lt;code&gt;UPDATE ... published&lt;/code&gt; arasında relay çökerse satır &lt;code&gt;pending&lt;/code&gt; kalır ve &lt;strong&gt;yeniden yayımlanır&lt;/strong&gt;. Bu kabul edilebilir — zaten at-least-once’tayız; çözüm tüketici tarafında, aşağıda. Tehlikeli olan tersidir: önce &lt;code&gt;published&lt;/code&gt; damgalayıp sonra &lt;code&gt;publish&lt;/code&gt; etmek. O zaman çökme &lt;strong&gt;kayıp event&lt;/strong&gt; üretir ki tam kaçtığımız şey. Yani sıra &lt;strong&gt;değişmez&lt;/strong&gt;: önce &lt;code&gt;publish&lt;/code&gt;, sonra damga.&lt;/p&gt;
&lt;h2 id=&quot;ordering-neyi-garanti-edebilirsiniz-neyi-edemezsiniz&quot;&gt;Ordering: neyi garanti edebilirsiniz, neyi edemezsiniz&lt;/h2&gt;
&lt;p&gt;“Event’ler yazıldığı sırada gelsin” sezgisel bir beklenti ama global ordering pahalı ve çoğu zaman gereksiz. Gerçekte ihtiyaç duyduğunuz şey &lt;strong&gt;aggregate başına&lt;/strong&gt; sıradır: aynı faturanın &lt;code&gt;created&lt;/code&gt; event’i &lt;code&gt;updated&lt;/code&gt;’dan önce gelsin; iki farklı faturanın birbirine göre sırası kimsenin umurunda değil.&lt;/p&gt;
&lt;p&gt;Bunu &lt;code&gt;aggregate_id&lt;/code&gt; ile partition’layarak alırsınız: aynı &lt;code&gt;aggregate_id&lt;/code&gt;’ye sahip event’ler aynı kuyruk partition’ına (ya da RabbitMQ’da consistent hash exchange ile aynı queue’ya) gider; o partition’ı &lt;strong&gt;tek consumer&lt;/strong&gt; sırayla işler. Farklı aggregate’ler paralel akar.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;outbox (created_at sırasıyla)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   │  hash(aggregate_id) % N&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ├── partition 0 ──► consumer-0   (aggregate A,D olayları — sıralı)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ├── partition 1 ──► consumer-1   (aggregate B olayları   — sıralı)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └── partition 2 ──► consumer-2   (aggregate C olayları   — sıralı)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İki şey ordering’i yine de bozar ve tasarımın bunlara hazır olması gerekir: çoklu relay &lt;code&gt;SKIP LOCKED&lt;/code&gt; ile satırları farklı sırada yayımlayabilir ve at-least-once tekrarlar araya girebilir. Bu yüzden global sıraya bel bağlamak yerine, tüketiciyi &lt;strong&gt;sırasız ve tekrarlı&lt;/strong&gt; event’e dayanıklı yazmak gerekir. Pratik kalkan: event’e monotonik bir &lt;code&gt;version&lt;/code&gt;/&lt;code&gt;sequence&lt;/code&gt; koyup tüketicide eskiyi (daha düşük versiyonu) düşürmek. Sıkı global ordering gerekiyorsa tek partition + tek consumer’a inersiniz — ki bu da throughput’u o tek consumer’a bağlar; çoğu sistem bu bedeli ödemek istemez.&lt;/p&gt;
&lt;h2 id=&quot;at-least-once-gelince-tüketici-idempotent-olmalı&quot;&gt;At-least-once gelince: tüketici idempotent olmalı&lt;/h2&gt;
&lt;p&gt;Relay tekrar yayımlayabildiği için her event’in en az bir, bazen birden çok kez geleceğini varsaymak gerekir. Çözüm event’i tekil yapmaya çalışmak değil — &lt;strong&gt;tüketiciyi&lt;/strong&gt; aynı event’i iki kez işlese de tek kez işlemiş gibi davranacak şekilde kurmak. İşlenen her event &lt;code&gt;id&lt;/code&gt;’sini bir dedupe tablosuna yazıp işi &lt;strong&gt;aynı transaction içinde&lt;/strong&gt; yapmak:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ()&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;inserted&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; DB&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;processed_messages&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;insertOrIgnore&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;message_id&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;   =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;processed_at&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;inserted&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt; // tekrar gelmiş; hiçbir yan etki üretme&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;applyBusinessEffect&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt; // asıl iş — yalnızca bir kez&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;processed_messages.message_id&lt;/code&gt; üstündeki &lt;strong&gt;unique&lt;/strong&gt; kısıt işin kalbi: ikinci kez gelen aynı &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;insertOrIgnore&lt;/code&gt; ile sessizce elenir. İş etkisini aynı transaction’a almak şart — yoksa “işledim ama damgalamadan çöktüm” boşluğu açılır. Dedupe anahtarının nereden geldiği ve çökmeden sağ çıkan dedupe tasarımı başlı başına bir konu; oraya &lt;a href=&quot;/tr/notes/idempotency-ayni-mesaj-iki-kez/&quot;&gt;ayrı bir notta&lt;/a&gt; girdim.&lt;/p&gt;
&lt;p&gt;Yan etki bir dış sistemse (e-fatura entegratörü, ödeme API’si) dedupe tek başına yetmez — çünkü yan etki transaction’ın dışındadır. Orada anahtarı &lt;strong&gt;dış servise taşımak&lt;/strong&gt; gerekir: aynı &lt;code&gt;id&lt;/code&gt;’yi entegratöre idempotency-key olarak geçmek, ya da göndermeden önce “bu zaten var mı?” diye sormak. Bu sınırı, numarayı dış çağrıdan önce mühürleyip recovery’yi bir lookup’a indiren &lt;a href=&quot;/tr/systems/ardisik-numara-uretiminde-race-condition-ve-gap/&quot;&gt;JIT rezervasyon yazısında&lt;/a&gt; ayrıntılı işledim.&lt;/p&gt;
&lt;h2 id=&quot;processed_messages-sonsuza-kadar-büyüyemez&quot;&gt;&lt;code&gt;processed_messages&lt;/code&gt; sonsuza kadar büyüyemez&lt;/h2&gt;
&lt;p&gt;Dedupe tablosu her event için bir satır biriktirir; budanmazsa kendisi bir performans sorununa döner. Anahtar gözlem: bir event &lt;code&gt;id&lt;/code&gt;’sini sonsuza kadar tutmanız gerekmez — yalnızca &lt;strong&gt;bir tekrarın gelebileceği pencere&lt;/strong&gt; kadar tutmanız yeter. Relay retry’ları ve broker redelivery’leri saatler mertebesindedir, günler değil.&lt;/p&gt;
&lt;p&gt;Pratikte sabit bir &lt;strong&gt;retention penceresi&lt;/strong&gt; belirleyip (örneğin 7 gün — en uzun olası redelivery’nin rahatça üstünde) eskisini budarım:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;DELETE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; processed_messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; processed_at &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; NOW&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; INTERVAL &lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; DAY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 10000&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- tek seferde küçük batch&amp;#39;ler, replica lag&amp;#39;i ve kilidi kontrol altında&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;message_id&lt;/code&gt; UUID ise tablo ve indeks büyür; budama bunu sınırlı tutar. Pencereyi seçerken kuralı tersinden kurun: &lt;em&gt;en uzun gecikmeli redelivery ne kadar sonra gelebilir?&lt;/em&gt; Cevabın güvenli üstünü retention yapın. Pencereyi çok kısa tutarsanız geç gelen bir tekrar dedupe’a takılmaz ve yan etki ikinci kez çalışır.&lt;/p&gt;
&lt;h2 id=&quot;exactly-once-neden-çoğu-zaman-bir-yanılsama&quot;&gt;Exactly-once neden çoğu zaman bir yanılsama&lt;/h2&gt;
&lt;p&gt;Dağıtık sistemde “tam bir kez teslim” cazip ama uçtan uca garanti edilemez. Sebep aynı dual-write’ın broker sınırındaki hali: tüketici işi yaptı ama &lt;code&gt;ack&lt;/code&gt;’i broker’a ulaştıramadan çöktü mü, broker mesajı yeniden teslim eder — çünkü &lt;code&gt;ack&lt;/code&gt; görmemiştir. “İşledim” ile “işlediğimi bildirdim” iki ayrı adımdır ve aralarına çökme girebilir. Bu, iki general probleminin pratikteki yüzü.&lt;/p&gt;
&lt;p&gt;Bu yüzden hedef exactly-once &lt;strong&gt;delivery&lt;/strong&gt; değil, exactly-once &lt;strong&gt;effect&lt;/strong&gt;. Bunu da iki ucuz garantiyi birleştirerek alırsınız:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;at-least-once delivery  +  idempotent consumer  =  effectively-once&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yani teslimi tam bir kez yapmaya çalışmayı bırakıp &lt;strong&gt;tekrarı zararsız&lt;/strong&gt; kılarsınız. “Exactly-once” pazarlayan sistemlerin altında çoğu zaman tam olarak bu vardır: at-least-once + bir dedupe katmanı. Garantiyi teslimde değil, etkide arayın.&lt;/p&gt;
&lt;h2 id=&quot;eventin-içinden-geçen-kişisel-veri-alan-seviyesi-şifreleme&quot;&gt;Event’in içinden geçen kişisel veri: alan seviyesi şifreleme&lt;/h2&gt;
&lt;p&gt;Outbox oturunca yeni bir yüzey çıkar: event’lerin payload’ında müşteri adı, vergi numarası, adres gibi kişisel veri var ve bu payload artık &lt;strong&gt;kalıcı&lt;/strong&gt; bir tabloda (&lt;code&gt;outbox&lt;/code&gt;) duruyor, üstelik broker’dan geçiyor. Veriyi yerinde bırakırsam, GDPR/KVKK kapsamındaki alanları şifresiz biçimde birden çok yere kopyalamış olurum.&lt;/p&gt;
&lt;p&gt;Bütün payload’ı şifrelemek istemedim — &lt;code&gt;topic&lt;/code&gt;, &lt;code&gt;aggregate_id&lt;/code&gt;, &lt;code&gt;invoice_id&lt;/code&gt; gibi alanları relay yönlendirmesi ve gözlemlenebilirlik için açık görmem gerekiyordu. İhtiyaç &lt;strong&gt;alan seviyesi&lt;/strong&gt; şifreleme: yalnızca hassas alanlar şifreli, gerisi açık. Hassas alanları şemada &lt;code&gt;x-gdpr-sensitive&lt;/code&gt; ile işaretleyip serileştirmede yalnızca onları şifreledim:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;x-gdpr-sensitive&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;customer_name&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;x-gdpr-sensitive&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;tax_id&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;        =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;x-gdpr-sensitive&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;total&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;         =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;x-gdpr-sensitive&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; collect&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;raw&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;x-gdpr-sensitive&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ??&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;        ?&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; Crypt&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;encryptString&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;  // yalnızca hassas alan şifreli&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Şemanın hassas alanı beyan etmesi tesadüf değil — bunu kuyruğun kenarında şema doğrulamasıyla birleştirince, “hangi alan kişisel veri ve doğru tipte mi?” sorularının ikisi de &lt;a href=&quot;/tr/notes/semayi-kuyrugun-kenarinda-dogrulamak/&quot;&gt;kenarda&lt;/a&gt; cevaplanır. Asıl mühendislik kararı şifrelemenin kendisinde değil, çevresindeki üç soruda:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Anahtar yönetimi ve rotasyon.&lt;/strong&gt; Laravel &lt;code&gt;Crypt&lt;/code&gt;, &lt;code&gt;APP_KEY&lt;/code&gt; ile AES-256 kullanır. Tek bir anahtara bağlanırsanız rotasyon kabusa döner: eski anahtarla yazılmış outbox satırlarını yeni anahtar açamaz. Çözüm, şifreli değerin yanına &lt;strong&gt;anahtar versiyonu&lt;/strong&gt; (key id) yazmak ve birden çok anahtarı aynı anda tanıyan bir keyring tutmak — yeni yazımlar güncel anahtarı kullanır, eski değerler kendi versiyonuyla açılır. Laravel’de &lt;code&gt;config/app.php&lt;/code&gt;’deki &lt;code&gt;key&lt;/code&gt; + &lt;code&gt;previous_keys&lt;/code&gt; tam bunun içindir; decrypt sırasıyla eski anahtarları da dener. Rotasyonu bu yüzden veri taşımadan yapabilirsiniz.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Şifreli alanda arama.&lt;/strong&gt; &lt;code&gt;Crypt::encryptString&lt;/code&gt; her çağrıda farklı ciphertext üretir (rastgele IV), ki bu güvenlik için doğru ama &lt;code&gt;WHERE tax_id = ?&lt;/code&gt; ile aramayı imkânsız kılar. Eşitlik araması gerekiyorsa &lt;strong&gt;blind index&lt;/strong&gt; eklersiniz: aranabilir alanın deterministik bir HMAC’ini ayrı bir kolonda tutup onu sorgularsınız. Şifreli kolon gizliliği, blind index aranabilirliği taşır; ikisi ayrı kolondur çünkü iki ayrı işi vardır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Anahtarı kaybetmek veriyi kaybetmektir.&lt;/strong&gt; Alan şifrelemesi &lt;code&gt;APP_KEY&lt;/code&gt;’i bir &lt;strong&gt;availability&lt;/strong&gt; bağımlılığına çevirir: anahtar giderse şifreli alanlar kalıcı çöptür. Anahtar yedeği ve erişim kontrolü, şifrelemenin kendisi kadar tasarımın parçasıdır.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-bu-kalıbı-kurmazsınız&quot;&gt;Ne zaman bu kalıbı kurmazsınız?&lt;/h2&gt;
&lt;p&gt;Outbox + idempotent tüketim + alan şifrelemesi ucuz değil: bir tablo, bir relay süreci (ya da CDC connector), bir dedupe tablosu ve onun budama işi, bir de anahtar yönetimi getirir. Karşılığında ne aldığınızı bilmiyorsanız ödemeyin. Şu durumlarda kurmuyorum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Event kaybı tolere edilebiliyorsa.&lt;/strong&gt; Yayımladığınız şey best-effort bir bildirimse (cache invalidation, “yeni içerik var” sinyali) ve bir-iki kayıp önemsizse, doğrudan &lt;code&gt;publish&lt;/code&gt; yeterli. Outbox’ı kayıp önemli olduğu için kurarsınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yazma ile yayım aynı sistemdeyse.&lt;/strong&gt; Hedefiniz de aynı veritabanıysa (ayrı bir broker yoksa), dual-write zaten yok; outbox’a gerek kalmaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hacim tek consumer’a sığıyorsa ve ordering kritikse.&lt;/strong&gt; Düşük hacimde tek tüketicili bir kuyruk hem sırayı hem tekilliği basitçe verir; çoklu relay ve partition makinesini kurmak olmayan bir problemi çözmektir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kalıba ihtiyaç üç koşul &lt;strong&gt;birlikte&lt;/strong&gt; doğunca başlar: event kaybı kabul edilemez, üretim/tüketim paralel ve veri ayrı bir sisteme (broker) gidiyor. Üçü birden yoksa daha sade bir çözüm vardır.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Üç parça tek bir disipline iner: &lt;strong&gt;veriyi tek bir sisteme yaz, tekrarı tüketicide bastır, hassas alanı yola çıkmadan mühürle.&lt;/strong&gt; Outbox kuyruğa olan güveni, idempotent tüketim teslime olan güveni, alan şifrelemesi de payload’a olan güveni gereksiz kılar — her biri bir garantiyi koddan dışarı, tasarımın içine taşır.&lt;/p&gt;</content:encoded><category>architecture</category><category>messaging</category><category>idempotency</category><category>reliability</category><category>security</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Event-Driven Mimari: Ne Zaman Kurtarır, Ne Zaman Karmaşıklaştırır?</title><link>https://sade.dev/tr/notes/event-driven-mimari-ne-zaman/</link><guid isPermaLink="true">https://sade.dev/tr/notes/event-driven-mimari-ne-zaman/</guid><description>Event&apos;lerle gevşek bağ kurmanın bedeli: kazandırdığı esneklik ile sistem akışını takip edilemez hale getirme riski arasındaki denge.</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;“Sipariş oluşturulduğunda ne oluyor?” sorusuna bir ekipte kimsenin net cevap verememesini izledim. Kod tabanında &lt;code&gt;OrderCreated&lt;/code&gt; event’ini sekiz ayrı listener dinliyordu; hangisinin hangi sırada, hangi koşulda çalıştığı tek bir yerden okunamıyordu. Akış kodda değil, kafalarda — hem de eksik — yaşıyordu.&lt;/p&gt;
&lt;p&gt;Event-driven mimari bağı gevşetir. Bu yazı, gevşeyen başka bir şey daha olduğunu hatırlatmak için: nedensellik.&lt;/p&gt;
&lt;h2 id=&quot;event-ne-çözer&quot;&gt;Event ne çözer&lt;/h2&gt;
&lt;p&gt;Bir event yayınlayan kod, onu kimin dinlediğini bilmez. &lt;code&gt;OrderCreated&lt;/code&gt; yayınlanır; faturalama, bildirim ve analitik onu ayrı ayrı dinler. Sipariş kodu bu üçünden habersizdir.&lt;/p&gt;
&lt;p&gt;Kazanç gerçek: yeni bir tüketici eklemek, üreticiye dokunmadan olur. Bağımsız geliştirme, bağımsız dağıtım, fan-out. Birbirini tanımayan parçalar.&lt;/p&gt;
&lt;h2 id=&quot;event-neyi-gizler&quot;&gt;Event neyi gizler&lt;/h2&gt;
&lt;p&gt;Aynı madalyonun öbür yüzü: bağ gevşedikçe &lt;strong&gt;akış görünmez olur&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Doğrudan bir method çağrısında akış kodun kendisidir — tanımına gidersiniz, devamını okursunuz. Event’te öyle değil. &lt;code&gt;OrderCreated&lt;/code&gt; yayınlandığında ne olduğunu görmek için tüm listener’ları elle bulmanız gerekir. Compiler size yardım etmez: bir method çağrısının kontratını derleyici doğrular, bir event’in kontratını kimse doğrulamaz.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/&quot;&gt;Modüler monolit yazısındaki&lt;/a&gt; cümle burada da geçerli — bir network kontratı gibi, bir event kontratı da derleyicinin kör noktasındadır.&lt;/p&gt;
&lt;h2 id=&quot;ödenmeden-seçilmemeli-iki-ön-koşul&quot;&gt;Ödenmeden seçilmemeli: iki ön koşul&lt;/h2&gt;
&lt;p&gt;Event-driven mimariye geçmeden önce iki bedel peşin ödenir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Schema disiplini.&lt;/strong&gt; Event bir kontrattır. &lt;code&gt;OrderCreated&lt;/code&gt;’in payload’ı değiştiğinde, onu dinleyen sekiz yerin haberi olmaz — ta ki production’da kırılana kadar. Event’ler versiyonlanmalı, alanlar geriye uyumlu eklenmeli, payload bir şema ile doğrulanmalı. Bu disiplin yoksa, gevşek bağ “sessiz kırılma” demektir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;İzlenebilirlik.&lt;/strong&gt; Bir isteğin sistemden geçişini görebilmek için her event bir correlation ID taşımalı:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; OrderCreated&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;    orderId&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;    correlationId&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; request&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;X-Correlation-Id&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ??&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; Str&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;uuid&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu ID log’lara ve sonraki event’lere taşınmazsa, çok-listener’lı bir akışta “bu iş neden çalıştı?” sorusunun cevabı yoktur. Tracing, event-driven’ın isteğe bağlı bir eklentisi değil, ön koşuludur.&lt;/p&gt;
&lt;h2 id=&quot;laravelde-in-process-event--event-driven-mimari&quot;&gt;Laravel’de: in-process event ≠ event-driven mimari&lt;/h2&gt;
&lt;p&gt;Laravel’in &lt;code&gt;event()&lt;/code&gt;/listener mekanizması, senkron çalıştığında aslında düzenlenmiş bir method çağrısıdır — aynı process, aynı transaction, aynı stack trace. Bu güvenli ve izlenebilir; bunu rahatça kullanın.&lt;/p&gt;
&lt;p&gt;Event-driven mimarinin getirdiği zorluklar, event bir &lt;strong&gt;sınırı&lt;/strong&gt; geçtiğinde başlar: bir queue’ya, bir message broker’a, başka bir process’e. Listener’a &lt;code&gt;ShouldQueue&lt;/code&gt; ekleyip event’i RabbitMQ’ya gönderdiğiniz an, schema ve tracing borcu devreye girer. İkisini birbirine karıştırmayın: in-process listener kullanmak sizi “event-driven mimariye” geçirmez.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-event-drivena-geçilir&quot;&gt;Ne zaman event-driven’a geçilir&lt;/h2&gt;
&lt;p&gt;Event’i gerçekten bir sınırın ötesine taşımak şu durumlarda gerekçeli:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gerçek bir fan-out var.&lt;/strong&gt; Bir olayı, birbirini tanımaması gereken çok sayıda bağımsız tüketici dinliyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tüketici asenkron olmalı.&lt;/strong&gt; İş, üreticinin yanıtını bekletmemeli; “kabul edildi” yeterli.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tüketiciler bağımsız ölçeklenmeli ya da dağıtılmalı.&lt;/strong&gt; Bu da bizi &lt;a href=&quot;/tr/journal/mikroservise-ne-zaman-gecerim/&quot;&gt;mikroservis kararına&lt;/a&gt; götürür — aynı ölçülmüş-sinyal eşiği.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunlar yoksa, doğrudan bir service çağrısı hem daha okunur hem daha güvenlidir. Tek bir listener’ı olan bir event, sadece kılık değiştirmiş bir method çağrısıdır — üstelik izi sürülmesi daha zor olanı.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Event-driven mimari bağı gevşetir; bunu yaparken nedenselliği de gevşetir. Birincisini istiyorsanız, ikincisinin faturasını — schema ve tracing — peşin ödeyin.&lt;/p&gt;
&lt;p&gt;Gevşek bağ bedava değildir; görünmez akışla ödenir.&lt;/p&gt;</content:encoded><category>architecture</category><category>event-driven</category><category>messaging</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Çok Kümeli GitOps’ta Sıfır Statik Yetki</title><link>https://sade.dev/tr/systems/cok-kumeli-gitops-sifir-statik-yetki/</link><guid isPermaLink="true">https://sade.dev/tr/systems/cok-kumeli-gitops-sifir-statik-yetki/</guid><description>Uzun ömürlü kubeconfig ya da token olmadan çok kümeli GitOps. SPIFFE ID’ler, SPIRE’ın bastığı kısa ömürlü SVID’ler, trust-domain federasyonu — kurma sırası ve her adımın maliyeti.</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir GitOps controller’ı — Argo CD, Flux — bir kümede oturur ve birçoğunu reconcile eder. Diğerlerine ulaşmak için her uzak &lt;code&gt;kube-apiserver&lt;/code&gt;’a kimlik doğrulaması yapması gerekir. Bunu yapmanın varsayılan yolu bir kimlik bilgisini saklamaktır: kontrol kümesinde bir Kubernetes &lt;code&gt;Secret&lt;/code&gt; olarak tutulan bir bearer token, bir client sertifikası, bir kubeconfig. Argo CD’de bunlar &lt;code&gt;argocd.argoproj.io/secret-type: cluster&lt;/code&gt; etiketli nesnelerdir.&lt;/p&gt;
&lt;p&gt;Dur ve o &lt;code&gt;Secret&lt;/code&gt;’ın ne olduğuna bak. O, &lt;strong&gt;genelde geniş yetkili, tüm filonuza nişan almış, tek bir yerde atıl duran, uzun ömürlü bir kimlik bilgisidir.&lt;/strong&gt; Kendi kendine rotate olmaz. Yedeklerinizdedir. Kontrol kümenizde bir saldırganın bulabileceği en değerli tek şeydir, çünkü ona sahip olmak, eriştiği her kümeye sahip olmaktır. &lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;Boring architecture&lt;/a&gt; disiplininin tamamı, tutmak zorunda olmadığınız yükümlülükleri tutmamakla ilgilidir — ve saklanan filo geneli bir token, sırf kullanım anında taze ve kısa ömürlü bir tane basmak daha fazla iş gibi göründüğü için tuttuğunuz bir yükümlülüktür.&lt;/p&gt;
&lt;p&gt;Bu yazı o fazladan işi, sırasıyla yapmakla ilgili. Hedef &lt;strong&gt;sıfır statik yetki&lt;/strong&gt;: controller’ı bir uzak kümeye doğrulayan hiçbir uzun ömürlü kimlik bilgisi asla yazılı kalmaz. Controller’ın sunduğu kimlik talep anında basılır, yaklaşık bir saatte sona erer ve yalnızca bellekte yaşar. Mekanizma, kimlik modeli için SPIFFE ve onu basmak için SPIRE’dır.&lt;/p&gt;
&lt;p&gt;Her şeyden önce: bu, bir elin parmaklarıyla sayabileceğiniz üç küme için bir araç değil. SPIRE, import ettiğiniz bir kütüphane değil, işlettiğiniz bir kontrol düzlemidir — tam da yavaş üstlenmeniz gereken türden bir yetenek; tıpkı &lt;a href=&quot;/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/&quot;&gt;bölünmeyi hak edene kadar modüler monolitin mikroservisi yendiği gibi&lt;/a&gt;. Ağırlığını ancak gerçekten bir filo işlettiğinizde hak eder — çok küme, çok ekip, çalınan bir &lt;code&gt;Secret&lt;/code&gt;’ın geçemeyeceği bir denetim gereksinimi. Tek paylaşılan bir küme size yetiyorsa, en ucuz güvenli kimlik bilgisi, basmak için makineyi hiç kurmadığınız olandır. Geri kalanı, filoya gerçekten ulaştığınızı varsayar.&lt;/p&gt;
&lt;h2 id=&quot;tek-kuralı-baştan-söyleyeyim&quot;&gt;Tek kuralı baştan söyleyeyim&lt;/h2&gt;
&lt;p&gt;Mekanizmadan önce netleştireyim: &lt;strong&gt;bir küme sınırını geçen kimlik bilgisi kısa ömürlü olmalı ve asla saklanmamalı.&lt;/strong&gt; Aşağıdaki her şey bu tek cümlenin hizmetinde. Sonunda kimlik doğrulama yolunda hâlâ bir yere diske yazılmış uzun ömürlü bir token kalıyorsa, uğruna geldiğiniz özelliği satın almadan karmaşıklık inşa etmişsinizdir — eksik bir index’ten kaçmak için read replica eklemekle aynı yanlış dönüş.&lt;/p&gt;
&lt;p&gt;“Kısa ömürlü ve asla saklanmaz”ın burada kesin bir anlamı var. Kimlik, varsayılan ömrü &lt;strong&gt;bir saat&lt;/strong&gt; olan bir X.509 sertifikası (ya da varsayılan ömrü beş dakika olan bir JWT); workload tarafından yerel bir socket’ten, &lt;strong&gt;sunacak kendine ait bir token’ı olmadan&lt;/strong&gt; çekilir ve ömrünün yarısında otomatik rotate edilir. Yarın çalmaya değecek bir sır yok.&lt;/p&gt;
&lt;h2 id=&quot;spiffe-ve-spire-gerçekte-ne&quot;&gt;SPIFFE ve SPIRE gerçekte ne&lt;/h2&gt;
&lt;p&gt;SPIFFE bir spesifikasyon; SPIRE ise spesifikasyonun tarif ettiği şeyleri basan referans implementasyon. Dört isim tüm tasarımı taşıyor.&lt;/p&gt;
&lt;p&gt;Bir &lt;strong&gt;SPIFFE ID&lt;/strong&gt; bir URI’dir: &lt;code&gt;spiffe://&amp;lt;trust-domain&amp;gt;/&amp;lt;path&amp;gt;&lt;/code&gt;, örneğin &lt;code&gt;spiffe://prod.example.org/ns/argocd/sa/application-controller&lt;/code&gt;. Otorite kısmı &lt;strong&gt;trust domain&lt;/strong&gt;’dir — tek bir mantıksal güven kökü, tek bir CA. Path workload’u tanımlar. Bir workload’un elinde tuttuğunu kanıtladığı isim budur.&lt;/p&gt;
&lt;p&gt;Bir &lt;strong&gt;SVID&lt;/strong&gt; (SPIFFE Verifiable Identity Document) ise kanıttır. İki biçim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;X.509-SVID&lt;/strong&gt; — SPIFFE ID’si URI SAN’ında (CN’de değil, ki bu sonra önemli olacak) bulunan bir X.509 sertifikası. Onunla mTLS yaparsınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT-SVID&lt;/strong&gt; — &lt;code&gt;sub&lt;/code&gt;’ı SPIFFE ID, &lt;code&gt;aud&lt;/code&gt;’unu hedef doğrulayıcıya scope’ladığın imzalı bir JWT. Onu bearer token olarak sunarsınız.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Asıl mesele ömürler. SPIRE’ın sunucu konfigürasyonunda varsayılanlar &lt;code&gt;default_x509_svid_ttl: 1h&lt;/code&gt; ve &lt;code&gt;default_jwt_svid_ttl: 5m&lt;/code&gt;’dir; bu SVID’ler, kendi &lt;code&gt;ca_ttl&lt;/code&gt;’i varsayılan &lt;code&gt;24h&lt;/code&gt; olan bir CA tarafından imzalanır. Bunlar bilerek kısadır: sızdırılmış bir SVID bir saat içinde değersizleşir, ki tam da bu yüzden eskiden token sakladığınız yerde onu kullanmak güvenlidir.&lt;/p&gt;
&lt;p&gt;“Asla saklanmaz”ı mümkün kılan şey, bir SPIRE &lt;strong&gt;agent&lt;/strong&gt;’ı tarafından yerel bir Unix domain socket üzerinden sunulan &lt;strong&gt;Workload API&lt;/strong&gt;’dir (belgelenmiş varsayılan &lt;code&gt;/tmp/spire-agent/public/api.sock&lt;/code&gt;; çoğu kurulum onu &lt;code&gt;/run/spire/sockets/...&lt;/code&gt; altında mount eder). Workload o socket’e bağlanır ve SVID’sini ister. &lt;strong&gt;Bunu yapmak için hiçbir kimlik bilgisi sunmaz.&lt;/strong&gt; Agent, çağıran process’i kernel üzerinden inceleyerek tanır — PID’sini, oradan da container’ını, namespace’ini, service account’unu — ve bir dizi selector’a karşı &lt;em&gt;workload attestation&lt;/em&gt; yapar. Kimlik, &lt;em&gt;hangi sırrı tuttuğuyla&lt;/em&gt; değil, &lt;em&gt;process’in doğrulanabilir biçimde ne olduğuyla&lt;/em&gt; kurulur. Saklanacak bir şeyin olmamasının tüm nedeni o tersine çevirme.&lt;/p&gt;
&lt;p&gt;Altında SPIRE iki bileşen ve iki attestation’dır:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;spire-server&lt;/strong&gt; CA’yı tutar, SVID’leri imzalar ve registration entry’leri saklar. CA’sı varsayılan olarak self-signed, ya da bir &lt;code&gt;UpstreamAuthority&lt;/code&gt; plugin’i aracılığıyla kurumsal PKI’nıza zincirlenmiş bir intermediate’tir — bir kez verdiğiniz ve birlikte yaşadığınız bir karar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spire-agent&lt;/strong&gt; her node’da çalışır. Önce &lt;em&gt;node’u&lt;/em&gt; sunucuya kanıtlar — &lt;strong&gt;node attestation&lt;/strong&gt; — &lt;code&gt;k8s_psat&lt;/code&gt; gibi bir plugin’le; bu, projeli bir service-account token’ını Kubernetes &lt;code&gt;TokenReview&lt;/code&gt; API’si üzerinden doğrular. Node’a yerleştirilmiş bir paylaşılan sır yoktur. Sonra her yerel process için, ona bir SVID vermeden önce &lt;strong&gt;workload attestation&lt;/strong&gt; yapar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bir &lt;strong&gt;registration entry&lt;/strong&gt;, bir SPIFFE ID’yi bir parent’a (node/agent) ve bir selector kümesine (namespace, service account, image) bağlayan sunucu tarafı kayıttır. Politika odur: &lt;em&gt;bu workload, bu şekilde attest edilmiş, bu kimliği alır.&lt;/em&gt; Bu entry’lerin yaşam döngüsünü yönetmek gerçek bir iştir ve süregelen operasyonel maliyetin çoğu oradadır.&lt;/p&gt;
&lt;h2 id=&quot;federasyon-başka-bir-kümeden-gelen-bir-svidye-güvenmek&quot;&gt;Federasyon: başka bir kümeden gelen bir SVID’ye güvenmek&lt;/h2&gt;
&lt;p&gt;Bir trust domain, bir güven köküdür. Çok kümeli bir filo genellikle birden fazla trust domain demektir — küme başına ya da bölge başına bir tane — ve artık A domain’indeki bir workload, B domain’inde basılmış bir SVID’yi doğrulayabilmek zorundadır. İşte bu &lt;strong&gt;federasyon&lt;/strong&gt;’dur ve birimi &lt;strong&gt;trust bundle&lt;/strong&gt;’dır: bir domain’in SVID’lerini doğrulamanızı sağlayan public CA sertifikaları ve JWT imzalama anahtarları (bir JWKS).&lt;/p&gt;
&lt;p&gt;Her domain bir &lt;strong&gt;bundle endpoint&lt;/strong&gt; ayağa kaldırır — güncel bundle’ını sunan bir URL, OIDC’nin &lt;code&gt;jwks_uri&lt;/code&gt;’sinin SPIFFE karşılığı — ve her peer güncel kalmak için onu poll eder. İki profil var ve fark, tam da bootstrap sorusudur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;https_web&lt;/code&gt;&lt;/strong&gt; — endpoint, public bir CA’dan bir Web-PKI sertifikasıyla cephelenir. Peer onu sıradan public güven deposuyla doğrular, yani &lt;strong&gt;bant dışında hiçbir başlangıç bundle’ının değiş tokuş edilmesine gerek yoktur.&lt;/strong&gt; Güven mevcut Web PKI üzerinden bootstrap olur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;https_spiffe&lt;/code&gt;&lt;/strong&gt; — endpoint kendi X.509-SVID’siyle kimlik doğrular. Onunla ilk kez konuşmak için peer’in o domain’in başlangıç bundle’ını zaten elinde tutması gerekir. Yani &lt;strong&gt;ilk bundle bant dışında gelmek zorundadır&lt;/strong&gt;, ondan sonra ileriye dönük olarak en son çekilen bundle kullanılır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bir registration entry üzerinde &lt;code&gt;federatesWith&lt;/code&gt;, bir workload’un kimlik doğrulamasına izin verilen yabancı trust domain’leri listeler; agent sonra o yabancı bundle’ları workload’a Workload API üzerinden teslim eder. Bundle’lar &lt;code&gt;spiffe_refresh_hint&lt;/code&gt; aralığında (genelde beş dakika civarı) yenilenir ve yeni bir imzalama anahtarını, kullanmaya başlamadan birkaç yenileme döngüsü &lt;em&gt;önce&lt;/em&gt; yayınlarsınız ki rotation indiğinde federe peer’ler onu çoktan öğrenmiş olsun.&lt;/p&gt;
&lt;p&gt;“Sıfır statik yetki”nin dürüst şekline dikkat: federasyon, bootstrap güven kararının &lt;strong&gt;yerini değiştirir&lt;/strong&gt;, onu silmez. &lt;code&gt;https_spiffe&lt;/code&gt; ile bir başlangıç bundle’ını elle gönderirsiniz; &lt;code&gt;https_web&lt;/code&gt; ile public CA sistemine yaslanırsınız. Her iki yolda da güven kökü hâlâ &lt;em&gt;bir yerden&lt;/em&gt; gelir — ortadan kaldırdığınız şey, uzun ömürlü, geniş kapsamlı, küme başına kimlik bilgisidir; tek seferlik güven çıpası değil. Bu doğru takastır, ama olduğu gibi adlandırın.&lt;/p&gt;
&lt;h2 id=&quot;apiservera-bir-svidyi-kabul-ettirmek&quot;&gt;apiserver’a bir SVID’yi kabul ettirmek&lt;/h2&gt;
&lt;p&gt;Niyetin Kubernetes API’siyle buluştuğu ve yanılmanın kolay olduğu yer burası. İki mekanizma var ve bugün yalnızca biri temiz.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JWT-SVID-olarak-OIDC yolu (işleyen).&lt;/strong&gt; Kubernetes’e harici bir OIDC issuer’a güvenmesi söylenebilir. SPIRE, &lt;code&gt;/.well-known/openid-configuration&lt;/code&gt; ve SPIRE’ın JWT-SVID imzalama anahtarlarıyla beslenen bir JWKS sunan bir &lt;strong&gt;OIDC Discovery Provider&lt;/strong&gt; ile gelir. Uzak (spoke) kümenin apiserver’ını o provider’a bir OIDC issuer olarak yöneltirsiniz. Controller, yerel Workload API’sinden taze bir JWT-SVID çeker ve onu bir bearer token olarak sunar; apiserver onu SPIRE’ın JWKS’ine karşı doğrular ve bir subject’e map eder. &lt;code&gt;Secret&lt;/code&gt; yok, saklanan token yok — istek başına yeni bir JWT, dakikalar içinde sona eren.&lt;/p&gt;
&lt;p&gt;Bu bir düşünce deneyi değil. Red Hat, SPIFFE/SPIRE desteğini OpenShift 4.20’den itibaren Zero Trust Workload Identity Manager operatörü olarak sevk eder ve tam olarak bu bağlantıyı, OpenShift GitOps’un sevk ettiği bir ürün özelliği olarak değil, bir nasıl-yapılır rehberi olarak belgeler: Argo CD, &lt;code&gt;execProviderConfig&lt;/code&gt; aracılığıyla bir client-go &lt;code&gt;ExecCredential&lt;/code&gt; plugin’i (&lt;code&gt;apiVersion: client.authentication.k8s.io/v1beta1&lt;/code&gt;) kullanır; bu plugin her çağrıda SPIFFE socket’ini (&lt;code&gt;SPIFFE_ENDPOINT_SOCKET&lt;/code&gt;) okur, spoke apiserver’ın beklediği audience’la (&lt;code&gt;SPIFFE_JWT_AUDIENCE&lt;/code&gt;) bir JWT-SVID ister ve onu teslim eder. Kimlik bilgisi kullanım anında imal edilir ve atılır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;X.509-SVID-ile-mTLS yolu (tuzak).&lt;/strong&gt; İçgüdü düz mTLS yapmaktır: controller X.509-SVID’sini sunar, apiserver SPIFFE bundle’ına client CA olarak güvenir, bitti. Bitmedi. Kubernetes client-cert kimlik doğrulaması &lt;strong&gt;kullanıcı adını sertifikanın Subject CN’inden, grupları Subject O’sundan&lt;/strong&gt; türetir — ve SPIFFE ID &lt;strong&gt;URI SAN&lt;/strong&gt;’ında yaşar, ki apiserver client-cert kimlik doğrulaması onu okumaz. Bu yüzden ham bir SPIFFE X.509-SVID bir Kubernetes kullanıcısına map olmaz. mTLS kullanmak için apiserver’ın önüne bir proxy — Ghostunnel ya da Envoy — koyarsınız: SPIFFE mTLS’i terminate eder, URI-SAN SPIFFE ID’sini doğrular ve apiserver’ın anladığı bir kimlikle iletir. Ghostunnel kendi rotate olan SVID’leri için Workload API’yi doğrudan tüketir. Bu işler, ama her apiserver’ın önünde bir hareketli parçadır ve OIDC yolu onu hiç gerektirmez.&lt;/p&gt;
&lt;p&gt;Olgunluk konusunda dürüst olun. Upstream Argo CD’nin native SPIFFE kimlik doğrulaması yoktur; jenerik &lt;code&gt;ExecCredential&lt;/code&gt; plugin örüntüsüyle çalışır ve en olgun ürünleşme OpenShift’inkidir. Flux, SPIRE’ı &lt;em&gt;deploy etmek&lt;/em&gt; için GitOps ile yönetilebilir ve OCI registry kimlik doğrulaması bir JWT-SVID tüketebilir, ama “Flux’u bir uzak kümeye kubeconfig yerine SPIFFE ile doğrula” şeklinde birinci sınıf bir özellik belgelenmemiştir — aynı OIDC/exec-credential/proxy tesisatının üzerinden gider. Native uzak küme SPIFFE kimlik doğrulamasını, etkinleştirdiğiniz bir kutu değil, monte ettiğiniz bir örüntü olarak görün.&lt;/p&gt;
&lt;h2 id=&quot;ne-kırılır-ve-maliyeti-ne&quot;&gt;Ne kırılır ve maliyeti ne&lt;/h2&gt;
&lt;p&gt;Mimari diyagramların atladığı kısım bu. Saklanan bir kimlik bilgisini kaldırdınız; karşılığında bir kimlik servisini kritik yolda sıkı bir bağımlılık hâline getirdiniz ve kısa ömürlü şeyler, uzun ömürlülerin kırılmadığı yerlerde kırılır.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SVID rotation artık bir erişilebilirlik bağımlılığı.&lt;/strong&gt; SVID’ler kısadır — X.509 bir saat, JWT beş dakika — ve ömürlerinin yarısında rotate olur. Agent ya da Workload API ayakta değilse, ya da agent yenilemek için sunucuya ulaşamıyorsa, &lt;strong&gt;SVID sona erer ve kimlik doğrulama durur.&lt;/strong&gt; SPIRE sunucusu her basım ve yenileme için kritik yoldadır. Agent’lar kimlik bilgilerini cache’ler ve &lt;em&gt;kısa&lt;/em&gt; sunucu kesintilerine tolerans gösterir; agent’ın &lt;code&gt;availability_target&lt;/code&gt; ayarı (ayarlanırsa ≥ 24h olmalı) onu erken rotate ettirerek nazik bir kesinti için pay biriktirir. Ama hata modu gerçek ve yeni: kimlik düzlemi ayakta değilse filo kimlik doğrulaması da ayakta değildir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saat kayması (clock skew) artık bir kesinti sınıfı.&lt;/strong&gt; Beş dakikalık bir JWT affetmez. Bir controller ile bir spoke apiserver arasında birkaç dakikalık kayma, geçerli token’ları reddeder. Filo boyunca NTP disiplini, hijyen olmaktan çıkıp sıkı bir gereksinim olur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kararan bir federasyon bundle endpoint’i kümeler arası kimlik doğrulamasını kırar — sessizce, sonradan.&lt;/strong&gt; Bir peer’in bundle endpoint’i yenileme penceresini aşacak kadar ulaşılamazsa ve o domain anahtarlarını rotate ederse, cache’lenmiş bundle’ınız bayatlar ve &lt;strong&gt;domain’ler arası SVID doğrulaması başarısız olur&lt;/strong&gt;, her iki küme tek başına sağlıklı olsa bile. Hata, kesinti anında değil rotation anında ortaya çıkar, ki bu onu teşhis etmesi kötü kılar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kimlik düzlemi artık stateful, HA-kritik bir servis.&lt;/strong&gt; HA’daki SPIRE sunucuları tek bir SQL datastore paylaşır; varsayılan SQLite tek-node’dur, yani production demek, &lt;em&gt;tüm filonun kimlik doğrulayabilme yeteneğinin&lt;/em&gt; bağlı olduğu yüksek erişilebilir bir MySQL/PostgreSQL demektir. Çok küme için bir topoloji seçersiniz — nested SPIRE (bir root sunucunun downstream sunuculara intermediate basması, bir root kesintisinden sağ çıkması) ya da küme başına trust domain’ler arası federasyon — ve her biri kendi operasyonel yüzeyini ekler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operasyonel ağırlık baştan aşağı yeni bir kontrol düzlemi.&lt;/strong&gt; Her kümede sunucu artı agent’lar; registration-entry yaşam döngüsü (workload başına selector’lar, deployment’larla senkron tutulan); upstream-CA kararı; bundle endpoint’ler ve federasyon ilişkileri; OIDC Discovery Provider; ve mTLS yolunda apiserver başına bir proxy. Cevap “bir kubeconfig sakla” olduğunda bunların hiçbiri yoktu.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sıra-neden-önemli&quot;&gt;Sıra neden önemli&lt;/h2&gt;
&lt;p&gt;Adımlar bir menü değil; bir dizidir ve onu atlamak, ekiplerin maliyeti faydası olmadan nasıl aldığıdır.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. SPIRE’ı kur: sunucu + agent’lar, node attestation        (temel)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Controller’a Workload API üzerinden workload SVID’leri bas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Spoke apiserver’a SPIRE’a güvendir (önce OIDC yolu)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Kümeler boyunca trust domain’leri federe et&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. Saklanan kubeconfig Secret’larını sil                     (kazanç)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;adım tüm mesele ve yalnızca 1–4 gerçekten işlediğinde güvenli. Yaygın hata, eski &lt;code&gt;Secret&lt;/code&gt;’ı “yedek olarak” tutmaktır — bu da uzun ömürlü filo geneli kimlik bilgisinin hâlâ atıl, hâlâ yedeklerinizde, hâlâ saldırganın aldığı şey olması ve artık ayrıca SPIRE çalıştırıyor olmanız demektir. Kontrol düzleminin parasını ödediniz ve yükümlülüğü tuttunuz. Ya saklanan kimlik bilgisi yok olur ya da bunu yapmadınız; yarım kredi yoktur.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tersi hata, sıra fayda verecek hâle gelmeden SPIRE’a uzanmaktır — &lt;code&gt;Secret&lt;/code&gt;’ı gayet iyi olacak birkaç küme için federe bir kimlik düzlemi ayağa kaldırmak. Bu, güvenlik rozeti takmış &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;ileride-lazım-olur karmaşıklığıdır&lt;/a&gt;: henüz sahip olmadığınız bir patlama yarıçapı için kurulmuş makine. Makineyi haklı çıkaran şey filo, denetim gereksinimi ve çalınan bir token’ın patlama yarıçapıdır. O çizginin altında, en ucuz güvenli kimlik bilgisi gerçekten de hiç basmak zorunda olmadığınız sıkıcı olanıdır.&lt;/p&gt;
&lt;p&gt;Bu yaklaşımın gerçekten bittiği yer açık: statik bir küme başına kimlik bilgisinin kabul edilemez bir patlama yarıçapı olduğu kadar büyük bir filo, stateful, HA, kritik yolda bir kimlik servisini çalıştıracak ve saatlerini senkron tutacak operasyonel olgunlukla. Oraya varmış bir ekip o olgunluğun parasını zaten başka yerde öder — ve o noktada sıfır statik yetki bir süsleme değil, patlama yarıçapının baştan beri talep ettiği kimlik bilgisi modelidir. Daha önce değil.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://spiffe.io/docs/latest/spiffe-about/overview/&quot;&gt;SPIFFE&lt;/a&gt; ve &lt;a href=&quot;https://spiffe.io/docs/latest/spire-about/&quot;&gt;SPIRE&lt;/a&gt; belgeleri kimlik modelini ve sunucu/agent bileşenlerini tanımlar; &lt;a href=&quot;https://spiffe.io/docs/latest/spiffe-specs/spiffe_federation/&quot;&gt;SPIFFE Federation spec’i&lt;/a&gt; ise bu tasarımın dayandığı bundle-endpoint profillerini belirtir.&lt;/p&gt;</content:encoded><category>kubernetes</category><category>gitops</category><category>spiffe</category><category>security</category><category>architecture</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>eBPF ile GPU FinOps</title><link>https://sade.dev/tr/systems/ebpf-ile-gpu-finops/</link><guid isPermaLink="true">https://sade.dev/tr/systems/ebpf-ile-gpu-finops/</guid><description>GPU harcaması neden cgroup ve cAdvisor metriklerine görünmez ve eBPF chargeback için GPU zamanını ve belleğini ekiplere nasıl atfeder — ve CUDA sınırındaki, DCGM’in kapatması gereken dürüst limit.</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir GPU node’u, bir raf dolusu CPU makinesinden saatte daha fazlaya mal olur ve finansın onun hakkında sorduğu soru, her şey hakkında sorduğu soruyla aynıdır: &lt;strong&gt;bunu kim kullandı ve ne kadar?&lt;/strong&gt; CPU, bellek ve disk için bunu cevaplayabilirsiniz — cgroup’lar bunu process başına hesaplar, cAdvisor pod başına raporlar, Prometheus ekip başına toplar. GPU içinse çoğu kümede dürüst cevap, kimsenin bilmediğidir. Filodaki en pahalı kaynağın parasını ödüyorsunuz ve onun tek bir kuruşunu bile atfedemiyorsunuz.&lt;/p&gt;
&lt;p&gt;Bu, doldurmayı unuttuğunuz bir araç boşluğu değil. Yapısaldır: GPU, &lt;strong&gt;yığınınızın geri kalanının dayandığı muhasebe düzleminin dışındadır.&lt;/strong&gt; Bu yazı, bunun nedeni ve atfetmeyi geri kazanmak için eBPF kullanımı ile ilgili — ve eBPF’in ne görebildiği ve ne göremediğine dair sert bir çizgiyle birlikte, çünkü buradaki en pahalı hata, bir aracın fiziksel olarak cevaplayamadığı bir soruyu cevapladığına inanmaktır.&lt;/p&gt;
&lt;h2 id=&quot;gpu-mevcut-metriklerinize-neden-görünmez&quot;&gt;GPU mevcut metriklerinize neden görünmez&lt;/h2&gt;
&lt;p&gt;Üç ayrı körlük üst üste yığılır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cgroup’lar GPU’yu hesaplamaz.&lt;/strong&gt; Bir cgroup, CPU, bellek, block I/O ve PID’leri hesaplayan ve sınırlayan bir kernel yapısıdır. GPU bunların hiçbiri değil. Compute ve framebuffer (VRAM), NVIDIA kernel driver’ı ve userspace CUDA runtime’ı tarafından yönetilir ve cgroup hesaplamasına opaktır. Bir &lt;code&gt;device&lt;/code&gt; cgroup controller’ı var, ama o yalnızca device node’larına (&lt;code&gt;/dev/nvidia0&lt;/code&gt; ve dostları) &lt;em&gt;erişimi geçitler&lt;/em&gt; — izin ver ya da reddet. Tek bir saniye GPU zamanını ya da bir megabayt VRAM’i hesaplamaz. Yani size pod başına CPU ve bellek veren tüm cAdvisor → Prometheus boru hattının, kuruluş gereği, &lt;strong&gt;GPU hakkında söyleyecek hiçbir şeyi yoktur.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;nvidia.com/gpu&lt;/code&gt; bir tahsis sayısıdır, bir kullanım sinyali değil.&lt;/strong&gt; Kubernetes GPU’ları, onları kubelet’e extended resource &lt;code&gt;nvidia.com/gpu&lt;/code&gt; olarak kaydeden NVIDIA device plugin’i üzerinden öğrenir. Pod’lar bunu bir integer olarak ister — bütün GPU’lar. Scheduler istenen &lt;em&gt;sayıyı&lt;/em&gt; mevcut &lt;em&gt;sayıyla&lt;/em&gt; eşler. Tahsis katmanının bildiği tek şey budur. &lt;strong&gt;Bir GPU istemek, bir GPU kullanmak değildir.&lt;/strong&gt; Bir pod bütün bir cihazı tutabilir — ve bütün cihazın faturasını ödeyebilir — onu %3’te sürerken. Üzerinden chargeback yapabileceğiniz tahsis sayısı, tam da tüketim hakkında size hiçbir şey söylemeyen sayıdır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;nvidia-smi&lt;/code&gt;’nin kullanım sayısı bile size yalan söyler.&lt;/strong&gt; &lt;code&gt;nvidia-smi&lt;/code&gt;’ye uzanırsınız ve &lt;code&gt;utilization.gpu&lt;/code&gt; alanı kurtuluş gibi görünür. Sandığınız şey değil. NVML onu şöyle tanımlar: &lt;em&gt;örnekleme periyodu boyunca bir ya da daha fazla kernel’in çalıştığı zamanın yüzdesi.&lt;/em&gt; &lt;strong&gt;Zamansal varlığı — GPU hiç meşgul muydu — ölçer, ne kadarının meşgul olduğunu değil.&lt;/strong&gt; Onlarca SM’den birini işgal eden tek thread’lik bir kernel %100’e yakın raporlayabilir. Microsoft, A100’ler üzerinde 8B parametreli bir modeli servis ederken bellek-bağımlı decode fazında %10’un altında compute kullanımı raporlamıştır — naif bir “kullanım” okuması o GPU’ları dolu sayacakken. &lt;code&gt;utilization.gpu&lt;/code&gt; üzerinden chargeback yaparsanız, silikon neredeyse boştayken “meşgul” diyen bir sayı üzerinden faturalandırıyorsunuzdur.&lt;/p&gt;
&lt;p&gt;Yani: tahsis katmanı kullanımı-değil-sayıyı bilir, cgroup’lar hiçbir şey bilmez ve tek kolay yüzde yanlış şeyi ölçer. Boşluk budur.&lt;/p&gt;
&lt;h2 id=&quot;i̇ki-soru-ve-neden-farklı-araçlar-gerektirdikleri&quot;&gt;İki soru ve neden farklı araçlar gerektirdikleri&lt;/h2&gt;
&lt;p&gt;Herhangi bir araçtan önce iki soruyu ayırın, çünkü onları karıştırmak temel hatadır — &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;bir aracın çözdüğü problemi eldeki problemle karıştırmakla&lt;/a&gt; aynı şekil.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atfetme — GPU işini &lt;em&gt;kim&lt;/em&gt; yaptı ve &lt;em&gt;ne kadar&lt;/em&gt;?&lt;/strong&gt; Bu FinOps sorusudur. Pod başına, ekip başına GPU aktivitesi hesabı gerektirir: hangi workload kernel başlattı, VRAM tahsis etti, veri taşıdı ve ne kadar süreyle. Chargeback bunun üzerinden koşar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verimlilik — silikon &lt;em&gt;ne kadar iyi&lt;/em&gt; kullanıldı?&lt;/strong&gt; SM’ler gerçekten doluydu mu, tensor core’lar aktif miydi, darboğaz bellek bant genişliği miydi? Bu performans sorusudur. Bir ekibin harcamasının &lt;em&gt;haklı&lt;/em&gt; olup olmadığını söyler, ama &lt;em&gt;kimin&lt;/em&gt; harcaması olduğunu söyleyemez.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunlar iki farklı ölçüm düzlemine eşlenir ve aşağıdaki her şeyi örgütleyen kural şu: &lt;strong&gt;eBPF atfetmeyi cevaplar; GPU’nun kendi sayaçları verimliliği cevaplar. Hiçbiri diğerinin yerine geçmez.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;ebpfin-görebildiği-kontrol-düzlemi&quot;&gt;eBPF’in görebildiği: kontrol düzlemi&lt;/h2&gt;
&lt;p&gt;eBPF kernel’de çalışır ve kprobe’lara, uprobe’lara, tracepoint’lere ve syscall’lara takılır. Okunacak bir GPU-kullanımı tracepoint’i yok — ama GPU &lt;em&gt;işinin talep edildiği&lt;/em&gt; iki yüzey var ve ikisi de kernel’den görünür:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Driver ioctl sınırı.&lt;/strong&gt; Her parça GPU işi — kernel gönderimi, bellek tahsisi, senkronizasyon — sonunda &lt;code&gt;/dev/nvidiactl&lt;/code&gt;’a ve &lt;code&gt;/dev/nvidia0…N&lt;/code&gt;’e bir &lt;code&gt;ioctl()&lt;/code&gt; olur. Driver’ın giriş noktalarındaki (&lt;code&gt;nvidia_unlocked_ioctl&lt;/code&gt;, &lt;code&gt;nvidia_open&lt;/code&gt;) bir kprobe o trafiği görür. (Kapalı driver tarihsel olarak yalnızca tek bir tracepoint, donanım hata olayları için &lt;code&gt;nvidia:nvidia_dev_xid&lt;/code&gt;, sunar; geri kalan her şey kprobe’lanır.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CUDA kütüphane sınırı.&lt;/strong&gt; &lt;code&gt;libcuda.so&lt;/code&gt; / &lt;code&gt;libcudart.so&lt;/code&gt; üzerindeki bir uprobe API’nin kendisini izler: &lt;code&gt;cuLaunchKernel&lt;/code&gt;, &lt;code&gt;cuMemAlloc&lt;/code&gt;, &lt;code&gt;cuMemcpyHtoD&lt;/code&gt;, &lt;code&gt;cuStreamSynchronize&lt;/code&gt; ve dostları. Bir giriş uprobe’unu bir dönüş uretprobe’uyla eşlemek her çağrının süresini ölçer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunun &lt;em&gt;atfetme&lt;/em&gt; olmasının nedeni, eBPF’in her hook noktasında çağıran PID/TGID’yi ve cgroup’u native olarak okumasıdır — &lt;code&gt;bpf_get_current_pid_tgid&lt;/code&gt; ve cgroup id tam orada. Bu size &lt;strong&gt;PID → cgroup → pod → namespace → ekip&lt;/strong&gt; zincirini verir, uygulama değişikliği olmadan ve NVIDIA’nın işbirliği olmadan. Pod başına şunları alırsınız: kernel-başlatma sayıları, başlatma boyutları, VRAM tahsis boyutları, memcpy hacmi ve yönü, çağrı zamanlaması. Bu, tamamen sınırın kernel tarafından türetilen, gerçek ve savunulabilir bir chargeback sinyalidir.&lt;/p&gt;
&lt;p&gt;Buradaki ekosistem genç ama gerçek. Primitifler bugün işliyor — CUDA kütüphanelerini uprobe’layan ve nvidia ioctl yolunu kprobe’layan çalışan yazılar ve eğitimler var ve &lt;code&gt;bpftime&lt;/code&gt; eBPF mantığını GPU olaylarına bağlamayı keşfediyor. Tetragon (Cilium), Kubernetes pod kimliğiyle jenerik &lt;code&gt;process_uprobe&lt;/code&gt;, &lt;code&gt;process_kprobe&lt;/code&gt; ve ioctl izleme sevk eder, yani CUDA sembollerine ya da ioctl yoluna &lt;em&gt;yöneltilebilir&lt;/em&gt; — ama bunun sevk edilen bir “GPU FinOps” özelliği değil, yazdığınız bir &lt;code&gt;TracingPolicy&lt;/code&gt; olduğunu anlayın. Henüz baskın, anahtar-teslim bir eBPF chargeback ürünü yok. Size “tak-çalıştır eBPF GPU FinOps” satan biri, ekosistemin şu an ulaştığından öteye satıyordur.&lt;/p&gt;
&lt;h2 id=&quot;ebpfin-göremediği-silikon&quot;&gt;eBPF’in göremediği: silikon&lt;/h2&gt;
&lt;p&gt;Dürüst limit bu ve bir sürümde düzeltilecek bir olgunluk problemi değil — verinin nerede yaşadığının fiziği.&lt;/p&gt;
&lt;p&gt;eBPF &lt;strong&gt;kontrol düzlemini&lt;/strong&gt; görür: API çağrıları, ioctl’lar, tahsis boyutları, başlatma sayıları ve izleyebildiğiniz yerde gönderim-tamamlanma zamanlaması. GPU’nun &lt;em&gt;içini&lt;/em&gt; &lt;strong&gt;görmez&lt;/strong&gt;. SM occupancy’sini, tensor-core kullanımını ya da erişilen bellek bant genişliğini okuyamaz, çünkü bunlar &lt;em&gt;cihazda&lt;/em&gt; yaşayan ve yalnızca NVML / DCGM / CUPTI üzerinden açığa çıkan donanım performans sayaçlarıdır. eBPF’in onlara giden bir yolu yoktur. Bir pod’un on bin kernel başlattığını ve 40 GB VRAM tahsis ettiğini söyleyebilir; o kernel’lerin SM’leri doyurup doyurmadığını ya da onları %90 boş bırakıp bırakmadığını söyleyemez.&lt;/p&gt;
&lt;p&gt;İkinci, daha incelikli bir limit var. eBPF’in takılabildiği ioctl sınırında bile, &lt;em&gt;payload’lar&lt;/em&gt; büyük ölçüde opaktır. Driver’ın komut yapıları (&lt;code&gt;NV_ESC_*&lt;/code&gt; Resource Manager API’si) karmaşık ve fiilen proprietary. Bir ioctl’ın &lt;em&gt;olduğunu&lt;/em&gt; görebilirsiniz — komut numarasını, çağıran PID’yi, zamanlamayı — ama keyfi bir RM payload’unun semantik içeriğini decode etmek pratik dışı ve kırılgandır. İşin gerçeğini ve atfetmesini alırsınız, anlamının bedava bir okumasını değil.&lt;/p&gt;
&lt;p&gt;Cazip bir kapıyı kapatan bir not: NVIDIA’nın açık kernel modülleri (&lt;code&gt;open-gpu-kernel-modules&lt;/code&gt;, Turing ve sonrası) &lt;strong&gt;kernel arayüz katmanını&lt;/strong&gt; açar — modül init’ini, ioctl giriş noktalarını, &lt;code&gt;NV_ESC_*&lt;/code&gt; komut yüzeyini. Bu, &lt;em&gt;neyi&lt;/em&gt; hook’layacağınızı anlamanıza gerçekten yardımcı olur. Ama GPU’nun beyni kapalı kalır: Turing+’ta yönetimin çoğu GPU üzerindeki GSP firmware’inde koşar ve o firmware — modüllerin ihtiyaç duyduğu user-mode driver bileşenleriyle birlikte — hâlâ kapalı olarak dağıtılır. Kernel modüllerini açmak, eBPF’e donanım performans sayaçlarını &lt;strong&gt;açmaz&lt;/strong&gt;. Cihaz-içi limit değişmemiştir. Açık modüllerin eBPF’e SM occupancy okuttuğunu iddia eden biri, mevcut kanıtlara göre yanılıyordur.&lt;/p&gt;
&lt;h2 id=&quot;hâlâ-ihtiyaç-duyduğunuz-userspace-yolu-dcgm&quot;&gt;Hâlâ ihtiyaç duyduğunuz userspace yolu: DCGM&lt;/h2&gt;
&lt;p&gt;eBPF silikonu göremediği için, cevabın verimlilik yarısı userspace’ten gelir ve standart araç, Prometheus için &lt;strong&gt;dcgm-exporter&lt;/strong&gt; ile NVIDIA &lt;strong&gt;DCGM&lt;/strong&gt; (Data Center GPU Manager)’dır. DCGM, GPU’nun donanım sayaçlarını driver üzerinden okur ve eBPF’in erişemediği alanları açığa çıkarır:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DCGM_FI_PROF_SM_ACTIVE&lt;/code&gt; — bir multiprocessor’da en az bir warp’ın aktif olduğu zamanın oranı, hepsinin ortalaması.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DCGM_FI_PROF_SM_OCCUPANCY&lt;/code&gt; — resident warp’ların desteklenen maksimuma oranı: &lt;em&gt;gerçek&lt;/em&gt; occupancy.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DCGM_FI_PROF_PIPE_TENSOR_ACTIVE&lt;/code&gt; — tensor pipe’ın aktif olduğu döngülerin oranı.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DCGM_FI_PROF_DRAM_ACTIVE&lt;/code&gt; — bir bellek-bant-genişliği vekili.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DCGM_FI_DEV_FB_USED&lt;/code&gt; — gerçekten kullanımda olan framebuffer (VRAM).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunlar size bir ekibin pahalı tahsisinin ekmeğini kazanıp kazanmadığını söyleyen sayılardır. DCGM onları pod’lara, her GPU UUID’yi onu tutan pod’a eşleyen kubelet &lt;strong&gt;Pod Resources API&lt;/strong&gt;’si (&lt;code&gt;/var/lib/kubelet/pod-resources&lt;/code&gt; adresindeki bir gRPC servisi) üzerinden atfeder.&lt;/p&gt;
&lt;p&gt;Ama DCGM’in atfetmesinin, tam da eBPF’in yerini hak ettiği yerde sert bir kenarı var: &lt;strong&gt;DCGM cihaz-seviyesi metrikler üretir, bu yüzden tek bir fiziksel GPU’yu paylaşan tüketicileri ayırt edemez.&lt;/strong&gt; Time-slicing ya da MPS (Multi-Process Service) altında, bir GPU’yu paylaşan tüm pod’lar &lt;em&gt;aynı, kopyalanmış&lt;/em&gt; cihaz-seviyesi değerleri alır — &lt;code&gt;DCGM_FI_DEV_FB_USED&lt;/code&gt; dahil. NVIDIA’nın kendi exporter’ı, time-slicing etkinken metrikleri container’larla ilişkilendirmediğini belgeler; &lt;code&gt;--kubernetes-virtual-gpus=true&lt;/code&gt; ile her paylaşan pod tüm fiziksel GPU’nun durumunu aynalar. MIG altında atfetme, keyfi pod’lara değil GPU-instance seviyesine kayar. Yani tam da paylaşılan-GPU durumunda — ki bu durum, bütün-GPU tahsisi para israf ettiği &lt;em&gt;için&lt;/em&gt; var olur — DCGM size kimin ne tükettiğini söyleyemez. eBPF’in PID-seviyesi izlemesi söyleyebilir. İki araç, her birinin en zayıf olduğu dikişte birbirini tamamlar.&lt;/p&gt;
&lt;p&gt;(En derin verimlilik profilleme için &lt;strong&gt;CUPTI&lt;/strong&gt;, CUDA Profiling Tools Interface var; DCGM’in açığa çıkarmadığı sayaçları okuyabilir — ama Nsight Compute gibi metrik-replay profilleme kernel’leri birden çok kez yeniden çalıştırır ve ağır overhead taşır. Bu bir profilleme-oturumu aracıdır, her zaman açık tenant başına telemetri değil. DCGM düşük-overhead’li, örnekleme-bazlı sürekli yoldur; CUPTI ağır, derin olandır.)&lt;/p&gt;
&lt;h2 id=&quot;kernel-vs-userspace-takası-açıkça&quot;&gt;Kernel-vs-userspace takası, açıkça&lt;/h2&gt;























&lt;div class=&quot;table-wrap&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Düzlem&lt;/th&gt;&lt;th&gt;Araç&lt;/th&gt;&lt;th&gt;Görür&lt;/th&gt;&lt;th&gt;Göremez&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Kernel (kontrol)&lt;/td&gt;&lt;td&gt;eBPF&lt;/td&gt;&lt;td&gt;PID→pod atfetme, başlatma sayıları, tahsis boyutları, memcpy hacmi, çağrı zamanlaması — time-slicing altında pod başına &lt;em&gt;dahil&lt;/em&gt;&lt;/td&gt;&lt;td&gt;cihaz-içi SM/tensor occupancy, bellek bant genişliği, ioctl payload içleri&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Userspace (cihaz)&lt;/td&gt;&lt;td&gt;DCGM&lt;/td&gt;&lt;td&gt;gerçek SM occupancy, tensor aktivitesi, DRAM aktivitesi, kullanılan VRAM — donanım sayaçlarından&lt;/td&gt;&lt;td&gt;paylaşılan GPU altında (time-slicing/MPS) pod başına atfetme: tüm paylaşanlar aynı değerleri alır&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;eBPF düşük-overhead’lidir, uygulama değişikliği gerektirmez, vendor işbirliği gerektirmez ve PID/cgroup üzerinden native atfeder — &lt;em&gt;kim ve ne kadar&lt;/em&gt; için doğru araç odur. DCGM silikonu okur — &lt;em&gt;ne kadar iyi&lt;/em&gt; için doğru araç odur. Maliyetler de dürüst: uprobe’lar gerçek overhead taşır ve CUDA sembolleri &lt;strong&gt;versiyonludur&lt;/strong&gt; (&lt;code&gt;cuMemAlloc@CUDA_11.0&lt;/code&gt; vs &lt;code&gt;@CUDA_12.0&lt;/code&gt;), yani probe’lar dinamik sembol çözümü gerektirir ve driver yükseltmeleri boyunca kırılır; CUDA API çağrıları saniyede on binden fazla atabilir, yani handler’lar ucuz kalmalı yoksa ölçtükleri workload’u yavaşlatırlar. DCGM kendi örnekleme overhead’i olan userspace polling’dir ve yalnızca belirli sayaç gruplarının birlikte okunabildiği bir donanım kısıtı taşır.&lt;/p&gt;
&lt;h2 id=&quot;i̇kisine-de-neden-hangi-sırayla-ihtiyacınız-var&quot;&gt;İkisine de neden, hangi sırayla ihtiyacınız var&lt;/h2&gt;
&lt;p&gt;Yanlış dönüş, bir aracı seçip ona diğer aracın sorusunu sormaktır — ekipleri DCGM’in cihaz-seviyesi sayıları üzerinden faturalandırıp bir GPU’yu paylaşan herkesi sessizce fazla yüklemek, ya da eBPF’in başlatma sayılarına verimlilik vekili olarak güvenip meşgul görünen bir workload’un iyi kullanıldığı sonucuna varmak. Gerçekten işleyen sıra:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. eBPF: GPU işini pod/ekibe atfet           (kim, ne kadar — fatura)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. DCGM: cihaz-içi verimliliği ölç           (ne kadar iyi — haklı mıydı)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Onları pod kimliğinde birleştir            (ekibin harcaması VE verimliliği)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Atfetme önce, çünkü finansın gerçekten sorduğu soru o ve mevcut yığınınızın hiç cevaplayamadığı o. Verimlilik sonra, çünkü faturayı bir karara dönüştürür — pahalı bir tahsisi %4 occupancy’de tutan bir ekip, artık &lt;em&gt;görebileceğiniz&lt;/em&gt; bir &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;ileride-lazım-olur kapasite vergisi&lt;/a&gt; ödüyordur; oysa önceden GPU, kimsenin açamadığı düz bir kalemdi. Pod kimliğinde birleşince, nihayet her diğer kaynak için sahip olduğunuz şeye sahip olursunuz: atfedilmiş harcama, üzerinde harekete geçecek verimlilik bağlamıyla. İki düzlem gerektirmesinin nedeni, görünmez olmasının nedeniyle aynıdır — silikon muhasebe düzleminde hiç yaşamadı ve tek bir probe bile sınırı kat etmiyor.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://docs.nvidia.com/datacenter/dcgm/latest/installation/install-dcgm-exporter.html&quot;&gt;DCGM exporter belgeleri&lt;/a&gt; pod-atfetme yolunu, &lt;a href=&quot;https://docs.nvidia.com/datacenter/dcgm/latest/user-guide/feature-overview.html&quot;&gt;DCGM özellik genel bakışı&lt;/a&gt; ise profilleme alanlarını tanımlar; bunun yaslandığı eBPF primitifleri &lt;a href=&quot;https://ebpf.io/what-is-ebpf/&quot;&gt;ebpf.io&lt;/a&gt; ve &lt;a href=&quot;https://tetragon.io/&quot;&gt;Tetragon&lt;/a&gt;’da belgelenmiştir.&lt;/p&gt;</content:encoded><category>ebpf</category><category>gpu</category><category>finops</category><category>observability</category><category>kubernetes</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Dead-Letter Kuyruğunu Daha Kötü Yapmadan Replay Etmek</title><link>https://sade.dev/tr/notes/dead-letter-kuyrugunu-guvenle-replay-etmek/</link><guid isPermaLink="true">https://sade.dev/tr/notes/dead-letter-kuyrugunu-guvenle-replay-etmek/</guid><description>Kesinti sırasında DLQ doldu, geri istiyorsun. Naif replay kuyruğu yeniden zehirler ya da yan etkileri ikinci kez tetikler. Güvenli şekil: reset, dry-run, önce sandbox, select.</description><pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Aşağı akıştaki bir API yirmi dakika düştü. Dört bin mesaj retry’larını tüketip dead-letter kuyruğuna düştü. API artık geri geldi, mesajlar hâlâ geçerli ve bariz hamle onları geldikleri yere geri koymak. Broker konsoluna uzanıp &lt;code&gt;orders.dlq&lt;/code&gt;’dan &lt;code&gt;orders&lt;/code&gt;’a mesaj taşımaya başlıyorsun.&lt;/p&gt;
&lt;p&gt;Orada dur. Bir dead-letter kuyruğu bir bekleme alanıdır, mezarlık değil — ama mesajları oradan çıkarmak yüklü bir operasyondur ve “sadece geri taşı”, atlatılmış bir kesintiyi ikinci bir olaya çevirmenin yoludur.&lt;/p&gt;
&lt;h2 id=&quot;naif-bir-replayin-işi-daha-kötü-yapmasının-iki-yolu&quot;&gt;Naif bir replay’in işi daha kötü yapmasının iki yolu&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Kuyruğu yeniden zehirler.&lt;/strong&gt; DLQ’n nadiren sadece kesintinin kurbanlarını tutar. Aralarına gerçek bir nedenle ölen mesajlar karışmıştır — bozuk bir payload, bir bug, yönlendirilemeyen bir URN — ve &lt;em&gt;onlar&lt;/em&gt; replay ettiğin an yine başarısız olacak. Replay’in her şeyi körlemesine geri kürelerse, gerçekten bozuk mesajlar başarısız olur, tekrar DLQ’ya düşer, sen onları yine replay edersin. CPU yiyen, logları dolduran ve aslında başarılı olacak mesajları gömen bir döngü.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yan etkileri yeniden tetikler.&lt;/strong&gt; Üç denemeden sonra dead-letter’a düşen bir mesaj her seferinde &lt;em&gt;kısmen&lt;/em&gt; çalışmış olabilir. Kart çekildi ama onay e-postası patladı; mesaj başarısız oldu, retry oldu, dead-letter’a düştü. Onu naifçe replay et, handler baştan sona yine çalışır — şimdi nihayet gönderdiği e-postanın üstüne ikinci bir tahsilat. Kuyruk bir mesaj taşıdı; müşterin iki kez faturalandı.&lt;/p&gt;
&lt;p&gt;İki tuzak da replay’i “byte’ları geri taşı” sanmaktan gelir. Öyle değil. “&lt;strong&gt;Bir mesajı sıfırla ve güvenle yeniden işle&lt;/strong&gt;” — ve bu kelimelerin her biri bir emniyet kilidi.&lt;/p&gt;
&lt;h2 id=&quot;sıfırla-ama-kimliği-koru&quot;&gt;Sıfırla, ama kimliği koru&lt;/h2&gt;
&lt;p&gt;Dead-letter’a düşmüş bir mesaj, nasıl öldüğünü anlatan ek bir blok taşır — neden, orijinal kuyruk, deneme sayısı. Onu replay etmek için &lt;strong&gt;sıfırlaman&lt;/strong&gt; gerekir: o bloğu düşür ve deneme sayacını sıfıra geri al ki taze bir retry bütçesi alsın, anında yeniden tükenmesin.&lt;/p&gt;
&lt;p&gt;Ama reset, &lt;em&gt;yeni&lt;/em&gt; ile aynı şey değil. Mesajı tanımlayan her şeyi koru — id’sini, payload’ını ve hepsinden önemlisi korelasyon kimliğini (&lt;code&gt;trace_id&lt;/code&gt;). İki nedenle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;İzleyemediğin replay edilmiş bir mesaj operasyonel bir kör nokta. &lt;code&gt;trace_id&lt;/code&gt;’yi koru, replay orijinal hatayla aynı trace’te görünsün.&lt;/li&gt;
&lt;li&gt;Korunan mesaj id’si, replay’i &lt;strong&gt;retry’a güvenli&lt;/strong&gt; kılan şey. Tüketicilerin &lt;a href=&quot;/tr/notes/idempotency-ayni-mesaj-iki-kez/&quot;&gt;idempotent&lt;/a&gt; ise — mesaj id’si üzerinden dedupe ediyorsa — zaten yarı başarılı olmuş bir mesajı replay etmek dedupe tarafından yakalanır, yeniden çalıştırılmaz. Idempotency ve replay ortaktır: idempotency, bir kuyruğu nefesini tutmadan replay etmeni sağlayan şeydir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;önce-productiona-doğrultma&quot;&gt;Önce production’a doğrultma&lt;/h2&gt;
&lt;p&gt;Reset edilmiş ve idempotent olsa bile, bir replay canlı bir sisteme yazmadır. Güveni aşamalarla kazan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dry-run.&lt;/strong&gt; DLQ’yu oku ve neyin replay &lt;em&gt;edileceğini&lt;/em&gt; raporla — sayılar, hedefler, nedenler — ve her mesajı dokunmadan geri koy. Tek el atmadan patlama yarıçapını öğrenirsin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sandbox.&lt;/strong&gt; Replay’i, tüketicilerinin dış yan etkileri stub’lanmış production dışı bir kuyruğa yönlendir. Mesajlar gerçek handler mantığından akar; kimse faturalandırılmaz. “İyi” mesajların gerçekten iyi olduğunu burada öğrenirsin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sonra production&lt;/strong&gt;, ilk ikisi sıkıcı hâle geldiğinde.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sıra, herhangi bir tek adımdan daha önemli. Mesajlar “iyi görünüyor” diye doğrudan production’a replay etmek, ilk paragraftaki broker konsolunda onları taşıyan güvenin aynısıdır.&lt;/p&gt;
&lt;h2 id=&quot;select-et-saçma&quot;&gt;Select et; saçma&lt;/h2&gt;
&lt;p&gt;Kesintinin kurbanları ortak bir imza taşır — bir zaman penceresi, bir hata &lt;code&gt;reason&lt;/code&gt;’ı, bir URN. &lt;em&gt;O kümeyi&lt;/em&gt; replay et, tüm kuyruğu değil. Reason’a göre seçmek, “timeout’tan ölen 4.000’i replay et” ile “4.000 timeout artı yine geri gelecek 30 gerçekten zehirli mesajı replay et” arasındaki farktır. Zehirli mesajlar replay etme sorunun değil; &lt;em&gt;düzeltme&lt;/em&gt; sorunun ve onları görebildiğin DLQ’da kalmalılar.&lt;/p&gt;
&lt;h2 id=&quot;resetleyemeyeceğin-yan-etki&quot;&gt;Resetleyemeyeceğin yan etki&lt;/h2&gt;
&lt;p&gt;İşte dürüst olmak gerekirse zor kısım. Reset envelope’u temizler; idempotency birebir replay’leri dedupe eder; sandbox sen test ederken production’ı korur. Ama production’a &lt;em&gt;kasıtlı&lt;/em&gt; bir replay — gerçek olanı, aslında istediğin — handler’ı çalıştırır ve handler ne yapıyorsa onu yapar: tahsilat çeker, e-posta atar, üçüncü tarafı çağırır. Idempotency bir &lt;em&gt;kopyayı&lt;/em&gt; durdurur; &lt;em&gt;amaçlanan&lt;/em&gt; yeniden işlemenin yan etkileriyle birlikte işini yapmasını durdurmaz.&lt;/p&gt;
&lt;p&gt;Temiz çözüm, bir handler’a replay çalıştırdığını bildirip zaten olmuş dış etkileri atlamasını sağlamak — veritabanı yazmasını yeniden yap, ama e-postayı yeniden gönderme. İş şu ki &lt;em&gt;o bayrağı nereye koyacaksın&lt;/em&gt;: donmuş bir mesaj envelope’unda ekleyecek yer yok. Cevap, dağıtık izlemenin aynı kısıt için kullandığıyla aynı — onu &lt;strong&gt;bant dışında&lt;/strong&gt; taşı, mesajın yanında giden ve runtime’ın handler’a sunduğu bir transport header olarak. Handler’ın kontrol edebileceği, yalnızca replay edilmiş mesajlarda set edilen, normal yolda hiçbir maliyeti olmayan bir &lt;code&gt;replay-bypass&lt;/code&gt; işareti. En zahmetli parça ve en sona bırakılacak doğru parça — reset, dry-run, sandbox ve select replay’i rutin olacak kadar güvenli hâle getirdikten sonra.&lt;/p&gt;
&lt;p&gt;Bir dead-letter kuyruğu, çeyreğin en kötü gününde sistemindeki en faydalı şeydir. Onu replay etmeyi olduğu şey gibi ele al — canlı trafiği yeniden işlemek — ve faydalı kalır. Byte taşımak gibi ele al ve DLQ’nun devam filmi gelir.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://babelqueue.com/docs/spec/1.x/redrive-and-replay&quot;&gt;BabelQueue redrive-and-replay spec’i&lt;/a&gt; bu sıfırla-ve-yeniden-işle şeklini standartlaştırır; &lt;a href=&quot;https://github.com/BabelQueue/babelqueue-examples/tree/main/dlq-redrive&quot;&gt;&lt;code&gt;dlq-redrive&lt;/code&gt; örneği&lt;/a&gt; ise onun çalıştırılabilir bir hâlidir.&lt;/p&gt;</content:encoded><category>queue</category><category>dead-letter-queue</category><category>reliability</category><category>operations</category><category>architecture</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Polyglot Bir Kuyrukta Dağıtık İzleme</title><link>https://sade.dev/tr/systems/polyglot-kuyrukta-dagitik-izleme/</link><guid isPermaLink="true">https://sade.dev/tr/systems/polyglot-kuyrukta-dagitik-izleme/</guid><description>Bir mesaj PHP, Go ve Python arasında donmuş bir envelope üzerinden geçiyor. Yolculuğunu tek bir OpenTelemetry trace’ine çevirmek — yeni alan ve çekirdek bağımlılığı olmadan — ve dürüst sınırı.</description><pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir mesaj bir PHP servisi tarafından üretiliyor, bir kuyruğa düşüyor, bir Go worker’ı onu tüketiyor, o da bir devam mesajı yayınlıyor ve onu bir Python servisi işliyor. Dört hop, üç dil, aralarda bir ya da daha fazla broker. O akış yavaşladığında ya da on binde bir mesaj öldüğünde soru hep aynı: &lt;strong&gt;bu mesaj gerçekte nereye gitti ve her adımda ne oldu?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Çoğu polyglot kuyruk sistemi için dürüst cevap: kimse bilmiyor. Log’larda bir korelasyon kimliğin var — üç dilde de loglamayı hatırladıysan — ve farklı host’lardaki üç log akışında &lt;code&gt;grep&lt;/code&gt; çekecek sabrın var. Sahip olmadığın şey bir resim. “PHP’de 2ms’de üretildi → Redis’te 40ms bekledi → Go’da 210ms’de işlendi, iki kez retry oldu, DLQ’landı”yı &lt;strong&gt;tek bir bağlı şey&lt;/strong&gt; olarak göremiyorsun.&lt;/p&gt;
&lt;p&gt;İşte o resim bir &lt;strong&gt;dağıtık trace&lt;/strong&gt;’tir ve onu çizmenin standart yolu OpenTelemetry’dir. Bu yazı, onu tel formatı &lt;strong&gt;donmuş&lt;/strong&gt; ve çekirdekleri &lt;strong&gt;sıfır bağımlılık&lt;/strong&gt; taşıyan bir mesaj standardına eklemekle ilgili — bu iki kısıt, bir arada, en bariz yaklaşımı yasadışı kılıp daha ilginç olanı zorunlu hâle getiriyor.&lt;/p&gt;
&lt;h2 id=&quot;tek-kuralı-baştan-söyleyeyim&quot;&gt;Tek kuralı baştan söyleyeyim&lt;/h2&gt;
&lt;p&gt;Her şeyden önce şunu netleştireyim: &lt;strong&gt;envelope’a bir alan ekleme.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bir mesaj veriyolu boyunca dağıtık izleme istediğinde ilk içgüdü, bir W3C &lt;code&gt;traceparent&lt;/code&gt; taşımaktır — bir trace id’yi, bir span id’yi ve flag’leri kodlayan o standart 55 karakterlik string. HTTP tam olarak bunu bir header’da yapar. Bu içgüdü HTTP için doğru, burada yanlış; çünkü envelope donmuş bir kontrattır. Her dildeki her SDK, byte-bazında aynı şekli yayınlar: &lt;code&gt;job&lt;/code&gt;, &lt;code&gt;trace_id&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;meta&lt;/code&gt;, &lt;code&gt;attempts&lt;/code&gt;. Bir &lt;code&gt;traceparent&lt;/code&gt; alanı eklemek — opsiyonel olsa bile — o şekli değiştirir, bu da bir versiyon artışı, bu da altı dil implementasyonu ve her broker bağlaması boyunca koordine edilen bir tel değişikliği demektir. &lt;em&gt;Opsiyonel gözlemlenebilirlik&lt;/em&gt; olması gereken bir özellik için bu saçma bir bedel.&lt;/p&gt;
&lt;p&gt;Yani kural, kısıtın kendisi: izlemeyi &lt;strong&gt;tele dokunmadan&lt;/strong&gt; çöz. Aşağıdaki her şey bunu ciddiye almaktan çıkıyor.&lt;/p&gt;
&lt;h2 id=&quot;kimsenin-kullanmadığı-içgörü&quot;&gt;Kimsenin kullanmadığı içgörü&lt;/h2&gt;
&lt;p&gt;Envelope’un sana bedavaya verdiği şey şu. &lt;code&gt;trace_id&lt;/code&gt; alanı bir korelasyon kimliği — üretim anında basılan ve &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;her hop boyunca değişmeden taşınan&lt;/a&gt; bir UUID. Zaten tel üzerinde, zaten taşınıyor, zaten tüm akışı birbirine bağlayan o tek değer.&lt;/p&gt;
&lt;p&gt;Şimdi OpenTelemetry’nin bir trace’i birbirine bağlamak için kullandığı şeye bak: bir &lt;strong&gt;TraceID&lt;/strong&gt;. Spesifikasyonda bir TraceID tam olarak &lt;strong&gt;16 byte&lt;/strong&gt;’tır.&lt;/p&gt;
&lt;p&gt;Bir UUID tam olarak 16 byte’tır.&lt;/p&gt;
&lt;p&gt;Bütün hüner bu. UUID bir &lt;code&gt;trace_id&lt;/code&gt;, bir OTel TraceID’ye birebir eşlenir — tireleri at, 32 hex karakteri 16 byte’lık kimlik olarak oku, bitti. (UUID &lt;em&gt;olmayan&lt;/em&gt; bir &lt;code&gt;trace_id&lt;/code&gt; — diyelim BabelQueue dışı bir üreticiden geldi — SHA-256 ile deterministik olarak 16 byte’a hash’lenir.) Dolayısıyla aynı &lt;code&gt;trace_id&lt;/code&gt;’yi paylaşan her hop, &lt;strong&gt;aynı&lt;/strong&gt; OTel TraceID’yi türetir — bir anlaşma protokolü ve yeni bir alan olmadan. Zaten taşıdığın korelasyon kimliği &lt;em&gt;dağıtık trace’in ta kendisidir.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tasarım da kendini yazıyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tüketici&lt;/strong&gt; tarafında handler’ı sar. Çalışmadan önce &lt;code&gt;process &amp;lt;urn&amp;gt;&lt;/code&gt; adında bir span başlat, ama o span’i mesajın &lt;code&gt;trace_id&lt;/code&gt;’sinden türetilen trace’in içine zorla. Onu messaging convention’larıyla etiketle — &lt;code&gt;messaging.system&lt;/code&gt;, &lt;code&gt;messaging.destination.name&lt;/code&gt;, &lt;code&gt;messaging.message.id&lt;/code&gt; ve &lt;code&gt;trace_id&lt;/code&gt;’nin kendisine eşitlenen &lt;code&gt;messaging.message.conversation_id&lt;/code&gt; — handler’ı çalıştır ve herhangi bir exception’ı span’in hata durumu olarak kaydet. Runtime’ın retry / dead-letter davranışı el değmeden kalır; span sadece onu gözlemler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Üretici&lt;/strong&gt; tarafında aynası. Bir &lt;code&gt;publish &amp;lt;urn&amp;gt;&lt;/code&gt; span’i aç, &lt;em&gt;onun&lt;/em&gt; trace id’sini al, tekrar bir UUID’ye biçimlendir ve envelope’u kurarken mesajın &lt;code&gt;trace_id&lt;/code&gt;’sine bas. Aşağı akıştaki tüketici, TraceID’sini aynı &lt;code&gt;trace_id&lt;/code&gt;’den türeterek aynı trace’e iner.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jaeger, Tempo, Honeycomb ya da Datadog için bir &lt;code&gt;TracerProvider&lt;/code&gt; bağla; akış üç dil boyunca tek bir şelale olarak belirir, her hop’un zamanlamasıyla ve retry/hata işaretleriyle. Bağlama, hiçbir şey değişmez — tamamen opt-in.&lt;/p&gt;
&lt;h2 id=&quot;mekanizma-ve-bir-hayalet&quot;&gt;Mekanizma ve bir hayalet&lt;/h2&gt;
&lt;p&gt;Net olmaya değer bir ayrıntı var, çünkü tasarımın hem zekice hem de sınırlı olduğu yer orası.&lt;/p&gt;
&lt;p&gt;OpenTelemetry’de bir span’i &lt;em&gt;belirli bir trace’in içinde&lt;/em&gt; başlatmak için ona bir &lt;strong&gt;uzak ebeveyn&lt;/strong&gt; (remote parent) verirsin — istediğin trace id’yi taşıyan bir span context. Ama bir span context yalnızca &lt;em&gt;hem&lt;/em&gt; bir trace id &lt;em&gt;hem de&lt;/em&gt; bir span id’si varsa geçerlidir. Trace id’miz var (&lt;code&gt;trace_id&lt;/code&gt;’den); span id’miz &lt;strong&gt;yok&lt;/strong&gt;, çünkü bilinçle bir tane taşımadık. Bu yüzden &lt;code&gt;trace_id&lt;/code&gt;’yi hash’leyerek deterministik, sıfır-olmayan bir span id sentezliyoruz. Ebeveyn geçerli, tüketici span’i doğru trace’e iniyor — ama ebeveyni hiç var olmamış bir span’i gösteriyor. Bir &lt;strong&gt;hayalet&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Yukarıdaki tasarımın dürüst sınırı bu ve açıkça söylemeye değer. Hop’lar-arası span’lerin hepsi &lt;strong&gt;tek bir trace&lt;/strong&gt;’i paylaşır — bir mesajın hayatının her adımını bir arada, zamanlanmış, hatalar işaretlenmiş görebilirsin. Tek başına bunun &lt;strong&gt;vermediği&lt;/strong&gt; şey, hop’lar &lt;em&gt;arasında&lt;/em&gt; birebir ebeveyn-çocuk bağı: tüketicinin span’i üreticinin span’inin çocuğu olarak bağlanmaz, çünkü üreticinin gerçek span id’si tel boyunca hiç taşınmadı. Tek bir process içinde hiyerarşi doğrudur; kuyruğun ötesinde tek trace altında düzdür.&lt;/p&gt;
&lt;p&gt;Gerçek hop’lar-arası ebeveyn-çocuğu geri almak, bir span id taşımak demek, o da bir &lt;code&gt;traceparent&lt;/code&gt; demek — ve envelope donmuş olduğundan, o &lt;code&gt;traceparent&lt;/code&gt; &lt;strong&gt;bant dışında&lt;/strong&gt;, her broker bağlamasının &lt;code&gt;trace_id&lt;/code&gt; için zaten kullandığı yuvanın yanında bir transport header olarak gitmek zorunda: SQS, Pulsar ve Kafka’da bir &lt;code&gt;bq-trace-id&lt;/code&gt; alanı; RabbitMQ, Azure Service Bus ve ActiveMQ Artemis’te native correlation-id yuvası; hiç per-message metadata taşımayan Redis’te ise envelope gövdesinin kendisinden başka bir şey yok. Bu gerçek bir özellik, ama farklı ölçekte bir iş: her SDK’daki her transport bağlamasına dokunur. Yani bilinçli bir &lt;strong&gt;ikinci faz&lt;/strong&gt; olarak bırakıldı — ve o faz bu yazıdan iki gün sonra kapandı: ADR-0028 ile &lt;code&gt;traceparent&lt;/code&gt; transport-header taşıması 21 Haziran 2026’da altı SDK çekirdeğinin hepsine v0.2 olarak indi. Teslim edilen bir &lt;code&gt;traceparent&lt;/code&gt; artık tüketici span’ini üreticinin span’inin gerçek çocuğuna yükseltiyor; taşınamadığı yerde — kalan boşluk PHP’nin Kafka, Pulsar ve STOMP üreticileri — yukarıda anlatılan v0.1 &lt;code&gt;trace_id&lt;/code&gt; korelasyonuna temiz biçimde düşüyor. Sıralamanın kendisi zaten meseleydi: yukarıdaki birinci-faz tasarımı korelasyonu, hop başına zamanlamayı ve hata/retry görünürlüğünü — yani %90’ı — sıfır tel değişikliğiyle ve dil başına birkaç yüz satırla teslim eder ve v0.2’nin üzerine oturduğu zemin olarak kalır. %90’ı önce, son %10’u daha büyük bir işin ardına bırakarak göndermek doğru sıraydı — tıpkı &lt;a href=&quot;/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/&quot;&gt;pahalı ölçekleme adımlarından önce ucuzları yaptığın gibi&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;tek-semantik-altı-paketleme-deyimi&quot;&gt;Tek semantik, altı paketleme deyimi&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Çekirdeğin bağımlılıksız kalması&lt;/strong&gt; kısıtının bir sonucu var: OpenTelemetry kodu, OTel API’sini import etmek zorunda olduğu her yerde çekirdekte yaşayamaz. Bu yüzden altı dilin beşinde çekirdeğin &lt;em&gt;yanında&lt;/em&gt; yaşar, yalnızca opt-in ettiğinde erişilir — ve “çekirdeğin yanında, opsiyonel” her ekosistemde farklı yazılır. (.NET istisna; nedenine birazdan geliyoruz.) Altısında da semantik aynı; paketleme her dilin karakterini gösterdiği yer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt; — ayrı bir modül (&lt;code&gt;babelqueue-go/otel&lt;/code&gt;, kendi &lt;code&gt;go.mod&lt;/code&gt;’u), tıpkı transport alt-modülleri gibi. Çekirdek modül OTel bağımlılığını hiç görmez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python&lt;/strong&gt; — bir &lt;code&gt;[otel]&lt;/code&gt; extra’sı. &lt;code&gt;pip install babelqueue[otel]&lt;/code&gt;, &lt;code&gt;opentelemetry-api&lt;/code&gt;’yi getirir; modül onu import eder, yani yalnızca sen istediğinde import edilebilir. Buradaki bir TraceID byte değil, 128-bit bir &lt;code&gt;int&lt;/code&gt;’tir — aynı değer, farklı biçim.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node&lt;/strong&gt; — bir subpath export, &lt;code&gt;@babelqueue/core/otel&lt;/code&gt;, &lt;code&gt;@opentelemetry/api&lt;/code&gt; ile &lt;em&gt;opsiyonel&lt;/em&gt; bir peer dependency olarak. Kritik nokta: izleme kodu paket kökünden &lt;strong&gt;yeniden export edilmez&lt;/strong&gt;: edilseydi, &lt;code&gt;@babelqueue/core&lt;/code&gt;’u import etmek OTel import’unu erkenden yükler ve peer’i kurmayan herkes için kırılırdı. Subpath, ana girişi gerçekten bağımlılıksız tutar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Java&lt;/strong&gt; — &lt;code&gt;opentelemetry-api&lt;/code&gt; üzerinde &lt;code&gt;optional&lt;/code&gt; bir Maven bağımlılığı. Optional bağımlılıklar transitive değildir, yani izleme sınıflarına hiç dokunmayan bir tüketici OTel’i classpath’ine hiç çekmez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.NET&lt;/strong&gt; — ilginç olanı. .NET’te idiomatik izleme primitifi &lt;code&gt;System.Diagnostics.ActivitySource&lt;/code&gt;’tur, &lt;strong&gt;base class library&lt;/strong&gt; içinde yaşar — ve OpenTelemetry .NET’in üzerine kurulduğu &lt;em&gt;tam olarak&lt;/em&gt; odur. Yani .NET modülünün &lt;strong&gt;hiçbir bağımlılığı yok&lt;/strong&gt; — çekirdeğin yanında değil, &lt;em&gt;içinde&lt;/em&gt; durabilmesinin sebebi de bu (&lt;code&gt;BabelQueue.Core / Telemetry&lt;/code&gt;): &lt;code&gt;Activity&lt;/code&gt; nesneleri yayınlar, tüketicinin OTel boru hattı onları &lt;code&gt;AddSource(&amp;quot;BabelQueue&amp;quot;)&lt;/code&gt; çağırarak toplar. Çekirdek, bağımlılığı izole ederek değil, bağımlılığa sahip olmayarak sıfır-dep kalır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP&lt;/strong&gt; — bir Composer &lt;code&gt;suggest&lt;/code&gt; artı bir dev gereksinimi, mevcut opsiyonel yardımcıları aynalayarak. İzleme namespace’i orada; &lt;code&gt;open-telemetry/api&lt;/code&gt; yalnızca kullanırsan gerekir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Altı deyim, hepsinde tutulan tek kural: &lt;strong&gt;gözlemlenebilirliği kullanmak tüketicinin yaptığı bir seçimdir, asla çekirdeğin kestiği bir vergi değil.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;gerçekte-ne-elde-ediyorsun&quot;&gt;Gerçekte ne elde ediyorsun&lt;/h2&gt;
&lt;p&gt;Mekanizmayı bir kenara bırak, kazanç tarif etmesi küçük, sahip olması büyük. Mevcut izleme backend’inde, eskiden üç log samanlığında bir iğne olan bir mesaj tek bir şelaleye dönüşür: onu hangi servis üretti, broker’da ne kadar bekledi, her tüketici ne kadar sürdü, retry oldu mu, DLQ’landı mı — zaten ortalıkta dolaştırdıkları bir UUID dışında hiçbir konuda anlaşmasını gerektirmediğin diller ve broker’lar boyunca.&lt;/p&gt;
&lt;p&gt;Ve bunun tel üzerindeki maliyeti &lt;strong&gt;sıfır&lt;/strong&gt;. İzleme var olmadan önce giden envelope ile onunla birlikte giden envelope byte-byte aynı. Trace baştan beri &lt;code&gt;trace_id&lt;/code&gt;’nin içinde saklıydı; bütün iş, bir UUID ile bir TraceID’nin aynı on altı byte olduğunu fark etmekti.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://babelqueue.com/docs/spec/1.x/observability&quot;&gt;BabelQueue observability spec’i&lt;/a&gt; bu tasarımı bir standart olarak yazıya döker; onu uygulayan SDK’lar — PHP, Go, Python, Node, Java ve .NET boyunca — &lt;a href=&quot;https://github.com/BabelQueue&quot;&gt;BabelQueue ekosisteminde&lt;/a&gt; yaşar.&lt;/p&gt;</content:encoded><category>opentelemetry</category><category>observability</category><category>distributed-tracing</category><category>messaging</category><category>architecture</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Şemayı Kuyruğun Kenarında Doğrulamak</title><link>https://sade.dev/tr/notes/semayi-kuyrugun-kenarinda-dogrulamak/</link><guid isPermaLink="true">https://sade.dev/tr/notes/semayi-kuyrugun-kenarinda-dogrulamak/</guid><description>Bir mesajın data&apos;sı, kuyruğun kontrol etmeyeceği tipsiz bir sözleşmedir. Onu kenarda doğrula — üretici tarafında yayınlamadan önce, tüketici tarafında güvenlik ağı olarak.</description><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir worker production’da &lt;code&gt;KeyError&lt;/code&gt; fırlatmaya başladı. Sebep kendi kodu değildi — upstream bir servis, olayın payload’ından bir alanı düşüren bir değişiklik shiplemişti ve kuyruk bozuk mesajı tek kelime etmeden taşımıştı. Hata, kötü mesaj değildi. Hata, onu sınırda kontrol eden hiçbir şeyin olmamasıydı.&lt;/p&gt;
&lt;h2 id=&quot;kuyruk-byte-taşır-tip-değil&quot;&gt;Kuyruk byte taşır, tip değil&lt;/h2&gt;
&lt;p&gt;Bir request body tipli bir handler’a çarpar; yanlış şekildeyse framework onu kapıda reddeder. Kuyruk sana bunun hiçbirini vermez. Payload opak JSON’dur — broker byte taşır ve içine asla bakmaz. Üretici ne koyduysa, tüketici onu alır, geçerli olsun olmasın. Tek bir servisin içinde sahip olduğun tip güvenliği, veri telden geçtiği an buharlaşır.&lt;/p&gt;
&lt;p&gt;Yani bir mesajın şekli hakkında bir garanti istiyorsan, onu kendin eklemelisin — &lt;strong&gt;kenarda&lt;/strong&gt;, verinin girdiği ve çıktığı yerde.&lt;/p&gt;
&lt;h2 id=&quot;verinin-girdiği-ve-çıktığı-yerde-doğrula&quot;&gt;Verinin girdiği ve çıktığı yerde doğrula&lt;/h2&gt;
&lt;p&gt;İki kenar var ve farklı hataları yakalar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Üretici tarafında, yayınlamadan önce — birincil olan.&lt;/strong&gt; Payload’ı üretirken doğrula. Geçersiz veri kuyruğa hiç girmez, hata &lt;em&gt;senkron&lt;/em&gt; olarak gerçekten hatası olan serviste ortaya çıkar ve bir kez yakalanır — mesaj her tüketiciye yayılmadan önce. Başarısız olmanın ucuz yeri burası.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tüketici tarafında, alırken — güvenlik ağı.&lt;/strong&gt; Tüketirken yine doğrula. Bu, üretici tarafının yakalayamayacağını yakalar: kontrol etmediğin üreticilerden gelen mesajlar — başka bir takım, eski bir deploy edilmiş sürüm, elle hazırlanmış bir replay. Burada doğrulamayı geçemeyen mesaj zehirli bir mesajdır; handler’ı döngüde çökertmek yerine onu bir dead-letter kuyruğuna yönlendir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bilmeye değer bir incelik: çoğu kuyruk runtime’ında “anında reddet” kancası yoktur, yani tüketici tarafı bir ret genelde dead-letter’a gitmeden önce &lt;em&gt;retry&lt;/em&gt; eder — ve geçersiz veri retry’de geçerli olmaz. Bu boşa iştir; üretici tarafının değerli olmasının sebebi tam olarak budur. Tüketici tarafı emniyet kemeridir, direksiyon değil.&lt;/p&gt;
&lt;h2 id=&quot;şema-envelopela-değil-mesajla-yaşar&quot;&gt;Şema envelope’la değil, mesajla yaşar&lt;/h2&gt;
&lt;p&gt;Doğruladığın şey, &lt;em&gt;mesaj türü başına&lt;/em&gt; bir şemadır — olayın kimliğiyle anahtarlanır, taşıma envelope’una cıvatalanmaz. Onu bağımsız sürümlenen bir registry’de tut; &lt;a href=&quot;/tr/journal/sema-evrimi-sozlesmeyi-kirmadan/&quot;&gt;tüketicileri kırmadan dikkatle evrimleştirdiğin&lt;/a&gt; aynı şema, runtime’da zorladığın şema olur. Tek sözleşme, hem değişim-anında (bu düzenleme birini kırıyor mu?) hem runtime’da (bu mesaj ona uyuyor mu?) kontrol edilir.&lt;/p&gt;
&lt;p&gt;Bu, &lt;a href=&quot;/tr/notes/idempotency-ayni-mesaj-iki-kez/&quot;&gt;bir mükerrer teslimatı no-op yapmanın&lt;/a&gt; yapısal kuzeni: ikisi de ne geldiğini kontrol etmediğini kabul eder ve garantiyi göndericiye güvenmek yerine kendi sınırına koyar.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-buna-değmez&quot;&gt;Ne zaman buna değmez&lt;/h2&gt;
&lt;p&gt;Tek bir servisin iç kuyruğu — tek üretici, tek tüketici, birlikte deploy — buna ihtiyaç duymaz. Tip sistemi zaten iki ucu da kapsar; kenar doğrulaması törendir. &lt;em&gt;İkinci&lt;/em&gt;, bağımsız deploy edilen bir üretici ya da tüketici var olduğu an değmeye başlar — farklı bir takım, farklı bir dil, farklı bir sürüm temposu. O an aynı zamanda tipsiz payload’ın sessizce en kırılgan sözleşmen hâline geldiği andır.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://babelqueue.com/docs/spec/1.x/schema-validation&quot;&gt;BabelQueue schema-validation spec’i&lt;/a&gt; URN-başına şemayı ve nerede zorlandığını tanımlar; &lt;a href=&quot;https://github.com/BabelQueue/babelqueue-registry&quot;&gt;babelqueue-registry&lt;/a&gt; o şemaları tutar — kontrolü çalıştıran ise onun &lt;code&gt;bqschema&lt;/code&gt; aracıdır.&lt;/p&gt;</content:encoded><category>queue</category><category>schema</category><category>reliability</category><category>architecture</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Mimari Kararların Çürüyor Çünkü Onları Hiçbir Şey Çalıştırmıyor</title><link>https://sade.dev/tr/journal/mimari-kararlar-curur/</link><guid isPermaLink="true">https://sade.dev/tr/journal/mimari-kararlar-curur/</guid><description>Bir wiki&apos;ye yazılmış mimari karar bir dilektir. Zorlayamadığın sınırlar sessizce aşınır — diyagram kodla örtüşmeyi bırakana dek. Kuralı çalıştırılabilir yap.</description><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yeni bir mühendis bana domain katmanının neden database paketini import ettiğini sordu — “bir ADR (mimari karar kaydı) import etmemesi gerektiğini söylemiyor mu?” Söylüyordu. İki yıldır söylüyordu. Ve o iki yılın çoğunda, bir yerlerde, kural sessizce çiğnenmişti: şurada bir helper, burada bir “sadece bu seferlik” import, her biri kendi pull request’inde makul. Karar wiki’de hâlâ doğruydu. Kodda aylar önce doğru olmaktan çıkmıştı ve kimse fark etmedi, çünkü onu izleyen hiçbir şey yoktu.&lt;/p&gt;
&lt;p&gt;Yazıp da zorlayamadığın bir karar bir kural değildir. İyi formatlanmış bir dilektir.&lt;/p&gt;
&lt;h2 id=&quot;diyagram-ile-kod-küçük-ve-makul-adımlarla-ayrışır&quot;&gt;Diyagram ile kod, küçük ve makul adımlarla ayrışır&lt;/h2&gt;
&lt;p&gt;Kimse mimariyi çiğnemeye karar vermez. Her seferinde savunulabilir bir commit’le aşınır: async yolu yazmak daha yavaş olduğu için eklenen senkron bir çağrı, ihtiyacın olan fonksiyon öbür tarafta olduğu için bir sınırı geçen bir import. Her diff tek başına iyi görünür. Hasar &lt;strong&gt;birikimli ve yapısaldır&lt;/strong&gt; — tek bir değişiklik ölçeğinde görünmez, ancak geri çekilip katmanlamanın yok olduğunu bulunca belli olur.&lt;/p&gt;
&lt;p&gt;Bu tam da bir insan review’ının çalıştığı ölçek ve tam da kaçırdığı ölçek. &lt;a href=&quot;/tr/journal/code-review-kulturu/&quot;&gt;Review en iyi bir değişikliğin mimarisinde işler&lt;/a&gt;, ama bir reviewer tek bir PR görür, sınırı nihayet eriten bininci import’u değil. Bir insandan her commit’te tüm bağımlılık grafiğini kafasında tutmasını isteyemezsin. Wiki sayfası da tutamaz; o nesirdir ve nesir bir build’i patlatmaz.&lt;/p&gt;
&lt;h2 id=&quot;kısıtı-çalıştırılabilir-yap&quot;&gt;Kısıtı çalıştırılabilir yap&lt;/h2&gt;
&lt;p&gt;Çözüm daha iyi yazılmış bir ADR değil. Kararın &lt;em&gt;zorlanabilir kısmını&lt;/em&gt; nesirden çıkarıp çalışan bir şeye taşımak.&lt;/p&gt;
&lt;p&gt;“Domain altyapıya bağımlı olmamalı” özünde bir görüş değildir — import grafiği üzerinde bir kısıttır: &lt;code&gt;domain/&lt;/code&gt; altındaki dosyalar &lt;code&gt;db/&lt;/code&gt;’yi import edemez. Bu, bir aracın her commit’te deterministik olarak değerlendirebileceği ve çiğnendiğinde build’i kırabileceği bir kural — çiğneyen import &lt;code&gt;main&lt;/code&gt;’e ulaşmadan, iki yıl sonra değil. Yakalama maliyeti, “bir şey nihayet kırılınca yapılan bir arkeoloji seansı”ndan “onu sokan PR’daki kırmızı bir kontrol”e iner.&lt;/p&gt;
&lt;p&gt;Bu, &lt;a href=&quot;/tr/journal/sema-evrimi-sozlesmeyi-kirmadan/&quot;&gt;bir şemayı değiştirmek yerine sürümlemenin&lt;/a&gt; aynı hamlesi: herkesin &lt;em&gt;üzerinde anlaştığı&lt;/em&gt; bir kural, mekanik bir şey çizgiyi tutana dek hiçbir şey etmez — çünkü insan disiplini, her katkıcının her commit’ine ölçeklenmez.&lt;/p&gt;
&lt;h2 id=&quot;dokümanda-ne-kalır-ne-kod-olur&quot;&gt;Dokümanda ne kalır, ne kod olur&lt;/h2&gt;
&lt;p&gt;Bu ADR’yi öldürmez — onu ikiye böler. Bir mimari kararın iki parçası vardır: &lt;strong&gt;gerekçe&lt;/strong&gt; (hexagonal katmanlamayı niye seçtik, neyi feda ettik, ne zaman yeniden gözden geçiririz) ve &lt;strong&gt;kısıt&lt;/strong&gt; (domain hiçbir şey import etmez; API, DB’yle yalnızca bir repository üzerinden konuşur). Gerekçe nesre aittir — bir aracın tutamayacağı, bir insanın ihtiyaç duyduğu bağlam. Kısıt ise koda aittir, kontrol edilebileceği yere.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Neden&lt;/em&gt;’i yazmaya devam et. &lt;em&gt;Ne&lt;/em&gt;’yi zorlamak için nesre güvenmeyi bırak.&lt;/p&gt;
&lt;h2 id=&quot;bu-ne-zaman-önemsizleşir&quot;&gt;Bu ne zaman önemsizleşir?&lt;/h2&gt;
&lt;p&gt;Tüm sistemin iki katman ve tek bir masanın etrafına sığan bir takımsa, sınırlar herkesin kafasında yaşar ve onlar için bir CI kapısı törendir. Üçüncü bir katkıcı katıldığı ya da kod tabanı tek bir kişinin tutabileceğini aştığı an, zorlanmayan sınır &lt;em&gt;zaten&lt;/em&gt; aşınıyordur — sadece yeni mühendis domain’in neden database’i import ettiğini sorana dek görmezsin.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Bir mimari, karar verdiğin şey değildir; kodunun şu an yaptığı şeydir. Büyüyen bir kod tabanıyla temasta hayatta kalan tek kararlar, bir şeyin her commit’te kontrol ettikleridir. Gerekçeyi insanlar için yaz — ve sınırı, onu zorlamaktan asla yorulmayan bir makineye devret.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://github.com/muhammetsafak/archlint&quot;&gt;archlint&lt;/a&gt;, tam da bunun için yazdığım araç — katman kurallarını okuyup bir ihlalde build’i kıran, deterministik bir import-sınır linter’ı (Go, TypeScript ve Python), paketlenmiş bir CI Action’ıyla.&lt;/p&gt;</content:encoded><category>architecture</category><category>governance</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Idempotency: Aynı Mesaj İki Kez Geldiğinde</title><link>https://sade.dev/tr/notes/idempotency-ayni-mesaj-iki-kez/</link><guid isPermaLink="true">https://sade.dev/tr/notes/idempotency-ayni-mesaj-iki-kez/</guid><description>At-least-once teslimat, bir handler&apos;ın aynı mesajı iki kez göreceği anlamına gelir. Idempotency anahtarı, nereden geldiği ve çökmeden sağ çıkan dedupe.</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir müşteri tek sipariş için iki kez ücretlendirildi. Hiçbir şey bilerek iki kez çalışmadı: ödeme worker’ı kartı çekti, sonra mesajı ack’leyemeden çöktü — broker da sözünü tutup mesajı yeniden teslim etti.&lt;/p&gt;
&lt;p&gt;Hata, mükerrer teslimat değildi. Mükerrer teslimat için inşa edilmemiş handler’dı.&lt;/p&gt;
&lt;h2 id=&quot;at-least-once-bir-söz-bir-arıza-değil&quot;&gt;At-least-once bir söz, bir arıza değil&lt;/h2&gt;
&lt;p&gt;Neredeyse her queue — ve yeniden denenen her HTTP çağrısı — &lt;strong&gt;at-least-once&lt;/strong&gt;’tır. Bir handler aynı mesajı birden çok kez görebilir: bir çökme, bir release, bir redelivery ya da timeout’a düşüp yeniden deneyen bir istemci sonrası. Ağ üzerinde exactly-once, broker’ın ucuza veremeyeceği bir koordinasyon olmadan pratik değil; bu yüzden çok az broker onu vaat eder — vaat edenler de (Google Cloud Pub/Sub gibi) dar koşullara sıkıştırır: tek bölge, yalnızca pull aboneliği.&lt;/p&gt;
&lt;p&gt;Bu, problemi yeniden çerçeveler. Redelivery, yok edilecek bir başarısızlık değil; etrafında tasarım yapılacak bir garanti. (&lt;a href=&quot;/tr/notes/senkron-mu-asenkron-mu/&quot;&gt;Queue&lt;/a&gt;’nun başarısız bir job’u kaybetmek yerine yeniden denerken yaptığı pazarlığın aynısı.)&lt;/p&gt;
&lt;h2 id=&quot;idempotent-demek-iki-kez--bir-kez-demek&quot;&gt;Idempotent demek “iki kez = bir kez” demek&lt;/h2&gt;
&lt;p&gt;Bir operasyon, defalarca uygulandığında etkisi bir kez uygulanmışla aynıysa idempotent’tir — &lt;code&gt;f(f(x)) = f(x)&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Sipariş durumunu &lt;code&gt;paid&lt;/code&gt; yap” idempotent’tir.&lt;/li&gt;
&lt;li&gt;“Bakiyeyi 10 artır” &lt;strong&gt;değildir&lt;/strong&gt; — iki kez +20 eder.&lt;/li&gt;
&lt;li&gt;“Karşılama e-postasını gönder” &lt;strong&gt;değildir&lt;/strong&gt; — iki kez iki e-posta eder.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bütün iş, ikinci tür operasyonu birinciye çevirmek.&lt;/p&gt;
&lt;h2 id=&quot;idempotency-anahtarı-göndericiden-gelir&quot;&gt;Idempotency anahtarı göndericiden gelir&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Bu operasyon&lt;/em&gt; için kararlı bir kimliğe ihtiyacın var ve bunu &lt;strong&gt;gönderici&lt;/strong&gt; belirlemeli, alıcı değil:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bir queue mesajı için bu, &lt;strong&gt;üreticinin ürettiği mesaj-başına id&lt;/strong&gt;’dir — mesaj başına bir id, trace/correlation id’den ayrı (o, birçok mesajı kapsar).&lt;/li&gt;
&lt;li&gt;Bir HTTP yazımı için bu, &lt;strong&gt;istemcinin ürettiği &lt;code&gt;Idempotency-Key&lt;/code&gt; başlığı&lt;/strong&gt;’dır — Stripe modeli.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;İki kural anahtarı dürüst tutar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Yalnızca payload’dan türetme.&lt;/strong&gt; Meşru biçimde birbirinin aynı iki istek — aynı müşterinin aynı ürünü iki kez alması — çakışır ve ikincisi sessizce düşürülür.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Anahtarı alıcıya ürettirme.&lt;/strong&gt; Alıcı, bir retry’ı yepyeni bir çağrıdan ayıramaz; “bu, daha önce denediğim aynı operasyon” bilgisi yalnızca göndericide vardır.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;dedupe-zaten-yaptığını-hatırla&quot;&gt;Dedupe: zaten yaptığını hatırla&lt;/h2&gt;
&lt;p&gt;Handler, işten önce tek bir kontrol yapar: &lt;em&gt;bu anahtar işlendi mi?&lt;/em&gt; Evetse atla ve ack’le. Hayırsa işi yap, sonra anahtarı kaydet. Tüm desen bu — ağırlık, kaydın &lt;em&gt;nerede&lt;/em&gt; durduğunda.&lt;/p&gt;
&lt;h2 id=&quot;i̇ş-ile-kayıt-arasındaki-çökme&quot;&gt;İş ile kayıt arasındaki çökme&lt;/h2&gt;
&lt;p&gt;Zor kısım, işi yapmakla anahtarı kaydetmek arasındaki boşluk. İki şekil var ve doğrusu yan etkiye bağlı:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Seen-set — başarıdan sonra kaydet.&lt;/strong&gt; Handler döndükten sonra anahtarı Redis’e ya da bir tabloya yaz. Ucuz ve geniş çapta uygulanabilir. Pencere: yan etkiden &lt;em&gt;sonra&lt;/em&gt; ama kayıttan &lt;em&gt;önce&lt;/em&gt; çökersen, bir redelivery yeniden işler. Yan etkinin kendisi idempotent’se sorun değil — bir &lt;code&gt;UPSERT&lt;/code&gt;, bir “set to paid”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transactional — işle birlikte kaydet.&lt;/strong&gt; Idempotency kaydını, iş değişikliğiyle &lt;strong&gt;aynı veritabanı transaction’ında&lt;/strong&gt; yaz. Pencere yok: anahtar ve etki birlikte commit olur ya da birlikte geri alınır. Bedeli: etki, senin kontrol ettiğin bir DB yazımı olmalı ve anahtar deposu aynı veritabanı olmalı — ayrı bir Redis değil. Kendi kontrolündeki bir etki için mükerrer işlemeyi gerçekten bitiren budur.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yani yan etki senin yerine seçer: sahip olduğun bir satır → transactional; transaction’ına dahil edemeyeceğin üçüncü-taraf bir çağrı (e-posta, bir çekim) → seen-set ve kendi anahtarını onun &lt;code&gt;Idempotency-Key&lt;/code&gt;’i olarak ileterek downstream’i de idempotent yap.&lt;/p&gt;
&lt;h2 id=&quot;eşzamanlı-mükerrerlere-kontrol-değil-constraint-lazım&quot;&gt;Eşzamanlı mükerrerlere kontrol değil, constraint lazım&lt;/h2&gt;
&lt;p&gt;Aynı anahtarın, ikisi de kaydetmeden yarışan iki teslimatı bir “önce kontrol et sonra yaz” dizisinden sıyrılır — o dizi atomik değil. &lt;strong&gt;Anahtar kolonundaki bir unique constraint&lt;/strong&gt; atomiktir: ikinci insert, yakalayıp “zaten işlendi” diye ele aldığın bir conflict’e dönüşür. Bu, &lt;a href=&quot;/tr/systems/ardisik-numara-uretiminde-race-condition-ve-gap/&quot;&gt;sıralı numara üretiminde bir race’in açtığı gap’lerin&lt;/a&gt; aynı şekli — benzersizlik gerçekte uygulamada değil, veritabanında zorlanır.&lt;/p&gt;
&lt;h2 id=&quot;buna-ne-zaman-hiç-ihtiyacın-yok&quot;&gt;Buna ne zaman hiç ihtiyacın yok?&lt;/h2&gt;
&lt;p&gt;Handler zaten idempotent’se — kararlı bir id ile anahtarlanmış saf bir &lt;code&gt;UPSERT&lt;/code&gt;, bir “alanı şu değere ayarla” — hiçbir şeye ihtiyacın olmayabilir. Anahtar + dedupe’a, operasyonun &lt;strong&gt;idempotent olmayan bir yan etkisi&lt;/strong&gt; (para, e-posta, dış bir &lt;code&gt;POST&lt;/code&gt;) ya da &lt;strong&gt;birikimi&lt;/strong&gt; (increment, append) olduğunda uzan. Karmaşıklığı yalnızca ikinci çalıştırmanın gerçekten zarar verdiği yere harca.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;At-least-once, düzelteceğin kısım değil; kabul ettiğin kısım. Handler’ı, ikinci teslimat bir no-op olacak şekilde kur; o zaman redelivery bir olay olmaktan çıkar ve olduğu şeye döner — broker’ın sözünü tutması.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://babelqueue.com/docs/spec/1.x/idempotency&quot;&gt;BabelQueue idempotency spec’i&lt;/a&gt; anahtarın nereden geldiğini ve dedupe’un nasıl davrandığını sabitler; &lt;a href=&quot;https://github.com/BabelQueue/babelqueue-examples/tree/main/idempotency-payments&quot;&gt;&lt;code&gt;idempotency-payments&lt;/code&gt; örneği&lt;/a&gt; ise tam da bu çift-çekim vakasının çalıştırılabilir hâlidir.&lt;/p&gt;</content:encoded><category>queue</category><category>reliability</category><category>architecture</category><category>async</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>AI Darboğazı Kaldırmadı, Yerini Değiştirdi</title><link>https://sade.dev/tr/journal/ai-darbogazi-kaldirmadi/</link><guid isPermaLink="true">https://sade.dev/tr/journal/ai-darbogazi-kaldirmadi/</guid><description>Ucuz kod üretimi, review/entegrasyon/doğrulama yetişemiyorsa çıktıyı artırmaz. Darboğaz yalnızca aşağı kayar — ve asıl iş artık orada</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;İzlediğim bir ekip, ajanlara yaslanınca kabaca iki kat kod üretmeye başladı. Altı hafta sonra &lt;em&gt;daha yavaş&lt;/em&gt; teslim ediyorlardı. Pull-request kuyruğu üçe katlanmış, CI yeşilden çok kırmızıydı ve iki olayın kökü kimsenin gerçekten okumadığı değişikliklere çıkıyordu. Çıktı arttı; throughput düştü.&lt;/p&gt;
&lt;p&gt;Bunda gizem yok. Hattın tek bir istasyonunu hızlandırıp buna zafer dediler.&lt;/p&gt;
&lt;h2 id=&quot;tek-istasyonu-hızlandırıp-buna-throughput-diyemezsin&quot;&gt;Tek istasyonu hızlandırıp buna throughput diyemezsin&lt;/h2&gt;
&lt;p&gt;Bir teslimat hattı bir istasyon dizisidir: yaz → review → entegre et → doğrula → release. Throughput’u &lt;strong&gt;en yavaş&lt;/strong&gt; istasyon belirler, en hızlısı değil. Yazmayı 3× hızlandırırsın ama review ve doğrulama yerinde kalırsa throughput’u üçe katlamış olmazsın — kısıtı yalnızca bir istasyon aşağı taşımış ve onun önünde bir work-in-progress yığını büyütmüş olursun.&lt;/p&gt;
&lt;p&gt;O yığın bedavaya gelmez. Merge conflict’e yaşlanan okunmamış PR’lar, yarı entegre branch’ler, kimsenin yeşil tutamayacağı kadar hızlı büyüyen bir test süiti. Kod ucuzlayınca darboğaz yok olmadı. Yer değiştirdi — ve görülmesi zorlaştı, çünkü artık “kod yazmak” gibi değil “beklemek” gibi görünüyor.&lt;/p&gt;
&lt;h2 id=&quot;doranın-bunun-için-zaten-bir-kelimesi-var-büyüteç&quot;&gt;DORA’nın bunun için zaten bir kelimesi var: büyüteç&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://dora.dev/research/2024/dora-report/&quot;&gt;2024&lt;/a&gt; ve &lt;a href=&quot;https://dora.dev/dora-report-2025/&quot;&gt;2025&lt;/a&gt; DORA (DevOps Research and Assessment) raporlarının verisi aynı noktaya öbür taraftan iniyor: AI bir &lt;strong&gt;büyüteç, kısayol değil.&lt;/strong&gt; Güçlü teslimat pratiklerine sahip ekipler ondan daha çok verim alıyor; sahip olmayanlar disfonksiyonlarını daha hızlı sevk ediyor. Her iki rapor da AI benimsemesinin &lt;em&gt;istikrar düşüşüyle ilişkilendiğini&lt;/em&gt; gördü — çünkü gerçek kısıt her zaman süreçti, klavyede yazmak değil.&lt;/p&gt;
&lt;p&gt;Büyüteç, ne verirsen onu yükseltir. Temiz bir sinyal verirsen daha yüksek ve net olur; gürültü verirsen yalnızca daha yüksek gürültü. Hangisi olduğuna AI karar vermiyor. Aşağı tarafın veriyor.&lt;/p&gt;
&lt;h2 id=&quot;darboğaz-gerçekte-nereye-gitti&quot;&gt;Darboğaz gerçekte nereye gitti?&lt;/h2&gt;
&lt;p&gt;Hep pahalı olan ama şimdi boğulan kısımlara taşındı:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Review.&lt;/strong&gt; Üretim, insanların gerçek dikkat verebileceğinden hızlı PR çıkarıyor — böylece review bir lastik damgaya bozuluyor; okunmamış değişikliklerin production’a ulaşma yolu tam da budur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Doğrulama.&lt;/strong&gt; Test yazmak artık neredeyse bedava; süitler tam da bu yüzden, güveni inşa etmekten hızlı aşındıran &lt;a href=&quot;/tr/journal/kararsiz-testler-ve-determinizm/&quot;&gt;kararsız, zamana bağımlı testlerle&lt;/a&gt; doluyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entegrasyon.&lt;/strong&gt; Daha çok elin daha hızlı yazdığı daha çok kod; daha çok coupling ve &lt;a href=&quot;/tr/notes/idempotency-ayni-mesaj-iki-kez/&quot;&gt;aynı mesajın ya da isteğin iki kez gelip&lt;/a&gt; durumu bozmasının daha çok yolu demek.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunların hiçbiri bir klavye problemi değil. Hepsi, hızlı yazmanın faturası.&lt;/p&gt;
&lt;h2 id=&quot;kalıcı-yatırım-sıkıcı-olan-aşağı-taraf&quot;&gt;Kalıcı yatırım, sıkıcı olan aşağı taraf&lt;/h2&gt;
&lt;p&gt;Yani bu çağdaki mühendislik üstünlüğü “AI yazdı” değil. &lt;strong&gt;Yazılana güvenmenin maliyetini&lt;/strong&gt;, yeni darboğaz boğulmayacak kadar düşük tutmak. O iş cazip değil ve AI’ın geri verdiği zamanı harcayacağım yer orası:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doğrulamayı &lt;strong&gt;deterministik&lt;/strong&gt; yap; ki yeşil bir kontrol bir anlam taşısın ve review ona yaslanabilsin.&lt;/li&gt;
&lt;li&gt;Entegrasyonu &lt;strong&gt;idempotent ve dayanıklı&lt;/strong&gt; yap; ki daha hızlı, daha dağınık değişiklik redelivery’yi bir çift çekime çevirmesin.&lt;/li&gt;
&lt;li&gt;Review’ı &lt;a href=&quot;/tr/journal/code-review-kulturu/&quot;&gt;virgül değil mimari hakkında&lt;/a&gt; yap — mekanik kısmı araçlara bırak ki ölçekleyemediğin insan dikkati gerçekten önemli kararlara gitsin.&lt;/li&gt;
&lt;li&gt;Bariz hatayı, üç istasyon sonra CI’da yüz kat pahalıya değil, &lt;strong&gt;commit sınırında&lt;/strong&gt; yakala.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu, &lt;a href=&quot;/tr/journal/vibe-coding-ile-production/&quot;&gt;“vibe coding ilk sürümün maliyetini düşürür, yanılmanın maliyetini değil”&lt;/a&gt; dersinin aynısı — tek bir değişiklik yerine tüm hattın seviyesinde okunmuş hâli.&lt;/p&gt;
&lt;h2 id=&quot;bu-ne-zaman-geçerli-değil&quot;&gt;Bu ne zaman geçerli değil?&lt;/h2&gt;
&lt;p&gt;Gerçek kısıtın &lt;em&gt;hâlâ&lt;/em&gt; üretimse — sahici bir greenfield, tek kişilik bir prototip, henüz koruyacak bir aşağı tarafı olmayan bir spike — o zaman hızlı git ve var olmayan bir sistem için süreç kurma. Kayma ancak dürüst tutulacak bir şey olduğunda ısırır: bir ekip, bir review kuyruğu, başkalarının bağımlı olduğu bir production.&lt;/p&gt;
&lt;p&gt;Hangi dünyada olduğunu bilmek, asıl muhakeme. İki kat kod üreten çoğu ekip artık birincisinde değil.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;AI mühendisliği ucuzlatmadı; &lt;em&gt;bir kısmını&lt;/em&gt; ucuzlattı. Disiplin, kazandığın zamanı tam da darboğaza dönüşen kısma harcamak — zaten yığılmakta olandan daha fazlasını üretmeye geri dökmek değil.&lt;/p&gt;</content:encoded><category>ai-workflow</category><category>productivity</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Kararsız Testler: Retry Çözüm Değil</title><link>https://sade.dev/tr/journal/kararsiz-testler-ve-determinizm/</link><guid isPermaLink="true">https://sade.dev/tr/journal/kararsiz-testler-ve-determinizm/</guid><description>AI test yazmayı neredeyse bedava yaptı, kararsız süitler patladı. Retry ve quarantine neden bir koşu bandı ve kararsızlığı deterministik olarak ne yakalar</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;İncelememe bir pull request düştü: diff’in yarısı testti ve onları bir model yazmıştı. Yerelde geçiyorlardı. CI’da biri bir kez patladı, yeniden çalıştırınca geçti, iki gün sonra yük altında yine patladı. Koda kimse dokunmamıştı.&lt;/p&gt;
&lt;p&gt;O test yanlış değildi. &lt;strong&gt;Kararsızdı (flaky)&lt;/strong&gt; — ve kararsızlık, kendine has bir bozukluk türü.&lt;/p&gt;
&lt;h2 id=&quot;ai-kararsızlığı-icat-etmedi--seri-üretime-geçirdi&quot;&gt;AI kararsızlığı icat etmedi — seri üretime geçirdi&lt;/h2&gt;
&lt;p&gt;Eskiden test yazmanın bir maliyeti vardı; bu yüzden daha az yazar, her birini daha çok düşünürdün. Model bunları saniyeler içinde, yalnızca kodun o anki görünüşünden yazıyor. Eşzamanlılığı, kaynak limitlerini, bir async çağrının gerçek zamanlamasını göremez — o yüzden &lt;em&gt;doğru görünen&lt;/em&gt; ama sessizce deterministik olmayan kalıplara uzanır:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Async işi “beklemek” için sabit bir sleep: &lt;code&gt;time.Sleep(2 * time.Second)&lt;/code&gt;, &lt;code&gt;await page.waitForTimeout(2000)&lt;/code&gt;, &lt;code&gt;cy.wait(2000)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Her çalıştırmada farklı değer üreten, seed’siz bir rastgele kaynak: &lt;code&gt;Math.random()&lt;/code&gt;, &lt;code&gt;rand.Intn(100)&lt;/code&gt;, sabit seed’i olmayan bir &lt;code&gt;faker&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;DOM yapısına çakılı, kırılgan bir selector — &lt;code&gt;nth-child&lt;/code&gt;, otomatik üretilmiş bir class adı — markup azıcık kayınca kırılır.&lt;/li&gt;
&lt;li&gt;Her şeye bir mock; böylece test bir sonucu değil bir etkileşimi doğrular — &lt;a href=&quot;/tr/journal/sade-test-kulturu/&quot;&gt;hiçbir şey yapmayan testlerle coverage kovalamak&lt;/a&gt; yazısında anlattığım o boş yeşilin aynısı.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tek tek bakınca her biri makul. Ölçekte bir kararsızlık fabrikası.&lt;/p&gt;
&lt;h2 id=&quot;süit-büyüdükçe-daha-az-güvenilir-oluyor&quot;&gt;Süit büyüdükçe &lt;em&gt;daha az&lt;/em&gt; güvenilir oluyor&lt;/h2&gt;
&lt;p&gt;%0,5 ihtimalle boşuna patlayan tek bir test görünmez. Aynısından 800 tane koy, temiz bir çalıştırmada &lt;em&gt;herhangi&lt;/em&gt; bir testin patlama ihtimali 1 − 0,995^800 ≈ %98. Patlamalar birbirinden bağımsız da değil: CI, paylaşımlı ve throttle’lı donanımda koşar; laptop’unda 50 ms’de biten async test, yük altında timeout’a düşer.&lt;/p&gt;
&lt;p&gt;Model test eklemeye devam ettikçe bu sayı tırmanır ve süitin yanlış-kırmızı verme ihtimali kesinliğe doğru yürür. Önemsediğin geçiş, önemsemediğin gürültüde boğulur.&lt;/p&gt;
&lt;h2 id=&quot;retry-bir-koşu-bandı-tedavi-değil&quot;&gt;Retry bir koşu bandı, tedavi değil&lt;/h2&gt;
&lt;p&gt;Refleks, gürültüyü yutmak: job’u otomatik retry’la, testi flaky işaretle, quarantine’e al, bir ticket aç. Bunların hepsi &lt;strong&gt;reaktif&lt;/strong&gt; — test CI’ı zaten bozduktan, çoğu zaman birden çok kez bozduktan &lt;em&gt;sonra&lt;/em&gt; devreye girer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Retry gerçek CI dakikası yakar ve feedback loop’u uzatır; yeni hiçbir şey öğrenmemek için iki kez ödersin.&lt;/li&gt;
&lt;li&gt;Quarantine, sessizce büyüyen bir devre dışı test yığını üretir. O yığın borçtur ve faiziyle büyür — her atlanan test, artık kimsenin koşmadığı bir kontrol.&lt;/li&gt;
&lt;li&gt;Ticket, backlog gürültüsüne yaşlanır ve sessizce önemini yitirir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hiçbiri testi deterministik yapmaz. Belirtiyi gizler ve bunun bedelini sana keser.&lt;/p&gt;
&lt;h2 id=&quot;patlamayı-değil-anti-patterni-yakala&quot;&gt;Patlamayı değil, anti-pattern’i yakala&lt;/h2&gt;
&lt;p&gt;Kararsız bir test nadiren ince bir sebepten patlar. Az sayıda, iyi bilinen anti-pattern yüzünden patlar — ve bunlar &lt;strong&gt;test daha çalışmadan, diff’te görünür.&lt;/strong&gt; Sabit bir sleep, seed’siz bir RNG, duvar-saatine dayalı bir assertion, yapısal bir selector: her biri, test yazıldığı anda statik ve deterministik olarak tespit edilebilir.&lt;/p&gt;
&lt;p&gt;Ben bu kontrolü commit sınırında çalıştırıyorum — değişen test dosyalarının eklenen satırları üzerinde statik bir tarama, bilinen anti-pattern’leri işaretliyor. Aynı diff, aynı sonuç; model yok, network yok. Akıllı bir race’i yakalamaz, zaten amacı bu değil. Amaç daha dar ama çok değerli: &lt;em&gt;bariz&lt;/em&gt; kararsızlığı CI’a hiç ulaşmadan durdurmak — ki orada yakalamak yüz kat pahalı ve bir mühendisin öğleden sonrasına mal oluyor.&lt;/p&gt;
&lt;h2 id=&quot;testi-tespit-edilecek-bir-şey-kalmayacak-şekilde-tasarla&quot;&gt;Testi, tespit edilecek bir şey kalmayacak şekilde tasarla&lt;/h2&gt;
&lt;p&gt;Tespit, emniyet ağı. Asıl çözüm, testi en baştan deterministik yazmak:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sabit beklemeleri &lt;strong&gt;koşula dayalı&lt;/strong&gt; beklemeyle değiştir — beklediğin duruma ulaşana kadar yokla ya da çerçevenin &lt;code&gt;Eventually&lt;/code&gt;/&lt;code&gt;waitFor&lt;/code&gt;’ını kullan. Saati değil, bir olguyu bekle.&lt;/li&gt;
&lt;li&gt;Her rastgele kaynağı &lt;strong&gt;seed&lt;/strong&gt;’le; test ortasında &lt;code&gt;now()&lt;/code&gt; ve &lt;code&gt;Math.random()&lt;/code&gt; okumak yerine deterministik bir saat ve ID enjekte et.&lt;/li&gt;
&lt;li&gt;DOM pozisyonuna değil, &lt;strong&gt;role veya test id&lt;/strong&gt;’ye göre seç.&lt;/li&gt;
&lt;li&gt;Logic’i değil, &lt;strong&gt;sınırı&lt;/strong&gt; mock’la — ve en az bir gerçek integration yolunu koru, yoksa süit var olmayan bir dünyayı doğrular.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;bu-kalıp-ne-zaman-doğru-cevap-olmaktan-çıkar&quot;&gt;Bu kalıp ne zaman doğru cevap olmaktan çıkar?&lt;/h2&gt;
&lt;p&gt;Statik bir kapı isabet-önceliklidir: bariz anti-pattern’leri yakalar, kendi kodunun içine gömülü bir race condition’ı değil. Kurallarını muhafazakâr tut — gürültülü bir kapı yok sayılır, yok sayılan bir kapı ise hiç olmamasından kötüdür. Akıllı kararsızlığı kaçıracaktır; tahmin etmesine izin verme.&lt;/p&gt;
&lt;p&gt;Ve bunların hiçbiri daha zor olan okumanın yerine geçmez: &lt;em&gt;test edilen sistem&lt;/em&gt; deterministik olmadığı için kararsız olan bir test, sana test hakkında bir şey söylemiyor. Sana sistem hakkında bir şey söylüyor.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Güvenmek için yeniden çalıştırman gereken yeşil bir CI, yeşil değildir. Kararsız bir testi öldürmenin en ucuz yeri, yazıldığı satır — sana bir build’e mal olmadan önce.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; burada anlatılan commit-sınırı kontrolünü &lt;a href=&quot;https://commitbrief.com&quot;&gt;CommitBrief&lt;/a&gt;’te shipledim — &lt;a href=&quot;https://github.com/CommitBrief/commitbrief&quot;&gt;flaky-test dedektörü&lt;/a&gt; değişen test dosyalarındaki sabit sleep’leri ve seed’siz rastgeleliği, modelden önce, deterministik olarak statik yakalar.&lt;/p&gt;</content:encoded><category>testing</category><category>ci</category><category>ai</category><category>quality</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Şema Evrimi: Sözleşmeyi Kırmadan Değiştirmek</title><link>https://sade.dev/tr/journal/sema-evrimi-sozlesmeyi-kirmadan/</link><guid isPermaLink="true">https://sade.dev/tr/journal/sema-evrimi-sozlesmeyi-kirmadan/</guid><description>Bir mesajın data şekli, servisler arası bir sözleşmedir ve onu atomik değiştiremezsin. Kural: additive güvenli, kırıcı değişiklik yeni bir kimlik ister — ve önce tüketiciler yükseltir</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Biri bir olaya zorunlu bir &lt;code&gt;customer_id&lt;/code&gt; alanı ekledi ve üreticiyi deploy etti. Tüketicilerin yarısı henüz bu değişikliği shiplememişti. Yirmi dakika boyunca, eski tüketicilerin bir yıldır sorunsuz işlediği her mesaj birden validation’dan kalmaya başladı — ta ki rollout tamamlanana kadar. Hiçbir kod “yanlış” değildi. Bir sözleşme, ona bağlı olanların altından çekilip değiştirildi.&lt;/p&gt;
&lt;p&gt;Bir mesajın &lt;code&gt;data&lt;/code&gt; şekli bir API’dir. Response body yerine queue’da JSON olması onu daha az sözleşme yapmaz — &lt;em&gt;daha zor&lt;/em&gt; bir sözleşme yapar, çünkü iki tarafı aynı anda değiştiremezsin.&lt;/p&gt;
&lt;h2 id=&quot;dağıtık-bir-sözleşmeyi-atomik-değiştiremezsin&quot;&gt;Dağıtık bir sözleşmeyi atomik değiştiremezsin&lt;/h2&gt;
&lt;p&gt;HTTP API’sinin kontrol ettiğin tek bir sunucusu vardır. Yayınlanan bir mesajın ise &lt;strong&gt;bağımsız deploy edilen, kendi takvimlerinde olan birçok tüketicisi&lt;/strong&gt; vardır — ve at-least-once teslimatla, eski şekille üretilmiş yoldaki mesajlar, sen yenisini yayarken hâlâ geliyordur. “Herkesin yeni şemada olduğu” bir an yoktur. Yani tek güvenli değişiklikler, &lt;strong&gt;eski ve yeninin bir arada var olabildiği&lt;/strong&gt; değişikliklerdir.&lt;/p&gt;
&lt;p&gt;Bu tek kısıt sana tüm kural setini verir.&lt;/p&gt;
&lt;h2 id=&quot;ne-güvenli-ne-değil&quot;&gt;Ne güvenli, ne değil&lt;/h2&gt;
&lt;p&gt;Bir değişiklik, eski şema altında geçerli olan data yeni şema altında da geçerliyse backward-compatible’dır — böylece erken yükselten bir tüketici, henüz yükseltmemiş bir üreticinin yaydığı mesajları hâlâ kabul eder.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Opsiyonel bir alan ekle&lt;/strong&gt; — güvenli. Eski data onsuz olur, olur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bir &lt;code&gt;required&lt;/code&gt; kısıtını kaldır&lt;/strong&gt; (alanı opsiyonel yap) — güvenli. Eski data daha gevşek kuralı zaten karşılar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bir &lt;code&gt;enum&lt;/code&gt;’ı genişlet, bir &lt;code&gt;minimum&lt;/code&gt;’u gevşet&lt;/strong&gt; — güvenli.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ve ısıranlar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zorunlu bir alan ekle&lt;/strong&gt; ya da &lt;strong&gt;opsiyonel bir alanı zorunlu yap&lt;/strong&gt; — kırıcı. Eski data onu içermez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bir alanı kaldır, yeniden adlandır ya da tipini değiştir&lt;/strong&gt; — kırıcı. Yeniden adlandırma, bir kaldırma artı bir eklemedir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kuralları sıkılaştır&lt;/strong&gt; — bir &lt;code&gt;enum&lt;/code&gt; değerini düşür, bir &lt;code&gt;minimum&lt;/code&gt;’u yükselt, &lt;code&gt;additionalProperties&lt;/code&gt;’i kapat — kırıcı.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Asimetri asıl mesele: &lt;strong&gt;gevşetmek güvenli, sıkılaştırmak değil.&lt;/strong&gt; Bir tüketici, beklediğinden daha hoşgörülü bir data’yı, artık talep ettiği bir şeyi eksik bir data’ya göre çok daha kolay tolere eder.&lt;/p&gt;
&lt;h2 id=&quot;kırıcıysa-kimliği-sürümle--değiştirme&quot;&gt;Kırıcıysa, kimliği sürümle — değiştirme&lt;/h2&gt;
&lt;p&gt;Kırıcı bir değişiklikte içgüdü “şemayı güncelleyiver” demek. Deme. Mevcut bir kimliğin arkasındaki şekli değiştirmek, yukarıdaki rollout’u kıran şeyin ta kendisi. Bunun yerine &lt;strong&gt;yeni bir kimlik üret&lt;/strong&gt; — yeni bir mesaj URN’i (&lt;code&gt;urn:babel:orders:created.v2&lt;/code&gt;), yeni bir topic, yeni bir olay adı — ve ikisini paralel koştur:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Üreticiler &lt;code&gt;v1&lt;/code&gt; yaymaya devam eder; sen &lt;code&gt;v2&lt;/code&gt;’yi yanında yayınlarsın.&lt;/li&gt;
&lt;li&gt;Tüketiciler kendi takvimlerinde &lt;code&gt;v2&lt;/code&gt;’ye göç eder.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v1&lt;/code&gt;’in tüketicisi kalmayınca onu emekliye ayır.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Her adımın altındaki kural: &lt;strong&gt;önce tüketiciler yükseltir.&lt;/strong&gt; Hiçbir deploy edilmiş tüketicinin anlamadığı bir sürümü asla yayma. (Bu, &lt;a href=&quot;/tr/notes/idempotency-ayni-mesaj-iki-kez/&quot;&gt;bir mükerrer teslimat no-op olacak şekilde handler tasarlamanın&lt;/a&gt; aynı şekli — sırayı kontrol etmediğin için, değişikliği her sırada uygulanabilir kılarsın.)&lt;/p&gt;
&lt;h2 id=&quot;kuralı-mekanik-hâle-getir&quot;&gt;Kuralı mekanik hâle getir&lt;/h2&gt;
&lt;p&gt;Bunların hiçbiri, cuma akşamı 5’te bir code review’da yeniden türetmek isteyeceğin bir yargı değil. Yukarıdaki uyumluluk kuralları deterministik — eski ve yeni şema verildiğinde, bir araç sana “additive, ship et” ya da “kırıcı, yeni sürüm üret” diyebilir, hiçbir görüş katmadan. O yüzden ben bunu kapıya koyuyorum: sınırda iki şemayı diff’leyip kırıcı değişiklikte build’i kıran bir kontrol — aşağı tarafa ulaşmadan önce. Orada yakalamak bir PR yorumuna mal olur; production’da yakalamak yukarıdakine benzer bir rollout penceresine.&lt;/p&gt;
&lt;p&gt;Bu, &lt;a href=&quot;/tr/journal/ai-darbogazi-kaldirmadi/&quot;&gt;“darboğaz aşağı kaydı”&lt;/a&gt; tezinin sıkıcı-altyapı ucu: şema ucuza değiştirilir ama &lt;em&gt;yanlış&lt;/em&gt; değiştirmek pahalıdır, o yüzden bir parça araçla o ikinci maliyeti masadan kaldırırsın.&lt;/p&gt;
&lt;h2 id=&quot;bu-ne-zaman-önemsizleşir&quot;&gt;Bu ne zaman önemsizleşir?&lt;/h2&gt;
&lt;p&gt;Bir mesajın tam olarak tek bir üreticisi ve birlikte deploy olan tek bir tüketicisi varsa — tek bir servisin iç queue’su — sözleşme aslında dağıtık değildir; iki tarafı aynı anda değiştirebilirsin. O zaman bu tören fazlalıktır. Kurallar, &lt;em&gt;ikinci&lt;/em&gt;, bağımsız deploy edilen bir tüketici var olduğu an karşılığını vermeye başlar. İlk ayını aşan çoğu olay o noktaya ulaşır.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Bir şema sahip olduğun bir struct değildir; başka servislerin etrafında plan yaptığı bir sözdür. Onu, geri alamayacağın herhangi bir sözü nasıl evrimleştirirsen öyle evrimleştir — additive olarak ya da yeni bir ad altında. Asla eskisini sessizce yeniden tanımlayarak değil.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;İlgili:&lt;/em&gt; &lt;a href=&quot;https://babelqueue.com/docs/spec/1.x/schema-validation&quot;&gt;BabelQueue schema-validation spec’i&lt;/a&gt; bu uyumluluk kurallarını yazıya döker; &lt;a href=&quot;https://github.com/BabelQueue/babelqueue-registry&quot;&gt;babelqueue-registry&lt;/a&gt; URN-başına şemaların yaşadığı yerdir — onun &lt;code&gt;bqschema&lt;/code&gt; aracı (ve paketlenmiş Action’ı) kırıcı değişiklikte build’i kıran sınır kontrolüdür.&lt;/p&gt;</content:encoded><category>architecture</category><category>schema</category><category>queue</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Katmanlı Mimari mi, Clean Architecture mı?</title><link>https://sade.dev/tr/journal/katmanli-mimari-mi-clean-architecture-mi/</link><guid isPermaLink="true">https://sade.dev/tr/journal/katmanli-mimari-mi-clean-architecture-mi/</guid><description>Klasik katmanlı yapı çoğu projeye yeterken neden soyutlama katmanlarında boğuluyoruz; Clean Architecture gerçekten ne zaman değer katar?</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir Laravel projesini ilk açtığımda, tek bir “kullanıcı oluştur” işleminin yedi dosyaya yayıldığını gördüm: Controller, Request DTO, UseCase, Domain Entity, Repository Interface, Eloquent Repository ve iki yönlü bir Mapper. İçindeki tek iş kuralı, e-postanın benzersiz olmasıydı.&lt;/p&gt;
&lt;p&gt;Bu, Clean Architecture’ın yanlış uygulanmış hâliydi — ama suç Clean Architecture’da değil. Suç, hangi projenin hangi disipline ihtiyacı olduğunu hiç sormamakta.&lt;/p&gt;
&lt;h2 id=&quot;i̇ki-yaklaşım-kısaca&quot;&gt;İki yaklaşım, kısaca&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Katmanlı (layered) mimari&lt;/strong&gt; tanıdık olandır: Controller → Service → Repository → veritabanı. Bağımlılıklar yukarıdan aşağı akar, en altta framework durur. Laravel’in kendisi de zaten budur — Eloquent bir active record, controller’lar HTTP’ye bağlı, her şey framework’e gömülü.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean Architecture&lt;/strong&gt; (ve akrabaları hexagonal, onion) bağımlılık yönünü tersine çevirir: merkezde framework’ten habersiz domain durur, bağımlılıklar &lt;strong&gt;içeri&lt;/strong&gt; doğru bakar. Veritabanı, HTTP, framework — hepsi en dıştaki halkada, birer detay. Domain, Laravel’in var olduğunu bilmez.&lt;/p&gt;
&lt;p&gt;Fark felsefi değil, çok somut: katmanlı mimaride domain Eloquent’e bağımlıdır; Clean’de Eloquent domain’e bağımlıdır.&lt;/p&gt;
&lt;h2 id=&quot;clean-architecture-neyi-satın-alır&quot;&gt;Clean Architecture neyi satın alır&lt;/h2&gt;
&lt;p&gt;Bedava değil, ama gerçek şeyler verir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Framework’ten bağımsız test.&lt;/strong&gt; Domain kuralları, veritabanı olmadan, saf birim testlerle koşulur. Hızlı; kırılgan değil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Framework’ün ömründen uzun domain.&lt;/strong&gt; İş kuralları, Laravel sürümlerinden bağımsız yaşar. On yıllık bir domain, üç major framework upgrade’i görebilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Birden çok teslim mekanizması.&lt;/strong&gt; Aynı domain’i HTTP API, CLI komutu ve queue worker üzerinden çağırmak doğallaşır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Karşılığında ödediğiniz de somut: her katman geçişinde mapping, daha çok dosya, daha çok dolaylılık. Tek satırlık bir kural için yedi dosya — yazının başındaki manzara.&lt;/p&gt;
&lt;h2 id=&quot;çoğu-laravel-projesi-için-katmanlı-yeter&quot;&gt;Çoğu Laravel projesi için katmanlı yeter&lt;/h2&gt;
&lt;p&gt;Açık konuşayım: çoğu web uygulaması özünde CRUD’dur. Veriyi doğrula, kaydet, oku, göster. Böyle bir uygulamada Eloquent’e karşı “clean code” uğruna savaşmak, kazanılan değerden pahalıdır.&lt;/p&gt;
&lt;p&gt;Laravel’i bir detay değil, bilinçle seçilmiş bir temel olarak kabul edin. Controller + Form Request + Eloquent model + ince bir service sınıfı — bu, projelerin büyük çoğunluğu için doğru ağırlıktır. Framework’ü kucaklamak bir ödün değil, bir karardır; tıpkı &lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;boring architecture&lt;/a&gt; gibi.&lt;/p&gt;
&lt;p&gt;Yedi dosyalık bir CRUD ise &lt;a href=&quot;/tr/journal/ileride-lazim-olur-kodunun-faturasi/&quot;&gt;spekülatif genelliğin&lt;/a&gt; mimari ölçekteki hâlidir: bugün olmayan bir karmaşıklık için bugünden vergi ödemek.&lt;/p&gt;
&lt;h2 id=&quot;clean-architecture-ne-zaman-karşılığını-verir&quot;&gt;Clean Architecture ne zaman karşılığını verir&lt;/h2&gt;
&lt;p&gt;Disiplin, domain karmaşıklığı yüksek olduğunda gerçek getiri sağlar. Şu sinyaller varsa ciddiye alın:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;İş kuralları “kaydet/oku”nun çok ötesinde: çok adımlı hesaplamalar, durum makineleri, sektörel düzenlemeler.&lt;/li&gt;
&lt;li&gt;Bu kuralların framework’ten uzun yaşaması bekleniyor.&lt;/li&gt;
&lt;li&gt;Aynı domain birden fazla arayüzden besleniyor — API, CLI, mesaj kuyruğu, panel.&lt;/li&gt;
&lt;li&gt;Kural değişiklikleri sık ve riskli; veritabanına dokunmadan test edebilmek gerçek bir hız kazandırıyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sigorta primlendirme, muhasebe, lojistik optimizasyonu — bu alanlarda Clean Architecture’ın dolaylılığı kendini öder. Bir blog ya da standart bir yönetim panelinde ödemez.&lt;/p&gt;
&lt;h2 id=&quot;hep-ya-da-hiç-değil&quot;&gt;Hep ya da hiç değil&lt;/h2&gt;
&lt;p&gt;En sık atlanan nokta: bu bir ikili seçim değil. Aynı kod tabanında ikisi bir arada yaşayabilir.&lt;/p&gt;
&lt;p&gt;Domain’in gerçekten karmaşık olduğu modülü — diyelim fiyatlandırma — Clean tarzı izole edin: saf domain, interface’le ayrılmış repository, framework’ten bağımsız testler. Geri kalan CRUD modüllerini düz bırakın: controller, Eloquent, ince service. &lt;a href=&quot;/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/&quot;&gt;Modüler monolitin&lt;/a&gt; sağladığı sınırlar bunu doğal kılar — her modül kendi ağırlığını taşır.&lt;/p&gt;
&lt;p&gt;Mimari, projeye tek tip dağıtılan bir kural değil; karmaşıklığın yoğunlaştığı yere göre ayarlanan bir bütçedir.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Clean Architecture bir rozet değil, bir araç. Domain’iniz onun çözdüğü problemi yaşıyorsa paha biçilmez; yaşamıyorsa, tek satırlık bir kuralı yedi dosyaya bölen bir törenden ibaret.&lt;/p&gt;
&lt;p&gt;Mimariyi domain seçer — moda değil.&lt;/p&gt;</content:encoded><category>architecture</category><category>clean-architecture</category><category>simplicity</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Vibe Coding ile Production&apos;a Çıkılır mı?</title><link>https://sade.dev/tr/journal/vibe-coding-ile-production/</link><guid isPermaLink="true">https://sade.dev/tr/journal/vibe-coding-ile-production/</guid><description>Vibe coding bir ilk sürümün maliyetini düşürür, yanlış olmanın maliyetini değil. O fatura build&apos;de değil, production&apos;da çıkar — ve onu kimin elinde çıkardığına bağlıdır.</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;“Yerelde çalışıyordu” cümlesinin yeni bir akrabası var: “demoda çalışıyordu.” Bir hafta önce tarif edilip dakikalar içinde ayağa kalkan, ilk kullanıcılarını bile almış bir ürün — sonra ilk gerçek yük geldiğinde yavaşlıyor, ilk meraklı kullanıcı başkasının faturasını görüyor, ilk şema değişikliği migration’da patlıyor. Üreten kişi şaşırıyor: kod çalışıyordu.&lt;/p&gt;
&lt;p&gt;Çalışıyordu. Ama vibe coding’in düşürdüğü maliyet &lt;strong&gt;üretim&lt;/strong&gt;di; yanlış olmanın maliyeti değil. O maliyet ertelenir, silinmez — ve ertelenen maliyet faiz işletir.&lt;/p&gt;
&lt;h2 id=&quot;vibe-codingin-ucuzlattığı-şey-gerçek&quot;&gt;Vibe coding’in ucuzlattığı şey gerçek&lt;/h2&gt;
&lt;p&gt;Strawman kurmayalım. Bir ilk sürümü çıkarmak hiç bu kadar ucuz olmamıştı: bir CRUD ekranı, bir REST uç noktası, bir form doğrulaması — tarif et, çalışsın. Prototip, throwaway, iç araç, öğrenme projesi için bu mükemmel bir hızlandırıcı.&lt;/p&gt;
&lt;p&gt;Ucuzlatmadığı tek şey var: veri modeli, güvenlik sınırı ve failure mode hakkında &lt;strong&gt;yanlış&lt;/strong&gt; olmanın bedeli. Bu üç şey production’da yanlışsa, fatura build anında değil, çok sonra — genelde gece 2’de — gelir.&lt;/p&gt;
&lt;h2 id=&quot;fatura-buildde-değil-productionda-görünür&quot;&gt;Fatura build’de değil, production’da görünür&lt;/h2&gt;
&lt;p&gt;Vibe coding ile çıkan sistemlerde tekrar tekrar gördüğüm dört kalıp. Hiçbiri “kod derlenmiyor” tipinde değil; hepsi &lt;strong&gt;intent ile gerçeklik arasındaki boşlukta&lt;/strong&gt; yaşıyor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Göç edemeyen veri modeli.&lt;/strong&gt; Dolu bir tabloya &lt;code&gt;NOT NULL&lt;/code&gt; kolon, &lt;code&gt;DEFAULT&lt;/code&gt;’suz. Boş tabloda sorunsuz, production tablosunda fail. Daha derini: ilişkileri yanlış kurulmuş bir şema altı ay sonra basit bir özelliği imkânsız kılar. Veri modelini sonradan düzeltmek bir refactor değil, bir &lt;a href=&quot;/tr/journal/buyuk-veri-sendromu/&quot;&gt;veri göçü riskidir&lt;/a&gt; — ve en pahalı anda, gerçek veriyle yapılır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Düşman girdiyi hesaba katmayan güvenlik.&lt;/strong&gt; Kimlik kontrolünden geçen ama sahiplik kontrolünden geçmeyen object ID’ler (IDOR), query’ye ulaşan kullanıcı girdisi, koda gömülü secret. Agent’ın ürettiği “standart” çözüm, girdinin düşman olabileceğini varsaymaz; çünkü ona sorulan soru “bu çalışsın” idi, “bu saldırıya dayansın” değil.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Ölçeklenmeyi görmeyen erişim deseni.&lt;/strong&gt; Döngü içinde sorgu (N+1), &lt;code&gt;SELECT *&lt;/code&gt;, pagination’sız liste, eş zamanlı olması gereken yerde senkron çağrı. On satırlık tabloda görünmez; yüz binlik tabloda p95’i uçurur.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Gözlemlenemeyen ve geri alınamayan sistem.&lt;/strong&gt; Log yok, metrik yok, rollback planı yok. Kırıldığında &lt;em&gt;neden&lt;/em&gt; kırıldığını göremezsiniz — ki vibe-coded sistemde en çok ihtiyaç duyacağınız şey budur.&lt;/p&gt;
&lt;p&gt;Ortak payda şu: araç en güvenli göründüğü yerde en tehlikeli. Yaygın kalıpları sağlam yazar; problemin kendine özgü boyutunu görmeden “standart” bir çözüm verir. &lt;strong&gt;Standart çözüm, sormadığınız problemin cevabıdır.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;çıktının-kalitesini-belirleyen-yazma-hızı-değil-denetim&quot;&gt;Çıktının kalitesini belirleyen: yazma hızı değil, denetim&lt;/h2&gt;
&lt;p&gt;Aynı agent’ı üç farklı elde düşünün. Fark, kimin daha hızlı yazdırdığı değil; &lt;strong&gt;sorulan sorunun ve yapılan review’un kalitesi.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hiç/çok az bilgi.&lt;/strong&gt; Soruyu çerçeveleyemez, çıktıyı eleştirel okuyamaz. “Standart çözüm” yukarıdaki dört kalıpla birlikte production’a olduğu gibi çıkar. “Çalışıyor” ile “doğru çalışıyor” arasındaki farkı henüz hissedemez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Az-orta bilgi.&lt;/strong&gt; Bazı soruları doğru kurar, çıktının bir kısmını yakalar. Tavanı sistemin sınırlarında: yük altında nerede kırılır, bu soyutlama doğru mu, bu veri modeli hangi değişikliği kaldırır. Sonuç: iyi parçalar, kırılgan bütün.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Senior + sistem mimarisi.&lt;/strong&gt; Agent bir ikame değil, bir &lt;strong&gt;çarpan&lt;/strong&gt;. Plan yaptır → onayla → adım adım yazdır → bir junior’ın kodu gibi review et → savunamayacağın satırı merge etme. Bu disiplinin somut hâli ayrı bir yazıda: &lt;a href=&quot;/tr/journal/ai-assisted-engineering-workflow/&quot;&gt;AI-Assisted Engineering Workflow’um&lt;/a&gt;. Bu elde hız artar, kalite düşmez — çünkü muhakeme insanda kalır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu bir bütçe ifadesidir, ideoloji değil: senior, ertelenen maliyete karşı &lt;strong&gt;en ucuz sigortadır.&lt;/strong&gt; Doğru veri modelini baştan kurmak bir öğleden sonra; yanlışını canlı sistemde düzeltmek bir çeyrek. “Para kazanınca düzeltiriz” bu yüzden absürt — para kazandıran ürün, aynı zamanda elinizi bağlayan, göçü en riskli üründür. Senior sonradan değil, önceden ucuzdur.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-vibe-coding-doğru-cevap&quot;&gt;Ne zaman vibe coding doğru cevap?&lt;/h2&gt;
&lt;p&gt;Her öneri gibi bunun da bir geçerlilik sınırı var — ve sınırı söylemeden öneri yapmam.&lt;/p&gt;
&lt;p&gt;Vibe coding &lt;strong&gt;doğru&lt;/strong&gt; araçtır: prototip, throwaway demo, hobi, gerçek verisi/kullanıcısı olmayan iç araç, öğrenme. Buralarda yanlış olmanın maliyeti düşük olduğu için ertelemek rasyoneldir.&lt;/p&gt;
&lt;p&gt;Denklem şu üç şeyden biri masaya gelince çevrilir: &lt;strong&gt;gerçek kullanıcı, gerçek veri, gerçek para&lt;/strong&gt; (ya da gerçek uptime taahhüdü). O eşikten sonra ertelenen maliyet faizini ödetmeye başlar ve direksiyonu tutan birine ihtiyaç doğar.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Yapay zekâ agent’larıyla üretim mümkün, hatta gerçekten hızlı. Ama agent, kodu kimin yazdığını değiştirdi; kimin sorumlu olduğunu değil. &lt;strong&gt;Vibe coding üretimi ucuzlattı, yanlış olmanın maliyetini değil&lt;/strong&gt; — ve o maliyet, production’a kimin elinde çıktığına bakar.&lt;/p&gt;</content:encoded><category>ai-workflow</category><category>vibe-coding</category><category>production</category><category>architecture</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>&quot;İleride Lazım Olur&quot; Kodunun Production Faturası</title><link>https://sade.dev/tr/journal/ileride-lazim-olur-kodunun-faturasi/</link><guid isPermaLink="true">https://sade.dev/tr/journal/ileride-lazim-olur-kodunun-faturasi/</guid><description>YAGNI&apos;yi pratikte uygulamak: bugün gerekmeyen esnekliği eklemenin bakım, okunabilirlik ve hata yüzeyi olarak geri ödenen bedeli.</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir code review’da şu yorumla sık karşılaşıyorum: &lt;code&gt;// ileride başka sağlayıcı da gelebilir&lt;/code&gt;. Hemen altında tek implementasyonlu bir interface, onu yaratan bir factory ve hiç okunmayan bir config anahtarı durur. Planlanan ikinci sağlayıcı yoktur — sadece ihtimali vardır.&lt;/p&gt;
&lt;p&gt;Bu kod bedava yazıldı sanılır. Değil. “İleride lazım olur” diye eklenen her esneklik, kullanılmaya başlanana kadar her gün faiz ödeyen bir borçtur.&lt;/p&gt;
&lt;h2 id=&quot;spekülatif-genellik-nedir&quot;&gt;Spekülatif genellik nedir?&lt;/h2&gt;
&lt;p&gt;Spekülatif genellik, bugün var olmayan bir gereksinim için bugünden esneklik inşa etmektir. Tek satıra yetecek yere bir strateji deseni, tek tipe yetecek yere generic bir parametre, tek çağrı için bir event sistemi.&lt;/p&gt;
&lt;p&gt;Gerekçe hep aynı kelimelerle gelir: “ileride”, “ne olur ne olmaz”, “genişletilebilir olsun”. Hepsi geleceğe dair bir tahmindir. Ve tahmin, bir kod tabanının en pahalı parçasıdır.&lt;/p&gt;
&lt;h2 id=&quot;faturanın-kalemleri&quot;&gt;Faturanın kalemleri&lt;/h2&gt;
&lt;p&gt;Kullanılmayan esneklik boş durmaz; her gün şu kalemleri öder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Okuma maliyeti.&lt;/strong&gt; Kodu okuyan herkes “bunun ikinci bir kullanımı var mı?” diye arar, bulamaz, kafası karışmış hâlde devam eder. Esneklik, var olmayan bir senaryoyu sürekli düşündürür.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bakım maliyeti.&lt;/strong&gt; Soyutlama, altındaki gerçek koda her dokunuşta birlikte güncellenir. Boş bir katman bile bakım ister.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hata yüzeyi.&lt;/strong&gt; Çalışmayan kod, çalışan kodu kırabilir — yanlış dallanma, ölü bir branch’te unutulmuş bir bug, hiç test edilmemiş bir yol.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yanlış soyutlamaya kilitlenme.&lt;/strong&gt; En pahalısı bu. Bugün tahminle çizdiğiniz interface, yarın gerçek ihtiyaç geldiğinde neredeyse hiç uymaz — ama artık onu söküp atmak, baştan yazmaktan zordur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test yükü.&lt;/strong&gt; Her fazladan dolaylılık, test kurulumunu uzatır; mock sayısını artırır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;Boring architecture&lt;/a&gt; yazısındaki “karmaşıklık bütçesi” burada da geçerli: spekülatif genellik o bütçeyi, hiçbir şey teslim etmeden harcar.&lt;/p&gt;
&lt;h2 id=&quot;tahmin-neredeyse-hep-yanlış-çıkar&quot;&gt;Tahmin neredeyse hep yanlış çıkar&lt;/h2&gt;
&lt;p&gt;“İleride lazım olur” kodunun en acı yanı şu: ileride gerçekten bir şey lazım olduğunda, lazım olan şey tahmin ettiğiniz şey değildir.&lt;/p&gt;
&lt;p&gt;İkinci ödeme sağlayıcısı sonunda gelir — ama sizin interface’inizin hiç varsaymadığı bir async webhook akışıyla gelir. İkinci kiracı (tenant) gelir — ama satır bazlı izolasyonla değil, ayrı şema ile. Önceden çizilmiş esneklik, gerçek gereksinimle karşılaşınca bir yardım değil, bir engel olur: önce onu kaldırmanız gerekir.&lt;/p&gt;
&lt;p&gt;Domain’i en az tanıdığınız an, projenin ilk haftasıdır; geleceğe dair en kötü tahminleri tam da o an verirsiniz. &lt;a href=&quot;/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/&quot;&gt;Modüler monolit yazısında&lt;/a&gt; sınırların ilk haftada neredeyse kesin yanlış çizileceğini söylemiştim — aynı şey her erken soyutlama için geçerlidir.&lt;/p&gt;
&lt;h2 id=&quot;yagniyi-pratikte-uygulamak&quot;&gt;YAGNI’yi pratikte uygulamak&lt;/h2&gt;
&lt;p&gt;“You Aren’t Gonna Need It” bir tembellik izni değil, bir zamanlama kuralıdır: esnekliği, ihtiyaç &lt;strong&gt;kanıtlandığında&lt;/strong&gt; ekle — tahmin edildiğinde değil.&lt;/p&gt;
&lt;p&gt;Pratik karşılığı:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bugünün bilinen vakası için yaz.&lt;/strong&gt; Tek sağlayıcı varsa tek sağlayıcıyı yaz. Düz, doğrudan, okunur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Üç kuralı.&lt;/strong&gt; Bir kalıbı soyutlamadan önce üç gerçek kullanımını bekle. İki, tesadüf olabilir; üç, bir desendir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dikişi sonra aç.&lt;/strong&gt; İkinci sağlayıcı gerçekten geldiğinde interface’i çıkarmak bir IDE refactor’ı — birkaç saat. O birkaç saati bugünden ödemenin hiçbir getirisi yok.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Asimetri şurada: düz kodu sonradan soyutlamak kolaydır. Yanlış soyutlamayı sonradan düzleştirmek zordur. Bu asimetri YAGNI’den yanadır.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-önden-esneklik-haklı&quot;&gt;Ne zaman önden esneklik haklı?&lt;/h2&gt;
&lt;p&gt;Her şeyi son ana bırakmak da değil. Ayrım, kararın &lt;strong&gt;geri dönülebilir&lt;/strong&gt; olup olmadığında:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Geri dönülebilir kararlar (çift yönlü kapı).&lt;/strong&gt; Bir sınıfın iç yapısı, bir fonksiyonun imzası, kod organizasyonu. Bunları sonradan değiştirmek ucuz — bugünden esnek tutmaya gerek yok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geri dönülmesi zor kararlar (tek yönlü kapı).&lt;/strong&gt; Veritabanı şeması, dışarıya verilmiş bir API kontratı, veri formatı, güvenlik sınırı. Bunlarda ileriyi düşünmek spekülasyon değil, sorumluluktur — çünkü hatanın bedeli production’da veri göçü demektir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kural şu: &lt;strong&gt;kodun içinde&lt;/strong&gt; cömertçe basit ol; &lt;strong&gt;dışa açtığın ve veriye yazdığın&lt;/strong&gt; yerde tedbirli ol. Esnekliği, geri almanın gerçekten pahalı olduğu yere sakla.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;“İleride lazım olur” cümlesi bir gereksinimi değil, bir korkuyu ifade eder. Kodu korkuyla değil, kanıtla büyütün.&lt;/p&gt;
&lt;p&gt;En kolay silinen kod, hiç yazılmamış olandır.&lt;/p&gt;</content:encoded><category>architecture</category><category>simplicity</category><category>decisions</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Ardışık Numara Üretiminde Race Condition ve Gap: JIT Rezervasyon</title><link>https://sade.dev/tr/systems/ardisik-numara-uretiminde-race-condition-ve-gap/</link><guid isPermaLink="true">https://sade.dev/tr/systems/ardisik-numara-uretiminde-race-condition-ve-gap/</guid><description>Yasal olarak ardışık ve boşluksuz numaraları paralel worker&apos;larla üretmek: race condition ile gap&apos;i birlikte çözen JIT rezervasyon kalıbı.</description><pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Resmî entegratörden dönen hata kısaydı: &lt;code&gt;Duplicate / Already Exists&lt;/code&gt;. İki worker aynı saniyede aynı fatura numarasını üretmiş, ikisi de aynı numarayla göndermiş, biri reddedilmişti. O seride fatura kesimi tıkandı ve arkadaki her iş onun gerisinde bekledi.&lt;/p&gt;
&lt;p&gt;Bu kalıbı yasal belge numaralandıran birkaç sistemde aynı şekilde kurdum; her seferinde aynı iki kısıt çatışır ve akla ilk gelen çözümlerin hepsi kırılır. Aşağıda hem neyin kırıldığını hem de bugün kullandığım çözümü — JIT rezervasyon — anlatıyorum.&lt;/p&gt;
&lt;h2 id=&quot;i̇ki-kısıt-aynı-anda&quot;&gt;İki kısıt aynı anda&lt;/h2&gt;
&lt;p&gt;Yasal belge numaralandırmasını (e-fatura, makbuz, irsaliye) zorlaştıran şey, iki kısıtın &lt;strong&gt;birlikte&lt;/strong&gt; sağlanma zorunluluğudur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mükerrersiz&lt;/strong&gt; — aynı numara iki kez kullanılamaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boşluksuz&lt;/strong&gt; — seride atlanan numara olamaz. Denetimde &lt;code&gt;1454&lt;/code&gt; yoksa, “nereye gitti” sorusunun cevabı verilmek zorundadır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Her kısıt tek başına kolaydır. &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; mükerrersizliği halleder; bir sayaç boşluksuzluğu halleder. Ama numaranın başarısız olabilen bir dış sisteme (entegratör, GİB) gönderildiği an, ikisini birden tutmak bir kilit değil &lt;strong&gt;zamanlama&lt;/strong&gt; problemine döner.&lt;/p&gt;
&lt;h2 id=&quot;sistemin-şekli--ve-onu-açığa-çıkaran-göç&quot;&gt;Sistemin şekli — ve onu açığa çıkaran göç&lt;/h2&gt;
&lt;p&gt;Bu problem aslında bir mimari göçün kuyruğunda çıktı. Eskiden akış &lt;strong&gt;senkrondu&lt;/strong&gt;: kullanıcı panelde “Faturamı oluştur” butonuna basar, fatura o request içinde, kullanıcı sonucu ekranda beklerken kesilirdi. O modelde numaralandırma yıllarca “çalıştı” — çünkü her istek, kullanıcının bloke beklemesi boyunca fiilen seri işleniyordu; gerçek bir eşzamanlılık hiç oluşmuyordu.&lt;/p&gt;
&lt;p&gt;Sonra akışı ayrıştırdım. Bugün farklı proje ve framework’ler faturalarını &lt;strong&gt;ortak bir RabbitMQ kuyruğuna&lt;/strong&gt; yazıyor; kuyruğu sürekli dinleyen &lt;strong&gt;ayrı bir consumer/worker mikroservisi&lt;/strong&gt; mesajları tüketiyor, e-fatura formatına çeviriyor ve entegratöre iletiyor.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Proje A (framework X) ──┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Proje B (framework Y) ──┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Scheduler (cron)      ──┼──►  [ ortak RabbitMQ ]  ──►  Consumer mikroservisi (N worker)  ──►  Entegratör API ──► GİB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Kullanıcı aksiyonu    ──┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buton artık senkron çağrı yapıp orada faturalandırmıyor; &lt;a href=&quot;/tr/notes/senkron-mu-asenkron-mu/&quot;&gt;HTTP ile kuyruk arasındaki sınır&lt;/a&gt; tam buradan geçer: dış API çağrısı saniyeler sürebilir, onu bir request-response döngüsünde tutmak istemedim. Sonuç &lt;a href=&quot;/tr/notes/event-driven-mimari-ne-zaman/&quot;&gt;event-driven&lt;/a&gt; bir yapı — ölçeklenebilir, dayanıklı ve üreticilerden bağımsız.&lt;/p&gt;
&lt;p&gt;Ama bu ayrıştırma, yıllardır gizli kalan bir şeyi açığa çıkardı. Senkron model numaralandırmayı kazara seri işliyordu; consumer’ı eşzamanlı çalıştırır çalıştırmaz o kaza ortadan kalktı ve altta hep var olan race condition yüzeye vurdu. Bug küçük ve gözden kaçacak cinstendi — tam da yalnızca eşzamanlılık gerçek olunca ortaya çıktığı için. Buradan sonra tüm tasarım kararı tek bir soruda düğümlenir: &lt;strong&gt;numara nereden ve ne zaman gelir?&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bir göç eşzamanlılık hatası &lt;strong&gt;yaratmaz&lt;/strong&gt;; seri bir yürütme modelinin sakladığı hatayı &lt;strong&gt;görünür kılar.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;naif-çözüm-1--gönderim-anında-max-race-condition&quot;&gt;Naif çözüm 1 — gönderim anında &lt;code&gt;MAX()&lt;/code&gt; (race condition)&lt;/h2&gt;
&lt;p&gt;İlk kurduğumda numarayı gönderim anında ürettim:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; MAX&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(invoice_no) &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; outgoing_invoices &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; series &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tek worker’la kusursuz çalıştı. İkinci worker’ı açtığım gün çöktü:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Worker-1: SELECT MAX(...) → 1453&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Worker-2: SELECT MAX(...) → 1453   (Worker-1 henüz INSERT etmedi)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Worker-1: API&amp;#39;ye 1454 ile gider  ✓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Worker-2: API&amp;#39;ye 1454 ile gider  ✗ Duplicate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Klasik &lt;strong&gt;read-modify-write race condition&lt;/strong&gt;’ı. &lt;code&gt;MAX()&lt;/code&gt; okuması ile yazma arasındaki pencere, iki worker’ın aynı değeri görmesine yetecek kadar geniş. Hacim arttıkça pencere genişler — yani sorun yük altında, en kötü zamanda büyür.&lt;/p&gt;
&lt;h2 id=&quot;naif-çözüm-2--numarayı-baştan-üret-gap&quot;&gt;Naif çözüm 2 — numarayı baştan üret (gap)&lt;/h2&gt;
&lt;p&gt;İkinci refleksim çoğu geliştiriciyle aynıydı: numarayı mümkün olan en erken yerde, kuyruğa mesaj atarken üret ve mühürle. Mükerrerlik bitti, ama &lt;strong&gt;gap&lt;/strong&gt; doğdu:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1454&lt;/code&gt; numaralı fatura kuyrukta beklerken işlenir.&lt;/li&gt;
&lt;li&gt;Entegratörde kalıcı bir hata alır (geçersiz VKN, iş kuralı reddi) ya da kullanıcı kuyruktayken iptal eder.&lt;/li&gt;
&lt;li&gt;O fatura düşer; arkadan gelen &lt;code&gt;1455&lt;/code&gt; ile yola devam eder.&lt;/li&gt;
&lt;li&gt;Seride &lt;code&gt;1454&lt;/code&gt; boşluğu kalır — denetimde hesabı verilemeyecek bir gap.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;İkilem buydu: &lt;strong&gt;numarayı geç üretirsem race, erken üretirsem gap.&lt;/strong&gt; Mesele bir yöntemi diğeriyle değiştirmek değildi; ikisini birden tutmaktı.&lt;/p&gt;





























&lt;div class=&quot;table-wrap&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Yaklaşım&lt;/th&gt;&lt;th&gt;Mükerrersiz&lt;/th&gt;&lt;th&gt;Boşluksuz&lt;/th&gt;&lt;th&gt;Kırılma&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Gönderimde &lt;code&gt;MAX()+1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✗&lt;/td&gt;&lt;td&gt;✓&lt;/td&gt;&lt;td&gt;Paralel worker’da çakışma&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Enqueue’da üret&lt;/td&gt;&lt;td&gt;✓&lt;/td&gt;&lt;td&gt;✗&lt;/td&gt;&lt;td&gt;Başarısız/iptal → gap&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;JIT rezervasyon&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✓&lt;/td&gt;&lt;td&gt;✓&lt;/td&gt;&lt;td&gt;— (maliyeti aşağıda)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;h2 id=&quot;neden-hazır-bir-sequence-yetmez&quot;&gt;Neden hazır bir sequence yetmez&lt;/h2&gt;
&lt;p&gt;“Sayaç boşluksuzluğu halleder” dedim; ama veritabanının kendi sequence’ı (&lt;code&gt;SEQUENCE&lt;/code&gt;, &lt;code&gt;AUTO_INCREMENT&lt;/code&gt;) bunu &lt;strong&gt;yapmaz&lt;/strong&gt; — tersine, gap üretmek üzere tasarlanmıştır. Sequence performans için transaction’dan bağımsız ilerler: &lt;code&gt;nextval&lt;/code&gt; çağıran transaction rollback olsa bile tüketilen değer geri gelmez. PostgreSQL’de başarısız bir &lt;code&gt;INSERT&lt;/code&gt;’in &lt;code&gt;id&lt;/code&gt;’si kalıcı olarak atlanır; MySQL &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; aynı şekilde davranır. Bu çoğu sistemde &lt;strong&gt;istenen&lt;/strong&gt; davranıştır — birincil anahtarda boşluk kimsenin umurunda değil. Bizim problemimizde ise tam olarak kaçındığımız şey. Bu yüzden boşluksuzluğu uygulama katmanında, açık bir sayaç tablosuyla yönetiyorum.&lt;/p&gt;
&lt;p&gt;İkinci itiraz: “tek satırlık sayaç tablosu kendi başına bottleneck olmaz mı?” Olmaz — çünkü o satırın kilidi yalnızca &lt;code&gt;UPDATE ... SET next_no = next_no + 1&lt;/code&gt; deyimini içeren kısa transaction commit olana kadar, yani tek haneli milisaniyeler boyunca tutulur; dış çağrı boyunca değil. Yine de yük çok yüksekse seriyi bölmek (&lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;… ayrı sayaçlar) contention’ı doğrusal düşürür; her serinin kendi boşluksuz dizisi olur ve seriler birbirini beklemez.&lt;/p&gt;
&lt;h2 id=&quot;jit-rezervasyon-statei-dış-çağrıdan-bir-ms-önce-mühürlemek&quot;&gt;JIT rezervasyon: state’i dış çağrıdan bir ms önce mühürlemek&lt;/h2&gt;
&lt;p&gt;Bende çözüm şu oldu: numarayı ne çok erken ne çok geç, &lt;strong&gt;dış API çağrısından tam bir an önce&lt;/strong&gt; üretip kalıcılaştırmak. İki parça ekledim:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Atomik bir &lt;strong&gt;sekans sayacı&lt;/strong&gt; (&lt;code&gt;invoice_sequences&lt;/code&gt;) — seriyi tek kaynaktan ilerletir.&lt;/li&gt;
&lt;li&gt;Taslak fatura üzerinde bir &lt;strong&gt;rezervasyon kolonu&lt;/strong&gt; (&lt;code&gt;reserved_no&lt;/code&gt;) — üretilen numaranın, dış çağrı başarısız olsa bile &lt;strong&gt;korunduğu&lt;/strong&gt; yer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kilidi yalnızca rezervasyon için tutuyorum; dış çağrı kilidin &lt;strong&gt;dışında&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│── TX başlat ─────────────────│&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│  invoice&amp;#39;u lockForUpdate     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│  reserved_no boşsa:          │   ← satır kilidi yalnızca burada (tek haneli ms)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│    sequence++ → reserved_no  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│    status = SENDING          │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│── COMMIT ────────────────────│&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                └──► Entegratör API çağrısı (200ms–2sn, kilit YOK)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      ├─ başarılı → outgoing_invoices&amp;#39;a INSERT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      └─ başarısız → reserved_no DURUR, status = ERROR&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sayacı satır kilidiyle atomik ilerletiyorum:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  SELECT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; next_no &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; invoice_sequences &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; series &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FOR&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; UPDATE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;  UPDATE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; invoice_sequences &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; next_no &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; next_no &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; series &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;COMMIT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;  &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;-- kilit ms içinde açılır; bir sonraki worker beklemeden devralır&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Worker tarafında rezervasyonu &lt;strong&gt;idempotent&lt;/strong&gt; kılan tek bir kontrol var:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; DB&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ()&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoiceId&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; Invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;whereKey&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoiceId&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;lockForUpdate&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;first&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;    // Retry&amp;#39;da numara KORUNUR — yeniden üretilmez.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;reserved_no&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;        $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;reserved_no&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;sequence&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt; // FOR UPDATE&amp;#39;li sayaç&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;        $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;      =&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;SENDING&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;        $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt; // TX burada commit olur; kilit kalkar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;integrator&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;($&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;invoice&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt; // saniyeler sürebilir — kilit tutmuyoruz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kritik satır &lt;code&gt;if ($invoice-&amp;gt;reserved_no === null)&lt;/code&gt;. Bir fatura hata alıp retry edildiğinde alan doludur; yeni numara üretmem, eldekini korurum. Aynı fatura kaç kez denenirse denensin &lt;strong&gt;tek bir numara&lt;/strong&gt; tüketir. Gap’i kapatan yarı budur: başarısız fatura numarasını kaybetmez, üzerinde asılı tutar ve aynısıyla yeniden dener.&lt;/p&gt;
&lt;h2 id=&quot;erken-commit-neden-pazarlık-konusu-değil&quot;&gt;Erken commit neden pazarlık konusu değil&lt;/h2&gt;
&lt;p&gt;Kalıbın kalbi &lt;strong&gt;erken commit&lt;/strong&gt;. Yanlış yaparsanız tüm kalıp bir bottleneck’e döner.&lt;/p&gt;
&lt;p&gt;Naif refleks, “atomik olsun” diye tüm işi tek transaction’a sarmaktır: kilidi al, numarayı üret, &lt;strong&gt;dış API’yi çağır&lt;/strong&gt;, sonra commit et. Bu felakettir. Entegratör çağrısı 200ms ile 2 saniye arası sürer; o süre boyunca satır kilidi tutulursa aynı seriyi bekleyen her worker sıraya girer. Throughput, tek bir dış çağrının gecikmesine iner — saniyede onlarca fatura kesmesi gereken sistem, saniyede bire düşer.&lt;/p&gt;
&lt;p&gt;Bu yüzden kilidi yalnızca rezervasyon için tutup hemen commit ediyorum. Kilit tek haneli milisaniyede açılır; paralel worker’lar &lt;code&gt;1454, 1455, 1456...&lt;/code&gt; rezerve etmeye devam eder. Dış çağrının yavaşlığı artık kimseyi bloklamaz.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kural: &lt;strong&gt;kilit DB state’i için tutulur, dış I/O için değil.&lt;/strong&gt; Bir transaction içinde network çağrısı varsa, kilit süreniz o network’ün insafına kalmıştır.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;birden-çok-üreticiyi-güvende-tutmak&quot;&gt;Birden çok üreticiyi güvende tutmak&lt;/h2&gt;
&lt;p&gt;Birden çok üretici (scheduler, kullanıcı butonu, farklı projeler) aynı seriyi beslediği için, numara &lt;strong&gt;tek bir noktadan&lt;/strong&gt; rezerve edilmek zorunda. Hepsi consumer aşamasında aynı &lt;code&gt;reserved_no&lt;/code&gt; kontrolünden geçer. Üretici kim olursa olsun rezervasyon kaynağı tektir; numara üretimi üreticiye değil, consumer’daki tek koda bağlıdır. Numarayı üreticide (enqueue anında) üretmeyi denemek, bizi yukarıdaki “erken üret → gap” tuzağına geri götürürdü; o yüzden rezervasyon tek bir yerde, consumer’da yaşar.&lt;/p&gt;
&lt;p&gt;İkinci mesele &lt;strong&gt;queue starvation&lt;/strong&gt;’ı. Scheduler’ın toplu bastığı binlerce mesaj, canlıdaki bir kullanıcının anlık isteğini ezmemeli — yoksa kullanıcı, arkasında 4.000 toplu işin olduğu bir kuyruğun sonunu bekler. RabbitMQ’da priority queue ile kullanıcı isteklerini yüksek öncelikle araya sokuyorum; toplu iş arka planda akarken buton tetikli iş loading ekranını saniyelere değil milisaniyelere indirir. Öncelik doğruluğu değiştirmez — numara yine tek kaynaktan gelir — yalnızca &lt;strong&gt;adalet&lt;/strong&gt; ekler. (Kuyruğun neden ve nasıl tıkandığı ayrı bir konu: &lt;a href=&quot;/tr/notes/laravel-queue-production-yavaslama/&quot;&gt;Laravel queue production’da neden yavaşlar&lt;/a&gt;.)&lt;/p&gt;
&lt;h2 id=&quot;dual-write-problemi-ve-orphan-recovery&quot;&gt;Dual-write problemi ve orphan recovery&lt;/h2&gt;
&lt;p&gt;Geriye en sinsi edge case kalır. API çağrısı &lt;strong&gt;başarılı&lt;/strong&gt; olur ama yanıtını yerel DB’ye yazarken worker çöker ya da network kopar. Entegratörde fatura yasal olarak kesilmiştir; sizin tarafınızda ise hâlâ &lt;code&gt;SENDING&lt;/code&gt; statüsünde, numarası üzerinde asılı durur. Bu klasik &lt;strong&gt;dual-write problemi&lt;/strong&gt;dir: iki ayrı sistemi tek bir atomik işlemde tutamazsınız. Worker’ın deploy ya da ölçeklenme sırasında yarıda kapanması da aynı izi bırakır — &lt;a href=&quot;/tr/notes/graceful-shutdown/&quot;&gt;graceful shutdown&lt;/a&gt; bunu azaltır ama sıfırlamaz; tasarım yine de bu izi temizleyebilmeli.&lt;/p&gt;
&lt;p&gt;Çözüm, asılı kalanı tespit eden bir &lt;strong&gt;orphan recovery&lt;/strong&gt; servisi. &lt;code&gt;SENDING&lt;/code&gt; statüsünde 5 dakikadan uzun bekleyen faturaları tarar ve &lt;strong&gt;kör bir retry atmaz&lt;/strong&gt; — önce entegratörde bir &lt;strong&gt;idempotency check&lt;/strong&gt; yapar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bu numarayla fatura entegratöre &lt;strong&gt;gitmiş mi?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Gittiyse: yerel DB’yi onarır (&lt;code&gt;outgoing_invoices&lt;/code&gt;’a yazar, statüyü kapatır). Yeni numara üretmez.&lt;/li&gt;
&lt;li&gt;Gitmemişse: &lt;strong&gt;aynı&lt;/strong&gt; &lt;code&gt;reserved_no&lt;/code&gt; ile güvenle yeniden dener.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunu mümkün kılan şey, numaranın çağrıdan önce mühürlenmiş olması. Numara elde olduğu için sorgu deterministiktir: “şu numara orada mı?” Numara çağrı sırasında üretilseydi, neyi soracağınızı bile bilemezdiniz. JIT rezervasyon, recovery’yi tahmin işi olmaktan çıkarıp bir lookup’a indirir.&lt;/p&gt;
&lt;p&gt;Bir not: entegratör kalıcı olarak hata veriyorsa (geçici değil), orphan servisi sonsuza kadar denememeli. Dış servise yüklenmeyi kesmek için araya bir &lt;a href=&quot;/tr/notes/circuit-breaker-dis-servis-entegrasyonu/&quot;&gt;circuit breaker&lt;/a&gt; koyuyorum; numara korunur, fatura &lt;code&gt;ERROR&lt;/code&gt;’da bekler, ama entegratöre saniyede yüzlerce boş istek gitmez.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-bu-kalıbı-bırakırsınız&quot;&gt;Ne zaman bu kalıbı bırakırsınız?&lt;/h2&gt;
&lt;p&gt;Bu makine ucuz değil — bir rezervasyon kolonunun yaşam döngüsü, atomik bir sayaç, bir orphan servisi ve entegratörde idempotency check zorunluluğu getirir. Karşılığında neyi satın aldığınızı bilmiyorsanız ödemeyin. Şu durumlarda kurmuyorum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gap serbestse.&lt;/strong&gt; Numaranız yasal olarak boşluksuz olmak zorunda değilse (iç referans, sipariş no, log id), düz bir DB sequence ya da &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; yeter. Gap’i kovalamak için bu mekanizmayı kurmak, olmayan bir problemi çözmektir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dış çağrı yoksa.&lt;/strong&gt; Numara üretimi ile kalıcılaştırma aynı transaction’da kalabiliyorsa (araya başarısız olabilen bir I/O girmiyorsa), rezervasyon kolonuna gerek yok; tek bir &lt;code&gt;FOR UPDATE&lt;/code&gt; sayacı problemi bitirir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek worker, düşük hacim.&lt;/strong&gt; Paralellik yoksa race condition da yoktur. Bir global advisory lock ya da tek tüketicili kuyruk, JIT makinesinden çok daha basit ve yeterlidir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kalıba ihtiyaç, üç koşul &lt;strong&gt;birlikte&lt;/strong&gt; doğduğunda başlar: boşluksuzluk yasal zorunluluk, üretim paralel ve son adım başarısız olabilen bir dış çağrı. Üçü birden yoksa daha sade bir çözüm vardır — onu seçin.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Sıralı, boşluksuz numara üretmek bir kilit sorunundan çok bir &lt;strong&gt;zamanlama&lt;/strong&gt; sorunu. Numarayı dış dünyaya gitmeden tam bir an önce mühürlersiniz: race’i engelleyecek kadar geç, gap’i engelleyecek kadar erken. Geri kalan her şey — erken commit, idempotent retry, orphan recovery — o tek mührün etrafına dizilir.&lt;/p&gt;</content:encoded><category>architecture</category><category>production</category><category>concurrency</category><category>queue</category><category>idempotency</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Mikroservise Ne Zaman Geçerim?</title><link>https://sade.dev/tr/journal/mikroservise-ne-zaman-gecerim/</link><guid isPermaLink="true">https://sade.dev/tr/journal/mikroservise-ne-zaman-gecerim/</guid><description>Monolitten dağıtık mimariye geçişi haklı çıkaran ölçülmüş sinyaller — ve geçiş için henüz erken olduğunu söyleyen yaygın yanlış gerekçeler.</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir planlama toplantısında “bu monolit artık çok büyüdü, mikroservislere bölelim” cümlesini duyduğumda ilk sorum hep aynı oluyor: &lt;strong&gt;hangi ölçüm bunu söylüyor?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cevap çoğu zaman bir ölçüm değil. Bir his — “kod tabanı kocaman” —, bir blog yazısı ya da bir özgeçmiş kaygısı. Üçü de gerçek etkenler, ama hiçbiri mimari bir gerekçe değil.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/&quot;&gt;Modüler monolit yazısında&lt;/a&gt; sınırları erken çizip dağıtımı geç yapmayı savunmuştum. Bu yazı o “geç”in ne zaman geldiğini — ve gelmediğini nasıl anlayacağınızı — anlatıyor.&lt;/p&gt;
&lt;h2 id=&quot;mikroservis-bir-çözüm-değil-bir-takas&quot;&gt;Mikroservis bir çözüm değil, bir takas&lt;/h2&gt;
&lt;p&gt;Mikroservis mimarisi özünde tek bir şeyi değiştirir: modüller arası bir method çağrısını bir network çağrısına dönüştürür. Aldığınız şey bağımsız deploy ve bağımsız ölçekleme. Karşılığında ödediğiniz şey uzun:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In-process’te ücretsiz olan her çağrı artık serializasyon, latency ve partial failure demek.&lt;/li&gt;
&lt;li&gt;Tek bir DB transaction’ı yerine saga, outbox, eventual consistency.&lt;/li&gt;
&lt;li&gt;Tek bir log akışı yerine, distributed tracing olmadan takip edilemeyen bir istek yolu.&lt;/li&gt;
&lt;li&gt;Tek bir deploy yerine, versiyonlanması ve geriye uyumlu tutulması gereken servis kontratları.&lt;/li&gt;
&lt;li&gt;Compiler’ın doğruladığı bir refactor yerine, kimsenin doğrulamadığı bir network kontratı.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunların hiçbiri “kötü” değil; hepsi gerçek sistemlerde gerçekten ödenen bedeller. Asıl soru şu: bu bedeli bir şeyin &lt;strong&gt;karşılığında&lt;/strong&gt; mı ödüyorsunuz, yoksa peşin mi?&lt;/p&gt;
&lt;h2 id=&quot;geçişi-haklı-çıkarmayan-gerekçeler&quot;&gt;Geçişi haklı çıkarmayan gerekçeler&lt;/h2&gt;
&lt;p&gt;Mikroservis kararını en sık tetikleyen sebepler, ne yazık ki en zayıf olanlar.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Kod tabanı çok büyük.”&lt;/strong&gt; Büyük kod tabanının çözümü modül sınırı, dürüst dosya organizasyonu ve ölü kod temizliğidir — ayrı deploy değil. 200 bin satırlık bir monolit ile 20 servise bölünmüş 200 bin satır aynı miktarda koddur; ikincisi sadece araya network koymuş hâlidir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Deploy yavaş ve korkutucu.”&lt;/strong&gt; Yavaş pipeline’ın çözümü pipeline’ı hızlandırmaktır. Korkutucu deploy’un çözümü test kapsamı, aşamalı yayın ve hızlı rollback’tir. Bunların hiçbiri sistemi bölmeyi gerektirmez; bölmek aksine deploy sayısını artırır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Bağımsız ölçeklemem lazım.”&lt;/strong&gt; Monolitin kendisi de yatayda ölçeklenir: aynı uygulamayı bir load balancer arkasında N kopya çalıştırmak mikroservis istemez. Bağımsız ölçekleme ancak modüllerin kaynak &lt;strong&gt;profili&lt;/strong&gt; gerçekten ayrıştığında anlam kazanır — ki bu ayrı bir madde, aşağıda.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Modern mimari bu.”&lt;/strong&gt; Modernlik bir mimari gerekçe değil. Bir sistemin iyi olup olmadığını, kaç parçaya bölündüğü değil, bir hatayı gece 2’de izlenebilir kılıp kılmadığı söyler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Netflix böyle yapıyor.”&lt;/strong&gt; Netflix’in mikroservisi finanse eden bir ölçeği ve yüzlerce mühendisi var. Üç kişilik bir ekip bu mimariyi kopyaladığında Netflix’in çözdüğü problemi değil, yalnızca operasyon yükünü kopyalar. Bu, &lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;boring architecture&lt;/a&gt; yazısındaki innovation token mantığının ta kendisi: bütçeyi yanlış yere harcamak.&lt;/p&gt;
&lt;p&gt;Ortak hata kalıbı: bunların hepsi gerçek rahatsızlıklar, ama hiçbiri mikroservisle iyileşmiyor. Doğru hastalığa yanlış ilaç.&lt;/p&gt;
&lt;h2 id=&quot;geçişi-haklı-çıkaran-ölçülmüş-sinyaller&quot;&gt;Geçişi haklı çıkaran ölçülmüş sinyaller&lt;/h2&gt;
&lt;p&gt;Modüler monolit yazısında bir modülü servise çıkarmak için dört sinyal saymıştım. Sistem ölçeğinde de eşik aynı — şu sinyallerden &lt;strong&gt;en az biri ölçülmüş&lt;/strong&gt; olmalı:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ölçeklenme profili ayrışıyor.&lt;/strong&gt; Bir parça kaynakları geri kalanından bambaşka tüketiyor: sürekli CPU’yu doyuran bir rapor/PDF motoru, ya da dakikada on binlerce hafif çağrı alan bir bildirim modülü. İkisini tek deploy biriminde tutmak, birini diğerinin kapasite planına mahkûm eder. Bunu metrikle gösterebiliyorsanız ayırmak yatay ölçeklemeden ucuza gelir.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sahiplik organizasyonel olarak ayrıştı.&lt;/strong&gt; Ekip sayısı büyüdü ve aynı deploy hattındaki release çakışması artık ölçülebilir bir gecikme. Üç ekip her gün birbirinin deploy’unu beklemeye başladıysa, sınır artık teknik değil organizasyoneldir — Conway yasası faturayı kesiyordur.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Farklı bir runtime gerçekten gerekiyor.&lt;/strong&gt; Bir iş yükü PHP’de pahalı, Go ya da Rust’ta belirgin biçimde ucuz — ve bu “daha hızlı olur” hipotezi değil, ölçülmüş bir darboğaz. O parçayı ayrı bir serviste farklı bir dille çalıştırmak gerekçeli.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hata izolasyonu zorunlu ve process içinde garanti edilemiyor.&lt;/strong&gt; Bir modülün çökmesi gerçekten geri kalan sistemi düşürmemeli ve bunu tek process içinde sağlayamıyorsunuz.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dördünün ortak noktası: hepsi &lt;strong&gt;ölçülür&lt;/strong&gt;. “Sanırım”, “ileride”, “olabilir” ile başlayan hiçbir cümle bu listede yok. Sinyal bir grafikte görünmüyorsa, henüz sinyal değildir.&lt;/p&gt;
&lt;h2 id=&quot;hepsini-değil-bir-modülü&quot;&gt;Hepsini değil, bir modülü&lt;/h2&gt;
&lt;p&gt;Sinyal geldiğinde bile doğru hamle “monoliti mikroservislere bölmek” değildir. Doğru hamle, sinyali veren &lt;strong&gt;tek modülü&lt;/strong&gt; servise çıkarmaktır.&lt;/p&gt;
&lt;p&gt;Modüler monolitle başladıysanız bu zaten mekanik bir iş: sınır net, public yüzey tek bir sınıf. O modülü strangler kalıbıyla dışarı alırsınız — yeni servis ayağa kalkar, çağrılar kademeli ona yönlenir, eski kod silinir. Geri kalan sistem mutlu mesut monolit olarak kalır.&lt;/p&gt;
&lt;p&gt;Big-bang rewrite — her şeyi aynı anda servislere bölmek — bilinen en pahalı ve en sık başarısız olan göç biçimidir. Bir sistemi tek seferde değil, sinyal verdikçe parça parça ayırın.&lt;/p&gt;
&lt;h2 id=&quot;monolit-ne-zaman-yanlış-cevaba-dönüşür&quot;&gt;Monolit ne zaman yanlış cevaba dönüşür?&lt;/h2&gt;
&lt;p&gt;Monolit, yukarıdaki sinyallerden hiçbiri ölçülmediği sürece doğru cevaptır. Yanlış cevaba dönüştüğü an da bellidir: birden çok ekip aynı deploy biriminde sıkıştığında, bir parçanın kaynak profili sistemin geri kalanını rehin aldığında, ya da bir bileşenin çöküşü düzenli olarak tüm sistemi düşürdüğünde.&lt;/p&gt;
&lt;p&gt;O gün geldiğinde mikroservise geçmek bir yenilgi değil — planlanmış bir adımdır. Sınırları baştan çizmiş olmanın bütün değeri de buradadır: geçiş, korkutucu bir göç değil, sırası gelmiş bir karar olur.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Mikroservis bir hedef değil, bir faturadır. O faturayı ödemek için bir sebebiniz olmalı — ve o sebep bir grafikte görünmeli, bir blog yazısında değil.&lt;/p&gt;
&lt;p&gt;Sinyali bekleyin; kararı moda değil, ölçüm versin.&lt;/p&gt;</content:encoded><category>architecture</category><category>microservices</category><category>decisions</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Gitflow mu, GitHub Flow mu? Neye Göre Seçiyorum?</title><link>https://sade.dev/tr/journal/gitflow-mu-github-flow-mu/</link><guid isPermaLink="true">https://sade.dev/tr/journal/gitflow-mu-github-flow-mu/</guid><description>Branch stratejisi seçimini ürün modeli, sürüm sayısı ve deploy ritmine göre yapmanın pratik kriterleri — ve iki modelin yanlış yerde nasıl kırıldığı.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yeni projenin ilk haftası, kick-off toplantısı, kahve daha soğumadan biri soruyor: “Hangi git workflow’unu kullanalım?” Sonraki 40 dakikada Gitflow şemaları çizilir, GitHub Flow savunulur, biri “trunk-based” der ve kimse zemin bulmadan toplantı biter.&lt;/p&gt;
&lt;p&gt;Cevap o toplantıda değil — ürünün kendisinde duruyor.&lt;/p&gt;
&lt;h2 id=&quot;i̇ki-modelin-mekaniği-kısaca&quot;&gt;İki modelin mekaniği, kısaca&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gitflow.&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt; (prod), &lt;code&gt;develop&lt;/code&gt; (entegrasyon), &lt;code&gt;feature/*&lt;/code&gt; (geliştirme), &lt;code&gt;release/*&lt;/code&gt; (sürüm hazırlığı), &lt;code&gt;hotfix/*&lt;/code&gt; (acil yamalar). Sürüm odaklı. Bir özellik &lt;code&gt;feature&lt;/code&gt; → &lt;code&gt;develop&lt;/code&gt; → &lt;code&gt;release&lt;/code&gt; → &lt;code&gt;main&lt;/code&gt; yolunu izler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Flow.&lt;/strong&gt; Tek &lt;code&gt;main&lt;/code&gt; ve ondan dallanan kısa ömürlü branch’ler. Her merge prod’a deploy edilebilir. Sürüm kavramı yok — tek yaşayan sürüm vardır, o da prod’daki.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;İkisi de gerçek projelerde gerçekten çalışıyor. Soru hangisinin “doğru” olduğu değil, hangisinin &lt;strong&gt;ürününüzün şekline&lt;/strong&gt; uyduğu.&lt;/p&gt;
&lt;h2 id=&quot;gitflowun-doğal-habitatı&quot;&gt;Gitflow’un doğal habitatı&lt;/h2&gt;
&lt;p&gt;Gitflow, “aynı anda birden çok sürüm yaşıyor” gerçeği olan ürünler için tasarlandı:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mobil uygulamalar — store onay süresi nedeniyle 3.4 cep telefonlarına ulaşırken 3.5 review’da, 3.6 develop’ta yaşar.&lt;/li&gt;
&lt;li&gt;SDK’lar ve kütüphaneler — kullanıcının yükselme temposu sizin elinizde değil; 2.x’i bir süre destekliyorsunuz.&lt;/li&gt;
&lt;li&gt;On-premise / packaged yazılım — müşteri “biz 4.2’deyiz” der; bu sürüme hotfix vermek bir lüks değil, sözleşme.&lt;/li&gt;
&lt;li&gt;Hardware ile birlikte gönderilen yazılım — firmware ile koordine sürüm pencereleri.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu sahnelerde &lt;code&gt;release/*&lt;/code&gt; branch bir bürokrasi değil, paralel zaman çizgilerinin yaşadığı yerdir. &lt;code&gt;hotfix/*&lt;/code&gt; lane’i de “müşteri prod’da yanıyor ama biz develop’tayız” probleminin doğal cevabıdır.&lt;/p&gt;
&lt;h2 id=&quot;github-flowun-doğal-habitatı&quot;&gt;GitHub Flow’un doğal habitatı&lt;/h2&gt;
&lt;p&gt;GitHub Flow ise tek yaşayan sürüm gerçeği olan ürünlerde nefes alıyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web uygulamaları, SaaS — kullanıcının kullandığı sürüm her zaman prod’daki.&lt;/li&gt;
&lt;li&gt;İç araçlar, dashboard’lar — tek bir kurulum, sürekli güncel.&lt;/li&gt;
&lt;li&gt;API servisleri (versiyonsuz ya da rotalarla versiyonlanmış) — geriye uyumluluk kodda yaşar, branch’te değil.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Burada &lt;code&gt;release/*&lt;/code&gt; branch bir altyapı değil, gereksiz bir katmandır. Hotfix de ayrı bir lane değildir — sadece sıraya giren bir PR’dır.&lt;/p&gt;
&lt;h2 id=&quot;neyi-gerçekten-seçiyorum&quot;&gt;Neyi gerçekten seçiyorum&lt;/h2&gt;
&lt;p&gt;Üç soruya verdiğim cevap, branch stratejisini benim yerime seçiyor:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Aynı anda kaç sürüm canlı?&lt;/strong&gt; Bir tane → GitHub Flow. Birden fazla → Gitflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Müşteri deploy frekansımı görüyor mu?&lt;/strong&gt; Hayır, ben istediğim zaman deploy ediyorum → GitHub Flow. Evet, sürümler arası bir sözleşme var → Gitflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hotfix’in prod’a giderken bir “release” üzerinden gitmesi gerekiyor mu?&lt;/strong&gt; Hayır → GitHub Flow. Evet → Gitflow.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Üçü de aynı yöne işaret ediyorsa karar verilmiştir. Karışıyorsa, ürünün şekli henüz oturmamıştır — o zaman geçici olarak basit olanı, GitHub Flow’u seçiyorum; çünkü GitHub Flow’dan Gitflow’a geçmek, tersine göre daha küçük bir göç projesidir.&lt;/p&gt;
&lt;h2 id=&quot;yaygın-eşleşme-hataları&quot;&gt;Yaygın eşleşme hataları&lt;/h2&gt;
&lt;p&gt;İki modeli yanlış yere koyduğum (ya da bir başkasının koyduğunu izlediğim) durumlar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SaaS’ta Gitflow.&lt;/strong&gt; Tek sürüm yaşayan bir web app’te &lt;code&gt;develop&lt;/code&gt; branch gereksiz bir gölge prod yaratır; PR’lar iki kez merge edilmek zorunda kalır. Sonuç: kazanılan izolasyon, ödenen sürtünmeye değmiyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packaged üründe GitHub Flow.&lt;/strong&gt; Aynı anda 3.x’i alan müşteri varken &lt;code&gt;main&lt;/code&gt;’i 4.x ile dolduran ekip, bir hotfix istendiğinde geriye dönmek için olmayan bir branch’i aramaya başlar. Bu noktada “Gitflow’u sonradan kurmak” bir göç projesidir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Karma model “kendi sürümümüzü uyduralım.”&lt;/strong&gt; Çoğu zaman ekibin yarısının Gitflow, yarısının GitHub Flow zannettiği yamalı bir şey çıkar. İki modelden birini doğru uygulamak, üçüncü bir model uydurmaktan her zaman ucuzdur.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;workflow-yazılımın-yaşam-döngüsünün-şeklidir&quot;&gt;Workflow, yazılımın yaşam döngüsünün şeklidir&lt;/h2&gt;
&lt;p&gt;Branch stratejisi bir özelliğin doğduktan kaç gün sonra prod’a vardığını, hangi koridorlardan geçtiğini, nasıl bir bakım hattında yaşlandığını belirleyen şeydir. Yani &lt;strong&gt;yazılımın yaşam döngüsünü&lt;/strong&gt; — kodun değil, kararın hızını — şekillendirir.&lt;/p&gt;
&lt;p&gt;İki şirket aynı dili, aynı framework’ü kullansın; biri Gitflow, biri GitHub Flow seçtiyse aynı özelliği prod’a çıkarma süreleri tek başına bu kararla 3’e katlanır. Bu yüzden branch stratejisi tooling sorusu değil, ürün sorusudur.&lt;/p&gt;
&lt;h2 id=&quot;i̇kisi-de-yetmediğinde&quot;&gt;İkisi de yetmediğinde&lt;/h2&gt;
&lt;p&gt;Bazı ekiplerde deploy ritmi öyle hızlanır ki — günde 5+ deploy — GitHub Flow’un kısa ömürlü branch’leri bile bir sürtünme kaynağı olmaya başlar. O eşikten sonra ben &lt;a href=&quot;/tr/journal/trunk-based-ne-zaman-geciyorum/&quot;&gt;trunk-based’e geçiyorum&lt;/a&gt;: branch’ler saatlerle ölçülür, yarım iş feature flag arkasında kodda yaşar.&lt;/p&gt;
&lt;p&gt;Üçü de farklı bir tempo için tasarlanmış araçlar. Hangisinin sizi rahat ettireceği, yazdığınız kod ile prod’unuz arasındaki &lt;strong&gt;mesafenin&lt;/strong&gt; kaç saat olmasını istediğinize bağlı.&lt;/p&gt;
&lt;h2 id=&quot;karar-değişir-doğrudur&quot;&gt;Karar değişir, doğrudur&lt;/h2&gt;
&lt;p&gt;Bugünkü ürün şeklinizi 2 yıl sonraki ekibinize dayatmak en pahalı hatalardan. Üç sinyalden biri değiştiğinde — sürüm sayısı çoğaldı, müşteri tipi değişti, deploy frekansı 10’a fırladı — branch stratejisini de yeniden açın. Workflow bir kez seçilen değil, ürün değiştikçe yeniden değerlendirilen bir karardır.&lt;/p&gt;
&lt;p&gt;Branch stratejisinin günlük operasyonel yanını — workflow seçimi, rebase ile tarihçe hijyeni, bisect ve reflog, &lt;code&gt;--force&lt;/code&gt; disiplini — bir bütün hâlinde &lt;a href=&quot;https://www.muhammetsafak.com.tr/blog/production-git-senior-pratik-rehberi/&quot;&gt;Production’da Git: Senior’ın Pratik Rehberi&lt;/a&gt; yazısında topladım. Bu seçim, o resmin yalnızca giriş kapısı.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Gitflow ile GitHub Flow arasındaki seçim, “hangisi modern” değil “ürün şekliniz hangisini doğal kılıyor” sorusudur. Yanlış cevap, yıllarca her PR’da iki dakika ödenir.&lt;/p&gt;
&lt;p&gt;Git workflow’unu ürüne göre seçin; ürünü workflow’a göre değil.&lt;/p&gt;</content:encoded><category>git</category><category>workflow</category><category>branching</category><category>decisions</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Projelere Neden Modüler Monolit ile Başlıyorum?</title><link>https://sade.dev/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/</link><guid isPermaLink="true">https://sade.dev/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum/</guid><description>Mikroservislere atlamadan önce tek deploy edilebilir bir uygulamada net modül sınırları kurmanın gerekçesi — ve bu kararı ne zaman bıraktığım.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yeni bir projenin ilk gününde, mimari tahtasının başında biri mutlaka şu soruyu soruyor: “Servisleri nasıl böleceğiz?” Cevabım çoğu zaman aynı: &lt;strong&gt;“Şimdilik bölmüyoruz.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bu bir erteleme ya da üşengeçlik değil. Neredeyse her yeni projeye bilinçli olarak modüler monolit ile başlıyorum: tek codebase, tek deploy, tek veritabanı — ama içeride net modül sınırları. Bu yazı neden böyle yaptığımın gerekçesi.&lt;/p&gt;
&lt;h2 id=&quot;modüler-monolit-ne-demek&quot;&gt;”Modüler monolit” ne demek?&lt;/h2&gt;
&lt;p&gt;Üç şeyi birbirinden ayırmak gerekiyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Klasik monolit&lt;/strong&gt; — sınır yok. Her şey her şeye erişir, zamanla “big ball of mud”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mikroservisler&lt;/strong&gt; — sınır var, ama sınır aynı zamanda bir network çağrısı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modüler monolit&lt;/strong&gt; — sınır var, ama sınır process içinde; bir method çağrısı.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Asıl mesele şu: &lt;strong&gt;modül sınırı çizmek&lt;/strong&gt; ile &lt;strong&gt;onu ayrı bir servise koymak&lt;/strong&gt; iki ayrı karardır. Mikroservis mimarisi bu ikisini tek bir karara sıkıştırıyor — sınır demek, otomatik olarak ayrı deploy, ayrı veritabanı, network demek. Modüler monolit bu ikisini ayırıyor: sınırı bugün çizin, dağıtımı gerçekten gerekince yapın.&lt;/p&gt;
&lt;h2 id=&quot;sınırlar-kodda-nasıl-görünüyor&quot;&gt;Sınırlar kodda nasıl görünüyor&lt;/h2&gt;
&lt;p&gt;Laravel tarafında kullandığım yerleşim kabaca şu:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;app/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── Modules/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── Billing/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    │   ├── Domain/            # entity&amp;#39;ler, value object&amp;#39;ler — modüle özel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    │   ├── Application/       # use-case&amp;#39;ler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    │   ├── Infrastructure/    # repository, dış servis adaptörleri&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    │   └── BillingApi.php     # modülün TEK public yüzeyi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── Catalog/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── Notification/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kural basit: bir modül, başka bir modülün yalnızca &lt;code&gt;*Api.php&lt;/code&gt; sınıfını çağırır. &lt;code&gt;Domain&lt;/code&gt; ve &lt;code&gt;Infrastructure&lt;/code&gt; içindeki her şey o modüle özeldir — dışarıdan görünmez.&lt;/p&gt;
&lt;p&gt;İkinci kural veritabanında: bir modül, başka bir modülün tablosuna doğrudan &lt;code&gt;SELECT&lt;/code&gt; atmaz, foreign key bağlamaz. Catalog’un bir faturaya ihtiyacı varsa &lt;code&gt;BillingApi&lt;/code&gt;’den geçer, &lt;code&gt;billing_invoices&lt;/code&gt; tablosuna el uzatmaz. Bu disiplin sıkıcı görünüyor — değeri tam da burada.&lt;/p&gt;
&lt;h2 id=&quot;neden-buradan-başlıyorum&quot;&gt;Neden buradan başlıyorum&lt;/h2&gt;
&lt;p&gt;Beş somut sebep:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sınırı yanlış çizme maliyeti düşük.&lt;/strong&gt; Projenin ilk haftasında domain’i tam anlamıyorsunuz. Modül sınırı yanlış çıkarsa, modüler monolitte bunu düzeltmek bir refactor — IDE’nin “move” komutu ve birkaç saat. Aynı hatayı mikroserviste düzeltmek iki servisin API kontratını, iki deploy pipeline’ını ve canlı veri taşımayı içeriyor. Erken dönemde sınırlar &lt;em&gt;kesinlikle&lt;/em&gt; yanlış olacak; ucuz düzeltilebilen yapıyı seçin.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tek transaction.&lt;/strong&gt; İki modülü tek bir DB transaction’ı içinde tutarlı güncelleyebiliyorum. Mikroserviste aynı şey saga, outbox, eventual consistency demek. Bunlar gerçek araçlar — ama gerçekten gerekene kadar ertelenmesi gereken bir karmaşıklık.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Operasyonel yük neredeyse sıfır.&lt;/strong&gt; Tek deploy, tek log akışı, izlenecek tek hedef. Servisler arası retry, distributed tracing, kontrat versiyonlama — bunların hiçbiri yok. Üç kişilik bir ekip için bu, haftada kazanılan günlerdir.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Refactor araçları çalışıyor.&lt;/strong&gt; “Rename”, “find usages”, statik analiz — hepsi process sınırı içinde işliyor. Bir method çağrısını compiler doğruluyor; bir network kontratını kimse doğrulamıyor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mikroservise geçişi kolaylaştırıyor.&lt;/strong&gt; En sık atlanan nokta bu: sınırlar zaten net olduğu için, bir modülü ayrı servise çıkarmak ileride mekanik bir iş haline geliyor. Modüler monolit, mikroservislerin &lt;em&gt;alternatifi&lt;/em&gt; değil; doğru yapıldığında onlara giden en ucuz yol.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;sınırı-ayakta-tutan-disiplin&quot;&gt;Sınırı ayakta tutan disiplin&lt;/h2&gt;
&lt;p&gt;Modüler monolitin tek zayıf noktası şu: sınırlar zorla uygulanmıyorsa, yapı sessizce çürüyüp eski tip monolite döner. “Sadece bu sefer” diye atılan her doğrudan çağrı bir delik açar.&lt;/p&gt;
&lt;p&gt;Sınırı ayakta tutan dört pratik:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Statik bağımlılık kuralları.&lt;/strong&gt; &lt;code&gt;deptrac&lt;/code&gt; ya da benzeri bir araçla “Catalog, Billing’in &lt;code&gt;Domain&lt;/code&gt; katmanına erişemez” kuralını CI’da zorunlu kılın. İhlal, build’i kırsın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek public yüzey.&lt;/strong&gt; Her modülün dışarı açık API’si tek bir sınıf; geri kalan her şey internal. Görünürlüğü dilin izin verdiği ölçüde daraltın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Veri sınırı.&lt;/strong&gt; Modüller arası ilişki foreign key ile değil, ID + public çağrı ile kurulur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review’da tek soru.&lt;/strong&gt; Kod incelemesinde sorulacak en önemli soru: “Bu değişiklik bir modül sınırını izinsiz geçiyor mu?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sınır CI tarafından zorlanmıyorsa, elinizde modüler monolit değil, sadece klasör isimleri düzgün bir monolit var. Bütün değer disiplinde.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-ayrı-servise-bölüyorum&quot;&gt;Ne zaman ayrı servise bölüyorum?&lt;/h2&gt;
&lt;p&gt;Modülü ayrı bir servise çıkarmak için &lt;em&gt;ölçülmüş&lt;/em&gt; bir sebep beklerim. Dört sinyalden biri yeterli:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ölçeklenme profili ayrışıyor.&lt;/strong&gt; Notification modülü CPU-bound, geri kalan sistem I/O-bound. Birini diğerinden bağımsız ölçeklemek istiyorsam, ayırmak artık karşılığını veriyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sahiplik ayrışıyor.&lt;/strong&gt; Modül ayrı bir ekibin sorumluluğuna geçti ve bağımsız deploy artık teknik değil, organizasyonel bir ihtiyaç.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Farklı bir runtime gerekiyor.&lt;/strong&gt; Ağır bir hesaplama Go’da çok daha ucuz, geri kalan PHP’de kalmalı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hata izolasyonu zorunlu.&lt;/strong&gt; Bir modülün çökmesi gerçekten geri kalan sistemi düşürmemeli ve bunu process içinde garanti edemiyorum.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bunlardan biri ölçülerek ortaya çıkana kadar bölmek erken optimizasyondur. Bu, &lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;boring architecture savunmamın&lt;/a&gt; doğrudan uzantısı: karmaşıklığı, onu zorlayan kanıt gelene kadar erteleyin. Sınırlar baştan net olduğu için, o gün geldiğinde bölme işi korkutucu bir göç değil, planlı bir adım oluyor.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Mikroservisler bir mimari tarzı değil, bir dağıtım kararıdır. O kararı projenin ilk gününde — domain hakkında en az şey bildiğiniz anda — vermek için hiçbir sebep yok. Modüler monolit bana o kararı erteleme ve zamanı geldiğinde hazır olma lüksünü veriyor.&lt;/p&gt;
&lt;p&gt;Sınırları erken çizin, dağıtımı geç yapın.&lt;/p&gt;</content:encoded><category>architecture</category><category>modular-monolith</category><category>decisions</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Trunk-Based Development&apos;a Ne Zaman Geçiyorum?</title><link>https://sade.dev/tr/journal/trunk-based-ne-zaman-geciyorum/</link><guid isPermaLink="true">https://sade.dev/tr/journal/trunk-based-ne-zaman-geciyorum/</guid><description>Yüksek frekanslı deploy yapan ekiplerde release branch&apos;lerinin neden kırıldığı ve trunk-based&apos;in ne kadarının disiplin olduğu — geçtiğim ve geçmediğim eşikler.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cuma öğleden sonra prod’a 6’ncı deploy gidiyor. &lt;code&gt;release/2026.05&lt;/code&gt; branch’inden alınan hotfix &lt;code&gt;develop&lt;/code&gt;’a geri merge edilirken bir önceki hotfix ile çakışıyor. Üç kişilik bir kanal açılıyor, on dakika konuşuluyor, kimse “bu aslında trivial bir conflict” diye düşünmüyor — çünkü bu hafta dördüncüsü.&lt;/p&gt;
&lt;p&gt;Sahne tanıdık geliyorsa, branch stratejiniz deploy temponuza yetişemiyor demektir.&lt;/p&gt;
&lt;h2 id=&quot;trunk-based-ne-bir-cümlede&quot;&gt;Trunk-based ne, bir cümlede&lt;/h2&gt;
&lt;p&gt;Tek bir uzun ömürlü branch (&lt;code&gt;main&lt;/code&gt; ya da &lt;code&gt;trunk&lt;/code&gt;), ömrü saatlerle — en fazla 1–2 günle — ölçülen feature branch’ler ve her merge’in prod’a gidebilecek kadar yeşil tutulduğu bir tempo. Bitmemiş özellikler &lt;strong&gt;kodda yaşar&lt;/strong&gt;, kullanıcıya feature flag’le açılır.&lt;/p&gt;
&lt;p&gt;Daha fazlası yok — &lt;code&gt;release/*&lt;/code&gt; branch’i yok, &lt;code&gt;develop&lt;/code&gt; branch’i yok, sürüm numarasına bağlı uzun yaşayan dallar yok.&lt;/p&gt;
&lt;h2 id=&quot;yüksek-frekans-derken&quot;&gt;”Yüksek frekans” derken&lt;/h2&gt;
&lt;p&gt;Concrete olmazsa anlamsız: trunk-based’i savunduğum projelerin ortak özelliği günde 3–10 deploy. Haftada 1 deploy yapan bir ekibin trunk-based’e geçmesi için zorlayıcı bir sebep yok — &lt;a href=&quot;/tr/journal/gitflow-mu-github-flow-mu/&quot;&gt;Gitflow ya da GitHub Flow&lt;/a&gt; o ritimde sorunsuz çalışıyor.&lt;/p&gt;
&lt;p&gt;Eşik bende net: deploy frekansı haftada 5+ olduğunda release branch bir “düzen” değil, bir engel olmaya başlıyor.&lt;/p&gt;
&lt;h2 id=&quot;release-branch-yüksek-frekansta-neden-kırılıyor&quot;&gt;Release branch yüksek frekansta neden kırılıyor&lt;/h2&gt;
&lt;p&gt;Üç gerçek mekanizma:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Merge sürtünmesi lineer büyümez.&lt;/strong&gt; İki branch ne kadar uzun yan yana yaşarsa, çakışma olasılığı her gün artar. Haftalık release döngüsünde bu görünür değil; günlük döngüde her merge bir mini drama.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hotfix yolu paralel bir tarih yaratır.&lt;/strong&gt; &lt;code&gt;hotfix/*&lt;/code&gt; → &lt;code&gt;main&lt;/code&gt; → geri &lt;code&gt;develop&lt;/code&gt;’a port. Bu portu unutmak en sık görülen prod bug kaynağı; yüksek frekansta unutmamak istatistiksel olarak imkânsız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review ile deploy arasındaki mesafe açılır.&lt;/strong&gt; Bir PR’ın “merge’lendi” anı ile prod’a düştüğü an arasında günler varsa, code review aslında prod’u değil bir staging’i değerlendirir. İki ortam aynı şey değildir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;bu-workflowun-asıl-maliyeti-disiplin&quot;&gt;Bu workflow’un asıl maliyeti: disiplin&lt;/h2&gt;
&lt;p&gt;Trunk-based, “branch silelim” değildir. Bir kod parçasının doğumdan emekliliğe yolculuğunu — &lt;strong&gt;yazılımın yaşam döngüsünü&lt;/strong&gt; — daha sıkı bir tempoya bağlayan bir disiplindir. Bu disiplin olmadan yapılan trunk-based, yalnızca daha hızlı kırılan bir prod demektir.&lt;/p&gt;
&lt;p&gt;Şunlar olmadan denemeyin:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Yeşil trunk, sözleşme seviyesinde.&lt;/strong&gt; Kırık trunk = herkesin bloklandığı an. Bunu garantileyen CI yoksa (hızlı, güvenilir test piramidi + zorunlu PR check’leri), trunk-based bir ütopya.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature flag altyapısı.&lt;/strong&gt; Yarım bitmiş özelliklerin koda girip kullanıcıya kapalı kalması bu modelin temeli. Boolean flag’den profile-based rollout’a kadar bir &lt;a href=&quot;/tr/notes/feature-flags/&quot;&gt;feature flag disiplini&lt;/a&gt; şart.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hızlı pipeline.&lt;/strong&gt; Merge’den prod’a 30 dakika geçiyorsa, “her merge prod’a gidebilir” iddiası teorik. 5–10 dakika aralığı pratik eşik.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kısa ömürlü branch kültürü.&lt;/strong&gt; PR’ı 4 günde merge etmek trunk-based değil, gizli Gitflow. Branch açıldığı gün ya da ertesi gün kapanmalı.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bu dördü oturmadıysa, ekibe trunk-based dayatmak prod’u hızlandırmaz — yalnızca prod kazalarını hızlandırır.&lt;/p&gt;
&lt;h2 id=&quot;code-review-yavaşlatır-diye-duyacaksınız&quot;&gt;”Code review yavaşlatır” diye duyacaksınız&lt;/h2&gt;
&lt;p&gt;Yavaşlatmaz, parçalar. Trunk-based PR’lar tasarımı gereği küçüktür — 200–400 satır, tek bir mantıksal değişiklik. Bu boyutta review 60–90 dakikadır. 2.000 satırlık bir PR’ı kimsenin gerçekten okumadığını bilen herkes, küçük PR’a geçişin gerçek kazancını görür.&lt;/p&gt;
&lt;p&gt;Küçük PR + feature flag, “bu özellik prod’da görünür mü” sorusunu “bu kod prod’da derlenir mi” sorusundan ayırır. İkisi farklı kararlardır; trunk-based bu ayrımı zorunlu kılar.&lt;/p&gt;
&lt;h2 id=&quot;geçmediğim-durumlar&quot;&gt;Geçmediğim durumlar&lt;/h2&gt;
&lt;p&gt;Aynı önlem listesinin tersi geçerli. Trunk-based’e geçmediğim sahneler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Regüle sektörde zorunlu pre-prod sign-off.&lt;/strong&gt; Bir release’in compliance kapısından geçmesi gerekiyorsa, release branch teknik değil hukuki bir zorunluluk. Trunk-based onu kaldıramaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paralel sürüm bakımı.&lt;/strong&gt; Aynı anda 3.x ve 4.x desteklenecekse — packaged ürün, SDK, on-prem dağıtım — Gitflow’un sürüm hattı doğal cevaptır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI olgunluğu yok.&lt;/strong&gt; Trunk-based, kırık ana branch’in maliyetini ekibe yıkar. CI yeşilini garantileyemeyen bir ekipte bu maliyet günde 1–2 saat boşa giden zaman olarak ödenir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 kişiden küçük ekip, haftada 1 deploy.&lt;/strong&gt; Yatırım amorti olmaz; mevcut sıkıntınız trunk-based ile çözülmez çünkü zaten yoktur.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;geçişi-ölçtüğüm-metrikler&quot;&gt;Geçişi ölçtüğüm metrikler&lt;/h2&gt;
&lt;p&gt;“İyi gidiyor” hipotez değil, ölçülmüş olmalı. Trunk-based’in çalışıp çalışmadığını şu üç metrikten okuyorum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lead time for changes&lt;/strong&gt; — commit’ten prod’a kadar geçen p50 süre. Hedefim &amp;lt; 1 saat.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change failure rate&lt;/strong&gt; — prod’a giden değişikliklerin yüzde kaçı rollback ya da hotfix gerektiriyor. Hedefim &amp;lt; %15.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mean time to restore&lt;/strong&gt; — bozulan bir şey kaç dakikada düzeliyor. Hedefim &amp;lt; 30 dakika.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu üçü kötüye giderse trunk-based’i bırakmadan önce CI ve flag disiplinini gözden geçiriyorum — sebep neredeyse her zaman orada.&lt;/p&gt;
&lt;h2 id=&quot;workflow-tek-başına-gümüş-kurşun-değil&quot;&gt;Workflow tek başına gümüş kurşun değil&lt;/h2&gt;
&lt;p&gt;Trunk-based, Gitflow ya da GitHub Flow — hiçbiri tek başına prod’unuzu düzeltmiyor. Production’da branch stratejisi, code review temposu, deploy otomasyonu ve rollback hazırlığı birlikte çalışan bir bütün. Bu bütünün branch stratejisi ve commit/review hijyeni tarafını daha önce &lt;a href=&quot;https://www.muhammetsafak.com.tr/blog/production-git-senior-pratik-rehberi/&quot;&gt;Production’da Git: Senior’ın Pratik Rehberi&lt;/a&gt; yazısında derlemiştim; trunk-based o resimde temponun en hızlandığı uçtur.&lt;/p&gt;
&lt;p&gt;İki uç arasında nereye düştüğünüze karar vermek için, &lt;a href=&quot;/tr/journal/gitflow-mu-github-flow-mu/&quot;&gt;Gitflow ile GitHub Flow arasındaki seçimi&lt;/a&gt; ayrı bir yazıda anlattım.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Trunk-based bir araç değil, bir tempo kararıdır. Deploy frekansınız hangi tempoya oturuyorsa workflow’unuz da oraya oturmalı — tersi değil.&lt;/p&gt;
&lt;p&gt;Workflow’u modaya göre değil, prod’un nabzına göre seçin.&lt;/p&gt;</content:encoded><category>git</category><category>workflow</category><category>deploy</category><category>decisions</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Veri Yoğunluklu Sistemlerde Ölçek Kırılma Noktaları</title><link>https://sade.dev/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/</link><guid isPermaLink="true">https://sade.dev/tr/systems/veri-yogunluklu-sistemler-kirilma-noktalari/</guid><description>Veri yükü arttıkça boring mimari nerede kırılır? Working set, read replica, partitioning, yazma yükü ve ayrı veri katmanı — sıralı kırılma noktaları ve her birinin maliyeti.</description><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Web backend’lerinin çoğu compute-intensive değil, &lt;strong&gt;data-intensive&lt;/strong&gt;’dir. Darboğaz nadiren CPU’nun bir şeyi hesaplaması olur; neredeyse her zaman veriyi bir yerden bir yere taşımak, saklamak ve tutarlı tutmaktır. &lt;a href=&quot;/tr/journal/neden-boring-architecture/&quot;&gt;Boring architecture&lt;/a&gt; — tek VPS, paylaşılan PostgreSQL, paylaşılan Redis — bu yükün şaşırtıcı bir kısmını taşır.&lt;/p&gt;
&lt;p&gt;Ama veri yükü altında bu mimari &lt;strong&gt;kademeli olarak yavaşlamaz&lt;/strong&gt;. Ayrık noktalarda, hem de tahmin edilebilir bir sırayla kırılır. Bu yazı o noktaları haritalıyor: her birini hangi sinyal haber verir, sonraki adım nedir ve o adımın maliyeti nedir?&lt;/p&gt;
&lt;p&gt;Tek bir kuralı baştan söyleyeyim: &lt;strong&gt;bu adımları sırasıyla yapın.&lt;/strong&gt; Her adım size zaman kazandırır ve karşılığında operasyonel ağırlık ekler. 4. adımı 1. adımdan önce yapmak, hiçbir şey çözmeden karmaşıklık vergisi ödemektir.&lt;/p&gt;
&lt;h2 id=&quot;veri-yoğunluğu-hangi-eksende-kırılır&quot;&gt;Veri yoğunluğu hangi eksende kırılır&lt;/h2&gt;
&lt;p&gt;“Ölçeklenme” tek bir sayı değildir. Request sayısı bir eksen, veri hacmi bambaşka bir eksen. Bir sistem dakikada 50 request alıp yine de veri tarafından boğulabilir; bir başkası saniyede 5.000 request’i rahat karşılarken veri katmanı el değmemiş durur.&lt;/p&gt;
&lt;p&gt;Veri yoğunluklu sistemleri kıran asıl eksenler şunlar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Working set / RAM oranı&lt;/strong&gt; — sık erişilen verinin RAM’e sığıp sığmadığı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Okuma / yazma oranı&lt;/strong&gt; — yükün hangi tarafa baskın olduğu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek tablo boyutu&lt;/strong&gt; — tek bir ilişkinin index ve vacuum davranışını bozacak kadar büyümesi.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yazma throughput’u&lt;/strong&gt; — tek primary’nin saniyede absorbe edebileceği commit sayısı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutarlılık gereksinimi&lt;/strong&gt; — verinin ne kadar bayat okunabileceği.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu eksenlerin hepsi aynı anda kırılmaz. İyi haber bu: çünkü hepsini aynı anda çözmeniz gerekmiyor. Mimari kararı, “hangi eksen önce kırılacak” sorusuna dürüst cevap vermekle başlar — varsayımla değil, ölçümle.&lt;/p&gt;
&lt;h2 id=&quot;başlangıç-durumu&quot;&gt;Başlangıç durumu&lt;/h2&gt;
&lt;p&gt;Kırılma noktalarını konuşmadan önce kırılacak şeyi tarif edeyim. &lt;a href=&quot;/tr/systems/laravel-production-stack/&quot;&gt;Laravel production stack&lt;/a&gt; yazısındaki kurulumun veri katmanı şuna benzer:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   PHP-FPM ──┬──► pgBouncer ──► PostgreSQL 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │                  (tek primary)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             └──► Redis 7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  (cache · session · lock)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tek primary, önünde &lt;a href=&quot;/tr/notes/pgbouncer-auth-query/&quot;&gt;pgBouncer&lt;/a&gt; ile bağlantı havuzu. Redis cache, session ve lock taşıyor. Bu kurulum, veritabanı birkaç on GB’ta kaldığı ve working set RAM’e sığdığı sürece, dakikada binlerce request’e kadar hiç şikâyet etmeden çalışır. Asıl mesele: çalışmayı &lt;strong&gt;ne zaman&lt;/strong&gt; bırakacağı.&lt;/p&gt;
&lt;h2 id=&quot;kırılma-noktası-1--working-set-rame-sığmıyor&quot;&gt;Kırılma noktası 1 — Working set RAM’e sığmıyor&lt;/h2&gt;
&lt;p&gt;Veri yoğunluğunun ilk ve en sık kırılma noktası budur. PostgreSQL sık erişilen sayfaları &lt;code&gt;shared_buffers&lt;/code&gt;’ta, ötesini işletim sisteminin page cache’inde tutar. Sıcak veri — yani working set — bu iki katmana sığdığı sürece okumalar RAM hızındadır. Sığmadığı an her sorgu rastgele disk I/O’suna düşer ve p95 gecikme aniden, kademesiz biçimde tırmanır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sinyal!&lt;/strong&gt; Cache hit ratio’nun düşmesi. Bunu tahmin etmeyin, ölçün:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;  sum&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(blks_hit) &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    /&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; nullif&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;sum&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(blks_hit) &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; sum&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(blks_read), &lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; cache_hit_ratio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pg_stat_database;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sağlıklı bir OLTP sisteminde bu oran %99’un üzerindedir. %95’e doğru düşmeye başladıysa working set artık &lt;code&gt;shared_buffers&lt;/code&gt;’a sığmıyor demektir — ama bu yalnızca birinci katman: &lt;code&gt;blks_hit&lt;/code&gt; sadece PostgreSQL’in kendi buffer cache’indeki isabetleri sayar, işletim sisteminin page cache’inden dönen bir sayfa burada &lt;code&gt;blks_read&lt;/code&gt; olarak görünür. Bu yüzden oranı disk read IOPS ile birlikte okuyun: oran düşerken IOPS da sürekli yükseliyorsa working set gerçekten RAM’i taşırıyordur. &lt;code&gt;pg_statio_user_tables&lt;/code&gt;’ta belirli tablolarda &lt;code&gt;heap_blks_read&lt;/code&gt;’in &lt;code&gt;heap_blks_hit&lt;/code&gt;’e yaklaşması bunu hangi tabloların yaptığını gösterir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ne yapılır?&lt;/strong&gt; Sırasıyla:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Önce index disiplini.&lt;/strong&gt; Çoğu “RAM yetmiyor” vakası aslında “gereksiz veri okunuyor” vakasıdır. Eksik index yüzünden sequential scan yapan bir sorgu, working set’i olduğundan kat kat büyük gösterir. &lt;code&gt;pg_stat_user_tables&lt;/code&gt;’ta &lt;code&gt;seq_scan&lt;/code&gt; sayısı yüksek olan büyük tabloları bulun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sonra ölü veriyi ayırın.&lt;/strong&gt; Sıcak ve soğuk veri aynı tabloda yaşıyorsa working set yapay olarak şişer. Üç yıllık kapalı kayıtla bu haftanın aktif kayıtları aynı heap’te durmak zorunda değil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;En son vertical scale.&lt;/strong&gt; RAM eklemek bir kez yapılır, ucuzdur ve karmaşıklık eklemez. Working set’in birkaç katı RAM’iniz olsun. Bu, alınabilecek en sade ölçek hamlesidir — utanılacak bir şey değil, &lt;strong&gt;ilk tercihtir&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Maliyet.&lt;/strong&gt; Index bakımının her yazıya eklediği maliyet ve daha büyük bir VPS’in aylık farkı. İkisi de küçük. Bu yüzden 1. kırılma noktası en ucuz çözülenidir — ve bu yüzden ilk sıradadır.&lt;/p&gt;
&lt;h2 id=&quot;kırılma-noktası-2--okuma-yükü-tek-primaryyi-doyuruyor&quot;&gt;Kırılma noktası 2 — Okuma yükü tek primary’yi doyuruyor&lt;/h2&gt;
&lt;p&gt;Working set RAM’e sığıyor ama primary yine de doluyor. CPU sürekli yüksek, sorgular kuyrukta bekliyor ve yük ezici biçimde &lt;strong&gt;okuma&lt;/strong&gt; tarafında. Rapor sayfaları, listeleme endpoint’leri, arama — hepsi aynı primary’den okuyor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sinyal!&lt;/strong&gt; Primary’de CPU sürekli yüksekken yazma oranı düşük kalıyor. &lt;code&gt;pg_stat_statements&lt;/code&gt;’ta toplam süreyi &lt;code&gt;SELECT&lt;/code&gt;’ler domine ediyor. pgBouncer’da &lt;code&gt;cl_waiting&lt;/code&gt; sayacı sıfırdan kalkmış — yani client’lar bağlantı için sıraya giriyor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ne yapılır?&lt;/strong&gt; Bir &lt;strong&gt;read replica&lt;/strong&gt;. PostgreSQL’in streaming replication’ı ile primary’nin yanına hot standby bir kopya kurun, okuma trafiğini oraya yönlendirin. Laravel bunu connection ayrımıyla doğal destekler:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;// config/database.php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pgsql&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;driver&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;  =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pgsql&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;read&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;  =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;10.0.0.2&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]],&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;   // replica&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;10.0.0.1&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]],&lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;   // primary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;sticky&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;    // ...ortak ayarlar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sticky =&amp;gt; true&lt;/code&gt; kritik. Bir request içinde yazma yaptıysanız, aynı request’teki sonraki okumalar primary’ye gider — kullanıcı kendi yazdığı veriyi bayat görmez. Replica’nın kendi pgBouncer havuzu olur; primary’ninkinden ayrı.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maliyet.&lt;/strong&gt; Replication lag. Replica primary’nin birkaç milisaniye — yük altında birkaç saniye — gerisindedir. Yani &lt;strong&gt;read-after-write consistency’yi kaybedersiniz&lt;/strong&gt;, &lt;code&gt;sticky&lt;/code&gt;’nin kurtardığı request sınırının dışında. Lag’i izlemek artık bir operasyon kalemidir:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; client_addr, &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;       pg_wal_lsn_diff(pg_current_wal_lsn&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;, replay_lsn) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; lag_bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pg_stat_replication;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İkinci maliyet: her sorgunun “bu bayat veri okuyabilir mi” sorusuna cevap vermesi gerekir. Çoğu listeleme/rapor için cevap “evet”. Bakiye, stok, yetki kontrolü için cevap “hayır” — onlar primary’de kalır. Bu sınıflandırma artık mimarinin parçasıdır ve dokümante edilmesi gerekir.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Not:&lt;/strong&gt; Read replica yazma kapasitesine &lt;strong&gt;hiçbir şey&lt;/strong&gt; katmaz. Aynı yazmalar replica’da da tekrar oynanır. Replica, okuma yükü problemini çözer — yazma yükü problemini değil. İkisini karıştırmak en pahalı yanlış kırılma noktasıdır.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;kırılma-noktası-3--tek-tablo-fazla-büyük&quot;&gt;Kırılma noktası 3 — Tek tablo fazla büyük&lt;/h2&gt;
&lt;p&gt;Veritabanı toplamda makul, ama tek bir tablo onlarca-yüz milyon satıra ulaşmış. Genelde append ağırlıklı olanlar: &lt;code&gt;events&lt;/code&gt;, &lt;code&gt;audit_log&lt;/code&gt;, &lt;code&gt;notifications&lt;/code&gt;, zaman serisi ölçümler. Bu tablo artık kendi başına bir problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sinyal!&lt;/strong&gt; O tabloda autovacuum yetişemiyor — &lt;code&gt;pg_stat_user_tables&lt;/code&gt;’ta &lt;code&gt;n_dead_tup&lt;/code&gt; sürekli yüksek ve &lt;code&gt;last_autovacuum&lt;/code&gt; eskimiş. Index’ler şişiyor, tek satır güncellemesi bile yavaşlıyor. Eski veriyi &lt;code&gt;DELETE&lt;/code&gt; etmek saatler sürüyor ve arkasında devasa bir vacuum borcu bırakıyor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ne yapılır?&lt;/strong&gt; O tabloyu &lt;strong&gt;partition’lara&lt;/strong&gt; bölün. Append ağırlıklı veride doğal anahtar zamandır:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    id          &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;bigserial&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    occurred_at &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;timestamptz&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    payload     jsonb       &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;NOT NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;PARTITION&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; BY&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; RANGE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (occurred_at);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; events_2026_05&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; PARTITION&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; OF events&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    FOR&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; VALUES&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;2026-05-01&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;2026-06-01&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bunun kazandırdığı:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Partition pruning&lt;/strong&gt; — &lt;code&gt;occurred_at&lt;/code&gt; filtreli sorgular sadece ilgili partition’lara dokunur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ucuz silme&lt;/strong&gt; — eski veriyi atmak &lt;code&gt;DELETE&lt;/code&gt; değil, &lt;code&gt;DROP TABLE events_2026_01&lt;/code&gt;. Saniyeler, vacuum borcu yok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sınırlı vacuum&lt;/strong&gt; — autovacuum her partition’la ayrı ayrı, küçük parçalar hâlinde uğraşır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aylık partition oluşturmayı &lt;code&gt;pg_partman&lt;/code&gt; ile otomatikleştirin; elle yönetilen bir partition takvimi unutulmaya mahkûmdur.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maliyet.&lt;/strong&gt; Bir gerçek tuzak: partition’lı tabloda &lt;strong&gt;primary key, partition anahtarını içermek zorundadır&lt;/strong&gt;. Yani &lt;code&gt;id&lt;/code&gt; tek başına PK olamaz; &lt;code&gt;(id, occurred_at)&lt;/code&gt; olur. Bu, başka tablolardan bu tabloya foreign key vermeyi zorlaştırır — append-only log tablolarında zaten genelde istenmez, ama bilerek kabul edilen bir tavizdir. Saf append-only ve “sadece zaman aralığı sorgulanan” tablolarda B-tree yerine &lt;strong&gt;BRIN index&lt;/strong&gt; çok daha küçük ve yeterlidir.&lt;/p&gt;
&lt;h2 id=&quot;kırılma-noktası-4--yazma-yükü-tek-primaryyi-doyuruyor&quot;&gt;Kırılma noktası 4 — Yazma yükü tek primary’yi doyuruyor&lt;/h2&gt;
&lt;p&gt;Bu zor olanı. Replica okumayı kurtardı, partitioning tek tabloyu evcilleştirdi — ama primary yazma tarafında doyuyor. Saniyedeki commit sayısı, disk fsync ve WAL throughput’u tavana dayandı. Replica eklemek burada &lt;strong&gt;işe yaramaz&lt;/strong&gt;: aynı yazmalar her replica’da yeniden oynanır.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sinyal!&lt;/strong&gt; Primary’de I/O wait yüksek, &lt;code&gt;pg_stat_bgwriter&lt;/code&gt;’da checkpoint’ler sıklaşmış, WAL üretim hızı disk’in fsync kapasitesini zorluyor. Yazma gecikmesi okuma yükünden bağımsız tırmanıyor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ne yapılır?&lt;/strong&gt; Burada sihirli tek hamle yok; sırayla şu üçü:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Yazma amplifikasyonunu azaltın.&lt;/strong&gt; Her index’in her &lt;code&gt;INSERT&lt;/code&gt;/&lt;code&gt;UPDATE&lt;/code&gt;’e bir maliyeti vardır. &lt;code&gt;pg_stat_user_indexes&lt;/code&gt;’ta &lt;code&gt;idx_scan = 0&lt;/code&gt; olan index’leri — yani hiç kullanılmayanları — silin. Sık güncellenen tablolarda &lt;code&gt;fillfactor&lt;/code&gt;’ı düşürerek HOT update oranını artırın. Tek tek &lt;code&gt;INSERT&lt;/code&gt; yerine batch insert yapın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Append ağırlıklı veriyi primary’den çıkarın.&lt;/strong&gt; Yazma yükünün büyük kısmı genelde tek bir kaynaktan gelir: olay logu, metrik, analitik event. Bu veri çoğu zaman ilişkisel bütünlük de istemez. Onu ayrı bir store’a taşımak primary’yi asıl işine — transactional veriye — geri verir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;En son: veri alanını bölün.&lt;/strong&gt; Hâlâ yetmiyorsa, gerçek bir veri alanı ayrımı (functional sharding) gerekir — ki bu bizi bir sonraki kırılma noktasına götürür.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Maliyet.&lt;/strong&gt; Bu nokta, boring mimarinin gerçekten zorlandığı yerdir. 1–3 arası noktalar primary’yi tek tutmaya devam ediyordu; buradan sonrası tekillikten ödün vermek demek. Acele etmeyin: çoğu sistem 4. noktaya hiç ulaşmaz, ulaşanların çoğu da aslında 1. noktayı (eksik index, şişmiş working set) atlamış olduğu için erken gelir.&lt;/p&gt;
&lt;h2 id=&quot;kırılma-noktası-5--veri-katmanı-uygulamadan-ayrılmalı&quot;&gt;Kırılma noktası 5 — Veri katmanı uygulamadan ayrılmalı&lt;/h2&gt;
&lt;p&gt;Tek bir uygulama, paylaşılan veritabanında baskın hâle geldi. Yazma yükünü o üretiyor, en büyük tablolar onun, incident’ların kaynağı o. &lt;a href=&quot;/tr/systems/tek-vps-coklu-proje-mimarisi/&quot;&gt;Tek VPS üzerinde çoklu proje mimarisi&lt;/a&gt; yazısındaki “büyüme sinyalleri” tam olarak bunu haber verir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sinyal!&lt;/strong&gt; Paylaşılan PostgreSQL’de yükün tek bir database’den geldiği net. O uygulamanın bakım pencereleri diğer projeleri de etkiliyor. Kapasite planı artık “VPS” değil “o uygulama” ekseninde yapılıyor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ne yapılır?&lt;/strong&gt; O uygulamayı kendi veri katmanına taşıyın — ayrı bir PostgreSQL instance’ı, ya kendi sunucusunda ya managed bir serviste. Paylaşılan veritabanından bir uygulamayı çekmek, &lt;a href=&quot;/tr/notes/pgbackrest-postgresql-backup-stratejisi/&quot;&gt;pgBackRest&lt;/a&gt; ile alınan bir yedekten restore + logical replication ile fark kapatma + kısa bir cutover penceresiyle yapılır.&lt;/p&gt;
&lt;p&gt;Bu, “her şeyi mikroservise böl” demek değil. Tek bir veri-baskın uygulamayı kendi katmanına almak — diğerleri paylaşılan kurulumda mutlu mesut kalmaya devam ederken. Ayırma kararı &lt;strong&gt;veri profiliyle&lt;/strong&gt; verilir, organizasyon şemasıyla değil.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maliyet.&lt;/strong&gt; İkinci bir veritabanı: ikinci bir backup hattı, ikinci bir monitoring hedefi, ikinci bir upgrade takvimi. İki veritabanı arasında artık &lt;code&gt;JOIN&lt;/code&gt; yapamazsınız — o sınırı geçen sorgular uygulama katmanında birleşir. Bunlar gerçek maliyetler; tek bir uygulama paylaşılan kurulumu domine ettiği için &lt;strong&gt;karşılığında&lt;/strong&gt; ödenirler, peşin değil.&lt;/p&gt;
&lt;h2 id=&quot;yanlış-kırılma-noktaları&quot;&gt;Yanlış kırılma noktaları&lt;/h2&gt;
&lt;p&gt;Veri-yükü problemine benzeyen ama olmayan şeyler, en pahalı yanlış dönüşlere yol açar. Sık görülenler:&lt;/p&gt;



































&lt;div class=&quot;table-wrap&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Görünen&lt;/th&gt;&lt;th&gt;Gerçek&lt;/th&gt;&lt;th&gt;Doğru hamle&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;”Veritabanı yavaş”&lt;/td&gt;&lt;td&gt;N+1 sorgu — uygulama 200 sorgu atıyor&lt;/td&gt;&lt;td&gt;Eager loading; sorgu sayısını ölç&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”Replica lazım”&lt;/td&gt;&lt;td&gt;Tek bir sorguda eksik index&lt;/td&gt;&lt;td&gt;&lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;, index ekle&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”Sharding lazım”&lt;/td&gt;&lt;td&gt;Replica ve partitioning hiç denenmedi&lt;/td&gt;&lt;td&gt;Önce 2. ve 3. noktayı tüket&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”PostgreSQL yetmez, NoSQL’e geçelim”&lt;/td&gt;&lt;td&gt;&lt;code&gt;jsonb&lt;/code&gt;, partitioning, BRIN hiç kullanılmadı&lt;/td&gt;&lt;td&gt;PostgreSQL’i sonuna kadar kullan&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”Yazma yükü var, cache ekleyelim”&lt;/td&gt;&lt;td&gt;Cache yazmayı azaltmaz, okumayı azaltır&lt;/td&gt;&lt;td&gt;Yazma için 4. noktaya bak&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Ortak hata kalıbı şu: bir aracın çözdüğü problemle, eldeki problemi karıştırmak. Read replica okumayı ölçekler. Cache okumayı azaltır. Partitioning tek tabloyu evcilleştirir. Bunların &lt;strong&gt;hiçbiri&lt;/strong&gt; yazma throughput’unu artırmaz. Hangi eksenin kırıldığını ölçmeden araç seçmek, yanlış kırılma noktasına yatırım yapmaktır.&lt;/p&gt;
&lt;p&gt;Bir de “yeni veritabanı” cazibesi var. Cassandra, MongoDB, ayrı bir Kafka — bunları getirmeden önce PostgreSQL’in &lt;code&gt;jsonb&lt;/code&gt;’sini, &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;’ını, declarative partitioning’ini, BRIN index’lerini ve logical replication’ını tükettiğinizden emin olun. Çoğu ekip PostgreSQL’in %20’sini kullanıp “PostgreSQL yetmiyor” sonucuna varır.&lt;/p&gt;
&lt;h2 id=&quot;sıra-neden-önemli&quot;&gt;Sıra neden önemli&lt;/h2&gt;
&lt;p&gt;Bu beş nokta bir kontrol listesi değil, bir &lt;strong&gt;sıra&lt;/strong&gt;. Her adım bir öncekinin satın aldığı zamanla ertelenir:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Working set / RAM      → index + RAM      (en ucuz)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Okuma yükü             → read replica&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Tek tablo boyutu       → partitioning&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Yazma yükü             → amplifikasyon + ayrıştırma&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;5. Veri-baskın uygulama   → ayrı veri katmanı   (en pahalı)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sırayı atlamak iki yönde de pahalıdır. 1’i atlayıp 2’ye geçerseniz — yani eksik index’i görmezden gelip replica eklerseniz — bayat-veri okuma riskini, replication lag izlemeyi ve fazladan bir sunucuyu, aslında bir &lt;code&gt;CREATE INDEX&lt;/code&gt;’in çözeceği problem için satın almış olursunuz. Tersi de geçerli: 4. noktada gerçekten yazma duvarına çarpmış bir sistemi cache ekleyerek “çözmeye” çalışmak, sadece teşhisi geciktirir.&lt;/p&gt;
&lt;p&gt;Sade mimarinin gücü, tam da bu kırılma noktalarının &lt;strong&gt;görünür ve sıralı&lt;/strong&gt; olmasıdır. Her adımı bir metrik haber verir, her adımın maliyeti önceden bilinir ve hiçbir adım bir sonrakini zorunlu kılmaz. “Bütün veri katmanı çöktü” senaryosu yerine, “cache hit ratio %96’ya düştü, RAM eklemek lazım” senaryosunu yaşarsınız.&lt;/p&gt;
&lt;p&gt;Bu yaklaşımın gerçekten bittiği yer de bellidir: tek bir büyük makinenin fsync kapasitesini aşan sürdürülebilir yazma throughput’u, ya da coğrafi olarak dağıtık low-latency gereksinimi. Oraya ulaşan bir sistem zaten bunu finanse edecek bir iş modeline sahiptir — ve o noktada karmaşıklık artık bir borç değil, ödenmiş bir bedeldir. Ondan önce değil.&lt;/p&gt;</content:encoded><category>postgresql</category><category>architecture</category><category>production</category><category>scaling</category><category>replication</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Neden Boring Architecture&apos;ı Tercih Ediyorum</title><link>https://sade.dev/tr/journal/neden-boring-architecture/</link><guid isPermaLink="true">https://sade.dev/tr/journal/neden-boring-architecture/</guid><description>Yeni teknoloji peşinde koşmak yerine kanıtlanmış araçları seçmenin pragmatik gerekçesi. Bu bir korkaklık değil, bir bütçe kararı.</description><pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bir genç mühendis bana “neden Laravel ile uğraşıyorsun, X yeni framework daha hızlı” diye sorduğunda iç çekerek başlayan bir konuşma açıyorum. Cevap “Laravel daha iyi çünkü Y” değil. Cevap şu: &lt;strong&gt;“Her ekip için sınırlı bir karmaşıklık bütçesi var, ben onu daha önemli yerde harcamayı tercih ediyorum.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;boring-ne-demek&quot;&gt;&amp;quot;Boring” ne demek?&lt;/h2&gt;
&lt;p&gt;Dan McKinley’nin meşhur “Choose Boring Technology” yazısı bu tabiri popülerleştirdi. “Sıkıcı” burada kötü değil — &lt;strong&gt;öngörülebilir&lt;/strong&gt;, &lt;strong&gt;iyi belgelenmiş&lt;/strong&gt;, &lt;strong&gt;hata durumunda Stack Overflow’da on yıl önce verilmiş cevabı bulunabilen&lt;/strong&gt; anlamına geliyor.&lt;/p&gt;
&lt;p&gt;Boring stack benim için şunlar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP + Laravel ya da Symfony&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;Linux + systemd + Supervisor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yeni bir tane ekleyince — diyelim RabbitMQ — onu da boring kategorisine sokmak için 12–18 ay üretimde aktif kullanmam gerekiyor. O zamana kadar deneysel, kuşkulu.&lt;/p&gt;
&lt;h2 id=&quot;innovation-token-kavramı&quot;&gt;”Innovation token” kavramı&lt;/h2&gt;
&lt;p&gt;Aynı yazıda McKinley &lt;strong&gt;innovation token&lt;/strong&gt; diye bir mecaz kullanıyor: her ekibin harcayabileceği sınırlı sayıda inovasyon token’ı var. Yeni bir dil seçmek bir token. Yeni bir veritabanı seçmek bir token. Yeni bir deployment platformu bir token.&lt;/p&gt;
&lt;p&gt;Tipik küçük bir ekip için bütçe: &lt;strong&gt;3 token&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Bunları:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yeni bir programlama dili&lt;/li&gt;
&lt;li&gt;Yeni bir veritabanı türü&lt;/li&gt;
&lt;li&gt;Yeni bir mesajlaşma sistemi&lt;/li&gt;
&lt;li&gt;Yeni bir orchestration platformu&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;şeklinde harcadığınızda, ürünün kendisine — gerçekten değer üretecek karmaşıklığa — ayıracak token kalmıyor.&lt;/p&gt;
&lt;h2 id=&quot;üç-tokenımı-nerede-harcıyorum&quot;&gt;Üç token’ımı nerede harcıyorum?&lt;/h2&gt;
&lt;p&gt;Çoğu projede şu üçünü harcıyorum:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Domain karmaşıklığı.&lt;/strong&gt; İş kuralları, dağıtık iş akışları, eventual consistency gibi şeyler bütçe yer çünkü iş gerektiriyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yeni bir altyapı parçası&lt;/strong&gt; (örn. message broker eklemek, search engine eklemek). Yılda en fazla bir kez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;İlginç bir denemenin küçük bir bölümünde&lt;/strong&gt; — bir özelliği yeni bir araçla yapıp ne öğrendiğimizi görmek için. Çoğu zaman tekrar atılır.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Geri kalan her şey &lt;strong&gt;kanıtlanmış&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;birinci-dünya-sorunu-mu&quot;&gt;Birinci dünya sorunu mu?&lt;/h2&gt;
&lt;p&gt;Hayır. Boring tech savunması özellikle &lt;strong&gt;kaynaklarımız sınırlıyken&lt;/strong&gt; önemli. Büyük şirketler “tüm akışları GraphQL’e taşınıyoruz” derken 200 mühendisin bir kısmı bu işle ilgileniyor. Sizin 3 kişilik ekibinizin bunu yapması = 3 ay özellik geliştirmemek.&lt;/p&gt;
&lt;h2 id=&quot;ama-bu-altyapı-eski&quot;&gt;”Ama bu altyapı eski”&lt;/h2&gt;
&lt;p&gt;PostgreSQL 1996’dan beri var. PHP 1995’ten. Nginx 2004’ten. Redis 2009’dan.&lt;/p&gt;
&lt;p&gt;“Eski” doğru kelime değil — &lt;strong&gt;olgun&lt;/strong&gt; doğru kelime. Olgun yazılım:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Edge case’leri keşfedilmiş ve düzeltilmiş.&lt;/li&gt;
&lt;li&gt;Hata mesajları Google’a yazılınca cevap bulunuyor.&lt;/li&gt;
&lt;li&gt;Ekibin yeni katılan üyesi ilk gününde productive olabiliyor.&lt;/li&gt;
&lt;li&gt;“Bu bug’ı kim yaratmış” sorusu kütüphane sürümünüze çıkmıyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;i̇stisnalar-neler&quot;&gt;İstisnalar neler?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bir özellik kanıtlanmış araçla yapılamıyorsa.&lt;/strong&gt; Örnek: gerçek zamanlı collaborative editing için CRDT (Conflict-free Replicated Data Type) — bu mature bir alan değil, sıfırdan yazmak istemezsiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ekipte herkes yeni araçla deneyimli.&lt;/strong&gt; Eğer 5 mühendis Go ile yıllardır yazıyorsa, PHP’ye zorlamak yanlış.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performans gereksinimi açık ve ölçülmüş.&lt;/strong&gt; “Daha hızlı olur” hipotezi değil, “şu noktada darboğaz var, X dili çözüyor” demonstrasyonu.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;sonuç&quot;&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Yeni teknoloji yapmak heyecanlı. Eski teknoloji &lt;strong&gt;çalıştırmak&lt;/strong&gt; sıkıcı. Bu ikisi arasındaki fark, üretimde 2 yıllık bir ürün ile 6 aylık bir ürün arasındaki fark. Hangi tarafı önemsiyorsanız ona göre seçim yapın — ama farkı bilerek yapın.&lt;/p&gt;
&lt;p&gt;Boring tech bir korkaklık değil, bir bütçe kararı. Bütçenizi daha değerli problemlere ayırın.&lt;/p&gt;</content:encoded><category>architecture</category><category>decisions</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Shared Redis Kullanırken Namespace İzolasyonu</title><link>https://sade.dev/tr/notes/shared-redis-namespace-izolasyonu/</link><guid isPermaLink="true">https://sade.dev/tr/notes/shared-redis-namespace-izolasyonu/</guid><description>Tek bir Redis instance birden çok proje arasında paylaşılırken key collision ve yanlışlıkla silme kazalarından korunmak için kullandığım örüntü.</description><pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;VPS başına bir Redis çalıştırmak küçük-orta projelerde gayet makul bir karar. Sorun, birden çok uygulama aynı instance’ı paylaşmaya başladığında ortaya çıkıyor: bir projenin &lt;code&gt;users&lt;/code&gt; anahtarı diğerinin &lt;code&gt;users&lt;/code&gt; anahtarını eziyor, biri &lt;code&gt;FLUSHALL&lt;/code&gt; çağırınca herkes çuvallıyor.&lt;/p&gt;
&lt;p&gt;Çözüm yeni bir Redis kurmak değil; &lt;strong&gt;namespace disiplini.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;üç-katmanlı-bir-anahtar-şeması&quot;&gt;Üç katmanlı bir anahtar şeması&lt;/h2&gt;
&lt;p&gt;Üretimde şu kalıbı kullanıyorum:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;lt;env&amp;gt;:&amp;lt;app&amp;gt;:&amp;lt;bounded-context&amp;gt;:&amp;lt;key&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Örnek:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;prod:billing:invoice:42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;prod:auth:session:1c2f...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;staging:notify:queue:retry&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu üçlüye sahip olmak iki şeyi getiriyor:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Aramada netlik.&lt;/strong&gt; &lt;code&gt;redis-cli --scan --pattern &amp;#39;prod:billing:*&amp;#39;&lt;/code&gt; ile sadece bir projenin anahtarlarını görebiliyorum.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yanlışlıkla silmeyi zorlaştırma.&lt;/strong&gt; &lt;code&gt;FLUSHDB&lt;/code&gt; yerine &lt;code&gt;redis-cli --scan --pattern &amp;#39;staging:*&amp;#39; | xargs redis-cli DEL&lt;/code&gt; yazarken iki kez düşünüyorum.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;logical-dblere-güvenmeyin&quot;&gt;Logical DB’lere güvenmeyin&lt;/h2&gt;
&lt;p&gt;Redis’in 0-15 arası logical DB’leri var ama tek kelimeyle: &lt;strong&gt;kullanmayın.&lt;/strong&gt; Üç sebep:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt; komutu sadece o bağlantı için scope’lu — connection pool’da kaos.&lt;/li&gt;
&lt;li&gt;Bazı kütüphaneler &lt;code&gt;MULTI/EXEC&lt;/code&gt; blokları içinde DB switch’i desteklemiyor.&lt;/li&gt;
&lt;li&gt;Redis Cluster logical DB’leri zaten desteklemiyor; yarın cluster’a geçerseniz tüm varsayımınız çöker.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Namespace prefix’i öğrenip uygulamak tek yöntemdir.&lt;/p&gt;
&lt;h2 id=&quot;connection-level-izolasyon&quot;&gt;Connection-level izolasyon&lt;/h2&gt;
&lt;p&gt;Laravel kullanıyorsanız &lt;code&gt;config/database.php&lt;/code&gt; içinde her uygulama için ayrı bir Redis bağlantısı tanımlayın ve &lt;code&gt;prefix&lt;/code&gt; ile namespace’i bağlantıya gömün:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;redis&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;billing&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; env&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;REDIS_HOST&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; env&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;REDIS_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; env&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;REDIS_PORT&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 6379&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;database&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prefix&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prod:billing:&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;        // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prefix&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prod:auth:&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Artık &lt;code&gt;Redis::connection(&amp;#39;billing&amp;#39;)-&amp;gt;set(&amp;#39;invoice:42&amp;#39;, ...)&lt;/code&gt; çağrısı otomatik olarak &lt;code&gt;prod:billing:invoice:42&lt;/code&gt; yazıyor. Geliştirici yanlışlıkla namespace yazmayı unutursa bile sistem kendini koruyor.&lt;/p&gt;
&lt;h2 id=&quot;tehlikeli-komutları-kapatın&quot;&gt;Tehlikeli komutları kapatın&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;redis.conf&lt;/code&gt; içinde production’da şu komutları yeniden adlandırmak veya kapatmak iyi bir fikir:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rename-command FLUSHDB &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rename-command FLUSHALL &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rename-command KEYS &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;KEYS&lt;/code&gt; özellikle önemli — tarama boyunca event loop’u bloke ediyor ve diğer tüm istemciler sırada bekliyor. Redis’in kendi dokümantasyonu 1 milyon anahtarlı bir veritabanını giriş seviyesi bir dizüstünde yaklaşık 40 ms’de tarıyor, yani 100k key’li bir instance’da milisaniyeler mertebesindesiniz — ama maliyet anahtar sayısıyla birlikte büyüyor, milyonlarca anahtarlı instance’larda duraklama canınızı yakacak kadar uzuyor. Yerine &lt;code&gt;SCAN&lt;/code&gt; kullanılması zorunlu olmalı.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-bu-kalıbı-bırakırsınız&quot;&gt;Ne zaman bu kalıbı bırakırsınız?&lt;/h2&gt;
&lt;p&gt;Üç sinyalden biri varsa ayrı Redis instance’larına geçin:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bir projenin trafiği diğerlerinin gecikmesini ölçülebilir biçimde etkiliyor.&lt;/li&gt;
&lt;li&gt;Bir proje persistent storage istiyor (RDB/AOF), diğerleri cache-only.&lt;/li&gt;
&lt;li&gt;Bir proje farklı bir Redis sürüm/feature ihtiyacı duyuyor.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bunlardan önce şu basit kuralı aklınızda tutun: &lt;strong&gt;bir prefix yazmak, yeni bir process başlatmaktan ucuzdur.&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>redis</category><category>production</category><category>architecture</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>AI-Assisted Engineering Workflow&apos;um</title><link>https://sade.dev/tr/journal/ai-assisted-engineering-workflow/</link><guid isPermaLink="true">https://sade.dev/tr/journal/ai-assisted-engineering-workflow/</guid><description>Claude Code ve benzeri ajanları üretim kodunda güvenle kullanmak için takip ettiğim yedi kural — alaycı bir yazı değil, bir disiplin manifestosu.</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AI coding agent’larıyla 2 yıldır günlük çalışıyorum. İlk yıl naif kullandım, ikinci yıl disiplin geliştirdim. Bu yazı disiplinin somut hali.&lt;/p&gt;
&lt;p&gt;Sayısal olarak ne kadar hızlandığım önemli değil — önemli olan &lt;strong&gt;kodun kalitesi azalmadı&lt;/strong&gt;. Aksine, kod tabanına gelen junior’ların ürettiği bug’lardan daha azını ürettim. Bu bir başarı değil, ajan kullanmamayı öneren bir reklam değil — ne işe yaradığına dair bir gözlem.&lt;/p&gt;
&lt;h2 id=&quot;1-ajana-plan-yaptırma-onayla-sonra-yazdır&quot;&gt;1. Ajana plan yaptırma, onayla, sonra yazdır&lt;/h2&gt;
&lt;p&gt;“Bana auth modülü yaz” demek = kontrolsüz. İşe yarayan akış:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;“Auth modülü için bir plan çıkar. Endpoints, DB tabloları, edge case’ler.”&lt;/li&gt;
&lt;li&gt;Planı oku, sor, düzelt.&lt;/li&gt;
&lt;li&gt;“Plana göre yaz” — ama büyük modüllerde adım adım.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Onaylı planın olmadan kod yazma izni verme. &lt;strong&gt;Her zaman.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;2-sınırlı-erişim-ver&quot;&gt;2. Sınırlı erişim ver&lt;/h2&gt;
&lt;p&gt;Bir ajanın &lt;code&gt;rm -rf&lt;/code&gt; çalıştırabilmesi gereksiz. Sandbox tercih:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read-only filesystem mount’lar dışında.&lt;/li&gt;
&lt;li&gt;Network erişimi sadece belirli endpoint’lere.&lt;/li&gt;
&lt;li&gt;Production secrets’a erişim &lt;strong&gt;asla&lt;/strong&gt;. Local mock veya scrubbed copy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu “ajan kötü niyetli” varsayımı değil, “hata yapabilir” varsayımı.&lt;/p&gt;
&lt;h2 id=&quot;3-test-ettir-çalıştır-geri-al&quot;&gt;3. Test ettir, çalıştır, geri al&lt;/h2&gt;
&lt;p&gt;Ajan kod yazdı. Şimdi:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Test yazdır&lt;/strong&gt; (ya da var olan testleri çalıştır).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testi çalıştır.&lt;/strong&gt; Geçtiği iddiası yetmez — gerçekten gör.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manuel smoke test yap.&lt;/strong&gt; Ajanın iddialarına değil, gözüne güven.&lt;/li&gt;
&lt;li&gt;Şüphe varsa &lt;code&gt;git checkout .&lt;/code&gt; — tekrar dene.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ajanın özeti gerçek değil. Yalan söylemiyor — sadece kendi çıktısının iyi olduğunu varsayıyor. Doğrulama insan işi.&lt;/p&gt;
&lt;h2 id=&quot;4-code-review-aşamasını-atlama&quot;&gt;4. Code review aşamasını atlama&lt;/h2&gt;
&lt;p&gt;Ajanın ürettiği kod, junior bir mühendisin ürettiği kod gibi gözden geçirilmeli.
“Ajan zaten kontrol etmişti” yanılgısına düşme. Özellikle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Güvenlik.&lt;/strong&gt; SQL injection, XSS, auth bypass.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance.&lt;/strong&gt; N+1 query’ler, gereksiz cache invalidation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boundary condition’lar.&lt;/strong&gt; Boş input, çok büyük input, Unicode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bunları yakalamak için statik analiz iyi, ama insan gözü vazgeçilmez.&lt;/p&gt;
&lt;h2 id=&quot;5-bağlamı-saklı-tut&quot;&gt;5. Bağlamı saklı tut&lt;/h2&gt;
&lt;p&gt;Ajanın “geçmişe sahip olmadığını” unutma. Her oturumda hangi karara neden vardığını anlatmak gerekiyor. Bunu otomatikleştirmek için:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Repo’da &lt;code&gt;CLAUDE.md&lt;/code&gt; veya &lt;code&gt;.ssot/&lt;/code&gt; gibi yerlerde durumun anlık özetini tutuyorum.&lt;/li&gt;
&lt;li&gt;Önemli kararları oraya yazıyorum: “PostgreSQL kullanıyoruz çünkü…, Redis
cache-only modunda çalışıyor çünkü…”.&lt;/li&gt;
&lt;li&gt;Ajan oturumun başında bunu okur, daha tutarlı kararlar verir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-ajanı-tek-mühendise-yerleştirme&quot;&gt;6. Ajanı tek mühendise yerleştirme&lt;/h2&gt;
&lt;p&gt;Bir ajanı senior bir mühendis yerine &lt;em&gt;koymak&lt;/em&gt; yanlış. Ajanı bir senior mühendisin &lt;strong&gt;uzantısı&lt;/strong&gt; olarak kullanmak doğru. Junior mühendislere doğrudan “ajan’a yaptır” demek genellikle felaket — çünkü:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ajanın çıktısını eleştirel okumayı bilmiyorlar.&lt;/li&gt;
&lt;li&gt;Doğru soruyu sormayı henüz öğrenmediler.&lt;/li&gt;
&lt;li&gt;“Çalışıyor” ile “doğru çalışıyor” arasındaki farkı henüz hissedemiyorlar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Junior’a verilecek görev, ajanı &lt;strong&gt;denetlemek&lt;/strong&gt; olabilir — ama yönetmek değil.&lt;/p&gt;
&lt;h2 id=&quot;7-üretime-ulaşan-kodun-her-satırından-sorumlu-sensin&quot;&gt;7. Üretime ulaşan kodun her satırından sorumlu sensin&lt;/h2&gt;
&lt;p&gt;Bu en önemlisi. Ajanın yazdığı bir SQL injection açığını sen merge ettiysen, sorumluluk senin. “AI yazdı” ifadesi PR review’unda bahane değil.&lt;/p&gt;
&lt;p&gt;Pratik karşılığı: ajanın ürettiği her şeyin &lt;strong&gt;gerçekten okuduğun&lt;/strong&gt;, anladığın, gerekirse savunabildiğin kod olduğundan emin olana kadar merge etme.&lt;/p&gt;
&lt;h2 id=&quot;sonuç&quot;&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Ajanlar inanılmaz kuvvetli aletler. Onları “30 saniyede özellik üreten sihirli sopalar” olarak görmek başarısızlık. Onları “bir senior mühendisin uzantısı olan stajyer” olarak görmek başarı. Hangi tarafı seçtiğin, ortaya çıkan yazılımın kalitesini belirliyor.&lt;/p&gt;
&lt;p&gt;İki yılın sonunda hâlâ aynı disipline uyuyorum çünkü işliyor.&lt;/p&gt;</content:encoded><category>ai-workflow</category><category>productivity</category><category>opinion</category><category>Journal</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>pgBackRest ile PostgreSQL Backup Stratejisi</title><link>https://sade.dev/tr/notes/pgbackrest-postgresql-backup-stratejisi/</link><guid isPermaLink="true">https://sade.dev/tr/notes/pgbackrest-postgresql-backup-stratejisi/</guid><description>pg_dump tek başına yedek değil. pgBackRest ile PITR destekli, sıkıştırılmış, doğrulanabilir yedek mimarisi.</description><pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;pg_dump&lt;/code&gt; ile uğraşmayı bıraktığım gün, sistem yazılım mühendisi olarak ciddiyetimin arttığını düşünüyorum. &lt;code&gt;pg_dump&lt;/code&gt; bir araştırma aracı — production yedek aracı değil.&lt;/p&gt;
&lt;p&gt;Üretim yedeği üç şeyi sağlamalı:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Point-in-time recovery (PITR).&lt;/strong&gt; “Dün gece 02:13’te silindi” diyebilmek.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bütünlük doğrulaması.&lt;/strong&gt; Yedek dosyasının açıldığında çalıştığını test etmek.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saklamayı politikayla yönetmek.&lt;/strong&gt; 6 saatlik/günlük/haftalık rotasyon.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;pg_dump&lt;/code&gt; üç maddeyi de karşılamıyor.&lt;/p&gt;
&lt;h2 id=&quot;pgbackrest-kurulumu&quot;&gt;pgBackRest kurulumu&lt;/h2&gt;
&lt;p&gt;Ben hem yerel hem de S3 yedeği almayı tercih ve tavsiye ediyorum. Yerel yedekler hızlı, S3 yedekleri ise yangına dayanıklı.&lt;/p&gt;
&lt;h3 id=&quot;off-site-kopya&quot;&gt;Off-site kopya&lt;/h3&gt;
&lt;p&gt;Repository tek bir diskte duruyorsa bir VPS yangını her şeyi siler. pgBackRest S3 veya S3-uyumlu (R2, MinIO) bucket’a yazabiliyor:&lt;/p&gt;
&lt;p&gt;Tercih: bucket’ı versiyon-kilitli ve &lt;strong&gt;delete’i yasaklayan&lt;/strong&gt; bir policy ile koruyun. Ransomware senaryosunda kritik.&lt;/p&gt;
&lt;p&gt;Maliyetleri uzun vadeli kontrol altında tutmak için bucket yaşam döngüsünü ayarlayın:&lt;/p&gt;
&lt;p&gt;30 Gün → Glacier Anında Alma → 120 Gün → Sil&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/pgbackrest.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;global&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;###################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# Local Repository&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;###################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo1-path&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/lib/pgbackrest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo1-retention-full&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo1-retention-diff&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;14&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo1-bundle&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo1-block&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo1-cipher-pass&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo1-cipher-type&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;aes-256-cbc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;###################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# AWS S3 Repository&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;###################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-type&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;s3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-path&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-s3-bucket&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-s3-endpoint&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;s3.{region_name}.amazonaws.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-s3-region&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{region_name}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-s3-key&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-s3-key-secret&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-bundle&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-block&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-cipher-type&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;aes-256-cbc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;repo2-cipher-pass&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;###################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;compress-type&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;zst&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;compress-level&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;process-max&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;start-fast&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;log-level-console&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;info&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;log-level-file&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;detail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;log-path&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/log/pgbackrest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pg1-path&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/lib/postgresql/16/main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pg1-port&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;5432&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yetkileri sıkılaştırın:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; chown&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; postgres:postgres&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; /etc/pgbackrest.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; chmod&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 640&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; /etc/pgbackrest.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stanza oluşturun:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; postgres&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; pgbackrest&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; --stanza=main&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; stanza-create&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;postgresql.conf&lt;/code&gt; tarafında ise WAL archiving’i pgBackRest’e yönlendiriyoruz:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;archive_mode&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;archive_command&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;pgbackrest --stanza=main archive-push %p&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;archive_timeout&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 60&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;wal_level&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; replica&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;archive_timeout = 60&lt;/code&gt; — son 60 saniyenin işlemleri kaybedilebilir; çoğu uygulama için yeterli, finansal işlemler için değil.&lt;/p&gt;
&lt;h2 id=&quot;backup-planı&quot;&gt;Backup planı&lt;/h2&gt;
&lt;p&gt;Cron tarafında:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Yerel repository (repo1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0 2 * * 0          pgbackrest --stanza=main --repo=1 --type=full backup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0 2 * * 1-6        pgbackrest --stanza=main --repo=1 --type=diff backup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0 0,6,12,18 * * *  pgbackrest --stanza=main --repo=1 --type=incr backup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# S3 repository (repo2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0 4 * * 0          pgbackrest --stanza=main --repo=2 --type=full backup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0 4 * * 1-6        pgbackrest --stanza=main --repo=2 --type=diff backup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0 3,9,15,21 * * *  pgbackrest --stanza=main --repo=2 --type=incr backup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İki repo yapılandırıldığında &lt;code&gt;--repo&lt;/code&gt; şart: verilmezse pgBackRest yalnızca en yüksek öncelikli repoya (repo1) yedek alır, S3’te arşivlenen WAL’dan başka bir şey durmaz — tek başına restore edilemeyen bir kopya.&lt;/p&gt;
&lt;p&gt;Her repoda haftalık full, günlük diff, 6 saatlik incremental. Storage tüketimi şaşırtıcı derecede düşük — konfigürasyondaki &lt;code&gt;repoN-bundle&lt;/code&gt;/&lt;code&gt;repoN-block&lt;/code&gt; satırları block incremental yedeği açıyor, böylece pgBackRest dosyanın tamamını değil yalnızca değişen bloklarını kopyalıyor; ikisi de varsayılanda kapalı.&lt;/p&gt;
&lt;h2 id=&quot;bütünlük-yedek-varsa-restore-edilebilir-mi&quot;&gt;Bütünlük: yedek varsa restore edilebilir mi?&lt;/h2&gt;
&lt;p&gt;Yedeği almak yeterli değil. Haftada bir restore tatbikatı:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;pgbackrest&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; --stanza=main&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; --delta&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; restore&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; --pg1-path=/tmp/pg_restore_test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra o instance’ı geçici bir port üzerinde başlatıp birkaç sanity query çalıştırıyorum. Bu adım atlanırsa yedek &lt;strong&gt;sahte güven&lt;/strong&gt; olur.&lt;/p&gt;
&lt;h2 id=&quot;pitr-provası&quot;&gt;PITR provası&lt;/h2&gt;
&lt;p&gt;Belirli bir zamana geri dönmek:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;pgbackrest&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; --stanza=main&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;  --type=time&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;  --target=&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;2026-05-07 02:13:00+03&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;  restore&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu komutun &lt;em&gt;gerçek bir incident’te&lt;/em&gt; ilk kez çalıştırılması felakettir. Provayı önceden yapın — adımlar runbook’a yazılsın.&lt;/p&gt;
&lt;h2 id=&quot;saklama&quot;&gt;Saklama&lt;/h2&gt;
&lt;p&gt;Repository’deki kullanılmayan yedeklerin temizliği &lt;code&gt;expire&lt;/code&gt; komutuyla:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;pgbackrest&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; --stanza=main&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt; expire&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;repo1-retention-full=4&lt;/code&gt; ile son 4 full yedek tutuluyor; daha eskileri (ve onlara bağlı diff/incr’ler) otomatik temizleniyor.&lt;/p&gt;
&lt;h2 id=&quot;sonuç&quot;&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Schema kopyalamak veya küçük bir tabloyu taşımak için &lt;code&gt;pg_dump&lt;/code&gt; hâlâ doğru araç. Ama PITR isteyen herhangi bir sistemde yedek yazılımı pgBackRest (veya barman, ya da veritabanı sağlayıcının kendi aracı) olmalı. Backup’ın &lt;em&gt;test edilmediği&lt;/em&gt; anda “backup’ım var” demek yalan söylemenin sofistike bir yoludur.&lt;/p&gt;</content:encoded><category>postgresql</category><category>backup</category><category>production</category><category>pgbackrest</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Laravel Queue Production&apos;da Neden Yavaşlar?</title><link>https://sade.dev/tr/notes/laravel-queue-production-yavaslama/</link><guid isPermaLink="true">https://sade.dev/tr/notes/laravel-queue-production-yavaslama/</guid><description>Laravel queue worker&apos;ları yerelde mükemmel, production&apos;da yavaş. Geliştiricilerin gözden kaçırdığı en yaygın beş neden.</description><pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;“Bu queue yerelde çalışıyordu” cümlesi senelerdir aynı tonla söyleniyor. Production’da çalışıyor — sadece beklediğiniz hızda değil. Gerçek nedenler genelde Laravel’in dışında.&lt;/p&gt;
&lt;h2 id=&quot;1-queue-storage-için-yeterli-connection-pool-yok&quot;&gt;1. Queue Storage için yeterli connection pool yok&lt;/h2&gt;
&lt;p&gt;Queue Storage olarak Redis kullandığınızı varsayalım.&lt;/p&gt;
&lt;p&gt;Default &lt;code&gt;phpredis&lt;/code&gt; ya da &lt;code&gt;predis&lt;/code&gt; bağlantıyı job başına değil worker süreci başına kuruyor — &lt;code&gt;queue:work&lt;/code&gt; uzun ömürlü tek bir süreçtir ve Laravel’in &lt;code&gt;RedisManager&lt;/code&gt;’ı çözümlediği bağlantıyı önbelleğe alır. Her job için yeni süreç doğan kurulumlarda (&lt;code&gt;queue:listen&lt;/code&gt;, PHP-FPM istekleri) bağlantı gerçekten job başına açılır: her birinde bir TCP handshake’lik gecikme + arada bir &lt;code&gt;ECONNRESET&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Çözüm: &lt;code&gt;config/database.php&lt;/code&gt; içinde &lt;code&gt;persistent =&amp;gt; true&lt;/code&gt; (phpredis için) — Laravel aynı seçeneği &lt;code&gt;RedisCluster&lt;/code&gt;’a da geçirir, yani cluster kurulumu da kapsanır. Worker başlattığınızda bağlantıyı açık bırakın:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;redis&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;cluster&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; env&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;REDIS_CLUSTER&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;redis&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prefix&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; env&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;REDIS_PREFIX&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;persistent&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Not:&lt;/strong&gt; RabbitMQ tarafında optimizasyonun ana noktası persistent socket flag’i değil, connection lifecycle yönetimidir. Her job’da connection açmak yerine worker process başına uzun ömürlü AMQP connection kullanılmalı, channel reuse yapılmalı ve heartbeat/read-write timeout değerleri job sürelerine göre ayarlanmalıdır.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;2-job-içinde-sync-io-bekletmesi&quot;&gt;2. Job içinde sync I/O bekletmesi&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Http::get()&lt;/code&gt; çağrısı 30 saniye timeout ile bekleyebiliyor. O job o worker’ı tutar. 10 worker, 10 yavaş upstream — kuyruğun gerisi durur.&lt;/p&gt;
&lt;p&gt;İki kural:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Her HTTP/SQL çağrısının &lt;strong&gt;explicit timeout’u&lt;/strong&gt; olsun. Default 30 saniye değil 5.&lt;/li&gt;
&lt;li&gt;Beklenmesi gereken işler &lt;code&gt;delay&lt;/code&gt;’li ayrı bir kuyrukta tutulsun (örn. webhook retry’leri için &lt;code&gt;slow&lt;/code&gt; kuyruğu, hızlı işler &lt;code&gt;default&lt;/code&gt; kuyruğunda).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-supervisor-numprocs-yanlış&quot;&gt;3. Supervisor &lt;code&gt;numprocs&lt;/code&gt; yanlış&lt;/h2&gt;
&lt;p&gt;Tek bir queue worker tek bir PHP process’tir. Tek bir PHP process tek bir CPU core’unu kullanır. 4 core’lu sunucuda 1 worker çalıştırmak &lt;code&gt;nproc * 0.75&lt;/code&gt;’i atıl bırakıyor demektir.&lt;/p&gt;
&lt;p&gt;Tipik kural: &lt;code&gt;numprocs = nproc&lt;/code&gt; (CPU-bound) veya &lt;code&gt;numprocs = 2 * nproc&lt;/code&gt; (I/O-bound). Her uygulamayı ölçün.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;program:laravel-worker&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;process_name&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;%(program_name)s_%(process_num)02d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;php /var/www/app/artisan queue:work redis --&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;default --&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;1 --&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;tries&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;3 --&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;max-time&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;3600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;autostart&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;autorestart&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;numprocs&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;www-data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;redirect_stderr&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;stdout_logfile&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/log/laravel-worker.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;stopwaitsecs&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;3600&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;stopwaitsecs=3600&lt;/code&gt; önemli — Supervisor restart’ı sırasında uzun bir job’u yarıda kesmesin.&lt;/p&gt;
&lt;h2 id=&quot;4-memory-leake-karşı-yeniden-başlatmıyorsunuz&quot;&gt;4. Memory leak’e karşı yeniden başlatmıyorsunuz&lt;/h2&gt;
&lt;p&gt;Long-running PHP process’ler memory’i biriktirir. &lt;code&gt;--max-time=3600&lt;/code&gt; veya &lt;code&gt;--max-jobs=1000&lt;/code&gt; ile worker’lar periyodik olarak kendilerini sonlandırmalı ve Supervisor tarafından yeniden başlatılmalı. Aksi takdirde:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Worker 8 GB RAM tüketir.&lt;/li&gt;
&lt;li&gt;OOM killer onu öldürür.&lt;/li&gt;
&lt;li&gt;Hangi job’u yarıda kestiği belirsiz.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-job-batchleri-için-tek-bir-kuyruk-kullanmak&quot;&gt;5. Job batch’leri için tek bir kuyruk kullanmak&lt;/h2&gt;
&lt;p&gt;Yüksek hacimli “send notification” job’larıyla az sayıda “process payment” job’unu aynı kuyruğa atınca:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;50.000 notification 30 dakika kuyruğu tıkar.&lt;/li&gt;
&lt;li&gt;Payment job’u bekler.&lt;/li&gt;
&lt;li&gt;Müşteri “ödemem geçmedi” diye yazar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Prioritize:&lt;/strong&gt; önemli işleri ayrı kuyruklara koyun ve worker &lt;code&gt;--queue=payments,default,low&lt;/code&gt; şeklinde sıralı dinlesin.&lt;/p&gt;
&lt;h2 id=&quot;görmeniz-gereken-metrikler&quot;&gt;Görmeniz gereken metrikler&lt;/h2&gt;
&lt;p&gt;Üç şey yeterli:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kuyruk uzunluğu&lt;/strong&gt; (Redis: kuyruk listesi için &lt;code&gt;LLEN&lt;/code&gt; — delayed ve reserved job’lar ayrı sorted set’lerde durur, onların &lt;code&gt;ZCARD&lt;/code&gt;’ını da ekleyin).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job süresi p95&lt;/strong&gt; (kendi instrumentation’ınız — Horizon’un metrik panosu throughput, bekleme süresi ve &lt;em&gt;ortalama&lt;/em&gt; runtime verir, yüzdelik dilim vermez).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failed job sayısı&lt;/strong&gt; son 5 dakikada.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu üçü dashboard’da olsun ve eşik geçince alarm üretsin.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Queue yavaşlığının %90’ı bu listedeki bir şeydir. Geri kalan %10 ise gerçek bottleneck’ler (database, external API) — onları bulmak için doğru ölçümünüz olmalı. Production’da “queue yavaş” hipotez değil, ölçülmüş bir şey olmalı.&lt;/p&gt;</content:encoded><category>laravel</category><category>queue</category><category>production</category><category>redis</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Tek VPS Üzerinde Çoklu Proje Mimarisi</title><link>https://sade.dev/tr/systems/tek-vps-coklu-proje-mimarisi/</link><guid isPermaLink="true">https://sade.dev/tr/systems/tek-vps-coklu-proje-mimarisi/</guid><description>Bir VPS, birden fazla bağımsız uygulama. Kubernetes yerine bilinçli minimalizm: kullanıcı izolasyonu, ayrı PHP-FPM pool, paylaşılan PostgreSQL/Redis, sade deploy.</description><pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Küçük ve orta SaaS projeler için &lt;strong&gt;tek bir VPS&lt;/strong&gt;, birden fazla uygulamayı barındırmak için fazlasıyla yeterli. Kubernetes, mesh, “cloud native” baskısı pek çok ekibi bu yoldan saptırıyor — ama eğer toplam yük 1 makineye sığıyorsa, dağıtım maliyeti neredeyse her zaman değmez.&lt;/p&gt;
&lt;p&gt;Bu yazıda kullandığım örüntüyü adım adım göstereceğim.&lt;/p&gt;
&lt;h2 id=&quot;hedef&quot;&gt;Hedef&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;3–5 bağımsız Laravel uygulaması.&lt;/li&gt;
&lt;li&gt;Her uygulama kendi domain’i, kendi süreci, kendi kullanıcısı.&lt;/li&gt;
&lt;li&gt;PostgreSQL ve Redis paylaşılıyor: PostgreSQL’de role bazında izole, Redis’te prefix’le ayrılmış.&lt;/li&gt;
&lt;li&gt;Deploy bir komutla, zero-downtime.&lt;/li&gt;
&lt;li&gt;Backup ve monitoring tek yerden.&lt;/li&gt;
&lt;li&gt;Tüm bunlar &amp;lt; $40/aylık bir VPS’te.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kullanıcı-ve-dosya-yerleşimi&quot;&gt;Kullanıcı ve dosya yerleşimi&lt;/h2&gt;
&lt;p&gt;Her uygulama kendi unix kullanıcısını alıyor:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;billing:billing  /var/www/billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;notify:notify    /var/www/notify&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;blog:blog        /var/www/blog&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;www-data&lt;/code&gt; (Nginx) gruba dahil ediliyor, kod dizinine okuma erişimi için. Yazma sadece kendi uygulamasının kullanıcısında. Bu izolasyon &lt;strong&gt;hatalı davranan bir uygulama diğerlerine zarar veremesin&lt;/strong&gt; diye değil; &lt;strong&gt;bir geliştirici hatasının diğer projeyi etkilemesini önlemek için&lt;/strong&gt; — ki bu çok daha sık olan senaryo.&lt;/p&gt;
&lt;h2 id=&quot;php-fpm-poolları&quot;&gt;PHP-FPM pool’ları&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/tr/notes/nginx-php-fpm-pool-ayrimi/&quot;&gt;Nginx + PHP-FPM Pool Ayrımı&lt;/a&gt; yazısında detaylı. Burada özet: her uygulama kendi &lt;code&gt;pool.d/*.conf&lt;/code&gt; dosyasında &lt;code&gt;pm.max_children&lt;/code&gt;, &lt;code&gt;memory_limit&lt;/code&gt; ve unix kullanıcısıyla tanımlanıyor.&lt;/p&gt;
&lt;h2 id=&quot;veritabanı-paylaşılan-postgresql&quot;&gt;Veritabanı: paylaşılan PostgreSQL&lt;/h2&gt;
&lt;p&gt;Tek bir PostgreSQL cluster, her uygulama için ayrı bir &lt;strong&gt;database&lt;/strong&gt; ve &lt;strong&gt;role&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; ROLE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; billing_app &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;LOGIN&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; DATABASE&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; billing&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; OWNER&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; billing_app;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;REVOKE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; ALL &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; DATABASE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; billing &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; PUBLIC;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pg_hba.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;host  billing  billing_app  127.0.0.1/32  scram-sha-256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;host  notify   notify_app   127.0.0.1/32  scram-sha-256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;host  blog     blog_app     127.0.0.1/32  scram-sha-256&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hiçbir uygulama diğerinin DB’sini göremez bile.&lt;/p&gt;
&lt;h3 id=&quot;pgbouncerla-bağlantı-havuzu&quot;&gt;pgBouncer’la bağlantı havuzu&lt;/h3&gt;
&lt;p&gt;PHP-FPM bağlantıyı varsayılan olarak per-request açıp kapatıyor — kalıcı bağlantıyla (&lt;code&gt;PDO::ATTR_PERSISTENT&lt;/code&gt;) bile bağlantı tek bir worker’a bağlı kaldığı için paylaşılan bir havuz yok. PostgreSQL bağlantı kurulumu (~10ms) kümülatifte ciddi. pgBouncer önünde &lt;code&gt;pool_mode = transaction&lt;/code&gt; ile bunu çözüyoruz. &lt;a href=&quot;/tr/notes/pgbouncer-auth-query/&quot;&gt;auth_query yazısı&lt;/a&gt; detayı veriyor.&lt;/p&gt;
&lt;h2 id=&quot;redis-paylaşılan-namespacele-izole&quot;&gt;Redis: paylaşılan, namespace’le izole&lt;/h2&gt;
&lt;p&gt;Tek bir Redis instance. Her Laravel uygulaması farklı bir &lt;code&gt;prefix&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;redis&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;127.0.0.1&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;database&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prefix&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prod:billing:cache:&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;127.0.0.1&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;database&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prefix&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;prod:billing:queue:&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prefix bir adlandırma sözleşmesi, bir sınır değil: üç uygulama da aynı instance’a, aynı &lt;code&gt;database 0&lt;/code&gt; üzerine, &lt;code&gt;requirepass&lt;/code&gt; ve ACL olmadan bağlanıyor — yani biri diğerinin anahtarlarını okuyabilir. Bunun zorlanması gerekiyorsa her uygulamaya kendi Redis ACL kullanıcısını verin.&lt;/p&gt;
&lt;p&gt;Detay &lt;a href=&quot;/tr/notes/shared-redis-namespace-izolasyonu/&quot;&gt;namespace izolasyonu yazısında&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;queue-workerları&quot;&gt;Queue worker’ları&lt;/h2&gt;
&lt;p&gt;Supervisor her uygulamanın queue worker’ını ayrı bir process grubu olarak yönetiyor:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/etc/supervisor/conf.d/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── billing-worker.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── notify-worker.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── blog-worker.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Her dosyada uygulama kendi kullanıcısı ile çalışıyor:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;program:billing-worker&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;process_name&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;%(program_name)s_%(process_num)02d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;php /var/www/billing/current/artisan queue:work rabbitmq --&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;default --&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;1 --&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;max-time&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;3600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;numprocs&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;autostart&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;autorestart&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;stopwaitsecs&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;3600&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;deploy-symlink-swap&quot;&gt;Deploy: symlink swap&lt;/h2&gt;
&lt;p&gt;Her uygulama dizininde:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/var/www/billing/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── releases/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── 2026-05-12-1430/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── 2026-05-13-0900/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── 2026-05-13-1100/    ← yeni release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── shared/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── .env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── storage/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── uploads/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── current → releases/2026-05-13-1100&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deploy adımları:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Yeni release klasörünü oluştur, git checkout, composer install, build.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shared/&lt;/code&gt; içeriklerini sembolik linkle.&lt;/li&gt;
&lt;li&gt;Migrate (forward-compatible olmalı).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;current&lt;/code&gt; symlink’i atomik olarak yeni klasöre çevir (&lt;code&gt;ln -sfn&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;PHP-FPM’e &lt;code&gt;kill -USR2&lt;/code&gt; ile graceful reload gönder (opcache invalidation).&lt;/li&gt;
&lt;li&gt;Supervisor’ı sadece queue worker’ları için yeniden başlat.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Beşinci adımda bir uyarı var: &lt;code&gt;USR2&lt;/code&gt; FPM master’ına gidiyor ve &lt;em&gt;bütün&lt;/em&gt; pool’ları graceful biçimde yeniden yüklüyor — tek bir pool’u yeniden başlatan bir sinyal yok. Yani bir projenin deploy’u bütün projelerin worker’larını geri döndürüyor ve ortak opcache’i boşaltıyor; yukarıdaki izolasyon çalışma zamanında geçerli, deploy yolunda değil.&lt;/p&gt;
&lt;p&gt;Eski release’leri saklamak (örn. son 5) rollback’i bir komuta indirgiyor: symlink’i eski klasöre çevir, PHP-FPM reload.&lt;/p&gt;
&lt;h2 id=&quot;backup&quot;&gt;Backup&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;: pgBackRest, repository S3-uyumlu kovaya yazıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt;: AOF kapalı, RDB her saat. Cache-only kullanım için yeterli.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uploads&lt;/strong&gt; (&lt;code&gt;shared/uploads&lt;/code&gt;): rclone ile saatlik S3 sync.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/etc&lt;/code&gt;&lt;/strong&gt;: günlük tar + S3 (config’in versiyon kontrolü).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;monitoring&quot;&gt;Monitoring&lt;/h2&gt;
&lt;p&gt;Self-hosted bir Prometheus + Grafana abartı olur bu ölçek için. Tercih ettiğim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;uptime-kuma&lt;/strong&gt; — her domain’i HTTP check’le izliyor, Slack/Telegram alarmı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;node_exporter + Cloudflare metrics&lt;/strong&gt; — sunucu sağlığı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP-FPM &lt;code&gt;/status&lt;/code&gt; endpoint&lt;/strong&gt; — slow request sayısını izle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;logrotate&lt;/strong&gt; — &lt;code&gt;/var/log/{nginx,php-fpm,laravel}/*.log&lt;/code&gt; günlük.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;limitler-ve-büyüme-sinyalleri&quot;&gt;Limitler ve büyüme sinyalleri&lt;/h2&gt;
&lt;p&gt;Bu mimari aşağıdaki sinyallerden biri görülünce kırılır:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU sürekli &amp;gt; %70&lt;/strong&gt;. Vertical scale (daha büyük VPS) bir kez yapılır, sonra uygulama ayrıştırması.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek bir uygulama DB’de baskın hale gelirse&lt;/strong&gt;. O uygulama kendi PostgreSQL’ine taşınır — replica veya managed servis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coğrafi gecikme şikayetleri&lt;/strong&gt;. Bu durumda CDN’e statik asset taşıma yeterli; app server’ı taşımak ekstrem.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;niye-kubernetes-değil&quot;&gt;Niye Kubernetes değil?&lt;/h2&gt;
&lt;p&gt;Üç sebep:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Operasyon yükü.&lt;/strong&gt; Tek başınıza bir k8s cluster’ı çalıştırmak, üzerinde uygulama yazmaktan daha fazla zaman alır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maliyet.&lt;/strong&gt; Control plane’i ayrıca ücretlendiren sağlayıcılarda — EKS cluster başına saatlik $0.10, yani tek node eklenmeden ~$73/ay — 3 küçük node’lu managed k8s, eşdeğer VPS’in 3–5 katı tutuyor. Ücretsiz verenlerde fark kapanıyor: DigitalOcean’da 3 × $12/ay node ücreti VPS parasına yakın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Karmaşıklık ödülü yok.&lt;/strong&gt; Bu uygulamalar bağımsız ölçeklenmek zorunda değil.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bu mimari “lazy-eval k8s” değil — başka bir paradigma. Sade, anlaşılır, operasyonel olarak küçük bir mimari. Lazım olduğunda parçalanır, ondan önce değil.&lt;/p&gt;</content:encoded><category>vps</category><category>architecture</category><category>nginx</category><category>postgresql</category><category>redis</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Nginx + PHP-FPM Pool Ayrımı</title><link>https://sade.dev/tr/notes/nginx-php-fpm-pool-ayrimi/</link><guid isPermaLink="true">https://sade.dev/tr/notes/nginx-php-fpm-pool-ayrimi/</guid><description>Aynı VPS üzerinde birden fazla PHP uygulaması çalıştırırken, tek bir PHP-FPM pool yerine uygulama başına ayrı pool tanımlamanın somut faydaları.</description><pubDate>Sun, 26 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;VPS üzerinde iki, üç, beş PHP uygulaması çalıştırmak — küçük SaaS dünyasında çok normal. Default kurulumda hepsi tek bir &lt;code&gt;www.conf&lt;/code&gt; pool’unu paylaşıyor. Bu çoğu zaman çalışıyor — çalışmayana kadar.&lt;/p&gt;
&lt;h2 id=&quot;tek-poolun-sorunu&quot;&gt;Tek pool’un sorunu&lt;/h2&gt;
&lt;p&gt;Tek pool şu anlama geliyor:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bir uygulama &lt;code&gt;pm.max_children = 50&lt;/code&gt;’yi tüketirse diğerleri response üretemez.&lt;/li&gt;
&lt;li&gt;Bir uygulamadaki memory leak diğerlerinin worker’larını da OOM’a sürükler.&lt;/li&gt;
&lt;li&gt;Bir uygulamayı yeniden başlatmak (örn. opcache reset) hepsinin worker’larını yeniden başlatır.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pool ayrımı ilk iki sorunu çözüyor. Üçüncüsünü çözmüyor: PHP-FPM’de pool başına reload yok — tek yeniden yükleme sinyali &lt;code&gt;SIGUSR2&lt;/code&gt;, master altındaki tüm worker’ları yeniden yüklüyor.&lt;/p&gt;
&lt;h2 id=&quot;pool-başına-config&quot;&gt;Pool başına config&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/etc/php/8.4/fpm/pool.d/billing.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;billing&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; /run/php/billing.sock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;listen.owner&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; www-data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;listen.group&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; www-data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;listen.mode&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 0660&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; dynamic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm.max_children&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm.start_servers&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm.min_spare_servers&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm.max_spare_servers&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm.max_requests&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 500&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;request_terminate_timeout&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 30s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;catch_workers_output&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;decorate_workers_output&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;clear_env&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;php_admin_value[memory_limit] = 256M&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;php_admin_value[error_log] = /var/log/php-fpm/billing-error.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/etc/php/8.4/fpm/pool.d/notify.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;notify&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; notify&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;group&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; notify&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; /run/php/notify.sock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm.max_children&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 8       &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;; daha hafif uygulama&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;php_admin_value[memory_limit] = 128M&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İki şey kazandık:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Filesystem izolasyonu&lt;/strong&gt; — &lt;code&gt;user = billing&lt;/code&gt; ile billing uygulaması notify’ın dosyalarına yazamaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource izolasyonu&lt;/strong&gt; — billing’in &lt;code&gt;pm.max_children&lt;/code&gt;’i tükenirse notify hâlâ çalışır.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;nginx-tarafında&quot;&gt;Nginx tarafında&lt;/h2&gt;
&lt;p&gt;Her vhost kendi socket’ine bağlanıyor:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    server_name &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;billing.example.com&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    root &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/www/billing/public&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    location&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ~&lt;/span&gt;&lt;span style=&quot;color:#AB5E3F;--shiki-dark:#C4704F&quot;&gt; \.php$ &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        fastcgi_pass &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;unix:/run/php/billing.sock&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;        # standart fastcgi params&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    server_name &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;notify.example.com&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    root &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/www/notify/public&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    location&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ~&lt;/span&gt;&lt;span style=&quot;color:#AB5E3F;--shiki-dark:#C4704F&quot;&gt; \.php$ &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        fastcgi_pass &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;unix:/run/php/notify.sock&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;opcache-shared-mı-ayrı-mı&quot;&gt;opcache shared mı, ayrı mı?&lt;/h2&gt;
&lt;p&gt;PHP-FPM master process’i opcache’i paylaşıyor — bu yüzden pool’lar arasında opcache otomatik izole değildir. &lt;code&gt;opcache.validate_root = 1&lt;/code&gt; bunu değiştirmiyor: bu direktif chroot’lu ortamlardaki ad çakışmalarını önlemek için &lt;code&gt;/&lt;/code&gt; dizininin inode’unu önbellek anahtarına katıyor, yani ayırt ettiği şey chroot kökleri, metindeki Nginx &lt;code&gt;root&lt;/code&gt; path’leri değil — ve buradaki pool’larda &lt;code&gt;chroot&lt;/code&gt; yok. Pratikte: aynı master, ayrı pool’lar yeterli.&lt;/p&gt;
&lt;h2 id=&quot;i̇zleme&quot;&gt;İzleme&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/status&lt;/code&gt; endpoint’ini pool’a açın:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pm.status_path&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; /status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra Nginx tarafında bunu sadece localhost’a açın:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ~&lt;/span&gt;&lt;span style=&quot;color:#AB5E3F;--shiki-dark:#C4704F&quot;&gt; ^/status$ &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    access_log off&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    allow &lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;127.0.0.1&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    deny all&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    fastcgi_pass &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;unix:/run/php/billing.sock&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;curl localhost/status&lt;/code&gt; ile aktif worker, kuyruk uzunluğu, slow request sayısı gibi metrikleri alıp Prometheus exporter veya basit bir cron’la izleyebilirsiniz.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-tek-pool-yeterli&quot;&gt;Ne zaman tek pool yeterli?&lt;/h2&gt;
&lt;p&gt;Aynı kod tabanının iki kopyasını çalıştırıyorsanız (örn. &lt;code&gt;app.example.com&lt;/code&gt; ve &lt;code&gt;staging.example.com&lt;/code&gt;) ayrı pool gereksiz. Farklı domain’ler ama aynı süreç sınıfı. Ayrım, uygulamaları &lt;strong&gt;operasyonel olarak bağımsız&lt;/strong&gt; kılmak istediğinizde değerli.&lt;/p&gt;</content:encoded><category>nginx</category><category>php-fpm</category><category>production</category><category>vps</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>Laravel Production Stack</title><link>https://sade.dev/tr/systems/laravel-production-stack/</link><guid isPermaLink="true">https://sade.dev/tr/systems/laravel-production-stack/</guid><description>Nginx + PHP-FPM + Redis + PostgreSQL + Supervisor + Horizon ile production-grade bir Laravel kurulumu. Her bileşenin işi ve değiştirildiğinde ne kaybedileceği.</description><pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Laravel’i geliştirmek bir saat, production’a ciddi şekilde koymak bir hafta sürer. Bu yazıda kullandığım stack’i ve her bileşenin niye orada olduğunu anlatıyorum. “Her şey gerekli” demiyorum — gerekli olduğunu anlayana kadar minimum tutmayı savunan biriyim — ama her bileşenin kazandırdığı somut.&lt;/p&gt;
&lt;h2 id=&quot;neden-sunucuları-cloudflare-arkasına-koyuyorum&quot;&gt;Neden sunucuları Cloudflare arkasına koyuyorum?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DDoS koruması, WAF, rate limiting gibi özellikler out-of-the-box.&lt;/li&gt;
&lt;li&gt;Gerçek client IP’sini &lt;code&gt;X-Forwarded-For&lt;/code&gt; ve &lt;code&gt;CF-Connecting-IP&lt;/code&gt; header’larıyla geçiriyor; aşağıdaki &lt;code&gt;real_ip&lt;/code&gt; yönergeleriyle uygulamanın gördüğü IP bu oluyor.&lt;/li&gt;
&lt;li&gt;SSL termination’ı Cloudflare’da yapıp backend’e &lt;code&gt;http&lt;/code&gt; ile konuşmak mümkün.&lt;/li&gt;
&lt;li&gt;CDN özellikleriyle static asset’leri Cloudflare cache’inden servis etmek kolay.&lt;/li&gt;
&lt;li&gt;Ücretsiz 15 yıllık SSL sertifikası sağlıyor, yönetimi basitleştiriyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;nginx--neden-apache-değil&quot;&gt;Nginx — neden Apache değil?&lt;/h2&gt;
&lt;p&gt;İkisi de çalışıyor. Nginx tercih ediyorum çünkü:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Asenkron event loop modeli düşük gecikmede daha tutarlı.&lt;/li&gt;
&lt;li&gt;TLS termination’ı, static asset serving’i, fastcgi’yi tek configde tertemiz hallediyor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;try_files $uri $uri/ /index.php?$query_string&lt;/code&gt; deyimi Laravel için kanonik.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tipik server block:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# -----------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# HTTP → HTTPS redirect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# -----------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    listen &lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    server_name &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;app.example.com&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 301&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; https://&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;request_uri&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# -----------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# HTTPS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;# -----------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    listen &lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;443&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; ssl http2&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    server_name &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;app.example.com&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    root &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/www/app/current/public&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    index &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;index.php&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    add_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;X-Frame-Options &lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;&amp;quot;SAMEORIGIN&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    add_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;X-Content-Type-Options &lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;&amp;quot;nosniff&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    add_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;Strict-Transport-Security &lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;&amp;quot;max-age=31536000; includeSubDomains&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; always&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    client_max_body_size &lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;25M&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    access_log &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/log/nginx/app.example.com.access.log&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    error_log &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; /var/log/nginx/app.example.com.error.log &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;warn&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;    # Cloudflare arkasında gerçek client IP, her aralık için bir satır: https://www.cloudflare.com/ips/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    set_real_ip_from &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;173.245.48.0/20&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    set_real_ip_from &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;103.21.244.0/22&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;    # ... kalan Cloudflare aralıkları&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    real_ip_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;CF-Connecting-IP&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    location&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        try_files &lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/ /index.php?&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;query_string&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    location&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ~&lt;/span&gt;&lt;span style=&quot;color:#AB5E3F;--shiki-dark:#C4704F&quot;&gt; \.php$ &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        fastcgi_pass &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;unix:/run/php/php8.4-fpm-app-example.sock&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        fastcgi_index &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;index.php&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        fastcgi_param &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;SCRIPT_FILENAME &lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;realpath_root&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#B07D48;--shiki-dark:#BD976A&quot;&gt;fastcgi_script_name&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        include &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;fastcgi_params&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        fastcgi_buffering on&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        fastcgi_read_timeout &lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt;60s&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    location&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ~*&lt;/span&gt;&lt;span style=&quot;color:#AB5E3F;--shiki-dark:#C4704F&quot;&gt; \.(css|js|png|jpg|jpeg|gif|svg|woff|woff2)$ &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        expires &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;1y&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        add_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;Cache-Control &lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;&amp;quot;public, max-age=31536000, immutable&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;        # bu seviye kendi add_header&amp;#39;ını tanımlayınca üsttekiler miras alınmıyor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        add_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;X-Frame-Options &lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;&amp;quot;SAMEORIGIN&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        add_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;X-Content-Type-Options &lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;&amp;quot;nosniff&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        add_header &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;Strict-Transport-Security &lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;&amp;quot;max-age=31536000; includeSubDomains&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; always&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        access_log off&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;    location&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; ~&lt;/span&gt;&lt;span style=&quot;color:#AB5E3F;--shiki-dark:#C4704F&quot;&gt; /\.(?!well-known).* &lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;        deny all&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;set_real_ip_from&lt;/code&gt; listesi bütün Cloudflare aralıklarını kapsamalı; kapsamazsa Nginx client’ın değil Cloudflare’in IP’sini loglamayı sürdürür. Nokta dosyası bloğunun tek olması da bilinçli: Nginx eşleşen ilk regex location’da durur, dolayısıyla bunun ardına konan istisnasız bir &lt;code&gt;location ~ /\.&lt;/code&gt; bloğu &lt;code&gt;/.well-known/&lt;/code&gt; isteklerini de yutar ve ACME doğrulamasını kırar.&lt;/p&gt;
&lt;h2 id=&quot;php-fpm--pool-yapılandırması&quot;&gt;PHP-FPM — pool yapılandırması&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/tr/notes/nginx-php-fpm-pool-ayrimi/&quot;&gt;Detayını ayrı yazıda&lt;/a&gt; ele aldım. Production’a özel opcache ayarları:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;opcache.enable&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;opcache.memory_consumption&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;192&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;opcache.interned_strings_buffer&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;opcache.max_accelerated_files&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;20000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;opcache.validate_timestamps&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;0   &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;; deploy&amp;#39;da reset gerek&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;opcache.preload&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/www/app/current/preload.php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;opcache.preload_user&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;app        &lt;/span&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;; php-fpm master root başlar, bu olmadan preload reddedilir&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;validate_timestamps=0&lt;/code&gt; ile dosya değişikliklerini PHP otomatik algılamıyor — deploy sonrası &lt;code&gt;kill -USR2&lt;/code&gt; ile FPM reload zorunlu. Karşılığında saniyede yüzlerce request farkı.&lt;/p&gt;
&lt;h2 id=&quot;postgresql--neden-mysql-değil&quot;&gt;PostgreSQL — neden MySQL değil?&lt;/h2&gt;
&lt;p&gt;İkisi de çalışıyor. PostgreSQL’i tercih etme sebebim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JSON/JSONB&lt;/strong&gt; desteği MySQL’inkinden olgun (GIN index, query operators).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CTE’ler, window function’lar, materialized view’lar&lt;/strong&gt; native.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logical replication&lt;/strong&gt; ve &lt;strong&gt;PITR&lt;/strong&gt; tooling’i daha temiz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transactional DDL&lt;/strong&gt; — migration ortasında crash olsa veritabanı temiz kalır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Karşılığında — kurulum biraz daha disiplin ister. Tipik &lt;code&gt;postgresql.conf&lt;/code&gt; hassasiyetleri:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;shared_buffers = 4GB                # toplam RAM&amp;#39;in ~%25&amp;#39;i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;effective_cache_size = 12GB          # RAM&amp;#39;in ~%75&amp;#39;i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work_mem = 16MB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;maintenance_work_mem = 256MB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;wal_buffers = 16MB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;max_connections = 500&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;checkpoint_timeout = 15min&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;checkpoint_completion_target = 0.9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;random_page_cost = 1.1              # SSD için&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;effective_io_concurrency = 200&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yedekler &lt;code&gt;pg_dump&lt;/code&gt; ile değil pgBackRest ile alınıyor: full + incremental, üstüne WAL arşivinden PITR.&lt;/p&gt;
&lt;h2 id=&quot;pgbouncer--bağlantı-havuzu&quot;&gt;pgBouncer — bağlantı havuzu&lt;/h2&gt;
&lt;p&gt;PHP per-request bağlantı açar, kapatır. PostgreSQL’de bağlantı başlatmak maliyetli (fork edilen process). Önüne pgBouncer koyup &lt;code&gt;transaction&lt;/code&gt; mode ile bunu absorb ediyoruz. &lt;a href=&quot;/tr/notes/pgbouncer-auth-query/&quot;&gt;auth_query notu&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;redis--üç-şapka&quot;&gt;Redis — üç şapka&lt;/h2&gt;
&lt;p&gt;Aynı Redis instance üç rol oynuyor:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cache&lt;/strong&gt; — &lt;code&gt;Cache::remember(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session&lt;/strong&gt; — &lt;code&gt;SESSION_DRIVER=redis&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lock&lt;/strong&gt; — &lt;code&gt;Cache::lock(...)&lt;/code&gt; ile distributed mutex.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hepsi tek Redis’te güvenle çalışır — yeter ki:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maxmemory policy: &lt;code&gt;volatile-lru&lt;/code&gt; (yalnızca TTL’i olan key’leri LRU ile sil).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;appendonly no&lt;/code&gt; (cache, session ve lock için RDB yeterli).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;save 900 1 300 10 60 10000&lt;/code&gt; (RDB snapshot kuralları).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;rabbitmq-opsiyonel&quot;&gt;RabbitMQ (opsiyonel)&lt;/h2&gt;
&lt;p&gt;Laravel’in queue driver’ı olarak &lt;code&gt;rabbitmq&lt;/code&gt; kullanıyorum — Database ya da Redis yerine. Neden?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Queue&lt;/strong&gt; — &lt;code&gt;QUEUE_CONNECTION=rabbitmq&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Redis queue driver’ı basit ama RabbitMQ’nun özellikleriyle karşılaştırıldığında eksik kalıyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Persistence&lt;/strong&gt; — RabbitMQ kalıcı mesajları diske yazar; Redis kuyruğu bellekten servis eder, diske yalnızca RDB snapshot’ları ya da AOF ile iner.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acknowledgements&lt;/strong&gt; — RabbitMQ’da mesaj, consumer ack verdikten sonra siliniyor; Laravel’in Redis driver’ı da biten job’ı reserved kümesinden kendi siliyor. Fark temizlikte değil yeniden teslimde: Redis &lt;code&gt;retry_after&lt;/code&gt; zaman aşımına, RabbitMQ consumer’ın bağlantısına bakıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Routing&lt;/strong&gt; — RabbitMQ’da mesajın hangi queue’ya düşeceğine exchange’ler karar veriyor; Redis’te &lt;code&gt;onQueue()&lt;/code&gt; ile istediğiniz kadar adlandırılmış queue açabilirsiniz ama yönlendirme kararı uygulamada kalıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring&lt;/strong&gt; — RabbitMQ yönetim paneli queue’ları, mesajları, consumer’ları broker düzeyinde gösteriyor; Redis tarafında aynı görünürlüğü Horizon veriyor, o da uygulamanın dispatch ettiğiyle sınırlı.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;supervisor--horizon&quot;&gt;Supervisor + Horizon&lt;/h2&gt;
&lt;p&gt;Queue worker’ları Supervisor altında çalışıyor. Horizon Laravel’in queue dashboard’u — worker’ları ona orchestrate ettirebilmek için Supervisor &lt;code&gt;horizon&lt;/code&gt; process’ini çalıştırıyor:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;program:horizon&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;process_name&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;%(program_name)s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;php /var/www/app/current/artisan horizon&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;autostart&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;autorestart&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;redirect_stderr&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;stdout_logfile&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;/var/log/horizon.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;stopwaitsecs&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;3600&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Horizon kutudan çıktığı hâliyle yalnızca &lt;code&gt;redis&lt;/code&gt; queue bağlantısını sürüyor; yukarıdaki &lt;code&gt;rabbitmq&lt;/code&gt; bağlantısında &lt;code&gt;vladimir-yuldashev/laravel-queue-rabbitmq&lt;/code&gt; paketinin Horizon modu gerekiyor — &lt;code&gt;RABBITMQ_WORKER=horizon&lt;/code&gt;, bağlantı böylece Horizon’un okuduğu event’leri yayıyor. Horizon kendi içinde worker process’lerini fork ediyor — siz &lt;code&gt;config/horizon.php&lt;/code&gt; ile her queue’nun worker sayısını ve memory limit’ini ayarlıyorsunuz.&lt;/p&gt;
&lt;h2 id=&quot;scheduler&quot;&gt;Scheduler&lt;/h2&gt;
&lt;p&gt;Tek bir cron yetiyor:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;* * * * *  app  cd /var/www/app/current &amp;amp;&amp;amp; php artisan schedule:run &amp;gt;&amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Laravel kendi scheduler’ını içeride yönetiyor. &lt;code&gt;withoutOverlapping()&lt;/code&gt; ve &lt;code&gt;onOneServer()&lt;/code&gt; modifier’larına dikkat — çoklu sunucuda race condition önler.&lt;/p&gt;
&lt;h2 id=&quot;logging&quot;&gt;Logging&lt;/h2&gt;
&lt;p&gt;JSON formatında structured log:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#A0ADA0;--shiki-dark:#758575DD&quot;&gt;// config/logging.php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;channels&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;production&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;driver&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;stack&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;channels&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;daily&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;stderr&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;daily&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;driver&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;daily&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; storage_path&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;logs/laravel.log&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;level&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; env&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;LOG_LEVEL&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;days&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#2F798A;--shiki-dark:#4C9A91&quot;&gt; 14&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;formatter&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt; Monolog&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;Formatter&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color:#998418;--shiki-dark:#B8A965&quot;&gt;JsonFormatter&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/etc/logrotate.d/laravel-app&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/var/www/app/shared/storage/logs/*.log {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    daily&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    rotate 14&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    compress&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    delaycompress&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    missingok&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    notifempty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    create 0640 app app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sharedscripts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;env-management&quot;&gt;.env management&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; her zaman &lt;code&gt;shared/&lt;/code&gt; dizininde, deploy’lara symlink. &lt;strong&gt;Hiçbir zaman&lt;/strong&gt; repo’da değil. Secrets için bunu basitliğin gücüyle yapıyorum — vault gerekirse ekleriz, başta gerek yok.&lt;/p&gt;
&lt;h2 id=&quot;deploy&quot;&gt;Deploy&lt;/h2&gt;
&lt;p&gt;Symlink swap modeli. Detaylar &lt;a href=&quot;/tr/systems/tek-vps-coklu-proje-mimarisi/&quot;&gt;çoklu proje mimarisi&lt;/a&gt; yazısının “Deploy” bölümünde.&lt;/p&gt;
&lt;h2 id=&quot;hangi-parçası-kaldırılırsa-ne-kaybedersiniz&quot;&gt;Hangi parçası kaldırılırsa ne kaybedersiniz?&lt;/h2&gt;

































&lt;div class=&quot;table-wrap&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Bileşen&lt;/th&gt;&lt;th&gt;Çıkarınca olan&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;pgBouncer&lt;/td&gt;&lt;td&gt;Connection storm’larında PostgreSQL bağlantısı tükenir, request’ler 500 atar&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Supervisor&lt;/td&gt;&lt;td&gt;Worker’lar crash sonrası geri gelmez, alarm kurmak zorundasınız&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Horizon&lt;/td&gt;&lt;td&gt;Görünürlük çöker, “queue neden yavaş” sorusunu kör cevaplarsınız&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;opcache&lt;/td&gt;&lt;td&gt;Her request PHP dosyasını disk’ten okur ve parse eder, ~5x yavaşlama&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;pgBackRest&lt;/td&gt;&lt;td&gt;&lt;code&gt;pg_dump&lt;/code&gt; kalır — PITR kaybedersiniz, gerçek bir incident’ta hata payınız tek bir günlük yedek&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Redis lock&lt;/td&gt;&lt;td&gt;Race condition’lara açıksınız, distributed-safe mutex’iniz kalmaz&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Boring stack’in gücü tam burada: her parça nicel olarak değerli ve değişimi kolay. “Bütün cluster çöktü” senaryosu yerine “Redis maxmemory ayarını kaçırmışım” senaryosu yaşarsınız — ki bunu da düzeltmek beş dakika.&lt;/p&gt;</content:encoded><category>laravel</category><category>php</category><category>production</category><category>architecture</category><category>System</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item><item><title>pgBouncer auth_query ile Çoklu DB Kullanıcısı</title><link>https://sade.dev/tr/notes/pgbouncer-auth-query/</link><guid isPermaLink="true">https://sade.dev/tr/notes/pgbouncer-auth-query/</guid><description>pgBouncer kullanırken `userlist.txt` yerine `auth_query` ile dinamik kullanıcı doğrulamasının pratik yapılandırması.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;pgBouncer&lt;/code&gt; connection pooler olarak harika; ancak default yapılandırması bir düşmanlığı kabul ediyor: kullanıcı listenizi manuel olarak &lt;code&gt;userlist.txt&lt;/code&gt;’ye yazmak zorundasınız. Her yeni DB kullanıcısı eklediğinizde dosyayı güncellemek, hash’i hesaplamak, pgBouncer’ı reload etmek. Operasyonel bir baş ağrısı.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;auth_query&lt;/code&gt; bunu çözüyor: pgBouncer doğrulama için &lt;strong&gt;PostgreSQL’in kendisine&lt;/strong&gt; sorgu atıyor.&lt;/p&gt;
&lt;h2 id=&quot;auth_user-kurulumu&quot;&gt;auth_user kurulumu&lt;/h2&gt;
&lt;p&gt;PostgreSQL tarafında düşük yetkili bir kullanıcı ve &lt;code&gt;pg_shadow&lt;/code&gt;’u okuyan bir fonksiyon oluşturuyoruz:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; ROLE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pgbouncer &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;LOGIN&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#B56959;--shiki-dark:#C98A7D&quot;&gt;s3cr€t_p@ssw0rd&lt;/span&gt;&lt;span style=&quot;color:#B5695977;--shiki-dark:#C98A7D77&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; SCHEMA&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; pgbouncer&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;GRANT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; USAGE &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; SCHEMA&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pgbouncer &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pgbouncer;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;CREATE OR REPLACE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt; pgbouncer&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;.user_lookup(&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; i_username &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;                                                 out&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; uname &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;                                                 out&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; phash &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;RETURNS&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; record &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;BEGIN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    SELECT&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; usename, passwd &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; pg_catalog&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;pg_shadow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    WHERE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; usename &lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; i_username &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;INTO&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; uname, phash;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;    RETURN&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;END&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;$$ &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;LANGUAGE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; plpgsql &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;SECURITY&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; DEFINER;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;REVOKE&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; ALL &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; pgbouncer&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;user_lookup&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; PUBLIC;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;GRANT&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; EXECUTE&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt; pgbouncer&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#A65E2B;--shiki-dark:#C99076&quot;&gt;user_lookup&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#AB5959;--shiki-dark:#CB7676&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pgbouncer;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SECURITY DEFINER&lt;/code&gt; önemli — fonksiyon &lt;code&gt;pg_shadow&lt;/code&gt;’a erişim için tanımlayan kullanıcının yetkilerini kullanıyor. Aksi takdirde &lt;code&gt;pgbouncer&lt;/code&gt; rolü &lt;code&gt;pg_shadow&lt;/code&gt;’u okuyamaz.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;auth_query&lt;/code&gt; hedef veritabanının içinde çalışır; bu yüzden fonksiyonu pgBouncer’ın hizmet vereceği her veritabanına kurmanız gerekir.&lt;/p&gt;
&lt;h2 id=&quot;pgbouncer-config&quot;&gt;pgBouncer config&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pgbouncer.ini&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;databases&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;* = &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;127.0.0.1 &lt;/span&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt;5432&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#59873A;--shiki-dark:#80A665&quot;&gt;pgbouncer&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;listen_addr&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 127.0.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;listen_port&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 6432&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;auth_type&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; scram-sha-256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;auth_user&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; pgbouncer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;auth_query&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; SELECT uname, phash FROM pgbouncer.user_lookup($1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;pool_mode&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; transaction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;max_client_conn&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;default_pool_size&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; 25&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Artık &lt;code&gt;userlist.txt&lt;/code&gt; yok. PostgreSQL’de yeni bir rol oluştursanız, pgBouncer restart’ı gerektirmeden doğrulama çalışıyor.&lt;/p&gt;
&lt;h2 id=&quot;md5-vs-scram-sha-256&quot;&gt;md5 vs scram-sha-256&lt;/h2&gt;
&lt;p&gt;PostgreSQL 14+ default olarak &lt;code&gt;scram-sha-256&lt;/code&gt; kullanıyor. pgBouncer bunu 1.11’den beri destekliyor; &lt;code&gt;auth_query&lt;/code&gt; ile SCRAM pass-through ise 1.14 ile geldi. Eski cluster’larda md5’e düşmek zorunda kalıyorsanız:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#1E754F;--shiki-dark:#4D9375&quot;&gt;auth_type&lt;/span&gt;&lt;span style=&quot;color:#999999;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#393A34;--shiki-dark:#DBD7CAEE&quot;&gt; md5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ama yeni kurulum yapıyorsanız &lt;code&gt;scram-sha-256&lt;/code&gt; kullanın — md5 artık zayıf.&lt;/p&gt;
&lt;h2 id=&quot;pool-mode-tercihi&quot;&gt;Pool mode tercihi&lt;/h2&gt;
&lt;p&gt;Üç mod var, çoğu Laravel/Django uygulaması için tercih:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;session&lt;/strong&gt; — bağlantı baştan sona aynı. Prepared statement’lar çalışır, ama pool faydası zayıf.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;transaction&lt;/strong&gt; — her transaction sonunda bağlantı serbest. En yaygın tercih. Protokol düzeyi prepared statement’lar 1.21+ gerektiriyor; 1.24’ten beri varsayılan olarak açık (&lt;code&gt;max_prepared_statements = 200&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;statement&lt;/strong&gt; — her query sonunda. Çoğu ORM ile sorunlu.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pratik: &lt;strong&gt;transaction&lt;/strong&gt;’la başlayın, sorun yaşarsanız session’a geçin.&lt;/p&gt;
&lt;h2 id=&quot;yaygın-hata-read-only-roleün-passwordü-yok&quot;&gt;Yaygın hata: read-only role’ün password’ü yok&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;auth_query&lt;/code&gt; &lt;code&gt;pg_shadow.passwd&lt;/code&gt;’i okuyor. Eğer kullanıcı SSO ile geliyorsa veya peer authentication kullanıyorsa, passwd alanı NULL’dur — pgBouncer login’i reddeder. Bu durumda &lt;code&gt;pg_hba.conf&lt;/code&gt; üzerinden trust veya peer kurmak gerekir, ama production’da bunu istemezsiniz.&lt;/p&gt;
&lt;h2 id=&quot;i̇zleme&quot;&gt;İzleme&lt;/h2&gt;
&lt;p&gt;pgBouncer kendi admin DB’sini sağlıyor:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes vitesse-light vitesse-dark&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;psql -p 6432 pgbouncer -U pgbouncer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;gt; SHOW POOLS;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;gt; SHOW STATS;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;gt; SHOW CLIENTS;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cl_waiting &amp;gt; 0&lt;/code&gt; görüyorsanız &lt;code&gt;default_pool_size&lt;/code&gt;’ı arttırmak gerekiyor olabilir.
&lt;code&gt;sv_idle&lt;/code&gt; çok yüksekse tam tersi — pool overprovision’da.&lt;/p&gt;</content:encoded><category>postgresql</category><category>pgbouncer</category><category>production</category><category>Note</category><author>info@muhammetsafak.com.tr (Muhammet Şafak)</author></item></channel></rss>