<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://pythonvesql.com/</id>
  <title>Python ve SQL</title>
  <subtitle>Python ve SQL üzerine pratik Türkçe teknik yazılar: kod örnekleri, sorgu optimizasyonu, ORM, veritabanı yönetimi ve gerçek geliştirme notları.</subtitle>
  <link href="https://pythonvesql.com/" rel="alternate"/>
  <link href="https://pythonvesql.com/feed.xml" rel="self"/>
  <updated>2026-05-10T00:00:00Z</updated>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-10-azure-sql-database-serverless-ile-maliyet-azaltma/</id>
    <title>Azure SQL Database Serverless ile Maliyet Azaltma</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-10-azure-sql-database-serverless-ile-maliyet-azaltma/"/>
    <published>2026-05-10T00:00:00Z</published>
    <updated>2026-05-10T00:00:00Z</updated>
    <author><name>Mehmet Demir</name></author>
    <summary>Azure SQL Database serverless katmanının nasıl çalıştığını, hangi iş yüklerinde kazandırıp hangilerinde kaybettirdiğini ve kurulumu anlatır.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazıda Azure SQL Database&apos;in serverless katmanına (compute tier) bir göz atalım. Konu basit gibi duruyor ama uygulamada bir sürü ince ayar var; özellikle &apos;hangi DB serverless&apos;a taşınmalı, hangisi taşınmamalı&apos; sorusu çoğu ekibin gözünden kaçıyor. Lafı çok uzatmadan başlayayım.&lt;/p&gt;
&lt;p&gt;Bir gece nöbetinde dashboard&apos;a baktığımda dev ve staging veritabanlarının neredeyse hiçbir iş yapmadığını ama provisioned olarak full kapasite faturalandığını görünce, &apos;burada ciddi bir israf var&apos; demiştim. İşte tam bu nokta, serverless&apos;in geldiği yer.&lt;/p&gt;
&lt;h2 id=&quot;serverless-tier-nedir&quot;&gt;Serverless Tier Nedir?&lt;a href=&quot;#serverless-tier-nedir&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Azure SQL Database&apos;in vCore satın alma modelindeki General Purpose service tier&apos;i altında iki compute seçeneği var: provisioned ve serverless. Provisioned&apos;da sabit bir vCore sayısı belirliyorsunuz ve database 7/24 açık olsa da olmasa da o kapasite için para ödüyorsunuz. Serverless ise iki şey yapıyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Otomatik ölçekleme&lt;/strong&gt;: Min ve max vCore aralığı tanımlıyorsunuz, gerçek CPU talebine göre database bu aralıkta aşağı yukarı gidiyor. Faturalandırma vCore-saniye üzerinden.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Otomatik durdurma (auto-pause)&lt;/strong&gt;: Database belirli bir süre (en az 60 dakika) boş kalırsa Azure DB&apos;yi &apos;pause&apos; ediyor. Pause halindeyken sadece storage için para ödüyorsunuz, compute sıfır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yeni bir bağlantı gelir gelmez database otomatik olarak ayağa kalkıyor. Ama uyanma süresi var; buna birazdan döneceğim.&lt;/p&gt;
&lt;h2 id=&quot;ne-zaman-kazandırır-ne-zaman-kaybettirir&quot;&gt;Ne Zaman Kazandırır, Ne Zaman Kaybettirir?&lt;a href=&quot;#ne-zaman-kazandırır-ne-zaman-kaybettirir&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bence bu en kritik kısım. Çünkü herkes &apos;serverless = ucuz&apos; diye düşünüp taşıyor, sonra production OLTP database&apos;inde ay sonu faturası iki katına çıkıyor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Serverless&apos;in parladığı durumlar&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dev ve test veritabanları (mesai dışı tamamen ölü)&lt;/li&gt;
&lt;li&gt;Sadece iş saatlerinde çalışan iç araçlar&lt;/li&gt;
&lt;li&gt;POC&apos;lar ve demo ortamları&lt;/li&gt;
&lt;li&gt;Günde 1-2 saat aktif olan batch işleyen DB&apos;ler&lt;/li&gt;
&lt;li&gt;Trafiği çok düşük, gecelik uzun boş dönemleri olan küçük production DB&apos;leri&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Serverless&apos;in kaybettirdiği durumlar&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Steady-state OLTP yükleri (CPU sürekli yüzde 30-40 bandında gezen sistemler)&lt;/li&gt;
&lt;li&gt;Gerçek zamanlı, düşük gecikme bekleyen API arka uçları&lt;/li&gt;
&lt;li&gt;7/24 sürekli yazılan loglama veya telemetri DB&apos;leri&lt;/li&gt;
&lt;li&gt;Çok sık küçük sorgu alan ama hiçbir zaman tam olarak boş kalmayan sistemler&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pratik bir eşik vereyim: database&apos;iniz günün yüzde 60-70&apos;inden fazla aktifse, provisioned neredeyse her zaman daha ucuz. Aktiflik bunun altındaysa serverless&apos;a geçmek mantıklı.&lt;/p&gt;
&lt;h2 id=&quot;cli-ile-kurulum&quot;&gt;CLI ile Kurulum&lt;a href=&quot;#cli-ile-kurulum&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Portal&apos;dan da yapabilirsiniz ama benim tercihim CLI; hem tekrar edilebilir hem de Terraform/Bicep geçişi için temel oluşturuyor. Yeni bir serverless DB açmak için:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;az sql db create \
    --resource-group rg-staging \
    --server sql-staging-01 \
    --name app-db \
    --edition GeneralPurpose \
    --compute-model Serverless \
    --family Gen5 \
    --min-capacity 0.5 \
    --capacity 2 \
    --auto-pause-delay 60 \
    --max-size 32GB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki &lt;code&gt;--min-capacity 0.5&lt;/code&gt; aktif haldeki taban maliyeti minimuma indiriyor. &lt;code&gt;--capacity 2&lt;/code&gt; ise tavan, yani yük patlasa bile 2 vCore&apos;u geçmez. &lt;code&gt;--auto-pause-delay 60&lt;/code&gt; 60 dakika hareketsizlikten sonra DB&apos;yi uyutuyor.&lt;/p&gt;
&lt;p&gt;Mevcut bir DB&apos;yi serverless&apos;a çevirmek için de update komutu var:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;az sql db update \
    --resource-group rg-staging \
    --server sql-staging-01 \
    --name app-db \
    --edition GeneralPurpose \
    --compute-model Serverless \
    --min-capacity 0.5 \
    --capacity 4 \
    --auto-pause-delay 120
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Auto-pause istemiyorsanız &lt;code&gt;--auto-pause-delay -1&lt;/code&gt; verin; sadece otomatik ölçekleme avantajını kullanırsınız.&lt;/p&gt;
&lt;h2 id=&quot;uyanma-gecikmesi---en-sık-karşılaşılan-tuzak&quot;&gt;Uyanma Gecikmesi - En Sık Karşılaşılan Tuzak&lt;a href=&quot;#uyanma-gecikmesi---en-sık-karşılaşılan-tuzak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Burada çoğu ekip yanılıyor. Pause halindeki bir DB&apos;ye bağlantı geldiğinde toparlanması 1-2 dakika sürüyor. Eğer uygulamanızın connection timeout değeri 15 saniyeyse, sabah ilk istek boş yere &lt;code&gt;&amp;lt;TimeoutError&amp;gt;&lt;/code&gt; alıyor.&lt;/p&gt;
&lt;p&gt;Connection string&apos;inize en az 60 saniye timeout verin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Server=sql-staging-01.database.windows.net;Database=app-db;Connection Timeout=60;Encrypt=true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bir de monitoring sistemleri konusu var. Eğer DB&apos;ye 5 dakikada bir health check ping atan bir sisteminiz varsa, auto-pause hiçbir zaman tetiklenmez ve serverless&apos;in tüm mantığı çöpe gider. Bunu fark etmesi bile bir hafta sürebiliyor; faturayı görünce anlıyorsunuz.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-hatalar&quot;&gt;Sık Karşılaşılan Hatalar&lt;a href=&quot;#sık-karşılaşılan-hatalar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tüm production&apos;ı serverless&apos;a taşımak&lt;/strong&gt;: Provisioned ucuz olduğu yük profilleri var. Önce metrik toplayın, sonra karar verin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Min vCore&apos;u gereksiz yüksek tutmak&lt;/strong&gt;: 0.5 yerine 1 koymak, taban maliyetinizi iki katına çıkarır. Zorunlu değilse 0.5 bırakın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-pause&apos;u 60 dakikaya çekip sonra her 30 dakikada bir ping atmak&lt;/strong&gt;: DB hiçbir zaman uyumaz, sadece serverless&apos;in ölçekleme kısmından faydalanırsınız - bu da çoğu zaman provisioned&apos;dan daha pahalıya çıkar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage&apos;i unutmak&lt;/strong&gt;: DB pause olsa bile storage faturası devam eder. 500 GB tutmanıza gerek yoksa küçültün.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Serverless tier, doğru yerde kullanıldığında gerçekten ciddi bir tasarruf aracı. Şahsi kanaatim, dev/test ortamlarının neredeyse hepsi serverless&apos;a taşınmalı; production tarafında ise önce metrikleri okuyup karar vermek lazım. 1-2 dakikalık uyanma gecikmesini tolere edemiyorsanız veya DB&apos;niz zaten sürekli meşgulse, provisioned&apos;da kalmak daha mantıklı. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-10-mysql-tablo-kilit-cakismalarini-izleme/</id>
    <title>MySQL&#039;de Tablo Kilit Çakışmalarını İzlemek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-10-mysql-tablo-kilit-cakismalarini-izleme/"/>
    <published>2026-05-10T00:00:00Z</published>
    <updated>2026-05-10T00:00:00Z</updated>
    <author><name>Murat Erol</name></author>
    <summary>MySQL tarafında tablo kilit çakışmalarını performance_schema ve InnoDB status ile nasıl tespit edersiniz, bir bakalım.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazıda MySQL&apos;de tablo kilit çakışmalarını (table lock contention) nasıl tespit ederiz, ona bakacağız. Konu kuru gibi gözüküyor olabilir ama eminim aklınıza şu sahne gelmiştir: sabah saat 9&apos;u biraz geçer, dashboard&apos;larda kırmızı alarm patlar, kullanıcılardan &apos;sayfa açılmıyor&apos; mesajları yağar, oysa CPU rahat, disk rahat, ağ rahat. İşte bu klasik bir kilit çakışması semptomudur. Hadi başlayalım.&lt;/p&gt;
&lt;h2 id=&quot;önce-semptomu-doğru-okuyalım&quot;&gt;Önce semptomu doğru okuyalım&lt;a href=&quot;#önce-semptomu-doğru-okuyalım&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bir sorgunun yavaşladığı her duruma &apos;kilit var galiba&apos; demek doğru değil. Yavaşlık çoğu zaman index eksiğinden, plan değişikliğinden, ya da bir batch job&apos;ın ortalığı süpürmesinden kaynaklanır. Kilit çakışmasının imzası farklı: aynı sorgu bazen 5ms&apos;de dönüyor, bazen 8 saniye bekliyor, bazen de timeout yiyor. Yani süre dağılımı bimodal. Bunu Grafana&apos;da histogram olarak çizdiğinizde iki tepeli bir şey görürseniz büyük ihtimalle kilit söz konusudur.&lt;/p&gt;
&lt;p&gt;Bir başka işaret de şu: yavaşlama belirli saatlere bağlı. Sabah 9&apos;da rapor cron&apos;u koşuyor ve &lt;code&gt;LOCK TABLES&lt;/code&gt; ile uzunca bir süre tabloyu tutuyorsa, o saatte gelen yazma istekleri kuyruğa girer. Diğer saatlerde her şey yağ gibi.&lt;/p&gt;
&lt;h2 id=&quot;global-sayaçlara-hızlı-bakış&quot;&gt;Global sayaçlara hızlı bakış&lt;a href=&quot;#global-sayaçlara-hızlı-bakış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İlk durağımız &lt;code&gt;performance_schema.global_status&lt;/code&gt;. Burada iki sayaç var, ikisini de oranlamamız lazım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (&apos;Table_locks_waited&apos;, &apos;Table_locks_immediate&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Table_locks_immediate&lt;/code&gt; beklemeden alınan kilitler, &lt;code&gt;Table_locks_waited&lt;/code&gt; ise sıraya girmek zorunda kalanlar. Tek başlarına bir anlam ifade etmezler, oranlamak gerek:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT ROUND(waited / (waited + immediate) * 100, 2) AS lock_wait_ratio_pct
FROM (
  SELECT
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME=&apos;Table_locks_waited&apos;)    AS waited,
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME=&apos;Table_locks_immediate&apos;) AS immediate
) t;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Şahsi kanaatim, %1&apos;in altı normal, %1-5 arası &apos;gözünü aç&apos;, %5 üstü &apos;müdahale et&apos;. Ama bu sayaç MySQL açıldığından beri kümülatif olduğu için tek bir okumayla yanıltıcı olabilir; iki dakika arayla iki ölçüm alıp delta&apos;sına bakmak daha sağlıklı.&lt;/p&gt;
&lt;h2 id=&quot;suçlu-tabloyu-bulmak&quot;&gt;Suçlu tabloyu bulmak&lt;a href=&quot;#suçlu-tabloyu-bulmak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Oran yüksekse, suçlu tabloyu bulmamız lazım. &lt;code&gt;performance_schema.table_lock_waits_summary_by_table&lt;/code&gt; bunu söyler:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  object_schema,
  object_name,
  COUNT_READ + COUNT_WRITE AS total_accesses,
  SUM_TIMER_WAIT / 1e12   AS total_wait_sec
FROM performance_schema.table_lock_waits_summary_by_table
WHERE SUM_TIMER_WAIT &amp;gt; 0
ORDER BY total_wait_sec DESC
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tipik olarak listenin tepesinde bir-iki tablo durur. O tabloya &lt;code&gt;SHOW CREATE TABLE&lt;/code&gt; çekip engine&apos;ine bakın - hâlâ MyISAM ise hikaye orada bitiyor zaten.&lt;/p&gt;
&lt;h2 id=&quot;anlık-çatışmayı-yakalamak&quot;&gt;Anlık çatışmayı yakalamak&lt;a href=&quot;#anlık-çatışmayı-yakalamak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Şu an ne oluyor sorusunun cevabı için &lt;code&gt;sys&lt;/code&gt; şemasındaki view daha pratik:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  waiting_pid     AS waiting_thread,
  waiting_query,
  blocking_pid    AS blocking_thread,
  blocking_query
FROM sys.schema_table_lock_waits;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;InnoDB tarafında satır kilidi şüpheniz varsa &lt;code&gt;SHOW ENGINE INNODB STATUS\G&lt;/code&gt; çıktısının &lt;code&gt;LATEST DETECTED DEADLOCK&lt;/code&gt; ve &lt;code&gt;TRANSACTIONS&lt;/code&gt; bölümlerine bakın; ya da &lt;code&gt;performance_schema.data_locks&lt;/code&gt; ile &lt;code&gt;data_lock_waits&lt;/code&gt; join&apos;i daha modern yöntem:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT r.trx_id AS waiting_trx, b.trx_id AS blocking_trx, w.OBJECT_NAME
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx r ON w.REQUESTING_ENGINE_TRANSACTION_ID = r.trx_id
JOIN information_schema.innodb_trx b ON w.BLOCKING_ENGINE_TRANSACTION_ID  = b.trx_id;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MyISAM tablosunu InnoDB sanmak&lt;/strong&gt;: Eski bir tablo yıllar önce import edilmiş olabilir, kimse engine&apos;e bakmamıştır. &lt;code&gt;ALTER TABLE &amp;lt;TblName&amp;gt; ENGINE=InnoDB&lt;/code&gt; çoğu zaman tek başına problemi çözer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;LOCK TABLES&lt;/code&gt;&apos;ı uygulama kodunda bırakmak&lt;/strong&gt;: Eski PHP scriptlerinde sıkça görülür. Transaction + &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; neredeyse her zaman daha iyi.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DDL&apos;i mesai saatinde almak&lt;/strong&gt;: &lt;code&gt;ALTER TABLE&lt;/code&gt; varsayılan olarak metadata lock alır. MySQL 8&apos;de &lt;code&gt;ALGORITHM=INPLACE, LOCK=NONE&lt;/code&gt; kombinasyonu mümkünse onu seçin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sayacı tek seferlik okumak&lt;/strong&gt;: Kümülatif değer servis açıldığından beri birikir; yorumlamak için delta lazım. &lt;code&gt;mysqladmin extended-status&lt;/code&gt; ile &lt;code&gt;watch -n 5&lt;/code&gt; kombinasyonu ya da basit bir bash döngüsü iş görür.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tablo kilit çakışması çoğunlukla mimari değil, miras bir kararın ürünü - eski engine, eski &lt;code&gt;LOCK TABLES&lt;/code&gt; çağrısı, kötü zamanlanmış DDL. Bence vakit harcanması gereken yer bu üç kalem. Önce ölçün, sonra suçluyu bulun, sonra çözün; sırasını şaşırmayın. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-10-oauth2-server-error-larini-cozmek/</id>
    <title>OAuth2 Server Error&#039;larını Çözmek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-10-oauth2-server-error-larini-cozmek/"/>
    <published>2026-05-10T00:00:00Z</published>
    <updated>2026-05-10T00:00:00Z</updated>
    <author><name>Hasan Aydın</name></author>
    <summary>OAuth2 server error (5xx) yanıtlarının kök nedenleri, ayıklama akışı ve üstel backoff ile yeniden deneme stratejilerine pratik bir bakış.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda OAuth2 ile çalışırken hepimizin er ya da geç yüzleştiği o sinir bozucu &lt;code&gt;server_error&lt;/code&gt; yanıtlarına bakacağız. Bilirsiniz, request gönderiyorsunuz, kod tarafında her şey kitabına uygun, ama yetkilendirme sunucusu size 500 ya da 503 fırlatıyor. Sebebini de açıklamıyor üstelik. Hadi konuya girelim.&lt;/p&gt;
&lt;p&gt;OAuth2&apos;de 4xx hataları size &apos;request&apos;in yanlış&apos; der; düzeltmek genelde sizin elinizdedir. 5xx ise &apos;sorun bende&apos; demektir ve genellikle geçici olur. İşin sıkıntılı yanı, sunucu logunu görme şansınız olmadığında bu geçici miydi yoksa kalıcı bir arıza mı, ayırt etmek zor.&lt;/p&gt;
&lt;h2 id=&quot;server-error-neye-benziyor&quot;&gt;Server Error neye benziyor?&lt;a href=&quot;#server-error-neye-benziyor&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tipik bir 5xx yanıtı şu şekilde gelir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;error&amp;quot;: &amp;quot;server_error&amp;quot;,
  &amp;quot;error_description&amp;quot;: &amp;quot;The authorization server encountered an unexpected condition that prevented it from fulfilling the request.&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada görmemiz gereken iki şey var: HTTP statüsü (500, 502, 503, 504) ve OAuth2&apos;nin kendi &lt;code&gt;error&lt;/code&gt; alanı. İkisi farklı şeyler söyleyebilir. Mesela 200 dönüp body&apos;de &lt;code&gt;error: server_error&lt;/code&gt; koyan bizim de baş belamız servisler oldu zamanında. O yüzden &lt;code&gt;response.ok&lt;/code&gt; kontrolü tek başına yetmez; body&apos;yi de okumak gerekir.&lt;/p&gt;
&lt;p&gt;Hangi statü ne anlama geliyor, kabaca:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;500&lt;/strong&gt;: Sunucu kendi içinde patladı. Genelde yetkilendirme servisinin loguna bakmak lazım.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;502&lt;/strong&gt;: Önündeki proxy ya da load balancer arka uca ulaşamıyor. Servis ayakta olabilir, route bozuk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;503&lt;/strong&gt;: Servis aşırı yük altında ya da bakımda. Bekleyip tekrar denemek mantıklı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;504&lt;/strong&gt;: Timeout. Genelde veri tabanı sorgusu uzadığında ya da downstream bir bağımlılık takıldığında görülür.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;yeniden-deneme-stratejisi-üstel-backoff&quot;&gt;Yeniden deneme stratejisi: üstel backoff&lt;a href=&quot;#yeniden-deneme-stratejisi-üstel-backoff&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Geçici hata için en doğru tepki sabırla yeniden denemektir, ama körlemesine değil. Üstel backoff (exponential backoff) tam burada işe yarar. Her başarısız denemede bekleme süresini katlıyoruz; üstüne biraz da rastgelelik (jitter) ekliyoruz ki tüm istemciler aynı anda saldırmasın.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class OAuth2RetryClient {
    constructor(config) {
        this.config = config;
        this.maxRetries = config.maxRetries ?? 3;
        this.baseDelay = config.baseDelay ?? 1000;
        this.maxDelay = config.maxDelay ?? 30000;
    }

    async tokenRequest(params) {
        const response = await fetch(this.config.tokenEndpoint, {
            method: &apos;POST&apos;,
            headers: { &apos;Content-Type&apos;: &apos;application/x-www-form-urlencoded&apos; },
            body: new URLSearchParams(params).toString(),
        });

        if (!response.ok) {
            const body = await response.json().catch(() =&amp;gt; ({}));
            const err = new Error(body.error_description ?? &apos;Token isteği başarısız&apos;);
            err.status = response.status;
            err.code = body.error;
            throw err;
        }
        return response.json();
    }

    async withRetry(operation) {
        for (let attempt = 0; attempt &amp;lt; this.maxRetries; attempt++) {
            try {
                return await operation();
            } catch (error) {
                if (!this.isRetryable(error) || attempt === this.maxRetries - 1) {
                    throw error;
                }
                const delay = this.calculateDelay(attempt);
                await new Promise(r =&amp;gt; setTimeout(r, delay));
            }
        }
    }

    isRetryable(error) {
        if (error.status &amp;gt;= 500 &amp;amp;&amp;amp; error.status &amp;lt; 600) return true;
        if (error.code === &apos;temporarily_unavailable&apos;) return true;
        return false;
    }

    calculateDelay(attempt) {
        const exp = this.baseDelay * Math.pow(2, attempt);
        const jitter = exp * 0.25 * Math.random();
        return Math.min(exp + jitter, this.maxDelay);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bence buradaki kritik nokta &lt;code&gt;isRetryable&lt;/code&gt; metodunun ne kadar dar tutulduğu. 4xx&apos;i tekrar denerseniz aynı hatayı tekrar alırsınız ve kullanıcı bekler durur. Sadece 5xx ve &lt;code&gt;temporarily_unavailable&lt;/code&gt; için tekrar deneyin.&lt;/p&gt;
&lt;h2 id=&quot;circuit-breaker-dur-dediğinde-dur&quot;&gt;Circuit breaker: dur dediğinde dur&lt;a href=&quot;#circuit-breaker-dur-dediğinde-dur&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yetkilendirme sunucusu gerçekten çökmüşse her istekte üç kez tekrar denemek sistemi daha da yorar. Burada devre kesici (circuit breaker) deseni devreye girer. Kısaca: belli sayıda art arda hata olduğunda devreyi &apos;OPEN&apos;a alıp bir süre hiç istek geçirmeyiz, sonra &apos;HALF_OPEN&apos;da bir deneme yapıp servis düzeldiyse &apos;CLOSED&apos;a döneriz.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class CircuitBreaker {
    constructor({ threshold = 5, resetMs = 30000 } = {}) {
        this.threshold = threshold;
        this.resetMs = resetMs;
        this.state = &apos;CLOSED&apos;;
        this.failures = 0;
        this.openedAt = null;
    }

    async execute(operation) {
        if (this.state === &apos;OPEN&apos;) {
            if (Date.now() - this.openedAt &amp;gt;= this.resetMs) {
                this.state = &apos;HALF_OPEN&apos;;
            } else {
                throw new Error(&apos;Devre kesici acik, istek gonderilmedi&apos;);
            }
        }
        try {
            const result = await operation();
            this.failures = 0;
            this.state = &apos;CLOSED&apos;;
            return result;
        } catch (e) {
            this.failures++;
            if (this.failures &amp;gt;= this.threshold) {
                this.state = &apos;OPEN&apos;;
                this.openedAt = Date.now();
            }
            throw e;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sık-karşılaşılan-hatalar&quot;&gt;Sık karşılaşılan hatalar&lt;a href=&quot;#sık-karşılaşılan-hatalar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tüm hatalarda tekrar denemek&lt;/strong&gt;: 400 &lt;code&gt;invalid_grant&lt;/code&gt; hatasını 3 kez denemek refresh token&apos;ı boşa harcar ve kullanıcıyı tekrar login&apos;e atar. Önce statüye bakın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Jitter koymamak&lt;/strong&gt;: 1000 istemci aynı anda 503 yiyip aynı anda yeniden denerse sunucuyu tekrar dize getirirsiniz. Rastgelelik şart.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kullanıcıya teknik mesaj göstermek&lt;/strong&gt;: &apos;server_error&apos; yazısı kullanıcıya bir şey ifade etmez. &apos;Giriş servisi şu an yoğun, az sonra tekrar deneyin&apos; gibi sade bir mesaj çok daha iyi.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retry-After başlığını okumamak&lt;/strong&gt;: Sunucu size ne kadar bekleyeceğinizi söylüyor olabilir. Kendi backoff&apos;unuzdan önce o değere bakın.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;5xx hataları çoğunlukla kendiliğinden geçer; bizim işimiz panik yapmadan, akıllı bir geri çekilmeyle sistemi ayakta tutmak. Bana sorarsanız üstel backoff ve devre kesici ikilisi olmadan hiçbir OAuth2 istemcisi production&apos;a çıkmamalı; bu ikisi olmadan ilk küçük dalgalanmada kullanıcı oturum açamıyor diye ortalık karışır. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-10-terraform-backend-ini-veri-kaybetmeden-gecirmek/</id>
    <title>Terraform Backend&#039;ini Veri Kaybetmeden Geçirmek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-10-terraform-backend-ini-veri-kaybetmeden-gecirmek/"/>
    <published>2026-05-10T00:00:00Z</published>
    <updated>2026-05-10T00:00:00Z</updated>
    <author><name>Selin Çetin</name></author>
    <summary>Terraform state dosyasını local&#039;den S3&#039;e ya da Terraform Cloud&#039;a güvenle taşımak: rollback planları ve sık karşılaşılan tuzaklara samimi bir rehber.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda Terraform&apos;un en gergin operasyonlarından birine bakacağız: backend geçişi. State dosyası tek bir JSON gibi görünür ama Terraform için kâinatın merkezidir; o dosyayı kaybederseniz Terraform yönettiği her şeyi &apos;tanımıyorum&apos; moduna alır. Resource&apos;lar duruyor, ama sizin onları yönetebilme yetiniz buharlaşıyor. Lafı uzatmadan başlayalım.&lt;/p&gt;
&lt;p&gt;Backend geçişi neden olur? Önce local ile başlamışsınızdır, ekip büyür, S3&apos;e geçmek istersiniz. Sonra Terraform Cloud&apos;un workspace yönetimini denemek istersiniz. Bazen de iki şirket birleşir, state&apos;leri tek bucket&apos;ta toplamak gerekir. Hepsi normal ihtiyaçlar; ama her biri yanlış adımda saatlerce uğraşacağınız bir kurtarma operasyonuna dönüşebilir.&lt;/p&gt;
&lt;h2 id=&quot;geçişten-önce-yapılması-gerekenler&quot;&gt;Geçişten önce yapılması gerekenler&lt;a href=&quot;#geçişten-önce-yapılması-gerekenler&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tek bir kuralı asla atlamayın: &lt;strong&gt;yedek&lt;/strong&gt;. Terraform&apos;un kendi &lt;code&gt;state push/pull&lt;/code&gt; mekanizması güvenli, ama dosyayı bir kez elden çıkarınca geri alamayabilirsiniz.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. Ekipteki herkesi haberdar edin, CI/CD pipeline&apos;larini durdurun
# 2. Mevcut state&apos;i tarihli bir dosyaya cekin
terraform state pull &amp;gt; state-backup-$(date +%Y%m%d_%H%M%S).json

# 3. Yedegin tam oldugunu dogrulayin
jq &apos;.resources | length&apos; state-backup-*.json
terraform state list | wc -l

# 4. Yedegi repo disinda bir yere koyun
aws s3 cp state-backup-*.json s3://my-backups/terraform/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bence bu adımı atlamak, geçişin başarısı umurumda değil, &lt;strong&gt;prensip olarak&lt;/strong&gt; kabul edilemez. Yedek olmadan başlanan migration&apos;ın bir adı vardır: kumar.&lt;/p&gt;
&lt;h2 id=&quot;localden-s3e&quot;&gt;Local&apos;den S3&apos;e&lt;a href=&quot;#localden-s3e&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En sık görülen senaryo bu. Önceden &lt;code&gt;backend &amp;quot;local&amp;quot;&lt;/code&gt; bloğunuz var ya da hiç yok; yeni durumda S3&apos;e yazıyorsunuz.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;# backend.tf - YENI hali
terraform {
  backend &amp;quot;s3&amp;quot; {
    bucket         = &amp;quot;myorg-terraform-state&amp;quot;
    key            = &amp;quot;prod/app/terraform.tfstate&amp;quot;
    region         = &amp;quot;eu-central-1&amp;quot;
    encrypt        = true
    dynamodb_table = &amp;quot;terraform-locks&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra &lt;code&gt;init&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;terraform init
# Terraform soracak:
# Do you want to copy existing state to the new backend?
# Cevap: yes

terraform plan
# Cikti: No changes. -&amp;gt; ise yaradi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plan boş çıktıktan sonra local dosyayı silin, bir saniye bile önce değil:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rm terraform.tfstate terraform.tfstate.backup
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;s3ten-terraform-clouda&quot;&gt;S3&apos;ten Terraform Cloud&apos;a&lt;a href=&quot;#s3ten-terraform-clouda&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Burası iki backend tipi farklı olduğu için Terraform&apos;un otomatik geçişi her zaman tetiklemediği bir yer. Manuel push&apos;a hazırlıklı olun.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;terraform {
  cloud {
    organization = &amp;quot;myorg&amp;quot;
    workspaces { name = &amp;quot;prod-app&amp;quot; }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Once mevcut state&apos;i alalim
terraform state pull &amp;gt; current-state.json

# backend.tf&apos;i guncelleyip init
terraform init

# Eger Terraform migrate teklif etmediyse manuel push:
terraform state push current-state.json

terraform plan
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;cross-cloud-s3ten-gcse&quot;&gt;Cross-cloud: S3&apos;ten GCS&apos;e&lt;a href=&quot;#cross-cloud-s3ten-gcse&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İki farklı cloud arası geçişte, &lt;strong&gt;kontrolü Terraform&apos;a bırakmak yerine elinize alın&lt;/strong&gt;. &lt;code&gt;init&lt;/code&gt; sırasında migrate&apos;e &apos;no&apos; deyin, ardından kendiniz push edin. Şahsi tercihim hep budur; bir adımı kendi gözümle görmek bana huzur veriyor.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;terraform state pull &amp;gt; migration-state.json

# backend.tf&apos;i GCS&apos;e guncelle, sonra:
terraform init -reconfigure
# Migrate teklifine: no

terraform state push migration-state.json
terraform plan
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;i̇şler-ters-giderse&quot;&gt;İşler ters giderse&lt;a href=&quot;#i̇şler-ters-giderse&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Panik yok, çünkü yedeğiniz var. Üç tipik senaryoya bakalım.&lt;/p&gt;
&lt;h3 id=&quot;state-hiçbir-backendde-yok&quot;&gt;State hiçbir backend&apos;de yok&lt;a href=&quot;#state-hiçbir-backendde-yok&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout backend.tf
terraform init -reconfigure
terraform state push state-backup-20260510_103000.json
terraform plan
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;state-iki-backendde-birden-var&quot;&gt;State iki backend&apos;de birden var&lt;a href=&quot;#state-iki-backendde-birden-var&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aslında bu kısmen iyi haber - hiçbir şey kaybolmadı. Hangisinin güncel olduğunu &lt;code&gt;serial&lt;/code&gt; numarasına bakarak anlayın:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;terraform state pull | jq &apos;.serial&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yüksek olan kazanır. Diğerini elle silin.&lt;/p&gt;
&lt;h3 id=&quot;lock-takılı-kaldı&quot;&gt;Lock takılı kaldı&lt;a href=&quot;#lock-takılı-kaldı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;terraform force-unlock LOCK_ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ama önce o lock&apos;u kimsenin gerçekten tutmadığından emin olun; başka bir CI job&apos;ı çalışıyorsa state&apos;i bozabilirsiniz.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Yedek almadan başlamak&lt;/strong&gt;: En tehlikeli hata bu. Geçiş başarılı olsa bile yedek olmadan başarıyı doğrulayamazsınız, çünkü &apos;eskiden ne vardı&apos; sorusunun cevabı kalmamış olur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local dosyayı erken silmek&lt;/strong&gt;: &lt;code&gt;terraform.tfstate&lt;/code&gt;&apos;i remote backend doğrulanmadan silmek, geri dönüşü olmayan bir karardır. Önce &lt;code&gt;plan&lt;/code&gt;, sonra silme.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workspace&apos;leri unutmak&lt;/strong&gt;: Birden fazla workspace varsa her biri ayrı state taşır. Sadece &lt;code&gt;default&lt;/code&gt;&apos;u taşıyıp diğerlerini unutursanız üç gün sonra fark edersiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD credentials güncellemesini ertelemek&lt;/strong&gt;: Geçiş bittikten sonra pipeline&apos;lar hâlâ eski backend&apos;e yazmaya çalışırsa hem state bozulur hem lock karışır. Backend değiştiği gün CI değişkenleri de değişmeli.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;-migrate-state&lt;/code&gt; ile &lt;code&gt;-reconfigure&lt;/code&gt; karıştırmak&lt;/strong&gt;: İlki state&apos;i taşır, ikincisi sadece backend ayarlarını yeniden okur. Yanlış flag yanlış sonuç verir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;doğrulama&quot;&gt;Doğrulama&lt;a href=&quot;#doğrulama&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Geçiş bittikten sonra şu beş şeyi sırayla kontrol edin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;terraform state list                  # State okunuyor mu?
terraform plan                        # Sifir degisiklik mi?
terraform state list | wc -l          # Kaynak sayisi yedekle ayni mi?
jq &apos;.resources | length&apos; state-backup-*.json
# Tum ekip terraform init calistirdi mi?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hepsi yeşilse rahat nefes alın. Eski backend&apos;i bir hafta daha açık bırakın - panik anında geri dönüş kapısı olarak işinize yarar.&lt;/p&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Terraform backend geçişi gözünüzde büyüttüğünüz kadar zor değil; disiplinli bir checklist&apos;le yarım saatlik bir iş. Bence en kritik nokta yedek - state&apos;i bir kez yazılı bir dosyaya almışsanız, gerisi en kötü senaryoda bile telafi edilebilir bir teknik problem. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-10-aws-te-talos-linux-icin-hazir-ami-leri-kullanmak/</id>
    <title>AWS&#039;te Talos Linux için Hazır AMI&#039;leri Kullanmak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-10-aws-te-talos-linux-icin-hazir-ami-leri-kullanmak/"/>
    <published>2026-05-10T00:00:00Z</published>
    <updated>2026-05-10T00:00:00Z</updated>
    <author><name>Esra Yalçın</name></author>
    <summary>Sidero Labs&#039;in yayınladığı resmi Talos Linux AMI&#039;lerini AWS bölgelerinde nasıl bulup seçeriz, küme açma akışına kısa ama dolu bir rehber.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda AWS üzerinde Talos Linux ile küme kurmaya niyetlenenler için bir kestirme yoldan bahsedeceğim: Sidero Labs&apos;in resmi olarak yayınladığı hazır AMI&apos;ler. Kendi image&apos;ınızı Packer ile pişirmeye, bir image pipeline&apos;ı sürdürmeye, her sürümde yeniden build almaya gerek yok. Doğru AMI&apos;yi bulup &lt;code&gt;user-data&lt;/code&gt; ile makine konfigürasyonunu geçiyorsunuz, gerisi Talos&apos;un işi. Lafı uzatmadan başlayalım.&lt;/p&gt;
&lt;h2 id=&quot;talos-amileri-kim-yayınlıyor&quot;&gt;Talos AMI&apos;leri kim yayınlıyor?&lt;a href=&quot;#talos-amileri-kim-yayınlıyor&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sidero Labs, her Talos sürümü için tüm AWS bölgelerinde hem &lt;code&gt;amd64&lt;/code&gt; hem &lt;code&gt;arm64&lt;/code&gt; mimarileri için AMI yayınlıyor. Bu image&apos;lar herkese açık ama AWS Marketplace&apos;in tipik vitrininde değiller (orada da bir kayıt var, ama doğrudan değil); resmi yol ya Talos dokümantasyonuna bakmak ya da AWS CLI ile sorgulamak.&lt;/p&gt;
&lt;p&gt;AMI&apos;lerin sahibi (owner ID) &lt;code&gt;540036508848&lt;/code&gt;. Bu Sidero Labs&apos;in AWS hesabı ve filtrelerken bu ID&apos;ye dayanmak başka bir &apos;talos&apos; isimli image&apos;a yanlışlıkla denk gelmemenizi sağlıyor. Bence AMI seçerken her zaman &lt;code&gt;--owners&lt;/code&gt; ile bu ID&apos;yi vermek iyi bir alışkanlık; isim eşlemesine güvenmek bir gün sizi yanıltır.&lt;/p&gt;
&lt;h2 id=&quot;doğru-amiyi-bulmak&quot;&gt;Doğru AMI&apos;yi bulmak&lt;a href=&quot;#doğru-amiyi-bulmak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bölgenizdeki son birkaç sürümü görmek için en hızlı yol şu:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Bölgenizdeki son Talos AMI&apos;lerini listele
aws ec2 describe-images \
  --owners 540036508848 \
  --filters &apos;Name=name,Values=talos-v1.7*&apos; \
  --region us-east-1 \
  --query &apos;Images | sort_by(@, &amp;amp;CreationDate) | [-5:].{Name:Name, ImageId:ImageId, Arch:Architecture, Date:CreationDate}&apos; \
  --output table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Çıktı son beş image&apos;ı tarih sırasıyla verir; en alttaki en yenisidir. Aynı işi &lt;code&gt;talosctl&lt;/code&gt; ile de yapabilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Belirli sürüm ve mimari için varsayılan AMI&apos;yi sor
talosctl image default --talos-version v1.7.0 --platform aws --arch amd64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İkisinin de pratik faydası farklı: CLI sorgusu hangi sürümlerin canlı olduğunu görmek için, &lt;code&gt;talosctl&lt;/code&gt; ise script&apos;lerden tek bir AMI ID çekmek için daha uygun.&lt;/p&gt;
&lt;h2 id=&quot;hangi-varyant&quot;&gt;Hangi varyant?&lt;a href=&quot;#hangi-varyant&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Talos&apos;un AWS image&apos;ları tek tip değil. En sık karşılaşacağınız üç varyant şöyle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Standart&lt;/strong&gt;: Çoğu donanım için derlenmiş kernel modülleriyle gelen varsayılan image. Tipik bir Kubernetes kümesi için bu yeter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NVIDIA GPU&lt;/strong&gt;: &lt;code&gt;p3&lt;/code&gt;, &lt;code&gt;p4&lt;/code&gt;, &lt;code&gt;g4&lt;/code&gt; ailesi GPU instance&apos;lar için NVIDIA sürücüleri içerir. ML iş yükü çalıştıracaksanız buna geçin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZFS&lt;/strong&gt;: Storage katmanınız ZFS gerektiriyorsa kernel modüllerini hazır getirir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Standart image&apos;ın işi görmediği özel bir senaryo yoksa onu seçin. Şahsi kanaatim, ihtiyaç doğmadan GPU veya ZFS varyantına atlamak gereksiz bagaj taşımak.&lt;/p&gt;
&lt;h2 id=&quot;instanceları-başlatmak&quot;&gt;Instance&apos;ları başlatmak&lt;a href=&quot;#instanceları-başlatmak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AMI ID&apos;yi aldıktan sonra önce konfigürasyonu üretiyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Talos konfigurasyonunu olustur
talosctl gen config my-cluster https://my-cluster-lb.us-east-1.elb.amazonaws.com:6443

# Bu komut su dosyalari uretir:
# - controlplane.yaml (control plane node&apos;lar icin)
# - worker.yaml (worker node&apos;lar icin)
# - talosconfig (talosctl kimlik dogrulamasi icin)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra control plane&apos;i kaldırıyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Ilk control plane node&apos;u baslat
aws ec2 run-instances \
  --image-id ami-0xxxxxxxxxxxxxxxxx \
  --instance-type m5.xlarge \
  --count 1 \
  --subnet-id subnet-xxxxxxxx \
  --security-group-ids sg-xxxxxxxx \
  --iam-instance-profile Name=talos-controlplane \
  --user-data file://controlplane.yaml \
  --tag-specifications &apos;ResourceType=instance,Tags=[{Key=Name,Value=cp-1},{Key=kubernetes.io/cluster/my-cluster,Value=owned}]&apos; \
  --block-device-mappings &apos;[{&amp;quot;DeviceName&amp;quot;:&amp;quot;/dev/xvda&amp;quot;,&amp;quot;Ebs&amp;quot;:{&amp;quot;VolumeSize&amp;quot;:50,&amp;quot;VolumeType&amp;quot;:&amp;quot;gp3&amp;quot;,&amp;quot;Encrypted&amp;quot;:true}}]&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki kritik nokta &lt;code&gt;--user-data&lt;/code&gt; ile &lt;code&gt;controlplane.yaml&lt;/code&gt;&apos;ı geçiriyor olmamız. Talos açıldığında maintenance mode&apos;a girer ve user-data&apos;da konfigürasyon görürse otomatik uygular; siz ayrıca SSH&apos;lamak zorunda değilsiniz, zaten Talos&apos;ta SSH yok.&lt;/p&gt;
&lt;p&gt;Worker&apos;ları da benzer şekilde, &lt;code&gt;worker.yaml&lt;/code&gt; ile başlatın. Disk boyutu için control plane&apos;de 50 GB rahat yeter; worker&apos;da container image cache&apos;i ve ephemeral storage&apos;ı baz alıp 100-200 GB civarı bir yerde tutuyorum genelde.&lt;/p&gt;
&lt;h2 id=&quot;clusterı-bootstrap-etmek&quot;&gt;Cluster&apos;ı bootstrap etmek&lt;a href=&quot;#clusterı-bootstrap-etmek&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Instance&apos;lar ayağa kalkınca etcd&apos;yi başlatmak için tek bir komut yetiyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# talosctl&apos;i kumeye yonlendir
export TALOSCONFIG=./talosconfig
talosctl config endpoint &amp;lt;ControlPlaneIp&amp;gt;
talosctl config node &amp;lt;ControlPlaneIp&amp;gt;

# Ilk control plane node&apos;unda etcd&apos;yi bootstrap et
talosctl bootstrap

# Kume ayaga kalksin, kubeconfig&apos;i al
talosctl kubeconfig ./kubeconfig
export KUBECONFIG=./kubeconfig
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu noktadan sonra elinizde çalışan bir Kubernetes kümesi var.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Owner ID vermeden filtreleme&lt;/strong&gt;: İsim filtresi tek başına başka birinin yayınladığı image&apos;a denk gelebilir. Her zaman &lt;code&gt;--owners 540036508848&lt;/code&gt; kullanın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EBS şifrelemesini unutmak&lt;/strong&gt;: gp3&apos;te şifreleme bedava, performans düşürmüyor; varsayılan olarak &lt;code&gt;Encrypted: true&lt;/code&gt; verin. Sonradan dönüp düzeltmek snapshot dansı gerektirir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yanlış mimari AMI&apos;si seçmek&lt;/strong&gt;: arm64 image&apos;ı x86 instance&apos;a (ya da tersi) verirseniz boot&apos;ta sessizce takılır. Sürüm filtresine &lt;code&gt;Name=architecture,Values=arm64&lt;/code&gt; eklemek hayat kurtarır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yeni node için launch template&apos;i güncellememek&lt;/strong&gt;: In-place upgrade çalışan node&apos;lar için tatlı, ama yeni katılan node hâlâ eski AMI ile geliyorsa cluster&apos;da sürüm karışıklığı olur. AMI&apos;yi değiştirdiğinizde launch template&apos;i de değiştirin.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sürüm-güncellemesi&quot;&gt;Sürüm güncellemesi&lt;a href=&quot;#sürüm-güncellemesi&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yeni bir Talos sürümü çıktığında her şeyi yeniden kurmanıza gerek yok, in-place upgrade var:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Bir node&apos;u yeni surume yukselt
talosctl upgrade --nodes &amp;lt;NodeIp&amp;gt; \
  --image ghcr.io/siderolabs/installer:v1.7.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Doğru ayarlanmış pod disruption budget&apos;larıyla iş yükleriniz drain edilip yeniden zamanlanır. Yine de yeni katılacak node&apos;lar için launch template&apos;i yeni AMI&apos;ye işaret edecek biçimde güncellemeyi ihmal etmeyin.&lt;/p&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hazır AMI&apos;ler, AWS üzerinde Talos ile küme kurmayı &apos;birkaç komut&apos; işine indiriyor; image pipeline derdiniz olmuyor, her bölgede her sürüm hazır. Bence yola çıkarken standart image&apos;la başlayın, Image Factory&apos;ye ancak gerçek bir ihtiyaç doğduğunda uzanın. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-10-gpu-uzerinde-tensorflow-serving-i-flux-cd-ile-dagitmak/</id>
    <title>GPU Üzerinde TensorFlow Serving&#039;i Flux CD ile Dağıtmak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-10-gpu-uzerinde-tensorflow-serving-i-flux-cd-ile-dagitmak/"/>
    <published>2026-05-10T00:00:00Z</published>
    <updated>2026-05-10T00:00:00Z</updated>
    <author><name>Barış Ulaş</name></author>
    <summary>TensorFlow Serving&#039;i GPU destekli node&#039;larda Flux CD ile dağıtmak: model versiyonlama, kaynak ayarı ve GitOps disipliniyle drift&#039;in önüne geçmek.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda TensorFlow Serving&apos;i GPU node&apos;ları üzerinde Flux CD ile nasıl dağıttığımıza bakacağız. ML modellerini production&apos;a almak başlı başına ayrı bir dert; bir de işin içine GPU, model versiyonu, kaynak ayarı, drift gibi şeyler girince elle yönetmek hızla kabusa dönüşüyor. Bence bu noktada GitOps şart, lafı çok uzatmadan girelim konuya.&lt;/p&gt;
&lt;p&gt;Olay aslında basit. Eğitilmiş bir TensorFlow modeli var, bunu düşük gecikmeyle servis etmek istiyoruz. CPU ile mümkün ama gerçek üretim trafiğinde GPU&apos;nun farkı çok bariz. TensorFlow Serving zaten bu iş için biçilmiş kaftan; üstüne Flux CD&apos;yi koyduğumuzda her değişiklik bir pull request&apos;e dönüyor, cluster da Git&apos;teki gerçeğe göre kendini sürekli düzeltiyor.&lt;/p&gt;
&lt;h2 id=&quot;ön-hazırlık&quot;&gt;Ön hazırlık&lt;a href=&quot;#ön-hazırlık&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Başlamadan önce şunlara ihtiyacınız olacak:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NVIDIA device plugin yüklü, GPU node&apos;lu bir Kubernetes cluster&lt;/li&gt;
&lt;li&gt;Git deponuza bootstrap edilmiş Flux CD v2&lt;/li&gt;
&lt;li&gt;Paylaşımlı bir volume veya object storage&apos;da (GCS, S3) duran SavedModel&lt;/li&gt;
&lt;li&gt;Cluster&apos;dan erişilebilen bir container registry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cluster&apos;a &lt;code&gt;kubectl get nodes -o json | jq &apos;.items[].status.allocatable&apos; | grep nvidia&lt;/code&gt; çekip GPU&apos;ların gerçekten görünür olduğunu doğrulamadan devam etmeyin. Tecrübeyle sabittir, sonradan &apos;pod neden Pending&apos;de takılı?&apos; diye saç baş yolmak yerine en başta görmek daha iyi.&lt;/p&gt;
&lt;h2 id=&quot;namespace-ve-model-config&quot;&gt;Namespace ve model config&lt;a href=&quot;#namespace-ve-model-config&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Önce namespace&apos;i ve TensorFlow Serving&apos;in okuyacağı model konfigürasyonunu hazırlayalım. Burada model versiyon politikasını da belirliyoruz; biz son iki versiyonu canlı tutacağız ki rollback bir komut uzaklığında olsun.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# clusters/prod/tf-serving/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: tf-serving
  labels:
    app.kubernetes.io/managed-by: flux
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: tf-serving-models
  namespace: tf-serving
data:
  models.config: |
    model_config_list {
      config {
        name: &apos;oneri-modeli&apos;
        base_path: &apos;/models/oneri-modeli&apos;
        model_platform: &apos;tensorflow&apos;
        model_version_policy {
          latest { num_versions: 2 }
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yeni bir model versiyonu yayınlamak istediğinizde bu ConfigMap&apos;e değil, model store&apos;a yeni klasörü atıyorsunuz; TensorFlow Serving en yenisini kendisi seçiyor.&lt;/p&gt;
&lt;h2 id=&quot;gpulu-deployment&quot;&gt;GPU&apos;lu deployment&lt;a href=&quot;#gpulu-deployment&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Asıl iş burada. GPU kaynağını &lt;code&gt;requests&lt;/code&gt; ve &lt;code&gt;limits&lt;/code&gt; üzerinde aynı değere set ediyoruz çünkü &lt;code&gt;nvidia.com/gpu&lt;/code&gt; paylaşılabilir bir kaynak değil, bütün veya hiç.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# clusters/prod/tf-serving/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tf-serving
  namespace: tf-serving
spec:
  replicas: 2
  selector:
    matchLabels:
      app: tf-serving
  template:
    metadata:
      labels:
        app: tf-serving
    spec:
      tolerations:
        - key: &apos;nvidia.com/gpu&apos;
          operator: &apos;Exists&apos;
          effect: &apos;NoSchedule&apos;
      containers:
        - name: tf-serving
          # GPU destekli imajın spesifik etiketi - asla &apos;latest&apos; kullanmayin
          image: tensorflow/serving:2.14.0-gpu
          args:
            - &apos;--model_config_file=/etc/tf-serving/models.config&apos;
            - &apos;--rest_api_port=8501&apos;
            - &apos;--grpc_port=8500&apos;
            - &apos;--enable_batching=true&apos;
          ports:
            - { containerPort: 8500, name: grpc }
            - { containerPort: 8501, name: rest }
          resources:
            requests:
              nvidia.com/gpu: 1
              memory: &apos;8Gi&apos;
              cpu: &apos;4&apos;
            limits:
              nvidia.com/gpu: 1
              memory: &apos;8Gi&apos;
              cpu: &apos;4&apos;
          env:
            - { name: TF_FORCE_GPU_ALLOW_GROWTH, value: &apos;true&apos; }
          volumeMounts:
            - { name: model-config, mountPath: /etc/tf-serving }
            - { name: model-store, mountPath: /models }
      volumes:
        - name: model-config
          configMap: { name: tf-serving-models }
        - name: model-store
          persistentVolumeClaim: { claimName: model-store-pvc }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;TF_FORCE_GPU_ALLOW_GROWTH&lt;/code&gt; çok kritik. Default&apos;ta TensorFlow GPU belleğinin tamamını başta cımbızlıyor; bu sizi tek model + tek pod senaryosuna kilitler. Açtığınızda bellek gerektikçe büyüyor, aynı GPU&apos;da farklı şeyler kohabit edebiliyor.&lt;/p&gt;
&lt;h2 id=&quot;flux-kustomization&quot;&gt;Flux Kustomization&lt;a href=&quot;#flux-kustomization&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tüm bu dosyaları Flux&apos;a teslim etmek için ufak bir Kustomization yetiyor. Sağlık kontrolünü ekliyoruz ki Flux pod gerçekten ayağa kalkmadan &apos;başarılı&apos; demesin.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# clusters/prod/flux-kustomization-tf-serving.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: tf-serving
  namespace: flux-system
spec:
  interval: 5m
  path: ./clusters/prod/tf-serving
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: tf-serving
      namespace: tf-serving
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;prune: true&lt;/code&gt; deyince Git&apos;ten silinen kaynaklar cluster&apos;dan da gidiyor; manuel &lt;code&gt;kubectl apply&lt;/code&gt; ile bıraktığınız hayalet kaynaklar varsa onlar da temizlenir, dikkatli olun.&lt;/p&gt;
&lt;h2 id=&quot;doğrulama&quot;&gt;Doğrulama&lt;a href=&quot;#doğrulama&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Deploy gittikten sonra basit bir kontrol:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flux get kustomizations tf-serving
kubectl rollout status deployment/tf-serving -n tf-serving

curl -X POST http://&amp;lt;svc-ip&amp;gt;:8501/v1/models/oneri-modeli:predict \
  -H &apos;Content-Type: application/json&apos; \
  -d &apos;{&amp;quot;instances&amp;quot;: [[1.0, 2.0, 3.0, 4.0]]}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cevap geldiyse gerisi konfor.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;latest&lt;/code&gt; imaj etiketi kullanmak&lt;/strong&gt;: Reprodüksiyon ölür, rollback belirsizleşir. Her zaman &lt;code&gt;2.14.0-gpu&lt;/code&gt; gibi sabit etiket.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batching&apos;i kapalı bırakmak&lt;/strong&gt;: GPU saniyede tek istek için boşa beslenir. &lt;code&gt;--enable_batching=true&lt;/code&gt; ve bir &lt;code&gt;batching.config&lt;/code&gt; ile kuyruğu doldurun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PodDisruptionBudget koymamak&lt;/strong&gt;: Node drain sırasında iki replikanız da aynı anda inebilir. &lt;code&gt;minAvailable: 1&lt;/code&gt; zorunlu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU&apos;yu CPU node&apos;una düşürmek&lt;/strong&gt;: Tolerasyon var ama nodeSelector yoksa scheduler bazen şaşırır; &lt;code&gt;nvidia.com/gpu.present: &apos;true&apos;&lt;/code&gt; etiketiyle hedef node&apos;u sabitleyin.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bu yazıda TensorFlow Serving&apos;i GPU node&apos;larında Flux CD ile nasıl yönetilebilir bir hâle getirdiğimize baktık. Bana sorarsanız ML platformunun en az kod yazılan ama en çok kazandıran kısmı tam burası: model versiyonu artık &apos;birinin laptopundan deploy ettiği şey&apos; olmaktan çıkıp sıradan bir PR&apos;a dönüşüyor. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-09-python-siniflarinda-slots-ile-bellek-tasarrufu/</id>
    <title>Python sınıflarında __slots__ ile bellek tasarrufu</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-09-python-siniflarinda-slots-ile-bellek-tasarrufu/"/>
    <published>2026-05-09T00:00:00Z</published>
    <updated>2026-05-09T00:00:00Z</updated>
    <author><name>Volkan Kaplan</name></author>
    <summary>Python sınıflarında __slots__ tanımlamak nesne başına düşen sözlük yükünü ortadan kaldırarak büyük örnek koleksiyonlarında ciddi bellek kazandırıyor.</summary>
    <content type="html">&lt;p&gt;Python, esnekliği ve kullanışlılığı ile bilinen bir programlama dilidir. Ancak bazı durumlarda, bellek kullanımı optimize edilmesi gereken bir konu haline gelebilir. Özellikle büyük veri kümelemeleri veya nesne koleksiyonları ile çalışırken, bellek verimliliği önemli bir rol oynar. Bu yazıda, Python sınıflarında &lt;strong&gt;slots&lt;/strong&gt; tanımlayarak nasıl bellek tasarrufu sağlayacağımızı inceleyeceğiz.&lt;/p&gt;
&lt;h2 id=&quot;sınıflarda-bellek-kullanımı&quot;&gt;Sınıflarda Bellek Kullanımı&lt;a href=&quot;#sınıflarda-bellek-kullanımı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Normalde, Python&apos;da bir sınıf tanımladığımızda, her bir nesne için bir sözlük (dictionary) oluşturulur. Bu sözlük, nesneye ait tüm özellikleri saklar. Ancak bu durum, bellek kullanımı açısından bazı dezavantajlar doğurur. Özellikle çok sayıda nesne oluşturduğumuz durumlarda, bu sözlük yapısı gereksiz yere fazla bellek tüketir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;slots&lt;/strong&gt; kullanarak, bu durumu düzeltmek mümkündür. Sınıfımıza &lt;strong&gt;slots&lt;/strong&gt; tanımladığımızda, her nesnenin özellikleri sabit bir yapı içinde saklanır. Bu yapı, nesne bazında bellek ihtiyacını azaltarak performans artışı sağlar.&lt;/p&gt;
&lt;h2 id=&quot;slots-kullanımı&quot;&gt;&lt;strong&gt;slots&lt;/strong&gt; Kullanımı&lt;a href=&quot;#slots-kullanımı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bir Python sınıfında &lt;strong&gt;slots&lt;/strong&gt; tanımlamak oldukça basittir. Aşağıda bir örnek üzerinden açıklayalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Numara:
    __slots__ = [&apos;isim&apos;, &apos;yas&apos;]
    
    def __init__(self, isim, yas):
        self.isim = isim
        self.yas = yas
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yukarıdaki örnekte, &lt;code&gt;Numara&lt;/code&gt; adında bir sınıf tanımladık ve &lt;code&gt;isim&lt;/code&gt; ve &lt;code&gt;yas&lt;/code&gt; adlı iki özellik için &lt;strong&gt;slots&lt;/strong&gt; kullandık. Bu durumda, &lt;code&gt;Numara&lt;/code&gt; nesneleri oluşturduğumuzda, her nesne bu iki özellik için bellek alanı ayıracak, ancak ek bellek tüketen sözlük yapısı kullanılmayacaktır.&lt;/p&gt;
&lt;h3 id=&quot;bellek-kazancı&quot;&gt;Bellek Kazancı&lt;a href=&quot;#bellek-kazancı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;slots&lt;/strong&gt; kullanarak oluşturduğumuz sınıfların bellek tüketimini gözlemlemek, yapılan optimizasyonun etkisini anlamak açısından faydalıdır. Aşağıda, &lt;strong&gt;slots&lt;/strong&gt; kullanmayan bir sınıf ile kullanan bir sınıf arasındaki bellek farkını incelemek için basit bir örnek kod verelim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

class Normal:
    def __init__(self, isim, yas):
        self.isim = isim
        self.yas = yas
        
class Optimizasyon:
    __slots__ = [&apos;isim&apos;, &apos;yas&apos;]
    
    def __init__(self, isim, yas):
        self.isim = isim
        self.yas = yas

normal_nesne = Normal(&apos;Ali&apos;, 30)
optimizasyon_nesne = Optimizasyon(&apos;Ayşe&apos;, 25)

print(&apos;Normal sınıf bellek kullanımı:&apos;, sys.getsizeof(normal_nesne))
print(&apos;Optimizasyon sınıf bellek kullanımı:&apos;, sys.getsizeof(optimizasyon_nesne))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu kodu çalıştırdığımızda, &lt;code&gt;Normal&lt;/code&gt; sınıfının bellek kullanımı daha fazla çıkacakken, &lt;code&gt;Optimizasyon&lt;/code&gt; sınıfı &lt;strong&gt;slots&lt;/strong&gt; sayesinde daha az bellek tüketecektir. Bu, büyük nesne koleksiyonlarıyla çalıştığımızda önemli bir avantaj sağlar.&lt;/p&gt;
&lt;h2 id=&quot;sonuç&quot;&gt;Sonuç&lt;a href=&quot;#sonuç&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python&apos;daki &lt;strong&gt;slots&lt;/strong&gt; ile bellek yönetimi, büyük projelerde performans artışı sağlamak amacıyla dikkate alınması gereken bir tekniktir. Kodlarımızda &lt;strong&gt;slots&lt;/strong&gt; kullanarak bellek tasarrufuna giderek sistem kaynaklarını daha verimli kullanabiliriz. Özellikle büyük veri kümeleri üzerinde çalışan geliştiricilerin &lt;strong&gt;slots&lt;/strong&gt; kullanımını göz önünde bulundurarak programlarını optimize etmeleri faydalı olacaktır.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-09-uv-ile-hizli-python-paket-yonetimi/</id>
    <title>uv ile hızlı Python paket yönetimi</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-09-uv-ile-hizli-python-paket-yonetimi/"/>
    <published>2026-05-09T00:00:00Z</published>
    <updated>2026-05-09T00:00:00Z</updated>
    <author><name>Volkan Kaplan</name></author>
    <summary>uv&#039;nin pip, venv ve pip-tools&#039;un yerini almasını sağlayan tek dosyalık paket çözücü; günlük Python geliştirme akışını nasıl hızlandırdığına yakından bakıyoruz.</summary>
    <content type="html">&lt;h2 id=&quot;uv-nedir&quot;&gt;uv Nedir?&lt;a href=&quot;#uv-nedir&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python geliştirme dünyasında paket yönetimi, projelerin sürdürülebilirliği ve güvenliği açısından kritik bir rol oynar. Geleneksel araçlar arasında yer alan &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;venv&lt;/code&gt; ve &lt;code&gt;pip-tools&lt;/code&gt;, genellikle geliştiriciler arasında yaygın olarak kullanılmaktadır. Fakat son zamanlarda, &lt;code&gt;uv&lt;/code&gt; adında tek dosyalık bir paket çözücü, bu araçların yerini almak için ön plana çıkmıştır. &lt;code&gt;uv&lt;/code&gt;, hem kullanıcı dostu olması hem de hızlı bir deneyim sunmasıyla dikkat çekiyor.&lt;/p&gt;
&lt;h2 id=&quot;uvnin-avantajları&quot;&gt;uv’nin Avantajları&lt;a href=&quot;#uvnin-avantajları&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt;, entegre bir paket yönetim aracı olarak, avantajları sayesinde geliştiricilere zaman kazandırmayı hedefler. İşte &lt;code&gt;uv&lt;/code&gt; kullanmanın bazı faydaları:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hızlı Kurulum&lt;/strong&gt;: &lt;code&gt;uv&lt;/code&gt;, paketlerin daha hızlı inşa edilmesini sağlarken, zamandan tasarruf etmenizi sağlar. Bu sayede, geliştirme süreçleri hızlanır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kolay Kullanım&lt;/strong&gt;: Basit bir CLI (Komut Satırı Arayüzü) sunarak, kullanıcıların paketleri hızlıca kurmasına ve yönetmesine yardımcı olur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek Dosya Yapısı&lt;/strong&gt;: &lt;code&gt;uv&lt;/code&gt; tek bir dosya olarak sunulmakta. Bu, kurulum ve yönetim süreçlerinde basitlik sağlar.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;temel-kullanım&quot;&gt;Temel Kullanım&lt;a href=&quot;#temel-kullanım&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; kullanımına başlamadan önce, sisteminizde &lt;code&gt;Python&lt;/code&gt; ve &lt;code&gt;pip&lt;/code&gt; kurulu olmalıdır. Kurulum adımları oldukça basittir. İlk olarak, &lt;code&gt;uv&lt;/code&gt; paketini kurmak için aşağıdaki komutu kullanabilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install uv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kurulumdan sonra, &lt;code&gt;uv&lt;/code&gt; ile yeni bir proje oluşturmak için şu adımları izlemeniz yeterli:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv create &amp;lt;ProjectName&amp;gt;
cd &amp;lt;ProjectName&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu komutlar, &lt;code&gt;&amp;lt;ProjectName&amp;gt;&lt;/code&gt; isimli yeni bir proje dizini oluşturacak ve o dizine geçiş yapacaktır. Projenizde ihtiyaç duyduğunuz paketleri eklemek için aşağıdaki gibi bir komut kullanabilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv add &amp;lt;PackageName&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki &lt;code&gt;&amp;lt;PackageName&amp;gt;&lt;/code&gt;, kurmak istediğiniz paketin adıdır. Birden fazla paket eklemek için isimleri aralarına boşluk koyarak ekleyebilirsiniz.&lt;/p&gt;
&lt;h2 id=&quot;proje-yönetimi&quot;&gt;Proje Yönetimi&lt;a href=&quot;#proje-yönetimi&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt;, projelerinizi yönetmek için güçlü bir yapı sunuyor. Proje içindeki paketleri görüntülemek ve güncellemek oldukça kolay. Aşağıdaki komutu kullanarak mevcut paketlerinizi ve sürümlerini görüntüleyebilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ayrıca, belirli bir paketin güncellemelerini kontrol etmek için:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv update &amp;lt;PackageName&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;komutunu kullanabilirsiniz. Bu komut, belirttiğiniz paketin en son sürümüne güncellenmesini sağlar.&lt;/p&gt;
&lt;h2 id=&quot;sonuç&quot;&gt;Sonuç&lt;a href=&quot;#sonuç&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt;, Python geliştirme akışını önemli ölçüde hızlandıran bir araç olarak dikkat çekmektedir. Tek dosyalık yapısı ve kullanıcı dostu arayüzü sayesinde projelerinizi daha verimli bir şekilde yönetebilirsiniz. Özellikle büyük projelerde zaman kazandırması, bu aracı denemeniz için yeterli bir sebep olabilir. Eğer henüz &lt;code&gt;uv&lt;/code&gt; kullanmadıysanız, projelerinize entegre etmeyi düşünmelisiniz.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-09-mysql-de-create-table-as-select-ile-hizli-tablo-uretmek/</id>
    <title>MySQL&#039;de CREATE TABLE AS SELECT ile Hızlı Tablo Üretmek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-09-mysql-de-create-table-as-select-ile-hizli-tablo-uretmek/"/>
    <published>2026-05-09T00:00:00Z</published>
    <updated>2026-05-09T00:00:00Z</updated>
    <author><name>Emine Öztürk</name></author>
    <summary>CTAS deyimiyle yeni tablo üretip tek seferde doldurmak: hangi tip korunur, hangi kısıt taşınmaz, üretimde hangi tuzaklara dikkat edilir.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda MySQL&apos;in az konuşulan ama gerçekten işe yarayan deyimlerinden birine, &lt;code&gt;CREATE TABLE AS SELECT&lt;/code&gt;&apos;e (kısaca CTAS) bakacağız. Konu basit gibi durur: &apos;bir SELECT yaz, sonucundan tablo üret&apos;, tamam. Ama bence asıl mevzu deyimin ne yaptığında değil, &lt;strong&gt;ne yapmadığında&lt;/strong&gt; gizli. Index taşımıyor, primary key kurmuyor, foreign key&apos;i unutuyor. Bunu bilmeden production&apos;a &lt;code&gt;CREATE TABLE ... AS SELECT&lt;/code&gt; atan biri ertesi sabah JOIN&apos;lerin neden bu kadar yavaşladığını soruyor olur. Hadi başlayalım.&lt;/p&gt;
&lt;h2 id=&quot;ctas-nedir-ne-işe-yarar&quot;&gt;CTAS nedir, ne işe yarar?&lt;a href=&quot;#ctas-nedir-ne-işe-yarar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CTAS, tek bir deyim içinde hem yeni bir tablo oluşturur hem de SELECT&apos;in döndürdüğü satırları o tabloya basar. MySQL, sütunların tipini SELECT&apos;in ifadesinden çıkarır (type inference). Yani siz &lt;code&gt;SUM(quantity * unit_price)&lt;/code&gt; yazdığınızda, MySQL ortaya çıkan değerin uygun bir &lt;code&gt;decimal(...)&lt;/code&gt; olduğuna kendi karar verir.&lt;/p&gt;
&lt;p&gt;Korunan şey sadece &lt;code&gt;NOT NULL&lt;/code&gt; ve karakter seti gibi sütun-düzeyinde temel attribute&apos;lar. Index&apos;ler, primary key, &lt;code&gt;AUTO_INCREMENT&lt;/code&gt;, &lt;code&gt;UNIQUE&lt;/code&gt;, foreign key - hiçbiri taşınmaz. Yani CTAS size &lt;strong&gt;çıplak&lt;/strong&gt; bir tablo verir; üzerine ne giydireceğiniz size kalmış.&lt;/p&gt;
&lt;p&gt;Tipik kullanım yerleri:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Periyodik tazelenen özet (summary) tabloları&lt;/li&gt;
&lt;li&gt;ETL pipeline&apos;larında geçici staging tabloları&lt;/li&gt;
&lt;li&gt;Analitik snapshot&apos;lar - &apos;mart sonu itibarıyla şu durumdaydı&apos; kayıtları&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;en-basit-hâli&quot;&gt;En basit hâli&lt;a href=&quot;#en-basit-hâli&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Önce küçük bir kullanıcı tablosu kuralım, sonra ondan yeni bir tablo türetelim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE users (
    id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username   VARCHAR(50)  NOT NULL,
    email      VARCHAR(255) NOT NULL,
    country    CHAR(2),
    created_at DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO users (username, email, country) VALUES
    (&apos;ali&apos;,   &apos;ali@ornek.com&apos;,   &apos;TR&apos;),
    (&apos;berk&apos;,  &apos;berk@ornek.com&apos;,  &apos;TR&apos;),
    (&apos;clara&apos;, &apos;clara@ornek.com&apos;, &apos;DE&apos;);

-- TR kullanicilarindan yeni bir tablo turetelim
CREATE TABLE tr_users AS
    SELECT id, username, email, created_at
    FROM users
    WHERE country = &apos;TR&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Şimdi &lt;code&gt;DESCRIBE tr_users&lt;/code&gt; yaparsanız ilginç bir şey görürsünüz: &lt;code&gt;id&lt;/code&gt; sütunu var, &lt;code&gt;int unsigned&lt;/code&gt;, &lt;code&gt;NOT NULL&lt;/code&gt; - hepsi tamam. Ama &lt;code&gt;Key&lt;/code&gt; sütunu boş, &lt;code&gt;Extra&lt;/code&gt; sütunu boş. Yani primary key yok, &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; gitmiş.&lt;/p&gt;
&lt;p&gt;Bu yüzden CTAS&apos;ı atar atmaz hemen ardından &lt;code&gt;ALTER TABLE&lt;/code&gt; ile eksikleri tamamlamak alışkanlık olmalı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ALTER TABLE tr_users
    ADD PRIMARY KEY (id),
    ADD UNIQUE INDEX uq_email (email),
    ADD INDEX idx_created_at (created_at);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;hesaplanmış-sütunlar-ve-takma-adlar&quot;&gt;Hesaplanmış sütunlar ve takma adlar&lt;a href=&quot;#hesaplanmış-sütunlar-ve-takma-adlar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CTAS&apos;ın gerçek gücü, agregasyon sonuçlarını materialize ederken ortaya çıkıyor. Burada &lt;code&gt;AS&lt;/code&gt; ile takma ad vermek &lt;strong&gt;şart&lt;/strong&gt;, çünkü yeni tablonun sütun adı buradan geliyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE order_totals AS
    SELECT
        user_id,
        COUNT(*)                          AS order_count,
        SUM(total_amount)                 AS lifetime_value,
        MIN(created_at)                   AS first_order_at,
        MAX(created_at)                   AS last_order_at
    FROM orders
    GROUP BY user_id;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Takma ad vermezseniz sütun adı &lt;code&gt;COUNT(*)&lt;/code&gt; gibi pek de sevimli olmayan bir şey olur ve sonradan refactor canavarı olarak karşınıza çıkar.&lt;/p&gt;
&lt;h2 id=&quot;boş-bir-tablo-sadece-yapısı-için&quot;&gt;Boş bir tablo, sadece yapısı için&lt;a href=&quot;#boş-bir-tablo-sadece-yapısı-için&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bir tablonun &lt;strong&gt;yalnız yapısını&lt;/strong&gt; kopyalamak istiyorsanız iki yol var. Birincisi CTAS hilesi:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE users_staging AS
    SELECT * FROM users WHERE 1 = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;WHERE 1 = 0&lt;/code&gt; hiçbir satıra uymaz, dolayısıyla tablo boş gelir ama yapısı çıkarılır. Şahsi kanaatim: bu yöntem hızlı bir hile için iyi, ama yine index&apos;siz/PK&apos;sız bir tablo verir. Ben yapısal kopya istediğimde &lt;code&gt;CREATE TABLE ... LIKE&lt;/code&gt; tercih ediyorum:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE users_staging LIKE users;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LIKE&lt;/code&gt; versiyonu index&apos;leri ve PK&apos;yı da taşır (FK hariç). Sonra &lt;code&gt;INSERT INTO users_staging SELECT ... FROM users&lt;/code&gt; ile veriyi doldurursunuz. Tip sadakati ve index&apos;lerle uğraşmak istemiyorsanız bu çok daha temiz.&lt;/p&gt;
&lt;h2 id=&quot;ctas-mı-like-mı&quot;&gt;CTAS mı, LIKE mı?&lt;a href=&quot;#ctas-mı-like-mı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İkisi farklı işler için. Karıştırmamak adına kısa bir karşılaştırma:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Özellik&lt;/th&gt;
&lt;th&gt;CTAS&lt;/th&gt;
&lt;th&gt;CREATE TABLE LIKE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Veriyi de kopyalar mı?&lt;/td&gt;
&lt;td&gt;Evet&lt;/td&gt;
&lt;td&gt;Hayır&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index&apos;leri taşır mı?&lt;/td&gt;
&lt;td&gt;Hayır&lt;/td&gt;
&lt;td&gt;Evet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary key&apos;i taşır mı?&lt;/td&gt;
&lt;td&gt;Hayır&lt;/td&gt;
&lt;td&gt;Evet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Foreign key&apos;i taşır mı?&lt;/td&gt;
&lt;td&gt;Hayır&lt;/td&gt;
&lt;td&gt;Hayır&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sütun tipleri&lt;/td&gt;
&lt;td&gt;Çıkarılır&lt;/td&gt;
&lt;td&gt;Birebir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;İdeal kullanım&lt;/td&gt;
&lt;td&gt;Özet, snapshot, ETL stage&lt;/td&gt;
&lt;td&gt;Yapısal kopya&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Primary key eklemeyi unutmak&lt;/strong&gt;: CTAS sonrası &lt;code&gt;ALTER TABLE ... ADD PRIMARY KEY&lt;/code&gt; atmazsanız tablo PK&apos;sız kalır. JOIN&apos;lerde ve replication&apos;da başınız ağrır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AUTO_INCREMENT&apos;in kaybolması&lt;/strong&gt;: Kaynak tabloda olsa bile yeni tabloya geçmez. Yeni tabloda ihtiyaç varsa elle eklemek lazım.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Foreign key beklemek&lt;/strong&gt;: Foreign key kesinlikle taşınmaz. Referans bütünlüğü gerekiyorsa CTAS sonrası elle tanımlayın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Üretimde uzun süren CTAS&lt;/strong&gt;: Büyük bir tablodan CTAS atmak metadata lock alır ve kaynak tabloda işlemleri bekletir. Yoğun saatlerde sakın yapmayın; replica üzerinde ya da gece batch&apos;inde tercih edin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tip çıkarımına güvenmek&lt;/strong&gt;: Bir &lt;code&gt;decimal(32,2)&lt;/code&gt; görürseniz şaşırmayın - MySQL geniş aralık seçer. Sonra &lt;code&gt;ALTER TABLE ... MODIFY&lt;/code&gt; ile daraltmak gerekebilir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bu yazıda CTAS&apos;ı, ne taşıdığını ve neyi geride bıraktığını gördük. Bence akılda kalması gereken tek cümle şu: CTAS size ham bir tablo verir, geri kalanı sizin işiniz. Periyodik özetler ve hızlı analiz snapshot&apos;ları için biçilmiş kaftan; ama yapısal kopya istiyorsanız &lt;code&gt;CREATE TABLE ... LIKE&lt;/code&gt; daha doğru adres. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-09-mysql-mediumblob-ile-ikili-veri-saklamak/</id>
    <title>MySQL MEDIUMBLOB ile İkili Veri Saklamak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-09-mysql-mediumblob-ile-ikili-veri-saklamak/"/>
    <published>2026-05-09T00:00:00Z</published>
    <updated>2026-05-09T00:00:00Z</updated>
    <author><name>Tolga Özkan</name></author>
    <summary>MySQL&#039;de 16 MB&#039;a kadar ikili veri için MEDIUMBLOB: nasıl tasarlanır, performans dengesi nedir, dosya saklamak için doğru seçim midir.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazıda MySQL&apos;in &lt;code&gt;MEDIUMBLOB&lt;/code&gt; tipine bakacağız. PDF&apos;leri, küçük resimleri, imza dosyalarını veritabanında tutmak isteyen ekipler bir noktada bu tipe denk geliyor; ben de yıllar içinde &apos;şu dosyayı veritabanına atalım, kolay olsun&apos; kararının bazen kurtarıcı, bazen ayak bağı olduğunu gördüm. Hadi tipin sınırlarına ve doğru kullanım senaryolarına dalalım.&lt;/p&gt;
&lt;h2 id=&quot;mediumblob-nedir&quot;&gt;MEDIUMBLOB nedir?&lt;a href=&quot;#mediumblob-nedir&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;MEDIUMBLOB&lt;/code&gt;, en fazla 16.777.215 bayt yani yaklaşık 16 MB ikili veri saklayabilen bir tiptir. MySQL&apos;in BLOB ailesinde &lt;code&gt;BLOB&lt;/code&gt; (64 KB) ile &lt;code&gt;LONGBLOB&lt;/code&gt; (4 GB) arasındaki boşluğu doldurur. Yani çok küçük olmayan ama devasa da sayılmayacak dosyalar için biçilmiş kaftan.&lt;/p&gt;
&lt;p&gt;Burada gözden kaçmayan bir detay var: InnoDB&apos;nin DYNAMIC satır formatında BLOB içeriği satırla aynı sayfada tutulmaz. Verinin kendisi off-page&apos;e gider, satırda yalnızca 9 ile 12 bayt arası bir işaretçi kalır. Bu sayede 65.535 baytlık satır limitine BLOB sütunları neredeyse hiç yük bindirmez. Aslında MEDIUMBLOB&apos;un cazibesinin yarısı bu mimari karardan geliyor.&lt;/p&gt;
&lt;h2 id=&quot;tablo-tasarımı&quot;&gt;Tablo tasarımı&lt;a href=&quot;#tablo-tasarımı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bir dosya yükleme tablosu kurarken ne istediğimizi en baştan netleştirelim. Aşağıdaki şema, hem dosyanın kendisini hem de işine yarayacak meta verileri tek satırda toplar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE uploaded_files (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    filename    VARCHAR(255) NOT NULL,
    mime_type   VARCHAR(100) NOT NULL,
    file_data   MEDIUMBLOB NOT NULL,
    file_size   INT UNSIGNED GENERATED ALWAYS AS (LENGTH(file_data)) STORED,
    uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;file_size&lt;/code&gt; sütununu generated column olarak tutmamın sebebi listeleme sorgularında &lt;code&gt;LENGTH(file_data)&lt;/code&gt; çağırarak BLOB&apos;u sayfaya taşımak zorunda kalmamak. Küçük bir numara ama büyük bir tasarruf.&lt;/p&gt;
&lt;h2 id=&quot;veri-ekleme-ve-okuma&quot;&gt;Veri ekleme ve okuma&lt;a href=&quot;#veri-ekleme-ve-okuma&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python tarafında &lt;code&gt;mysql-connector-python&lt;/code&gt; ile bir PDF eklemek oldukça düz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import mysql.connector

conn = mysql.connector.connect(host=&apos;localhost&apos;, user=&apos;root&apos;, database=&apos;mydb&apos;)
cursor = conn.cursor()

with open(&apos;sunum.pdf&apos;, &apos;rb&apos;) as f:
    pdf_bytes = f.read()

cursor.execute(
    &amp;quot;&amp;quot;&amp;quot;INSERT INTO uploaded_files (filename, mime_type, file_data)
       VALUES (%s, %s, %s)&amp;quot;&amp;quot;&amp;quot;,
    (&apos;sunum.pdf&apos;, &apos;application/pdf&apos;, pdf_bytes)
)
conn.commit()
print(f&apos;{len(pdf_bytes)} bayt yazildi&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Geri okumak da aynı şekilde sade. Tek dikkat edilecek nokta, &lt;code&gt;fetchone()&lt;/code&gt; BLOB&apos;u baştan sona belleğe alır. Yani 15 MB&apos;lık bir dosyayı okuyorsanız o satır anında 15 MB RAM tüketir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;cursor.execute(
    &apos;SELECT filename, mime_type, file_data FROM uploaded_files WHERE id = %s&apos;,
    (1,)
)
filename, mime_type, file_data = cursor.fetchone()

with open(f&apos;indirilen_{filename}&apos;, &apos;wb&apos;) as f:
    f.write(bytes(file_data))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;maxallowedpacket-ayarı&quot;&gt;max_allowed_packet ayarı&lt;a href=&quot;#maxallowedpacket-ayarı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Büyük insert&apos;lerde başınıza gelecek ilk hata genelde &lt;code&gt;Packet too large&lt;/code&gt; olur. Çünkü MySQL istemci ile sunucu arasındaki tek bir paketin boyutuna sınır koyar. MEDIUMBLOB ile çalışıyorsanız bu sınırı yükseltmeniz gerek:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Mevcut degeri gor
SHOW VARIABLES LIKE &apos;max_allowed_packet&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sunucu tarafında &lt;code&gt;my.cnf&lt;/code&gt; içine &lt;code&gt;max_allowed_packet = 20M&lt;/code&gt; yazmak çoğu durumda yeter. İstemci tarafında da bağlantı parametresiyle ayarlanır:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysql -u root -p mydb --max_allowed_packet=20M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu ayarı yapmadan yapılan testler &apos;lokalde çalışıyordu&apos; efsanesinin en büyük kaynaklarından biridir.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-hatalar&quot;&gt;Sık karşılaşılan hatalar&lt;a href=&quot;#sık-karşılaşılan-hatalar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SELECT *&lt;/code&gt; ile listelemek&lt;/strong&gt;: Listeleme sorgularında BLOB sütununu çekmek bütün satırları sayfaya yüklemenize yol açar. Sadece ihtiyacınız olan sütunları seçin, dosyayı yalnızca indirme anında çekin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MIME türü doğrulamamak&lt;/strong&gt;: İstemci &apos;image/png&apos; diyorsa diye doğrudan inanmayın. &lt;code&gt;python-magic&lt;/code&gt; veya benzer bir kütüphane ile baytlardan tipini doğrulayın; aksi halde XSS veya yanlış servis edilen dosya sorunları kapıda.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yedek boyutunu görmezden gelmek&lt;/strong&gt;: BLOB&apos;lar yedek dosyalarınızı şişirir. Bir noktada &lt;code&gt;mysqldump&lt;/code&gt; çıktısı 50 GB&apos;ı bulduğunda &apos;biz neyi nereye koyduk?&apos; diye soracaksınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versiyon yönetimi beklemek&lt;/strong&gt;: MEDIUMBLOB tek bir mevcut hali tutar; CDN, varyant üretimi, geçmiş sürümler istiyorsanız S3 benzeri nesne depolama daha doğru adres.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ne-zaman-mediumblob-ne-zaman-dış-depolama&quot;&gt;Ne zaman MEDIUMBLOB, ne zaman dış depolama?&lt;a href=&quot;#ne-zaman-mediumblob-ne-zaman-dış-depolama&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bence kural basit: dosya 5 MB altındaysa, çok sık okunmuyorsa ve satırla transaksiyonel olarak tutarlı kalması önemliyse MEDIUMBLOB iyi seçimdir. Mesela kullanıcı imzası, PDF rapor eki, küçük thumbnail. Ama dosyalar büyük, sık okunan, CDN üzerinden dağıtılması gereken şeylerse S3 veya GCS&apos;e gönderin, veritabanında sadece URL ile metadata tutun.&lt;/p&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;MEDIUMBLOB&lt;/code&gt;, doğru senaryoda hayatı çok kolaylaştıran bir tip; &apos;her dosyayı veritabanına atalım&apos; refleksiyle kullanılırsa da en hızlı şekilde sırtınıza yük olan bir tip. Boyutu, erişim sıklığını ve yedek maliyetini birlikte değerlendirin; karar zaten kendiliğinden çıkar. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-09-mysqlimport-ile-toplu-csv-yuklemek/</id>
    <title>mysqlimport ile Toplu CSV Yüklemek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-09-mysqlimport-ile-toplu-csv-yuklemek/"/>
    <published>2026-05-09T00:00:00Z</published>
    <updated>2026-05-09T00:00:00Z</updated>
    <author><name>Burak Aslan</name></author>
    <summary>mysqlimport komut satırı aracıyla CSV ve TSV dosyalarını MySQL tablolarına nasıl hızlı yükleriz, INSERT&#039;e göre kazanç ne kadar olur, sahadan notlar.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda MySQL&apos;in çoğu zaman gözden kaçan bir aletini, &lt;code&gt;mysqlimport&lt;/code&gt;&apos;u konuşacağız. Elinizde birkaç yüz binlik bir CSV varsa ve bir de tutup &lt;code&gt;INSERT&lt;/code&gt; döngüsüyle yüklemeye çalıştıysanız, ne demek istediğimi anlarsınız. Saatlerce bekleyen bir script, bir de yanında soğumuş bir kahve. Halbuki MySQL kutudan çıkar çıkmaz size hızlı bir yol sunuyor, biz farkında değiliz. Hadi başlayalım.&lt;/p&gt;
&lt;h2 id=&quot;mysqlimport-nedir&quot;&gt;mysqlimport nedir?&lt;a href=&quot;#mysqlimport-nedir&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mysqlimport&lt;/code&gt; aslında &lt;code&gt;LOAD DATA INFILE&lt;/code&gt; SQL ifadesinin komut satırı kılığına bürünmüş hali. Yani arka planda bambaşka bir motor yok, MySQL&apos;in kendi toplu yükleme komutu çalışıyor; siz sadece terminalden çağırıyorsunuz. Ama bu basit görünen sarmalama, sıradan &lt;code&gt;INSERT&lt;/code&gt; yığınına göre çoğu senaryoda 10 ila 50 kat hız farkı yaratıyor. Tek satırlık bir komutla milyonlarca satır akıtabiliyorsunuz.&lt;/p&gt;
&lt;p&gt;İlginç bir kuralı var: hedef tabloyu &lt;strong&gt;dosya adından&lt;/strong&gt; çıkarıyor. Yani &lt;code&gt;siparisler.csv&lt;/code&gt; dosyası otomatik olarak &lt;code&gt;siparisler&lt;/code&gt; tablosuna gidiyor. Tablo adını parametreden vermiyorsunuz. İlk başlarda bu bana garip gelmişti, ama bir-iki kullanımdan sonra mantığı oturuyor; toplu yükleme yapacaksınız zaten dosyalarınızı tablo adına göre düzenli tutmanız işinize geliyor.&lt;/p&gt;
&lt;h2 id=&quot;temel-kullanım&quot;&gt;Temel kullanım&lt;a href=&quot;#temel-kullanım&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En sade biçimi şöyle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysqlimport [secenekler] veritabani_adi dosya1 [dosya2 ...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bir örnek üzerinden gidelim. Diyelim &lt;code&gt;magaza&lt;/code&gt; veritabanında &lt;code&gt;musteriler&lt;/code&gt; adlı bir tablomuz var ve elimizde &lt;code&gt;/tmp/musteriler.csv&lt;/code&gt; dosyası duruyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Dosya adi tablo adiyla ayni olmali
mysqlimport -u root -p magaza /tmp/musteriler.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu kadar. Tek satır. CSV ne kadar büyükse, INSERT&apos;e göre kazancınız da o kadar büyür.&lt;/p&gt;
&lt;h2 id=&quot;ayraç-ve-tırnak-ayarları&quot;&gt;Ayraç ve tırnak ayarları&lt;a href=&quot;#ayraç-ve-tırnak-ayarları&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hayatta her CSV virgülle ayrılmış olmuyor maalesef. Bazıları tab, bazıları pipe (&lt;code&gt;|&lt;/code&gt;), bazıları da kim bilir hangi karaktere bayılmış birinin tercihiyle geliyor. &lt;code&gt;mysqlimport&lt;/code&gt; bunların hepsini idare ediyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Tab ile ayrilmis dosya
mysqlimport --fields-terminated-by=&apos;\t&apos; -u root -p magaza /tmp/siparisler.tsv

# Pipe ile ayrilmis dosya
mysqlimport --fields-terminated-by=&apos;|&apos; -u root -p magaza /tmp/urunler.txt

# Tirnak icine alinmis alanlar varsa
mysqlimport \
  --fields-terminated-by=&apos;,&apos; \
  --fields-enclosed-by=&apos;&amp;quot;&apos; \
  -u root -p magaza /tmp/musteriler.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bana sorarsanız &lt;code&gt;--fields-enclosed-by&lt;/code&gt; neredeyse her zaman lazım oluyor; çünkü gerçek CSV&apos;lerde bir yerlerde mutlaka virgül içeren bir ad ya da adres çıkıyor karşınıza.&lt;/p&gt;
&lt;h2 id=&quot;başlık-satırı-sütun-sırası-ve-çakışmalar&quot;&gt;Başlık satırı, sütun sırası ve çakışmalar&lt;a href=&quot;#başlık-satırı-sütun-sırası-ve-çakışmalar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Çoğu CSV&apos;nin ilk satırı sütun adlarıyla geliyor; bunu &lt;code&gt;--ignore-lines&lt;/code&gt; ile atlıyorsunuz. Dosyadaki sütunların sırası tablodakinden farklıysa &lt;code&gt;--columns&lt;/code&gt; ile mantıklı bir eşleme veriyorsunuz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Ilk satiri (basligi) atla
mysqlimport --ignore-lines=1 \
  --fields-terminated-by=&apos;,&apos; \
  -u root -p magaza /tmp/musteriler.csv

# Sutun sirasini elle belirt
mysqlimport \
  --columns=id,ad,eposta,olusturulma \
  --ignore-lines=1 \
  --fields-terminated-by=&apos;,&apos; \
  -u root -p magaza /tmp/musteriler.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yükleme sırasında aynı birincil anahtara sahip kayıt zaten varsa ne olacak? İki seçeneğiniz var: ya üzerine yazarsınız ya da o satırı atlarsınız.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Mevcut kayitlari yenisiyle degistir
mysqlimport --replace --fields-terminated-by=&apos;,&apos; \
  -u root -p magaza /tmp/musteriler.csv

# Cakisan satirlari sessizce gec
mysqlimport --ignore --fields-terminated-by=&apos;,&apos; \
  -u root -p magaza /tmp/musteriler.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hangisini seçeceğiniz veriyi nereden aldığınıza bağlı; gece koşan bir senkronizasyondaysanız &lt;code&gt;--replace&lt;/code&gt;, tek seferlik bir göç ise &lt;code&gt;--ignore&lt;/code&gt; çoğu zaman daha güvenli.&lt;/p&gt;
&lt;h2 id=&quot;paralel-yükleme-ve-load-data-karşılığı&quot;&gt;Paralel yükleme ve LOAD DATA karşılığı&lt;a href=&quot;#paralel-yükleme-ve-load-data-karşılığı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aylık dosyaları ayrı ayrı tutuyorsanız hepsini birden, paralel olarak yükleyebilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysqlimport --use-threads=4 \
  --fields-terminated-by=&apos;,&apos; \
  -u root -p magaza \
  /tmp/siparisler_ocak.csv \
  /tmp/siparisler_subat.csv \
  /tmp/siparisler_mart.csv \
  /tmp/siparisler_nisan.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perde arkasında üretilen SQL aslında bu:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;LOAD DATA INFILE &apos;/tmp/musteriler.csv&apos;
INTO TABLE musteriler
FIELDS TERMINATED BY &apos;,&apos;
ENCLOSED BY &apos;&amp;quot;&apos;
LINES TERMINATED BY &apos;\n&apos;
IGNORE 1 LINES
(id, ad, eposta, olusturulma);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yani &lt;code&gt;mysqlimport&lt;/code&gt; öğrenmek bir bakıma &lt;code&gt;LOAD DATA INFILE&lt;/code&gt; öğrenmek demek; biri terminalden, diğeri SQL istemcisinden hayatınıza giriyor.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dosya adı tablo adıyla uyuşmuyor&lt;/strong&gt;: &lt;code&gt;musteri_listesi.csv&lt;/code&gt; koyup &lt;code&gt;musteriler&lt;/code&gt; tablosuna gitmesini bekleyemezsiniz. Yüklemeden önce dosyayı yeniden adlandırın ya da sembolik bağ kurun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sunucuda dosya yok&lt;/strong&gt;: &lt;code&gt;mysqlimport&lt;/code&gt; varsayılanda dosyayı &lt;strong&gt;sunucuda&lt;/strong&gt; arar. Dosya sizdeyse &lt;code&gt;--local&lt;/code&gt; parametresini ekleyin; ayrıca &lt;code&gt;my.cnf&lt;/code&gt; içinde &lt;code&gt;local_infile=1&lt;/code&gt; açık olmalı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Karakter kodlama uyumsuzluğu&lt;/strong&gt;: Türkçe karakterler bozuk çıkıyorsa &lt;code&gt;--default-character-set=utf8mb4&lt;/code&gt; ekleyin. Çoğu hata buradan çıkıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Index&apos;ler darboğaz oluyor&lt;/strong&gt;: Çok büyük yüklemelerde önce &lt;code&gt;ALTER TABLE ... DISABLE KEYS&lt;/code&gt; ile indexleri devre dışı bırakın, yükleme bitince &lt;code&gt;ENABLE KEYS&lt;/code&gt; ile geri açın. Bence bir milyondan büyük her yüklemede bunu refleks haline getirmek lazım.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mysqlimport&lt;/code&gt; öğrenmesi bir öğleden sonra sürmeyen ama elinizde tuttukça size saatler kazandıran bir alet. Bence MySQL ile çalışan herkesin cebinde olması gereken o ufak ama keskin araçlardan biri. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-09-cloud-run-servisini-cloud-sql-ornegine-baglamak/</id>
    <title>Cloud Run Servisini Cloud SQL Örneğine Bağlamak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-09-cloud-run-servisini-cloud-sql-ornegine-baglamak/"/>
    <published>2026-05-09T00:00:00Z</published>
    <updated>2026-05-09T00:00:00Z</updated>
    <author><name>Levent Güneş</name></author>
    <summary>Cloud Run servisini Cloud SQL örneğine Unix soketi ve resmi connector üzerinden bağlamak; IAM, bağlantı havuzu ve credential yönetimi kararları.</summary>
    <content type="html">&lt;p&gt;Merhabalar, bu yazımda Cloud Run üstünde koşan bir servisi Cloud SQL örneğine nasıl bağladığımıza bakacağız. Konu kâğıt üstünde basit duruyor ama ilk denediğinizde &apos;connection refused&apos; veya &apos;too many connections&apos; hatasıyla karşılaşmamak için bilmeniz gereken birkaç ufak detay var. Lafı çok uzatmadan başlayayım.&lt;/p&gt;
&lt;p&gt;Cloud Run, GCP&apos;nin sunucusuz container platformu. Trafik geldiğinde sıfırdan N instance&apos;a ölçekleniyor, gelmeyince yine sıfıra iniyor. Bu güzel ama veritabanı tarafında bir gerçeklik var: Cloud SQL örneğinin sınırlı sayıda bağlantısı var ve siz patlayan instance sayısıyla doğru orantılı bağlantı açarsanız hızla duvara toslarsınız. O yüzden bağlantı şeklini ve havuzu doğru kurmak işin yarısı.&lt;/p&gt;
&lt;h2 id=&quot;bağlantı-için-iki-yol-var&quot;&gt;Bağlantı için iki yol var&lt;a href=&quot;#bağlantı-için-iki-yol-var&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pratikte iki ana yaklaşım kullanılıyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Yerleşik Cloud SQL connector + Unix socket&lt;/strong&gt;: Servisi deploy ederken &lt;code&gt;--add-cloudsql-instances&lt;/code&gt; parametresini geçiyorsunuz, Cloud Run container içine &lt;code&gt;/cloudsql/&amp;lt;INSTANCE_CONNECTION_NAME&amp;gt;&lt;/code&gt; yolunda bir Unix socket bind ediyor. Public IP veya private IP fark etmiyor, akış GCP&apos;nin kendi proxy&apos;si üzerinden geçiyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud SQL Connector kütüphaneleri&lt;/strong&gt;: Python için &lt;code&gt;cloud-sql-python-connector&lt;/code&gt;, Java için &lt;code&gt;postgres-socket-factory&lt;/code&gt; gibi paketler. IAM Authentication ve TLS&apos;i programatik olarak yönetiyorlar. IAM tabanlı auth kullanmak istiyorsanız genelde tercih edilen yol.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bence ilk projede socket yaklaşımı yeter. Connector kütüphaneleri IAM auth veya çok özel ihtiyaçlar varsa devreye girer; basit bir CRUD servisinde fazladan bağımlılık.&lt;/p&gt;
&lt;h2 id=&quot;iam-ve-servis-hesabı&quot;&gt;IAM ve servis hesabı&lt;a href=&quot;#iam-ve-servis-hesabı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İlk yapılacak iş Cloud Run servisinin koştuğu service account&apos;a &lt;code&gt;roles/cloudsql.client&lt;/code&gt; rolünü vermek. Cloud SQL Admin API&apos;sinin de açık olması lazım; aksi halde proxy bağlantı kuramıyor.&lt;/p&gt;
&lt;p&gt;Connection name&apos;i almakla başlayalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud sql instances describe my-postgres \
  --format=&apos;value(connectionName)&apos;
# ornegin: my-project:europe-west1:my-postgres
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu üçlü &lt;code&gt;project:region:instance&lt;/code&gt; ileride deploy ederken sürekli işimize yarayacak. Sonrasında deploy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud run deploy payment-api \
  --image=europe-west1-docker.pkg.dev/my-project/apps/payment-api:1.4.0 \
  --region=europe-west1 \
  --service-account=payment-api-sa@my-project.iam.gserviceaccount.com \
  --add-cloudsql-instances=my-project:europe-west1:my-postgres \
  --set-env-vars=DB_HOST=/cloudsql/my-project:europe-west1:my-postgres,DB_USER=payment,DB_NAME=payments \
  --set-secrets=DB_PASS=db-password:latest \
  --max-instances=20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada dikkat etmenizi istediğim iki şey var. Birincisi şifreyi &lt;code&gt;--set-env-vars&lt;/code&gt; ile değil &lt;code&gt;--set-secrets&lt;/code&gt; ile geçiyoruz; düz metin parola env&apos;e asla yazılmaz, Secret Manager bunun için var. İkincisi &lt;code&gt;--max-instances&lt;/code&gt; koymak. Cloud Run kendi haline bırakılırsa yüzlerce instance açabilir, her biri bağlantı tutar, Cloud SQL&apos;in &lt;code&gt;max_connections&lt;/code&gt; limiti kısa sürede dolar.&lt;/p&gt;
&lt;h2 id=&quot;uygulama-tarafı&quot;&gt;Uygulama tarafı&lt;a href=&quot;#uygulama-tarafı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python tarafında SQLAlchemy ile bağlanırken host olarak &lt;code&gt;/cloudsql/...&lt;/code&gt; socket yolunu veriyoruz. URL&apos;de host yerine query parametresinde belirtmek gerekiyor, çünkü TCP gibi davranmıyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
from sqlalchemy import create_engine
from sqlalchemy.engine.url import URL

instance = os.environ[&apos;DB_HOST&apos;]  # /cloudsql/proj:region:instance

url = URL.create(
    drivername=&apos;postgresql+psycopg2&apos;,
    username=os.environ[&apos;DB_USER&apos;],
    password=os.environ[&apos;DB_PASS&apos;],
    database=os.environ[&apos;DB_NAME&apos;],
    query={&apos;host&apos;: instance},
)

engine = create_engine(
    url,
    pool_size=3,
    max_overflow=2,
    pool_pre_ping=True,
    pool_recycle=1800,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pool_size=3&lt;/code&gt; ve &lt;code&gt;max_overflow=2&lt;/code&gt; belki size düşük gelebilir. Ama hesabı yapın: 20 instance × 5 bağlantı = 100. Cloud SQL küçük bir tier&apos;daysa default &lt;code&gt;max_connections&lt;/code&gt; zaten 100-200 civarında. Pool&apos;u büyütmeden önce instance sayısını sınırlamak daha sağlıklı.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pool_pre_ping=True&lt;/code&gt; da kritik. Cloud Run instance&apos;ları cold start sonrası uyuyabiliyor, bağlantı kopuk olabiliyor; pre-ping olmazsa ilk istek hata yiyor.&lt;/p&gt;
&lt;h2 id=&quot;private-ip-isteyenler-için-kısa-not&quot;&gt;Private IP isteyenler için kısa not&lt;a href=&quot;#private-ip-isteyenler-için-kısa-not&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Eğer Cloud SQL örneği sadece private IP ile erişilebiliyorsa, Cloud Run&apos;ın VPC&apos;ye girmesi lazım. Bunun için Serverless VPC Access connector oluşturuyoruz ve deploy sırasında &lt;code&gt;--vpc-connector&lt;/code&gt; ile bağlıyoruz. Bu yazıda detaya girmeyeceğim ama unutmayın: socket yöntemi public IP yokken bile çalışır, çünkü Cloud SQL Auth Proxy GCP içinden konuşuyor. Yani private IP zorunluluğu güvenlik gerekçesiyle değilse, socket genelde yeterli.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;connection refused&lt;/code&gt; hatası&lt;/strong&gt;: Çoğunlukla &lt;code&gt;--add-cloudsql-instances&lt;/code&gt; adı yanlış yazılmış veya servis hesabında &lt;code&gt;roles/cloudsql.client&lt;/code&gt; rolü yok. Bir de Cloud SQL Admin API&apos;nin açık olduğunu kontrol edin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;too many connections&lt;/code&gt;&lt;/strong&gt;: Pool size&apos;ı küçültün, &lt;code&gt;--max-instances&lt;/code&gt; koyun. Gerekirse Cloud SQL tarafında &lt;code&gt;max_connections&lt;/code&gt; flag&apos;ini artırın ama bu ikinci adım; önce uygulama tarafını disipline edin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection timeout&lt;/strong&gt;: Cold start sırasında Cloud SQL Auth Proxy&apos;nin ayağa kalkması bir-iki saniye sürebiliyor. Container&apos;ın &lt;code&gt;--timeout&lt;/code&gt; değerini ve client tarafındaki connect timeout&apos;u makul tutun, &lt;code&gt;pool_pre_ping&lt;/code&gt; zaten bayat soketleri eler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plaintext parola&lt;/strong&gt;: &lt;code&gt;--set-env-vars=DB_PASS=...&lt;/code&gt; yazma alışkanlığı çok yaygın. Loglara, revision history&apos;ye sızar. Secret Manager&apos;a 30 saniye veriyorsunuz, ömür boyu rahat ediyorsunuz.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cloud Run + Cloud SQL ikilisi doğru kurulduğunda çok rahat çalışıyor; iş, ölçeklenmenin veritabanı tarafında yarattığı baskıyı ciddiye almakta. Bana sorarsanız küçük pool, sıkı &lt;code&gt;max-instances&lt;/code&gt; ve Secret Manager üçlüsü neredeyse her senaryoya yetiyor; gerisi konfor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-python-listeyi-parcalara-bolme/</id>
    <title>Python&#039;da Listeyi Parçalara Bölmek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-python-listeyi-parcalara-bolme/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Doruk Tunç</name></author>
    <summary>Python listelerini sabit boyutlu parçalara bölmenin pratik yollarını, ne zaman hangisini seçmemiz gerektiğini ve sık karşılaşılan tuzakları anlatır.</summary>
    <content type="html">&lt;p&gt;Merhabalar, bu yazımda Python&apos;da bir listeyi daha küçük parçalara nasıl böldüğümüze bakacağız. Konu kulağa basit geliyor olabilir - aslında bir noktaya kadar öyle de - ama gerçek hayatta toplu veritabanı insert&apos;i, API sayfalama, paralel işleme gibi yerlerde tam olarak hangi yöntemi seçtiğin epey fark yaratıyor. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;Senaryo şu: elinizde 100 bin kayıt var, bunları veritabanına 1000&apos;lik gruplar halinde basmak istiyorsunuz. Ya da bir API rate limit&apos;i var, dakikada en fazla 50 istek geçebiliyor. Ya da &lt;code&gt;concurrent.futures&lt;/code&gt; ile paralelleştireceğiniz bir iş var, her worker&apos;a ayrı bir parça vermeniz gerekiyor. Hepsi aynı temel probleme dönüyor: listeyi parçalara böl.&lt;/p&gt;
&lt;h2 id=&quot;en-klasik-yol-list-comprehension-ile-slicing&quot;&gt;En klasik yol: list comprehension ile slicing&lt;a href=&quot;#en-klasik-yol-list-comprehension-ile-slicing&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aklınıza gelen ilk yöntem muhtemelen şu olur, ki bence çoğu zaman da en doğrusu budur:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def parcala(liste, boyut):
    return [liste[i:i + boyut] for i in range(0, len(liste), boyut)]

veri = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(parcala(veri, 3))
# [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Üç satır, okunaklı, sürpriz yok. Son parça eksik kalırsa kalır, kimseyi rahatsız etmez. Liste küçük-orta büyüklükteyse (diyelim 100 bin elemana kadar) bu yaklaşımı bırakıp başka bir şeye geçmek için ciddi bir gerekçeniz olmalı.&lt;/p&gt;
&lt;h2 id=&quot;bellek-dostu-sürüm-generator&quot;&gt;Bellek dostu sürüm: generator&lt;a href=&quot;#bellek-dostu-sürüm-generator&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Liste milyonlarca elemana çıkıyorsa veya daha kötüsü dosyadan satır satır okuyorsanız, hepsini bir anda parçalara bölüp belleğe almak israf. Generator burada işi çözüyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def parcala_gen(liste, boyut):
    for i in range(0, len(liste), boyut):
        yield liste[i:i + boyut]

for parca in parcala_gen(range(1_000_000), 10_000):
    # her seferinde sadece bir parca bellekte
    toplam = sum(parca)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tek değişiklik &lt;code&gt;return&lt;/code&gt; yerine &lt;code&gt;yield&lt;/code&gt;. Ama davranış tamamen başka: artık parçalar gerektiği anda üretiliyor, hepsi aynı anda bellekte durmuyor. Bunu özellikle ETL benzeri pipeline&apos;larda sıkça kullanırım.&lt;/p&gt;
&lt;h2 id=&quot;python-312-ile-gelen-itertoolsbatched&quot;&gt;Python 3.12 ile gelen &lt;code&gt;itertools.batched&lt;/code&gt;&lt;a href=&quot;#python-312-ile-gelen-itertoolsbatched&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Eğer 3.12 veya üstündeyseniz, standart kütüphane bu işi sizin için zaten yapıyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from itertools import batched

veri = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for parca in batched(veri, 3):
    print(parca)
# (1, 2, 3)
# (4, 5, 6)
# (7, 8, 9)
# (10,)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dikkat: parçalar tuple olarak dönüyor, list değil. Liste lazımsa &lt;code&gt;list(parca)&lt;/code&gt; yapacaksınız. Şahsen bu küçük detayı ilk gördüğümde &apos;ya niye?&apos; demiştim, ama tuple daha hafif ve immutable olduğu için aslında mantıklı bir tercih.&lt;/p&gt;
&lt;h2 id=&quot;indekslenemeyen-şeyler-için-itertoolsislice&quot;&gt;Indekslenemeyen şeyler için: &lt;code&gt;itertools.islice&lt;/code&gt;&lt;a href=&quot;#indekslenemeyen-şeyler-için-itertoolsislice&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Elinizdeki şey bir liste değil de bir generator veya iterator ise, &lt;code&gt;liste[i:i+n]&lt;/code&gt; çalışmaz - çünkü slicing yok. İşte &lt;code&gt;islice&lt;/code&gt; tam buraya:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from itertools import islice

def parcala_iter(iterable, boyut):
    it = iter(iterable)
    while True:
        parca = list(islice(it, boyut))
        if not parca:
            break
        yield parca
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu fonksiyon hem listeyi yer hem de bir DB cursor&apos;unu, bir generator&apos;ı, bir dosya okuyucuyu... her türlü iterable&apos;ı parçalara böler. 3.12 öncesinde bence en taşınabilir çözüm budur.&lt;/p&gt;
&lt;h2 id=&quot;numpy-ile-sayısal-veri&quot;&gt;NumPy ile sayısal veri&lt;a href=&quot;#numpy-ile-sayısal-veri&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sayısal verilerle uğraşıyorsanız ve zaten NumPy projedeyse, &lt;code&gt;array_split&lt;/code&gt; özellikle &apos;N eşit parçaya böl&apos; senaryolarında çok temiz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

dizi = np.arange(10)
for p in np.array_split(dizi, 3):
    print(p)
# [0 1 2 3]
# [4 5 6]
# [7 8 9]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eleman sayısı parça sayısına tam bölünmediğinde, &lt;code&gt;array_split&lt;/code&gt; farkı ilk parçalara dağıtır. &lt;code&gt;np.split&lt;/code&gt; ise tam bölünmüyorsa direkt hata verir, dikkat.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;chunk_size=0&lt;/code&gt; veya negatif değer geçmek&lt;/strong&gt;: &lt;code&gt;range(0, n, 0)&lt;/code&gt; ValueError fırlatır, ama negatifte sessizce boş liste döner. Fonksiyonun başında &lt;code&gt;if boyut &amp;lt; 1: raise ValueError(...)&lt;/code&gt; koymak ileride saatlerce debug etmekten kurtarır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generator&apos;ı iki kez tüketmeye çalışmak&lt;/strong&gt;: Generator bir kez biter ve biter. Üzerinden iki kez geçmeniz gerekiyorsa ya &lt;code&gt;list()&lt;/code&gt; ile materialize edin ya da &lt;code&gt;itertools.tee&lt;/code&gt; kullanın - ama &lt;code&gt;tee&lt;/code&gt; de sonuçta belleğe alıyor, sihir yok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;batched&lt;/code&gt; çıktısını liste sanmak&lt;/strong&gt;: Tuple döner. &lt;code&gt;parca.append(...)&lt;/code&gt; yazınca AttributeError yer, ekrana bakar &apos;ne oldu&apos; dersiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Çok küçük parçalarla DB&apos;ye basmak&lt;/strong&gt;: 50 kayıt için her seferinde yeni bir transaction açıyorsanız, asıl darboğaz parçalama değil, transaction overhead&apos;i. Batch boyutunu 500-5000 arasında tutmak çoğu PostgreSQL/MySQL için tatlı nokta.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;hangisini-seçmeli&quot;&gt;Hangisini seçmeli?&lt;a href=&quot;#hangisini-seçmeli&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tecrübeyle sabittir ki, %80 vakada list comprehension yeter. Liste devasa veya iterable indekslenemiyorsa generator&apos;a (&lt;code&gt;islice&lt;/code&gt;) geçin. Python 3.12 elinizdeyse &lt;code&gt;batched&lt;/code&gt; standart kütüphane çözümü olarak en temizi. NumPy zaten projedeyse ve sayısal veriyse &lt;code&gt;array_split&lt;/code&gt; doğal seçim.&lt;/p&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kısacası, listeyi parçalara bölmek için tek doğru yöntem yok; durum ne istiyorsa o. Bence aklınızda iki ana eksen olsun: &lt;strong&gt;bellek mi sıkışık, yoksa kod sadeliği mi öncelik&lt;/strong&gt;. İkisinden birini seçince geri kalanı kendiliğinden netleşiyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-python-sdk-ile-azure-blob-storage-yasam-dongusu/</id>
    <title>Python SDK ile Azure Blob Storage Yaşam Döngüsü Yönetimi</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-python-sdk-ile-azure-blob-storage-yasam-dongusu/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Hasan Aydın</name></author>
    <summary>Azure Blob Storage yaşam döngüsü politikalarını Python SDK ile nasıl yönetiriz, hangi katmana ne zaman geçirilir, dikkat edilmesi gereken tuzaklar.</summary>
    <content type="html">&lt;p&gt;Merhabalar, bu yazıda Azure Blob Storage&apos;ın yaşam döngüsü yönetimi (lifecycle management) tarafına Python SDK üzerinden bakacağız. Konu kulağa kuru gelebilir ama inanın bana, ay sonu faturasına bakınca insanın yüzünü güldüren tek özelliklerden biri bu. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;Şöyle bir senaryo: bir uygulama log&apos;larını, kullanıcıların yüklediği medya dosyalarını, bir de gece bittikten sonra kimsenin dönüp bakmadığı geçici raporları aynı container&apos;a atıyorsunuz. İlk gün hepsi sıcak (Hot) katmanda, mantıklı. Ama bir ay sonra hâlâ Hot&apos;ta duruyor olmaları hiç mantıklı değil. İşte yaşam döngüsü politikaları tam burada devreye giriyor.&lt;/p&gt;
&lt;h2 id=&quot;katmanları-kısaca-hatırlayalım&quot;&gt;Katmanları kısaca hatırlayalım&lt;a href=&quot;#katmanları-kısaca-hatırlayalım&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Azure&apos;da dört erişim katmanı var ve fiyat farkı ciddi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hot&lt;/strong&gt;: Depolama pahalı, erişim ucuz. Sürekli okuduğunuz veriler için.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cool&lt;/strong&gt;: Depolama daha ucuz, erişim biraz daha pahalı. En az 30 gün bekleyecek veriler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold&lt;/strong&gt;: Daha da ucuz. 90 gün ve üzeri bekleyecekler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archive&lt;/strong&gt;: En ucuz depolama, en pahalı erişim. Geri çağırması saatler sürebilir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Archive ile Hot arasındaki depolama maliyet farkı kabaca on katı civarında. Ama Archive&apos;dan bir blob&apos;u geri istediğinizde hem para hem zaman ödüyorsunuz; bunu bir kenara not edin, birazdan dönece­ğiz.&lt;/p&gt;
&lt;h2 id=&quot;politikayı-sdk-ile-kurmak&quot;&gt;Politikayı SDK ile kurmak&lt;a href=&quot;#politikayı-sdk-ile-kurmak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yaşam döngüsü politikası storage account seviyesinde tanımlanır, container seviyesinde değil. Bu yüzden management SDK kullanıyoruz, blob SDK değil. Önce gerekli paketleri kuruyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install azure-storage-blob azure-mgmt-storage azure-identity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Şimdi bir politika tanımlayalım. Üç farklı prefix için üç farklı kuralımız olsun: &lt;code&gt;logs/&lt;/code&gt; altındakiler kademe kademe arşive gitsin, &lt;code&gt;temp/&lt;/code&gt; altındakiler ertesi gün silinsin, &lt;code&gt;media/&lt;/code&gt; altındakiler bir hafta sonra Cool&apos;a düşsün.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from azure.identity import DefaultAzureCredential
from azure.mgmt.storage import StorageManagementClient
from azure.mgmt.storage.models import (
    ManagementPolicy, ManagementPolicySchema, ManagementPolicyRule,
    ManagementPolicyDefinition, ManagementPolicyFilter,
    ManagementPolicyAction, ManagementPolicyBaseBlob,
    DateAfterModification,
)

credential = DefaultAzureCredential()
client = StorageManagementClient(credential, &apos;&amp;lt;SubId&amp;gt;&apos;)

log_rule = ManagementPolicyRule(
    name=&apos;log-tiering&apos;,
    enabled=True,
    type=&apos;Lifecycle&apos;,
    definition=ManagementPolicyDefinition(
        filters=ManagementPolicyFilter(
            blob_types=[&apos;blockBlob&apos;],
            prefix_match=[&apos;logs/&apos;],
        ),
        actions=ManagementPolicyAction(
            base_blob=ManagementPolicyBaseBlob(
                tier_to_cool=DateAfterModification(days_after_modification_greater_than=30),
                tier_to_archive=DateAfterModification(days_after_modification_greater_than=90),
                delete=DateAfterModification(days_after_modification_greater_than=365),
            ),
        ),
    ),
)

policy = ManagementPolicy(policy=ManagementPolicySchema(rules=[log_rule]))

client.management_policies.create_or_update(
    resource_group_name=&apos;&amp;lt;RgName&amp;gt;&apos;,
    account_name=&apos;&amp;lt;AcctName&amp;gt;&apos;,
    management_policy_name=&apos;default&apos;,  # bu isim sabit, &apos;default&apos; olmak zorunda
    properties=policy,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada dikkat: &lt;code&gt;management_policy_name&lt;/code&gt; her zaman &lt;code&gt;&apos;default&apos;&lt;/code&gt;. Buraya yaratıcı bir isim yazarsanız Azure kabul etmez. Bir de filtreleri prefix bazlı yazıyoruz; yani container içinde mantıklı bir klasör yapısı kurduysanız hayatınız çok kolaylaşır.&lt;/p&gt;
&lt;h2 id=&quot;yerleşik-politikanın-yetmediği-yer&quot;&gt;Yerleşik politikanın yetmediği yer&lt;a href=&quot;#yerleşik-politikanın-yetmediği-yer&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yerleşik politika sadece son değişiklik veya oluşturulma zamanına bakar. Ama bazen &lt;code&gt;son erişim&lt;/code&gt;, dosya boyutu ya da metadata&apos;ya göre karar vermek isteriz. Bu durumda blob SDK ile kendi mantığımızı yazıp bir Azure Function ile zamanlamak en temiz çözüm:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from azure.storage.blob import BlobServiceClient
from datetime import datetime, timedelta, timezone

svc = BlobServiceClient(account_url=&apos;&amp;lt;Url&amp;gt;&apos;, credential=credential)

def cool_down_idle(container, days=30):
    cont = svc.get_container_client(container)
    cutoff = datetime.now(timezone.utc) - timedelta(days=days)
    for blob in cont.list_blobs(include=[&apos;metadata&apos;]):
        if blob.last_accessed_on and blob.last_accessed_on &amp;lt; cutoff:
            if blob.blob_tier == &apos;Hot&apos;:
                cont.get_blob_client(blob.name).set_standard_blob_tier(&apos;Cool&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;last_accessed_on&lt;/code&gt; özelliği için storage account üzerinde &lt;strong&gt;access time tracking&lt;/strong&gt;&apos;i açmış olmanız gerekiyor; varsayılanda kapalıdır. Açmadan bu kontrolü yazarsanız her blob için &lt;code&gt;None&lt;/code&gt; döner ve hiçbir şey hareket etmez.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Politikayı kurar kurmaz tetiklenmesini beklemek&lt;/strong&gt;: Azure politikayı bir kerede koşturmaz, günde bir kez değerlendirir. Test ederken sabırlı olun, dakikalar içinde sonuç beklemeyin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archive&apos;a geçirip ertesi gün geri istemek&lt;/strong&gt;: Hot&apos;a rehydrate etmek hem saatler sürer hem de okuduğunuz veri başına ücretlendirilir. Archive&apos;a sadece gerçekten &apos;unutulacak&apos; veri gönderin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Erken silme cezasını unutmak&lt;/strong&gt;: Cool&apos;da 30, Cold&apos;da 90, Archive&apos;da 180 günden önce silerseniz Azure kalan günlerin maliyetini sizden almaya devam eder. Yaşam döngüsünüzü buna göre kurun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Snapshot&apos;ları temizlememek&lt;/strong&gt;: Base blob&apos;u Cool&apos;a çekersiniz ama snapshot&apos;lar Hot&apos;ta kalır. &lt;code&gt;snapshot&lt;/code&gt; action&apos;ı da politikaya eklemeyi unutmayın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Politikayı kurup hiç doğrulamamak&lt;/strong&gt;: &lt;code&gt;list_blobs&lt;/code&gt; ile katman dağılımını periyodik raporlayın. Bence bu olmadan maliyet kazandığınızı gerçekten bilemezsiniz.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yaşam döngüsü yönetimi Azure&apos;da en hızlı geri dönüş veren optimizasyonlardan biri; bir öğleden sonra ayırıp doğru kurarsanız, ay sonunda farkını net görüyorsunuz. Şahsen ben yerleşik politikayı temel kuralları için, blob SDK&apos;sını da metadata ya da boyut tabanlı özel mantık için kullanmayı tercih ediyorum - ikisi birbirini güzel tamamlıyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-postgresql-array-tipleri-ve-sorgular/</id>
    <title>PostgreSQL Array Tipleri ve Sorgular</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-postgresql-array-tipleri-ve-sorgular/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Burak Aslan</name></author>
    <summary>PostgreSQL array veri tipi: kullanımı, operatörleri, GIN indeksleri ve gerçek senaryolarda ne zaman tercih edilir sorularına pratik cevaplar.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda PostgreSQL&apos;in çoğu zaman göz ardı edilen ama doğru yerde kullanıldığında ekstra tablolardan kurtaran array veri tipine bakacağız. ORM&apos;ler bu tipi nadiren first-class vatandaş olarak gördüğü için biraz unutulmuş bir özellik gibi geliyor; oysa etiket, telefon numarası, kısa permission listesi gibi yerlerde junction table açmaktan çok daha pratik. Hadi başlayalım.&lt;/p&gt;
&lt;h2 id=&quot;array-nedir-ne-zaman-tercih-edilir&quot;&gt;Array nedir, ne zaman tercih edilir?&lt;a href=&quot;#array-nedir-ne-zaman-tercih-edilir&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PostgreSQL&apos;de bir kolon, herhangi bir scalar tipin dizisi (array) olabilir: &lt;code&gt;TEXT[]&lt;/code&gt;, &lt;code&gt;INTEGER[]&lt;/code&gt;, hatta çok boyutlu &lt;code&gt;INTEGER[][]&lt;/code&gt;. Yani aynı kolonda birden fazla değer tutabiliyorsunuz, ayrıca bunları sorgulayabileceğiniz operatör seti de epey zengin.&lt;/p&gt;
&lt;p&gt;Peki her duruma uygun mu? Bence şu basit kontrol işe yarıyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eleman sayısı küçük ve nispeten sabit mi (etiketler, kısa rol listeleri, telefon numaraları)? Array uygun.&lt;/li&gt;
&lt;li&gt;Elemanların kendi başına foreign key, ayrı kolonlar veya per-eleman timestamp gibi metadata&apos;sı var mı? Array uygun değil, junction table açın.&lt;/li&gt;
&lt;li&gt;İç içe karmaşık yapı mı tutacaksınız? &lt;code&gt;JSONB&lt;/code&gt; daha doğru cevap.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Şahsi kanaatim, &lt;code&gt;tags&lt;/code&gt; kolonu için array; &lt;code&gt;comments&lt;/code&gt; ilişkisi için ayrı tablo. Bu sınır net olduğunda hayat kolaylaşıyor.&lt;/p&gt;
&lt;h2 id=&quot;tablo-ekleme-erişim&quot;&gt;Tablo, ekleme, erişim&lt;a href=&quot;#tablo-ekleme-erişim&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hadi minimum bir örnekle bakalım. Bir &lt;code&gt;articles&lt;/code&gt; tablosu kuruyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    tags TEXT[],                  -- metin dizisi
    ratings INTEGER[],            -- tam sayi dizisi
    published_dates DATE[]
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Veri eklerken iki yazım var. İlki literal sözdizimi, ikincisi &lt;code&gt;ARRAY&lt;/code&gt; constructor; ben okunabilirliği için ikincisini tercih ediyorum:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- literal yazim
INSERT INTO articles (title, tags, ratings)
VALUES (&apos;PostgreSQL Rehberi&apos;, &apos;{postgresql,veritabani,sql}&apos;, &apos;{5,4,5}&apos;);

-- ARRAY constructor (daha okunakli)
INSERT INTO articles (title, tags, ratings)
VALUES (&apos;Bir Baska Yazi&apos;, ARRAY[&apos;tech&apos;, &apos;programlama&apos;], ARRAY[4, 3, 5]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Erişim 1-indexli, buraya dikkat: &lt;code&gt;tags[1]&lt;/code&gt; ilk eleman demek. Slice de var, &lt;code&gt;tags[1:2]&lt;/code&gt; ilk iki elemanı veriyor. Negatif indeks yok; son elemana ulaşmak için &lt;code&gt;tags[array_length(tags, 1)]&lt;/code&gt; yazıyoruz. Biraz çirkin, evet.&lt;/p&gt;
&lt;h2 id=&quot;operatörler-ve-sorgular&quot;&gt;Operatörler ve sorgular&lt;a href=&quot;#operatörler-ve-sorgular&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İşin asıl gücü buradaki üç operatörde gizli: &lt;code&gt;@&amp;gt;&lt;/code&gt; (içerir), &lt;code&gt;&amp;lt;@&lt;/code&gt; (içerilir), &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; (kesişir). Bir de eski güzel &lt;code&gt;ANY&lt;/code&gt; var.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- &apos;postgresql&apos; etiketini iceren yazilar
SELECT * FROM articles WHERE tags @&amp;gt; ARRAY[&apos;postgresql&apos;];

-- &apos;postgresql&apos; veya &apos;mysql&apos; etiketinden herhangi biri varsa
SELECT * FROM articles WHERE tags &amp;amp;&amp;amp; ARRAY[&apos;postgresql&apos;, &apos;mysql&apos;];

-- her iki etiketi de tasiyorsa
SELECT * FROM articles WHERE tags @&amp;gt; ARRAY[&apos;postgresql&apos;, &apos;veritabani&apos;];

-- tek deger kontrolu
SELECT * FROM articles WHERE &apos;postgresql&apos; = ANY(tags);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki kritik nokta şu: &lt;code&gt;= ANY(tags)&lt;/code&gt; ile &lt;code&gt;tags @&amp;gt; ARRAY[&apos;postgresql&apos;]&lt;/code&gt; mantıken aynı şeyi yapıyor ama indeks açısından eşit değiller. GIN indeksi &lt;code&gt;@&amp;gt;&lt;/code&gt; ve &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; ile çalışırken, &lt;code&gt;= ANY&lt;/code&gt; query planner&apos;a göre bazen indekse uğramayabiliyor. Tecrübem o yönde, planner&apos;a güvenmeyin, &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; çekin.&lt;/p&gt;
&lt;p&gt;Toparlama tarafında da &lt;code&gt;array_agg&lt;/code&gt; çok işe yarıyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- aktif kullanicilarin isimlerini tek diziye topla
SELECT array_agg(name ORDER BY created_at DESC)
FROM users WHERE active = true;

-- diziyi satirlara ac
SELECT id, unnest(tags) AS tag FROM articles;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;unnest&lt;/code&gt;, array sorgularında en sık başvurduğum fonksiyon. Tag bulutu, frekans sayımı, distinct etiket listesi - hepsi bunun üzerine kurulu.&lt;/p&gt;
&lt;h2 id=&quot;gin-indeksi-olmadan-olmaz&quot;&gt;GIN indeksi olmadan olmaz&lt;a href=&quot;#gin-indeksi-olmadan-olmaz&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Birkaç bin satırda her şey hızlı görünür. Yüz binlere çıktığınızda containment sorgusu sequential scan&apos;e döner ve canınız yanar. Çare basit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX idx_articles_tags ON articles USING GIN(tags);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; ile &lt;code&gt;Bitmap Index Scan on idx_articles_tags&lt;/code&gt; satırını görmelisiniz. GiST de bir alternatif, daha küçük ama daha yavaş; tag aramaları için ben GIN&apos;i öneririm.&lt;/p&gt;
&lt;p&gt;İfade indeksi de bazen lazım oluyor. Mesela &apos;üçten fazla etiketi olan yazılar&apos; sorgusu için:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX idx_articles_tag_count ON articles(array_length(tags, 1));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tek elemanı in-place güncellemek&lt;/strong&gt;: &lt;code&gt;UPDATE articles SET tags[1] = &apos;yeni&apos; WHERE id = 1&lt;/code&gt; çalışır gibi görünür ama PostgreSQL her durumda tüm array&apos;i yeniden yazar. Bunun yerine &lt;code&gt;array_replace&lt;/code&gt;, &lt;code&gt;array_append&lt;/code&gt;, &lt;code&gt;array_remove&lt;/code&gt; kullanın; niyetiniz daha okunaklı olur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GIN indeksini unutmak&lt;/strong&gt;: Containment sorgusu var ve indeks yoksa veri büyüdükçe yavaşlama lineer değil, hissedilir. Tablo 10 bin satıra ulaştığında indeksi atın, sonra atmadığınıza pişman olmayın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Array&apos;i ilişki yerine kullanmak&lt;/strong&gt;: Elemanların kendi metadata&apos;sı varsa (oluşturulma tarihi, kim ekledi, ayrı silinebilir mi) array değil, ayrı tablo. &apos;Az kalsın junction&apos;dan kurtulurum&apos; diye başlayan her hikâye kötü bitiyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;= ANY&lt;/code&gt; ile indeks beklentisi&lt;/strong&gt;: Çalışıyor ama planner her zaman indekse gitmiyor. Performans hassassa &lt;code&gt;@&amp;gt;&lt;/code&gt; tercih edin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aşırı büyük array&apos;ler&lt;/strong&gt;: Bir satırda yüzlerce eleman tutmaya başladığınızda hem TOAST&apos;a düşersiniz hem de update maliyeti patlar. Sınır koyun.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;doğrulama&quot;&gt;Doğrulama&lt;a href=&quot;#doğrulama&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lokalde Docker ile bir Postgres ayağa kaldırın, küçük bir tablo doldurun ve &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; ile gerçekten indeksin devreye girip girmediğine bakın:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d --name pg-test -e POSTGRES_PASSWORD=test -p 5432:5432 postgres:16
psql -h localhost -U postgres -c &amp;quot;EXPLAIN ANALYZE SELECT * FROM articles WHERE tags @&amp;gt; ARRAY[&apos;postgresql&apos;];&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Çıktıda &lt;code&gt;Bitmap Index Scan&lt;/code&gt; görüyorsanız işiniz tamam; &lt;code&gt;Seq Scan&lt;/code&gt; görüyorsanız ya indeks eksik ya da tablo henüz çok küçük (planner küçük tablolarda zaten sequential tercih eder).&lt;/p&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bu yazıda PostgreSQL array tipinin kullanımına, operatörlerine ve indekslemenin neden hayati olduğuna baktık. Bence array, etiket ve kısa liste senaryolarında junction table&apos;dan çok daha pratik; ama ilişkinin kendi metadata&apos;sı varsa zorlamayın, ayrı tablo açın. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-postgresql-sema-migrasyonlarini-yonetmek/</id>
    <title>PostgreSQL Şema Migrasyonlarını Yönetmek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-postgresql-sema-migrasyonlarini-yonetmek/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Yasemin Akkaya</name></author>
    <summary>PostgreSQL şema migrasyonlarını sağlam yürütmek için Flyway, Liquibase ve Alembic seçenekleri ve sıfır kesintili stratejilere kısa bir rehber.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda PostgreSQL şema migrasyonlarını biraz toparlayacağız. Konuyu sevdiğim için anlatacağım ama uyarayım: migrasyon, kod kadar kolay konuşulan bir alan değil. Bir tablo silindi, bir sütun tipi değişti derken canlıdaki sorgular bir anda &apos;relation does not exist&apos; demeye başlıyor. Hadi nasıl bu duvara toslamadan ilerleriz, ona bakalım.&lt;/p&gt;
&lt;h2 id=&quot;neden-bir-araca-ihtiyacımız-var&quot;&gt;Neden bir araca ihtiyacımız var?&lt;a href=&quot;#neden-bir-araca-ihtiyacımız-var&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İlk akla gelen soru bu. &apos;Ben SQL dosyalarımı kendim yönetirim&apos; diyebilirsiniz, ama birkaç geliştiriciyle çalışmaya başlayınca işin rengi değişiyor. Hangi migration ne zaman çalıştı? Staging&apos;de var mı, prod&apos;da var mı? Hangi sıraya göre uygulanmalı? Bu soruların cevabını el ile vermeye çalışmak, bence kısa sürede ekibi tüketir.&lt;/p&gt;
&lt;p&gt;Migrasyon araçları aslında basit bir şey yapıyor: veritabanında küçük bir tablo (&lt;code&gt;flyway_schema_history&lt;/code&gt;, &lt;code&gt;alembic_version&lt;/code&gt; gibi) tutuyor ve hangi script&apos;in uygulandığını oraya yazıyor. Geri kalan her şey bu tablonun üzerine inşa ediliyor.&lt;/p&gt;
&lt;h2 id=&quot;üç-popüler-seçenek&quot;&gt;Üç popüler seçenek&lt;a href=&quot;#üç-popüler-seçenek&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Türk ekiplerinde en sık karşılaştığım üçlü Flyway, Liquibase ve Alembic. Şahsi kanaatim şu: SQL&apos;inizi olduğu gibi yazmak istiyorsanız Flyway, dilden bağımsız bir DSL istiyorsanız Liquibase, Python ekosistemindeyseniz Alembic. Hadi her birine kısaca bakalım.&lt;/p&gt;
&lt;h3 id=&quot;flyway&quot;&gt;Flyway&lt;a href=&quot;#flyway&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Flyway&apos;in en güzel yanı saf SQL ile çalışabilmesi. Dosya isimlendirmesi &lt;code&gt;V&amp;lt;sürüm&amp;gt;__&amp;lt;açıklama&amp;gt;.sql&lt;/code&gt; şeklinde:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- V1__kullanicilar_tablosu.sql
CREATE TABLE kullanicilar (
    id SERIAL PRIMARY KEY,
    eposta VARCHAR(255) UNIQUE NOT NULL,
    olusturulma TIMESTAMP DEFAULT NOW()
);

-- V2__kullanicilara_isim_ekle.sql
ALTER TABLE kullanicilar ADD COLUMN isim VARCHAR(100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Çalıştırması da bir o kadar düz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flyway migrate     # bekleyen migrasyonlari uygula
flyway info        # durum ozeti
flyway repair      # bozulan checksum&apos;lari onar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;alembic&quot;&gt;Alembic&lt;a href=&quot;#alembic&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python ile çalışıyorsanız, özellikle SQLAlchemy modelleriniz varsa Alembic&apos;in &lt;code&gt;--autogenerate&lt;/code&gt; özelliği ciddi zaman kazandırır. Modeli değiştiriyorsunuz, Alembic farkı çıkarıp size bir migration dosyası üretiyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;alembic revision --autogenerate -m &amp;quot;kullanicilar tablosunu olustur&amp;quot;
alembic upgrade head
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Üretilen dosya da okunaklı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# migrations/versions/001_kullanicilar_tablosu.py
from alembic import op
import sqlalchemy as sa


def upgrade():
    # Yeni tabloyu olusturuyoruz
    op.create_table(
        &apos;kullanicilar&apos;,
        sa.Column(&apos;id&apos;, sa.Integer(), primary_key=True),
        sa.Column(&apos;eposta&apos;, sa.String(255), nullable=False, unique=True),
    )


def downgrade():
    op.drop_table(&apos;kullanicilar&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;downgrade&lt;/code&gt; kısmını ihmal etmeyin; canlıda &apos;şuna geri dön&apos; demek bazen hayat kurtarıyor.&lt;/p&gt;
&lt;h2 id=&quot;sıfır-kesintili-migrasyon&quot;&gt;Sıfır kesintili migrasyon&lt;a href=&quot;#sıfır-kesintili-migrasyon&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bence yazının asıl önemli kısmı burası. Küçük bir tabloda &lt;code&gt;ALTER TABLE&lt;/code&gt; neyse de, milyonlarca satırlı bir tabloda yanlış komut size dakikalarca kilit getirir. Birkaç altın kural var.&lt;/p&gt;
&lt;p&gt;İlk olarak index&apos;leri her zaman &lt;code&gt;CONCURRENTLY&lt;/code&gt; ile oluşturun. Bu, yazma işlemlerini bloklamaz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX CONCURRENTLY idx_kullanicilar_eposta
    ON kullanicilar(eposta);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İkincisi, foreign key gibi kısıtları önce &lt;code&gt;NOT VALID&lt;/code&gt; ile ekleyip sonra ayrı bir adımda doğrulayın. Böylece tablo taraması kilit tutmadan ilerler:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ALTER TABLE siparisler
    ADD CONSTRAINT fk_kullanici
    FOREIGN KEY (kullanici_id) REFERENCES kullanicilar(id) NOT VALID;

ALTER TABLE siparisler VALIDATE CONSTRAINT fk_kullanici;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bir sütun tipini değiştirmek istiyorsanız, tek &lt;code&gt;ALTER COLUMN ... TYPE&lt;/code&gt; ile gitmek yerine adımları parçalayın: yeni sütunu nullable olarak ekleyin, veriyi parça parça doldurun, sonra &lt;code&gt;NOT NULL&lt;/code&gt; koyun, en son eski sütunu düşürün. Uzun gibi görünüyor, ama canlıyı düşürmüyor.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ALTER TABLE ... ADD COLUMN ... NOT NULL DEFAULT ...&lt;/code&gt; PostgreSQL 10 ve öncesinde tabloyu baştan yazar&lt;/strong&gt;: Yeni sürümlerde bu çoktan optimize edildi, ama hâlâ eski bir cluster&apos;la çalışıyorsanız ekleyip backfill etmek daha güvenli.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;CREATE INDEX&lt;/code&gt; (CONCURRENTLY olmadan) yazma kilidi alır&lt;/strong&gt;: Trafik varken çalıştırırsanız uygulama saniyelerce bekler. Üretimde bunu yapmak istemezsiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migration dosyasını sonradan düzenlemek&lt;/strong&gt;: Flyway checksum tutar, dosyayı değiştirirseniz bir sonraki &lt;code&gt;migrate&lt;/code&gt; komutu hata verir. Yapacağınız değişikliği yeni bir versiyon olarak ekleyin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geri alma planı olmadan deploy&lt;/strong&gt;: &lt;code&gt;downgrade&lt;/code&gt; veya &lt;code&gt;down.sql&lt;/code&gt; dosyasını şart koşun. &apos;Geri dönmeyiz nasılsa&apos; demeyin, bir sefer dönmeniz gerektiğinde pişman olursunuz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uzun migrasyonu tek transaction&apos;a sıkıştırmak&lt;/strong&gt;: Milyonlarca satırlık &lt;code&gt;UPDATE&lt;/code&gt;&apos;i tek seferde çalıştırmak hem WAL&apos;ı şişirir hem replica&apos;ları geride bırakır. Batch&apos;lere bölün.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;doğrulama&quot;&gt;Doğrulama&lt;a href=&quot;#doğrulama&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Migrasyonu canlıya açmadan önce temiz bir Docker container&apos;ında deneyin. Bence bu adımı atlamak en pahalı kestirme:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d --name test-db -e POSTGRES_PASSWORD=test -p 5433:5432 postgres:16
sleep 5
flyway -url=jdbc:postgresql://localhost:5433/postgres migrate
psql -h localhost -p 5433 -U postgres -c &amp;quot;\dt&amp;quot;
docker rm -f test-db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aynı script&apos;i CI&apos;a koyduğunuzda her PR&apos;da otomatik koşar; üç dakika sonra &apos;bu migration sıfırdan açıyor mu?&apos; sorusunun net cevabı elinizde olur.&lt;/p&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PostgreSQL şema migrasyonlarını yönetmek aslında bir araç seçimi değil, bir disiplin meselesi. Aracı seçtikten sonra sıfır kesintili kalıpları öğrenmek, geri dönüş planını her zaman hazır tutmak ve değişikliği önce temiz bir veritabanında denemek kalıyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-mysql-de-adddate-ve-subdate-ile-tarih-aritmetigi/</id>
    <title>MySQL&#039;de ADDDATE() ve SUBDATE() ile Tarih Aritmetiği</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-mysql-de-adddate-ve-subdate-ile-tarih-aritmetigi/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Hasan Aydın</name></author>
    <summary>MySQL&#039;de ADDDATE() ve SUBDATE() fonksiyonlarıyla tarih ekleme ve çıkarma: tam söz dizimi, kısa-yol şekli ve DATE_ADD ile farkları.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda MySQL&apos;in en sık karşımıza çıkan ama bir o kadar da kafa karıştırabilen iki fonksiyonuna, &lt;code&gt;ADDDATE()&lt;/code&gt; ve &lt;code&gt;SUBDATE()&lt;/code&gt;&apos;e bakacağız. Tarih aritmetiği görünüşte basit bir konu; ne var ki MySQL&apos;in iki ayrı kullanım şekli sunması, bir de aynı işi yapan &lt;code&gt;DATE_ADD()&lt;/code&gt; / &lt;code&gt;DATE_SUB()&lt;/code&gt;&apos;ın varlığı, yeni başlayanı haklı olarak duraklatıyor. Hadi kafa karışıklığını dağıtalım.&lt;/p&gt;
&lt;h2 id=&quot;adddate-ne-işe-yarar&quot;&gt;ADDDATE() ne işe yarar?&lt;a href=&quot;#adddate-ne-işe-yarar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ADDDATE()&lt;/code&gt; bir tarihe belirli bir süre ekler. İki farklı çağırma biçimi var: ya &lt;code&gt;INTERVAL&lt;/code&gt; ifadesiyle ay, hafta, saat gibi birimler verirsiniz, ya da ikinci argümana düz bir tam sayı geçip &apos;şu kadar gün ekle&apos; dersiniz. İkincisi, yani kısa-yol biçimi, &lt;code&gt;ADDDATE()&lt;/code&gt;&apos;in &lt;code&gt;DATE_ADD()&lt;/code&gt;&apos;den ayrıldığı tek nokta.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Tam INTERVAL söz dizimi (DATE_ADD ile aynı)
ADDDATE(tarih, INTERVAL ifade birim)

-- Kısa-yol: N gün ekle
ADDDATE(tarih, gun_sayisi)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Birkaç örnekle netleştirelim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- 10 gun ekle (kisa-yol)
SELECT ADDDATE(&apos;2026-05-08&apos;, 10);
-- Sonuc: &apos;2026-05-18&apos;

-- 3 ay ekle
SELECT ADDDATE(&apos;2026-05-08&apos;, INTERVAL 3 MONTH);
-- Sonuc: &apos;2026-08-08&apos;

-- 2 saat 30 dakika ekle
SELECT ADDDATE(&apos;2026-05-08 09:00:00&apos;, INTERVAL &apos;2:30&apos; HOUR_MINUTE);
-- Sonuc: &apos;2026-05-08 11:30:00&apos;

-- Negatif tam sayi: aslinda cikarma yapar
SELECT ADDDATE(&apos;2026-05-08&apos;, -7);
-- Sonuc: &apos;2026-05-01&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Son örnek ilginç: ikinci argümana negatif değer verince fonksiyon sessizce çıkarma moduna geçiyor. Bence bu pratik ama kodunuzu okuyan biri için niyetinizi gizleyen bir kullanım; tarih çıkarmak istiyorsanız doğrudan &lt;code&gt;SUBDATE()&lt;/code&gt; çağırın, okunurluk artar.&lt;/p&gt;
&lt;h2 id=&quot;subdate-tarafı&quot;&gt;SUBDATE() tarafı&lt;a href=&quot;#subdate-tarafı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SUBDATE()&lt;/code&gt; aynı mantığın ters yüzü. INTERVAL&apos;la her birimi çıkarır, ya da kısa-yol biçiminde gün sayısı alır.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- 10 gun cikar
SELECT SUBDATE(&apos;2026-05-08&apos;, 10);
-- Sonuc: &apos;2026-04-28&apos;

-- 1 ay cikar
SELECT SUBDATE(&apos;2026-05-08&apos;, INTERVAL 1 MONTH);
-- Sonuc: &apos;2026-04-08&apos;

-- Bugunden 90 gun once
SELECT SUBDATE(NOW(), 90);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SUBDATE(NOW(), 90)&lt;/code&gt; tarzı kullanımlar raporlama sorgularında çok işinize yarar. Aşağıda buna döneceğiz.&lt;/p&gt;
&lt;h2 id=&quot;peki-dateaddden-farkı-ne&quot;&gt;Peki DATE_ADD()&apos;den farkı ne?&lt;a href=&quot;#peki-dateaddden-farkı-ne&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Şöyle ki: &lt;code&gt;ADDDATE(tarih, INTERVAL ...)&lt;/code&gt; ile &lt;code&gt;DATE_ADD(tarih, INTERVAL ...)&lt;/code&gt; birebir aynı şey. Tek fark, &lt;code&gt;ADDDATE()&lt;/code&gt;&apos;in tam sayı kısa-yol biçimini de kabul etmesi.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Bu ucu de ayni sonucu verir
SELECT ADDDATE(&apos;2026-05-08&apos;, 5);
SELECT ADDDATE(&apos;2026-05-08&apos;, INTERVAL 5 DAY);
SELECT DATE_ADD(&apos;2026-05-08&apos;, INTERVAL 5 DAY);
-- Hepsi: &apos;2026-05-13&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bana sorarsanız ekipte tek tip kullanım belirleyin: ya hep &lt;code&gt;DATE_ADD&lt;/code&gt; / &lt;code&gt;DATE_SUB&lt;/code&gt; kullanın (birim her zaman açık görünür), ya da &lt;code&gt;ADDDATE&lt;/code&gt; / &lt;code&gt;SUBDATE&lt;/code&gt;&apos;e geçin ve gün sayısı söz konusu olduğunda kısa-yolu serbest bırakın. Aynı kod tabanında ikisinin karışması okumayı zorlaştırıyor.&lt;/p&gt;
&lt;h2 id=&quot;pratik-son-tarih-hesabı&quot;&gt;Pratik: son tarih hesabı&lt;a href=&quot;#pratik-son-tarih-hesabı&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Görev tablosunda her satıra ait bir &apos;kaç gün içinde teslim&apos; alanı varsa, son tarihi anlık hesaplamak için &lt;code&gt;ADDDATE()&lt;/code&gt; biçilmiş kaftan:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE gorevler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    gorev_adi VARCHAR(100),
    olusturma_tarihi DATE,
    sure_gun INT
);

INSERT INTO gorevler (gorev_adi, olusturma_tarihi, sure_gun) VALUES
(&apos;Rapor yaz&apos;,         &apos;2026-05-08&apos;, 7),
(&apos;Kod incele&apos;,        &apos;2026-05-08&apos;, 3),
(&apos;Prod yayina al&apos;,    &apos;2026-05-08&apos;, 14);

SELECT
    gorev_adi,
    olusturma_tarihi,
    ADDDATE(olusturma_tarihi, sure_gun) AS son_tarih
FROM gorevler;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada ikinci argüman hard-coded değil, doğrudan kolondan geliyor. Bu, kısa-yol biçiminin gerçekten kazandığı yer; INTERVAL ile aynısını yapmak için dinamik SQL&apos;e gerek duyardınız.&lt;/p&gt;
&lt;h2 id=&quot;raporlama-pencereleri&quot;&gt;Raporlama pencereleri&lt;a href=&quot;#raporlama-pencereleri&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Son N gün, önümüzdeki N gün gibi pencereler hemen her dashboard sorgusunda var:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Son 30 gun siparis
SELECT * FROM siparisler
WHERE siparis_tarihi &amp;gt;= SUBDATE(CURDATE(), 30);

-- Onumuzdeki 7 gun randevu
SELECT * FROM randevular
WHERE randevu_tarihi BETWEEN CURDATE() AND ADDDATE(CURDATE(), 7);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Diğer tarih fonksiyonlarıyla da güzel bestelenir; mesela &apos;üç ay sonraki ayın son günü&apos; tek satırda çıkar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT LAST_DAY(ADDDATE(CURDATE(), INTERVAL 3 MONTH)) AS gelecek_ay_sonu;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Negatif tam sayıyla &lt;code&gt;ADDDATE()&lt;/code&gt; kullanmak&lt;/strong&gt;: Çalışır ama okuyucuyu yanıltır. Niyetiniz çıkarmaksa &lt;code&gt;SUBDATE()&lt;/code&gt; yazın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NULL beklemediğiniz yerden gelir&lt;/strong&gt;: &lt;code&gt;ADDDATE(NULL, 10)&lt;/code&gt; ya da &lt;code&gt;ADDDATE(tarih, NULL)&lt;/code&gt; her zaman &lt;code&gt;NULL&lt;/code&gt; döner. Kolondan gelen değerler için &lt;code&gt;COALESCE&lt;/code&gt; ile bir varsayılan koymak çoğu zaman iyi fikir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ay sonu sürprizleri&lt;/strong&gt;: &lt;code&gt;ADDDATE(&apos;2026-01-31&apos;, INTERVAL 1 MONTH)&lt;/code&gt; size &lt;code&gt;2026-02-28&lt;/code&gt; verir. MySQL ay sonunu otomatik kırpar; bu davranışın farkında olmak iş kuralınızla uyumsuzluğa düşmenizi engeller.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Index&apos;siz tarih aritmetiği&lt;/strong&gt;: &lt;code&gt;WHERE ADDDATE(kolon, 30) &amp;gt; NOW()&lt;/code&gt; yazarsanız index kullanılmaz. Aritmetiği sabit tarafa taşıyın: &lt;code&gt;WHERE kolon &amp;gt; SUBDATE(NOW(), 30)&lt;/code&gt;. Klasik tuzak ama hâlâ sıkça gördüğüm bir şey.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ADDDATE()&lt;/code&gt; ve &lt;code&gt;SUBDATE()&lt;/code&gt;, MySQL tarih aritmetiğinin günlük araçları; INTERVAL ile esnek, tam sayı kısa-yoluyla pratik. Şahsi kanaatim, ekip içinde tek bir biçim seçip ona sadık kalmak en büyük kazancı veriyor; ondan sonra fonksiyonlar sessizce işini görüyor zaten. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-mysql-concat-ws-ile-null-a-takilmayan-birlestirme/</id>
    <title>MySQL CONCAT_WS() ile NULL&#039;a Takılmayan Birleştirme</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-mysql-concat-ws-ile-null-a-takilmayan-birlestirme/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Doruk Tunç</name></author>
    <summary>MySQL CONCAT_WS()&#039;in ne yaptığı, neden NULL değerleri sessizce atladığı ve virgülle ayrılmış listeler üretirken neden hayat kurtardığını anlatıyoruz.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda MySQL&apos;in görece sessiz kalan ama bir kez alıştığınızda elinizden düşmeyecek fonksiyonlarından biri olan &lt;code&gt;CONCAT_WS()&lt;/code&gt;&apos;e bakacağız. Bilhassa nullable kolonlardan ad-soyad ya da adres üreten herkesin başına gelmiştir: &lt;code&gt;CONCAT()&lt;/code&gt; ile string birleştiriyorsunuz, ortadaki bir kolon &lt;code&gt;NULL&lt;/code&gt; çıkıyor ve sonuç bir anda komple &lt;code&gt;NULL&lt;/code&gt; oluyor. Hadi &lt;code&gt;CONCAT_WS()&lt;/code&gt; bu kâbusu nasıl bitiriyor, bakalım.&lt;/p&gt;
&lt;h2 id=&quot;concatws-nedir&quot;&gt;CONCAT_WS() nedir?&lt;a href=&quot;#concatws-nedir&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İsmindeki &lt;code&gt;WS&lt;/code&gt;, &apos;with separator&apos; kısaltması; yani &apos;ayraçlı birleştir&apos;. Fonksiyon iki ya da daha fazla string&apos;i, ilk argüman olarak verdiğiniz ayraçla bir araya getiriyor. Asıl can alıcı özelliği ise şu: argümanlardan biri &lt;code&gt;NULL&lt;/code&gt; ise onu sessizce atlar, geriye düzgün bir sonuç döndürür.&lt;/p&gt;
&lt;p&gt;İmzası şöyle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CONCAT_WS(ayrac, str1, str2, str3, ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Üç kuralı baştan aklınızda tutun: ayraç zorunlu, ayraç &lt;code&gt;NULL&lt;/code&gt; olursa sonuç &lt;code&gt;NULL&lt;/code&gt; döner ve gerisindeki &lt;code&gt;NULL&lt;/code&gt; argümanlar atlanırken yerlerinde çift ayraç bırakmazlar. Son madde aslında en kıymetlisi - üreteceğiniz string&apos;in cımbızla temizlenmesine gerek kalmıyor.&lt;/p&gt;
&lt;h2 id=&quot;temel-kullanım&quot;&gt;Temel kullanım&lt;a href=&quot;#temel-kullanım&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Üç hızlı örnekle ısınalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT CONCAT_WS(&apos;, &apos;, &apos;Ayşe&apos;, &apos;Yılmaz&apos;, &apos;İstanbul&apos;);
-- Sonuc: &apos;Ayşe, Yılmaz, İstanbul&apos;

SELECT CONCAT_WS(&apos;-&apos;, &apos;2026&apos;, &apos;05&apos;, &apos;08&apos;);
-- Sonuc: &apos;2026-05-08&apos;

SELECT CONCAT_WS(&apos; | &apos;, &apos;MySQL&apos;, &apos;8.0&apos;, &apos;String Fonksiyonlari&apos;);
-- Sonuc: &apos;MySQL | 8.0 | String Fonksiyonlari&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Görüyorsunuz, ayraç tek karakter olmak zorunda değil; çok karakterli ayraç da çalışıyor. Bu sayede kayıt sınırlayıcı yerine &apos; - &apos; gibi okunabilir ayraçlar koyabiliyorsunuz.&lt;/p&gt;
&lt;h2 id=&quot;concattan-farkı-null-muamelesi&quot;&gt;CONCAT()&apos;tan farkı: NULL muamelesi&lt;a href=&quot;#concattan-farkı-null-muamelesi&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;İşin can damarı burası. Aynı veriyi iki fonksiyona verelim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- CONCAT() bir argüman bile NULL&apos;sa sonucu NULL yapar
SELECT CONCAT(&apos;Ayşe&apos;, NULL, &apos;Yılmaz&apos;);
-- Sonuc: NULL

-- CONCAT_WS() NULL argümanı görmemiş gibi geçer
SELECT CONCAT_WS(&apos; &apos;, &apos;Ayşe&apos;, NULL, &apos;Yılmaz&apos;);
-- Sonuc: &apos;Ayşe Yılmaz&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Şahsi kanaatim, kolonun nullable olduğu her senaryoda &lt;code&gt;CONCAT()&lt;/code&gt; kullanmak başınıza iş açar. &lt;code&gt;COALESCE(col, &apos;&apos;)&lt;/code&gt; ile bunu manuel çözmeye çalışırsanız bu sefer ortada boş ayraçlar belirir. &lt;code&gt;CONCAT_WS()&lt;/code&gt; ikisini de tek hamlede halleder.&lt;/p&gt;
&lt;h2 id=&quot;nullable-kolonlardan-tam-ad-üretmek&quot;&gt;Nullable kolonlardan tam ad üretmek&lt;a href=&quot;#nullable-kolonlardan-tam-ad-üretmek&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Klasik senaryo: ikinci adı opsiyonel olan bir kişi tablosu. Hadi gerçek veriyle bakalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE kisiler (
    kisi_id     INT PRIMARY KEY,
    ad          VARCHAR(50),
    ikinci_ad   VARCHAR(50),
    soyad       VARCHAR(50)
);

INSERT INTO kisiler VALUES
(1, &apos;Ayşe&apos;,  &apos;Nur&apos;,   &apos;Yılmaz&apos;),
(2, &apos;Mehmet&apos;, NULL,   &apos;Demir&apos;),
(3, &apos;Zeynep&apos;, &apos;Su&apos;,    NULL);

SELECT
    kisi_id,
    CONCAT_WS(&apos; &apos;, ad, ikinci_ad, soyad) AS tam_ad
FROM kisiler;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonuç:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;kisi_id&lt;/th&gt;
&lt;th&gt;tam_ad&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Ayşe Nur Yılmaz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Mehmet Demir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Zeynep Su&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;İkinci satırda &apos;Mehmet  Demir&apos; gibi çift boşluklu bir çıktı yok; üçüncü satırda da sondan sarkan bir boşluk yok. Çünkü &lt;code&gt;NULL&lt;/code&gt; argüman tamamen atlanıyor, yerine ayraç eklenmiyor.&lt;/p&gt;
&lt;h2 id=&quot;adresleri-ve-etiketli-stringleri-kurmak&quot;&gt;Adresleri ve etiketli string&apos;leri kurmak&lt;a href=&quot;#adresleri-ve-etiketli-stringleri-kurmak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adres kolonları neredeyse her zaman kısmen &lt;code&gt;NULL&lt;/code&gt;&apos;dur. &lt;code&gt;CONCAT_WS()&lt;/code&gt; burada da bir nimet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    CONCAT_WS(&apos;, &apos;, sokak, mahalle, ilce, sehir, posta_kodu) AS tam_adres
FROM adresler;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eksik kolon hangisi olursa olsun, çıktı temiz kalır. Aynı mantığı raporlama için de kullanabilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    calisan_id,
    CONCAT_WS(&apos; - &apos;, isim, COALESCE(departman, &apos;Atanmamis&apos;), CONCAT(&apos;₺&apos;, FORMAT(maas, 0))) AS etiket
FROM calisanlar;
-- Ornek: &apos;Ayşe Yılmaz - Mühendislik - ₺85,000&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada &lt;code&gt;COALESCE&lt;/code&gt;&apos;i bilinçli olarak kullandım: departmanın &lt;code&gt;NULL&lt;/code&gt; olmasını atlamak değil, &apos;Atanmamis&apos; yazmak istiyorum. &lt;code&gt;CONCAT_WS()&lt;/code&gt;&apos;in &lt;code&gt;NULL&lt;/code&gt; davranışı her zaman aradığınız şey olmayabilir, ikisini birlikte kullanmak şart.&lt;/p&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ayracın kendisinin NULL olması&lt;/strong&gt;: &lt;code&gt;SELECT CONCAT_WS(NULL, &apos;a&apos;, &apos;b&apos;);&lt;/code&gt; çağrısı &lt;code&gt;NULL&lt;/code&gt; döner. Eğer ayracı bir alt sorgudan ya da değişkenden alıyorsanız, &lt;code&gt;IFNULL(ayrac, &apos;,&apos;)&lt;/code&gt; ile garanti altına almakta fayda var.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boş string ile NULL&apos;u karıştırmak&lt;/strong&gt;: &lt;code&gt;&apos;&apos;&lt;/code&gt; (boş string) &lt;code&gt;NULL&lt;/code&gt; değildir; &lt;code&gt;CONCAT_WS()&lt;/code&gt; bunu atlamaz, normal değer gibi araya ekler ve çift ayraç oluşur. Boş string&apos;leri de atlamak istiyorsanız &lt;code&gt;NULLIF(col, &apos;&apos;)&lt;/code&gt; ile önce &lt;code&gt;NULL&lt;/code&gt;&apos;a çevirin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sayısal kolonlarda otomatik dönüşüm&lt;/strong&gt;: Sayısal kolonlar string&apos;e cast edilir, sorun yok; ama bilhassa &lt;code&gt;DECIMAL&lt;/code&gt; kolonlarda biçim beklediğiniz gibi gelmeyebilir. &lt;code&gt;FORMAT()&lt;/code&gt; ile önden biçimlendirmek sağlıklı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GROUP_CONCAT() ile karıştırmak&lt;/strong&gt;: &lt;code&gt;CONCAT_WS()&lt;/code&gt; aynı satırdaki kolonları birleştirir; birden fazla satırı tek string&apos;e toplamak için &lt;code&gt;GROUP_CONCAT()&lt;/code&gt; lazım. İkisi farklı işlerdir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CONCAT_WS()&lt;/code&gt; kâğıt üstünde küçük bir fonksiyon ama nullable kolonlarla çalışırken &lt;code&gt;CONCAT()&lt;/code&gt; + &lt;code&gt;IFNULL()&lt;/code&gt; kombinasyonunun sıkıntısını tamamen ortadan kaldırıyor. Bana sorarsanız, ad-soyad veya adres benzeri her birleştirmede varsayılanınız &lt;code&gt;CONCAT_WS()&lt;/code&gt; olsun; ihtiyacınız değişirse &lt;code&gt;CONCAT()&lt;/code&gt;&apos;a düşersiniz, tersi acı verici. Bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-python-sozluklerinde-anahtar-var-mi-kontrolu/</id>
    <title>Python Sözlüklerinde Anahtar Var mı Kontrolü</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-python-sozluklerinde-anahtar-var-mi-kontrolu/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Selin Çetin</name></author>
    <summary>Python sözlüklerinde anahtar var mı sorusunu yanıtlamanın yolları; in operatörü, get, setdefault ve EAFP yaklaşımı arasında nasıl seçim yapacağız.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazıda Python&apos;da küçük ama sürekli karşımıza çıkan bir konuya bakacağız: bir sözlükte (dict) belli bir anahtarın olup olmadığını nasıl kontrol ederiz? Soru basit görünüyor ama bir-iki tane tuzağı var; özellikle değerin &lt;code&gt;None&lt;/code&gt; olduğu durumlarda. Lafı çok uzatmadan başlayayım.&lt;/p&gt;
&lt;h2 id=&quot;en-pythonic-yol-in-operatörü&quot;&gt;En Pythonic yol: &lt;code&gt;in&lt;/code&gt; operatörü&lt;a href=&quot;#en-pythonic-yol-in-operatörü&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bir sözlükte anahtarın var olup olmadığını öğrenmenin en okunaklı yolu &lt;code&gt;in&lt;/code&gt; operatörü. Eski bir alışkanlıkla &lt;code&gt;has_key&lt;/code&gt; gibi bir şey aramayın; o Python 2&apos;de kalmış bir metot, artık yok.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;user = {&apos;name&apos;: &apos;Ada&apos;, &apos;age&apos;: 30}

if &apos;name&apos; in user:
    print(&apos;isim var&apos;)

if &apos;email&apos; not in user:
    print(&apos;email yok&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada dikkat: &lt;code&gt;&apos;name&apos; in user&lt;/code&gt; ifadesi sözlüğün &lt;strong&gt;anahtarlarına&lt;/strong&gt; bakar, değerlerine değil. &lt;code&gt;in user.keys()&lt;/code&gt; yazmaya gerek yok; doğrudan &lt;code&gt;in user&lt;/code&gt; aynı işi yapıyor ve aslında biraz daha hızlı çalışıyor çünkü ekstra bir &lt;code&gt;dict_keys&lt;/code&gt; view&apos;ı oluşturmuyoruz.&lt;/p&gt;
&lt;h2 id=&quot;dictget-ile-tek-hamlede-değer-ve-kontrol&quot;&gt;&lt;code&gt;dict.get&lt;/code&gt; ile tek hamlede değer ve kontrol&lt;a href=&quot;#dictget-ile-tek-hamlede-değer-ve-kontrol&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Anahtarın varlığını test ettikten sonra büyük ihtimalle değerini de okuyacağız. O zaman iki kez sözlüğe bakmak yerine &lt;code&gt;get&lt;/code&gt; metodu işimizi tek seferde halleder. Anahtar yoksa varsayılan olarak &lt;code&gt;None&lt;/code&gt; döner; istersen kendi varsayılanını da verebilirsin.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;config = {&apos;host&apos;: &apos;localhost&apos;, &apos;port&apos;: 5432}

host = config.get(&apos;host&apos;)           # &apos;localhost&apos;
timeout = config.get(&apos;timeout&apos;, 30) # 30 (varsayılan)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada bir tuzak var: değerin kendisi &lt;code&gt;None&lt;/code&gt; ise &lt;code&gt;get&lt;/code&gt; ile gerçekten eksik anahtarı ayırt edemezsiniz.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;data = {&apos;optional&apos;: None}

print(data.get(&apos;optional&apos;))  # None
print(data.get(&apos;missing&apos;))   # None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İkisi de &lt;code&gt;None&lt;/code&gt; döndü. Bu ayrımı yapmamız gerekiyorsa &lt;code&gt;in&lt;/code&gt; operatörüne geri dönüyoruz; o sözlükte &lt;code&gt;None&lt;/code&gt; değerli bir anahtar varsa bile &lt;code&gt;True&lt;/code&gt; döner.&lt;/p&gt;
&lt;h2 id=&quot;setdefault-ile-yoksa-ekle&quot;&gt;&lt;code&gt;setdefault&lt;/code&gt; ile yoksa ekle&lt;a href=&quot;#setdefault-ile-yoksa-ekle&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bazen sadece kontrol etmek değil, yoksa ekleyip varsa olanı kullanmak istiyoruz. Klasik kullanım yeri: gruplama (group-by). Mesela bir liste içindeki kelimeleri ilk harflerine göre gruplayalım.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;words = [&apos;Ada&apos;, &apos;Ali&apos;, &apos;Bora&apos;, &apos;Burak&apos;, &apos;Can&apos;]
groups = {}

for w in words:
    groups.setdefault(w[0], []).append(w)

# groups -&amp;gt; {&apos;A&apos;: [&apos;Ada&apos;, &apos;Ali&apos;], &apos;B&apos;: [&apos;Bora&apos;, &apos;Burak&apos;], &apos;C&apos;: [&apos;Can&apos;]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;setdefault&lt;/code&gt; anahtar yoksa onu boş bir liste ile ekliyor ve o listeyi geri döndürüyor; varsa zaten olan listeyi döndürüyor. Tabii bu işin daha temiz hali &lt;code&gt;collections.defaultdict&lt;/code&gt; ama tek seferlik küçük gruplamalar için &lt;code&gt;setdefault&lt;/code&gt; yeter.&lt;/p&gt;
&lt;h2 id=&quot;eafp-stili-tryexcept-keyerror&quot;&gt;EAFP stili: try/except KeyError&lt;a href=&quot;#eafp-stili-tryexcept-keyerror&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python topluluğunda &apos;önce iznini al&apos; (LBYL) yerine &apos;önce dene, hata olursa yakala&apos; (EAFP) yaklaşımı yaygındır. Anahtarın &lt;strong&gt;çoğu zaman var olduğunu&lt;/strong&gt; bekliyorsanız bu stil hem daha hızlı hem de daha doğal.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;try:
    score = scores[&apos;ada&apos;]
except KeyError:
    score = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bence bu yaklaşımı her yerde kullanmaya gerek yok; özellikle anahtarın yarı yarıya eksik olduğu durumlarda &lt;code&gt;get&lt;/code&gt; daha okunaklı. Ama performans kritik bir döngüde, anahtar çoğu zaman doluyorsa, &lt;code&gt;try/except&lt;/code&gt; sürekli &lt;code&gt;if&lt;/code&gt; kontrolünden hızlı çıkıyor.&lt;/p&gt;
&lt;h2 id=&quot;birden-fazla-anahtarı-tek-seferde-kontrol&quot;&gt;Birden fazla anahtarı tek seferde kontrol&lt;a href=&quot;#birden-fazla-anahtarı-tek-seferde-kontrol&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bir sözlükte birkaç anahtarın hepsinin var olup olmadığını kontrol etmek için tek tek &lt;code&gt;in&lt;/code&gt; yazmak gerekmiyor. Set işlemleri imdada yetişiyor.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;payload = {&apos;id&apos;: 1, &apos;name&apos;: &apos;Ada&apos;, &apos;email&apos;: &apos;ada@example.com&apos;}
required = {&apos;id&apos;, &apos;name&apos;}

if required &amp;lt;= payload.keys():
    print(&apos;zorunlu alanlar tamam&apos;)

eksikler = required - payload.keys()
print(eksikler)  # set()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;=&lt;/code&gt; burada &apos;alt küme&apos; demek. &lt;code&gt;dict.keys()&lt;/code&gt; zaten küme benzeri bir view döndürdüğü için doğrudan küme operatörlerini uygulayabiliyoruz. Eksikleri görmek için fark almak da çok pratik.&lt;/p&gt;
&lt;h2 id=&quot;i̇ç-içe-sözlüklerde-güvenli-erişim&quot;&gt;İç içe sözlüklerde güvenli erişim&lt;a href=&quot;#i̇ç-içe-sözlüklerde-güvenli-erişim&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;API cevaplarında genelde iç içe sözlükler oluyor ve her seviyede &lt;code&gt;in&lt;/code&gt; kontrolü yapmak yorucu. Küçük bir yardımcı fonksiyon işi kolaylaştırıyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def get_nested(data, *keys, default=None):
    cur = data
    for k in keys:
        if not isinstance(cur, dict) or k not in cur:
            return default
        cur = cur[k]
    return cur

resp = {&apos;user&apos;: {&apos;profile&apos;: {&apos;name&apos;: &apos;Ada&apos;}}}
print(get_nested(resp, &apos;user&apos;, &apos;profile&apos;, &apos;name&apos;))     # &apos;Ada&apos;
print(get_nested(resp, &apos;user&apos;, &apos;address&apos;, &apos;city&apos;, default=&apos;-&apos;))  # &apos;-&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Özetle: var-yok kontrolü için &lt;code&gt;in&lt;/code&gt;, değer de lazımsa &lt;code&gt;get&lt;/code&gt;, yoksa ekle istiyorsan &lt;code&gt;setdefault&lt;/code&gt;, çoğu zaman dolu olduğunu biliyorsan &lt;code&gt;try/except&lt;/code&gt;. Bana sorarsanız kodun çoğunda &lt;code&gt;in&lt;/code&gt; ile &lt;code&gt;get&lt;/code&gt; ikilisi yetiyor; diğerleri ise doğru senaryoda kullanıldığında kodu kısaltıyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-05-08-azure-spring-apps-uzerinde-dagitik-izleme-kurmak/</id>
    <title>Azure Spring Apps Üzerinde Dağıtık İzleme Kurmak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-05-08-azure-spring-apps-uzerinde-dagitik-izleme-kurmak/"/>
    <published>2026-05-08T00:00:00Z</published>
    <updated>2026-05-08T00:00:00Z</updated>
    <author><name>Cem Karadağ</name></author>
    <summary>Azure Spring Apps üstünde Application Insights ile uçtan uca dağıtık izleme kurmak; bağlantı dizesi, örnekleme oranı ve özel span üretimi pratikte nasıl olur.</summary>
    <content type="html">&lt;p&gt;Merhabalar, bu yazımda Azure Spring Apps üzerinde çalışan mikroservislerimize Application Insights ile dağıtık izleme (distributed tracing) nasıl bağlanır, ona bakacağız. Konu basit görünür ama &apos;checkout sayfası yavaş&apos; raporu masaya geldiğinde tek tek loglara bakmakla işin içinden çıkamadığımı tecrübeyle söyleyebilirim. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;Tipik bir akış şöyle: kullanıcı butona basıyor, istek API gateway&apos;e geliyor, oradan order servisine, order payment&apos;a, payment inventory&apos;ye uzanıyor. Yavaşlık varsa kim suçlu? İşte trace context bunu söyleyen şey; her servisin loguna &apos;aynı isteğin parçasıyım&apos; diyebilen bir kimlik koyuyor.&lt;/p&gt;
&lt;h2 id=&quot;application-insights-nedir-neden-spring-apps-ile&quot;&gt;Application Insights nedir, neden Spring Apps ile?&lt;a href=&quot;#application-insights-nedir-neden-spring-apps-ile&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Application Insights, Azure&apos;ın APM (application performance monitoring) servisi. Java agent&apos;ı sayesinde Spring Boot tarafında neredeyse hiç kod yazmadan SpringMVC çağrılarını, JDBC sorgularını, dış HTTP isteklerini ve log satırlarını otomatik enstrümante ediyor. Spring Apps tarafında ise platform-level bir entegrasyon var; uygulamayı yeniden paketlemenize gerek kalmadan agent&apos;ı binding olarak ekliyor.&lt;/p&gt;
&lt;p&gt;Bana sorarsanız buradaki en büyük kazanç, OpenTelemetry standardına oturmuş olması. Yarın provider&apos;ı değiştirmek istesek, custom span&apos;ları yeniden yazmıyoruz; sadece exporter değişiyor.&lt;/p&gt;
&lt;h2 id=&quot;resourceu-oluşturup-spring-appse-bağlamak&quot;&gt;Resource&apos;u oluşturup Spring Apps&apos;e bağlamak&lt;a href=&quot;#resourceu-oluşturup-spring-appse-bağlamak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Önce bir Application Insights component&apos;i açalım. &lt;code&gt;--kind java&lt;/code&gt; demeyi unutmayın; portaldaki Java-spesifik panellerin gözükmesi buna bağlı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;az monitor app-insights component create \
  --app orders-insights \
  --location westeurope \
  --resource-group orders-rg \
  --kind java

az spring app-insights update \
  --name orders-spring \
  --resource-group orders-rg \
  --app-insights orders-insights \
  --sampling-rate 50
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sampling-rate konusunda küçük bir not: development&apos;ta 100 mantıklı, her şeyi görmek istiyoruz. Production&apos;da ise 25-50 arası bir değer hem maliyeti dizginliyor hem de istatistiksel olarak yeterli görünürlük sağlıyor. Şahsi tercihim, trafik düşük servisler için 100, yüksek olanlar için 25.&lt;/p&gt;
&lt;h2 id=&quot;logları-trace-ile-korelasyonlamak&quot;&gt;Logları trace ile korelasyonlamak&lt;a href=&quot;#logları-trace-ile-korelasyonlamak&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Burası çok atlanan bir nokta. Agent trace&apos;i topluyor, logları topluyor, ama logback pattern&apos;inize &lt;code&gt;traceId&lt;/code&gt; ve &lt;code&gt;spanId&lt;/code&gt;&apos;yi koymadıysanız Kibana benzeri bir log aramasında trace&apos;e geri dönemiyorsunuz. &lt;code&gt;application.yml&lt;/code&gt; içinde:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;logging:
  pattern:
    console: &apos;%d{HH:mm:ss.SSS} [%thread] %-5level [traceId=%X{traceId} spanId=%X{spanId}] %logger{36} - %msg%n&apos;

management:
  tracing:
    sampling:
      probability: 1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu kadarı bile End-to-End Transaction Details ekranında log satırını ilgili span&apos;in altında görmek için yeterli.&lt;/p&gt;
&lt;h2 id=&quot;business-operasyonları-için-custom-span&quot;&gt;Business operasyonları için custom span&lt;a href=&quot;#business-operasyonları-için-custom-span&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Auto-instrumentation framework çağrılarını yakalar ama &apos;sipariş oluşturma&apos; gibi domain operasyonlarınızı bilmez. Onları kendiniz isimlendirmelisiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Span span = tracer.spanBuilder(&apos;createOrder&apos;)
    .setAttribute(&apos;order.customerId&apos;, customerId)
    .setAttribute(&apos;order.itemCount&apos;, items.size())
    .startSpan();

try (Scope scope = span.makeCurrent()) {
    return orderService.create(customerId, items);
} catch (Exception ex) {
    span.recordException(ex);
    span.setStatus(StatusCode.ERROR);
    throw ex;
} finally {
    span.end();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;try/finally&lt;/code&gt; zorunlu; aksi halde exception yolunda span hiç kapanmaz ve Application Map&apos;te uçuşan bir &apos;in-flight&apos; kalır. Bunu ben de ilk başlarda iki üç kez unuttum.&lt;/p&gt;
&lt;h2 id=&quot;portalda-ne-göreceğiz&quot;&gt;Portalda ne göreceğiz?&lt;a href=&quot;#portalda-ne-göreceğiz&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Application Map size servis topolojisini çıkarıyor; ok kalınlıkları çağrı hacmini, kırmızılar hata oranını gösteriyor. Transaction Search zamana göre arama yapıyor. End-to-End Transaction Details ise bir isteğin waterfall görünümü; kim ne kadar bekletti, net olarak ortada. KQL ile de kendi sorgunuzu yazabilirsiniz, mesela en yavaş 20 endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;requests
| where timestamp &amp;gt; ago(1h)
| top 20 by duration desc
| project name, duration, resultCode, operation_Id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bağımlılıkların 95&apos;inci yüzdelik gecikmesi:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;dependencies
| where timestamp &amp;gt; ago(1d)
| summarize p95=percentile(duration, 95) by name
| order by p95 desc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sık-karşılaşılan-tuzaklar&quot;&gt;Sık karşılaşılan tuzaklar&lt;a href=&quot;#sık-karşılaşılan-tuzaklar&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connection string set edilmedi&lt;/strong&gt;: Spring Apps binding&apos;i yapılmamışsa agent telemetri gönderecek bir hedef bulamaz, sessiz sessiz dropluyor. Önce &lt;code&gt;az spring app-insights show&lt;/code&gt; ile doğrulayın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;W3C trace context propagasyonu kopuk&lt;/strong&gt;: Servislerden biri eski B3 header&apos;ı kullanıyorsa trace iki parçaya bölünüyor. OpenTelemetry default&apos;u W3C; tüm servislerin aynı standardı konuştuğundan emin olun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sampling-rate&apos;i unutmak&lt;/strong&gt;: Default genellikle düşük geliyor. Yeni resource&apos;ta beklediğiniz trafiği göremezseniz ilk bakacağınız yer bu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom span&apos;i &lt;code&gt;try/finally&lt;/code&gt; olmadan açmak&lt;/strong&gt;: Exception olduğunda span kapanmaz, metric&apos;ler çarpık çıkar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanış&quot;&gt;Kapanış&lt;a href=&quot;#kapanış&quot; class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; title=&quot;Permalink&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bu yazıda Azure Spring Apps üzerinde Application Insights ile dağıtık izlemenin temel kurulumunu, log korelasyonunu ve custom span üretimini gördük. Bence dağıtık izlemenin asıl değeri ilk incident&apos;ta ortaya çıkıyor; o yüzden &apos;sonra kurarız&apos; demek yerine projenin ilk haftasında devreye almak gerçekten zaman kazandırıyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
</feed>
