<?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-06-14T00:00:00Z</updated>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-14-clickhouse-ve-postgresql-arasinda-cift-yonlu-senkronizasyon/</id>
    <title>ClickHouse ve PostgreSQL Arasinda Cift Yonlu Senkronizasyon</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-14-clickhouse-ve-postgresql-arasinda-cift-yonlu-senkronizasyon/"/>
    <published>2026-06-14T00:00:00Z</published>
    <updated>2026-06-14T00:00:00Z</updated>
    <author><name>Ece Soylu</name></author>
    <summary>ClickHouse ile PostgreSQL arasinda iki yonlu veri akisini, replikasyon donguleri olusturmadan kurmanin pratik yolu.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazıda ClickHouse ile PostgreSQL&apos;i aynı sistemin iki ucu gibi nasıl konuşturduğumuza bakacağız. Klasik bir &apos;master-master&apos; senaryosu değil bu; daha çok her sistemin &lt;strong&gt;kendi güçlü olduğu işi yaptığı&lt;/strong&gt;, sonuçların tek yönde değil, ihtiyaca göre iki yönde aktığı bir desen. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;OLTP tarafında PostgreSQL&apos;in alternatifi yok denecek kadar oturmuş; transaction&apos;ları, kısıtları, foreign key&apos;leri sorunsuz çekiyor. Ama gün sonunda &apos;son 30 gün ülke bazında ciro&apos; gibi bir analitik sorgu istediğinizde aynı tablo üzerinden saatler harcayabilirsiniz. ClickHouse burada devreye girer: kolon tabanlı, sıkıştırmalı, agregasyonu saniyede bitiren bir motor. Sorun şu: uygulamanız sonuçları yine PostgreSQL&apos;den okumak istiyor olabilir. İşte cift yonlu senkronizasyon tam bu boşluğu kapatıyor.&lt;/p&gt;
&lt;h2 id=&quot;mimari-nasıl-kurguluyoruz&quot;&gt;Mimari nasıl kurguluyoruz?&lt;a href=&quot;#mimari-nasıl-kurguluyoruz&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;Düşünmesi kolay olsun diye şöyle özetleyeyim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;PostgreSQL (yazma) --&amp;gt; ClickHouse (analitik)
                              |
                              v
              ClickHouse Agregasyonlari --&amp;gt; PostgreSQL (uygulama okur)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yani aynı veri iki yöne replicate edilmiyor. Ham operasyonel veri PostgreSQL&apos;den ClickHouse&apos;a akıyor; ClickHouse&apos;da hesaplanan özet sonuçlar ise PostgreSQL&apos;e geri yazılıyor. &lt;strong&gt;Aynı satırın iki kopyası gibi düşünmeyin&lt;/strong&gt; - bu daha çok &apos;farklı veri kümeleri için farklı yönler&apos; demek.&lt;/p&gt;
&lt;h2 id=&quot;yön-1-postgresqlden-clickhousea&quot;&gt;Yön 1: PostgreSQL&apos;den ClickHouse&apos;a&lt;a href=&quot;#yön-1-postgresqlden-clickhousea&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;ClickHouse&apos;un &lt;code&gt;MaterializedPostgreSQL&lt;/code&gt; motoru tam bu iş için var. Logical replication slot&apos;u açıyor, WAL&apos;i okuyor ve değişiklikleri ClickHouse tarafında yansıtıyor.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE DATABASE pg_live
ENGINE = MaterializedPostgreSQL(&apos;pg-host:5432&apos;, &apos;myapp&apos;, &apos;clickhouse_user&apos;, &apos;secret&apos;)
SETTINGS materialized_postgresql_schema = &apos;public&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Çalışıp çalışmadığını anlamak için:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT count() FROM pg_live.orders;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PostgreSQL tarafında aynı sayıyı görüyorsanız akış kuruldu demektir. PostgreSQL&apos;in &lt;code&gt;wal_level = logical&lt;/code&gt; ayarını ve &lt;code&gt;clickhouse_user&lt;/code&gt;&apos;a &lt;code&gt;REPLICATION&lt;/code&gt; yetkisini vermeyi unutmayın; en sık ben bu iki adımı atlanırken görüyorum.&lt;/p&gt;
&lt;h2 id=&quot;yön-2-clickhousetan-postgresqle-geri-yazma&quot;&gt;Yön 2: ClickHouse&apos;tan PostgreSQL&apos;e geri yazma&lt;a href=&quot;#yön-2-clickhousetan-postgresqle-geri-yazma&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 sefer ClickHouse&apos;un &lt;code&gt;PostgreSQL&lt;/code&gt; table engine&apos;ını kullanıyoruz. Hedef tabloyu önce PostgreSQL tarafında açın:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE clickhouse_reports.daily_revenue (
    report_date DATE,
    country VARCHAR(100),
    total_revenue NUMERIC(18, 4),
    order_count INTEGER,
    PRIMARY KEY (report_date, country)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra ClickHouse tarafında aynı tabloya bir &apos;tutamak&apos; tanımlıyoruz ve agregasyonun sonucunu doğrudan oraya yazıyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE pg_daily_revenue
(
    report_date Date,
    country String,
    total_revenue Decimal(18, 4),
    order_count UInt32
)
ENGINE = PostgreSQL(&apos;pg-host:5432&apos;, &apos;myapp&apos;, &apos;daily_revenue&apos;, &apos;clickhouse_user&apos;, &apos;secret&apos;);

INSERT INTO pg_daily_revenue
SELECT
    toDate(created_at) AS report_date,
    customer_country AS country,
    sum(total_amount) AS total_revenue,
    count() AS order_count
FROM pg_live.orders
WHERE toDate(created_at) = yesterday()
GROUP BY report_date, country;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uygulama tarafı bu sonuçları artık PostgreSQL&apos;den, kendi bildiği SQL diyalektiyle okuyabiliyor. ClickHouse&apos;un varlığından haberi olmasına gerek yok.&lt;/p&gt;
&lt;h2 id=&quot;replikasyon-dongusunu-nasıl-kırarız&quot;&gt;Replikasyon dongusunu nasıl kırarız?&lt;a href=&quot;#replikasyon-dongusunu-nasıl-kırarız&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 en kritik noktası burası. ClickHouse, az önce yazdığı &lt;code&gt;clickhouse_reports.daily_revenue&lt;/code&gt; tablosunu logical replication ile &lt;strong&gt;tekrar kendine&lt;/strong&gt; çekerse sonsuz döngüye girersiniz - en azından gereksiz veri trafiği yapar, en kötüsünde tutarlılık karışır.&lt;/p&gt;
&lt;p&gt;Çözüm sade: ClickHouse&apos;un yazdığı tabloları replikasyon kapsamından &lt;strong&gt;dışarıda bırakın&lt;/strong&gt;. Bunu ayrı bir schema ile veya &lt;code&gt;materialized_postgresql_tables_list&lt;/code&gt; ayarıyla yapabilirsiniz.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE DATABASE pg_live
ENGINE = MaterializedPostgreSQL(&apos;pg-host:5432&apos;, &apos;myapp&apos;, &apos;clickhouse_user&apos;, &apos;secret&apos;)
SETTINGS
    materialized_postgresql_schema = &apos;public&apos;,
    materialized_postgresql_tables_list = &apos;orders,customers,products&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada &lt;code&gt;clickhouse_reports&lt;/code&gt; schema&apos;sı listede yok, dolayısıyla CDC akışına dahil değil. Bence ayrı schema tercih etmek daha temiz; tablo isimlerini tek tek yazmak zorunda kalmıyorsunuz, ek bir tablo açtığınızda da yanlışlıkla kapsama girmiyor.&lt;/p&gt;
&lt;h2 id=&quot;agregasyonu-zamanlamak&quot;&gt;Agregasyonu zamanlamak&lt;a href=&quot;#agregasyonu-zamanlamak&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;Geri yazma işini elle tetiklemek istemezsiniz. Basit bir cron yeterli:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
clickhouse-client --query=&amp;quot;
INSERT INTO pg_daily_revenue
SELECT
    toDate(created_at) AS report_date,
    customer_country AS country,
    sum(total_amount) AS total_revenue,
    count() AS order_count
FROM pg_live.orders
WHERE toDate(created_at) = yesterday()
GROUP BY report_date, country
&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Daha modern bir yaklaşım isterseniz ClickHouse&apos;un refreshable materialized view&apos;larına bakabilirsiniz; aynı işi motorun içinde, harici bir scheduler olmadan yapı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;wal_level&lt;/code&gt; ayarını atlamak&lt;/strong&gt;: PostgreSQL &lt;code&gt;logical&lt;/code&gt; modda değilse &lt;code&gt;MaterializedPostgreSQL&lt;/code&gt; slot açamaz. &lt;code&gt;postgresql.conf&lt;/code&gt; değişikliği restart ister; planlayarak yapın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replication slot&apos;u temizlememek&lt;/strong&gt;: ClickHouse tarafını silmeden veritabanını DROP ederseniz PostgreSQL&apos;de yetim slot kalır ve WAL büyümeye başlar. &lt;code&gt;pg_replication_slots&lt;/code&gt;&apos;u zaman zaman kontrol edin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ClickHouse yazdığı tabloyu kapsamda bırakmak&lt;/strong&gt;: Yukarıda anlattığımız döngü tam olarak buradan başlıyor. Ayrı schema ya da explicit &lt;code&gt;tables_list&lt;/code&gt; şart.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decimal hassasiyetinin kayması&lt;/strong&gt;: &lt;code&gt;Decimal(18, 4)&lt;/code&gt; ve &lt;code&gt;NUMERIC(18, 4)&lt;/code&gt; aynı görünür, ama tip dönüşümleri bazen yuvarlama getirir. Geri yazma sonrası PostgreSQL tarafında birkaç sample satırı doğrulayı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;Her iki yönü de hızlıca kontrol etmek için:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT name, engine
FROM system.databases
WHERE engine = &apos;MaterializedPostgreSQL&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PostgreSQL tarafında ise:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
FROM clickhouse_reports.daily_revenue
WHERE report_date = CURRENT_DATE - 1
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İkisinden de beklediğiniz çıktıyı alıyorsanız boru hattı ayakta demektir.&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&apos;i transactional kaynak, ClickHouse&apos;u analitik motor olarak konumlandıran bir cift yonlu senkronizasyon desenini kurguladık. Şahsi kanaatim, bu mimari iki ucu da aynı şeyi yazmaya zorlamadığı için bakım yükü en düşük çözümlerden biri; yeter ki replikasyon kapsamını dikkatli tanımlayıp döngüye fırsat vermeyin. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-14-python-ile-redis-pratik-desenler-ve-tuzaklar/</id>
    <title>Python ile Redis: Pratik Desenler ve Tuzaklar</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-14-python-ile-redis-pratik-desenler-ve-tuzaklar/"/>
    <published>2026-06-14T00:00:00Z</published>
    <updated>2026-06-14T00:00:00Z</updated>
    <author><name>Pınar Polat</name></author>
    <summary>Python uygulamalarinda Redis kullanirken sik karsilasilan desenler, connection pool ayari ve gercek hayattaki tuzaklar uzerine notlar.</summary>
    <content type="html">&lt;p&gt;Merhabalar, bu yazımda Python tarafında &lt;code&gt;redis-py&lt;/code&gt; ile çalışırken aklımda bekleyen birkaç pratik konuyu derli toplu paylaşmak istiyorum. Sıfırdan Redis anlatan bir rehber değil bu; daha çok &apos;kodun çalışıyor ama prod&apos;a çıkınca canını sıkacak&apos; şeylere odaklanıyoruz. Bence en kıymetli kısım sonlardaki tuzak listesi, ama önce küçük bir ısınma turu yapalım.&lt;/p&gt;
&lt;h2 id=&quot;redis-py-ile-baglanti-gorunmeyen-detay&quot;&gt;redis-py ile baglanti: gorunmeyen detay&lt;a href=&quot;#redis-py-ile-baglanti-gorunmeyen-detay&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 satırı yazarken kimse bağlantı parametreleri üzerine düşünmüyor, bu çok normal. Ama tek bir &lt;code&gt;Redis()&lt;/code&gt; instance&apos;ının arka planda otomatik olarak bir connection pool taşıdığını bilmek faydalı.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import redis

r = redis.Redis(
    host=&apos;redis-cache&apos;,
    port=6379,
    db=0,
    decode_responses=True,
    socket_timeout=2,
    socket_connect_timeout=2,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;decode_responses=True&lt;/code&gt; parametresi olmadan her &lt;code&gt;get&lt;/code&gt; çağrısı &lt;code&gt;bytes&lt;/code&gt; döner; uygulama kodunda her yerde &lt;code&gt;.decode()&lt;/code&gt; yapmaktan kurtulmak için ben her zaman aktif tutuyorum. &lt;code&gt;socket_timeout&lt;/code&gt; ise asıl önemli olanı; vermediğinizde Redis bir şekilde tıkandıysa çağrı sonsuza kadar bekleyebilir. İki saniye genelde yeterli, ihtiyaca göre artırırsınız.&lt;/p&gt;
&lt;h2 id=&quot;cache-decorator-kisa-ama-dogru&quot;&gt;Cache decorator: kisa ama dogru&lt;a href=&quot;#cache-decorator-kisa-ama-dogru&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;Fonksiyon sonuçlarını cache&apos;lemek için 10 satırlık bir decorator iş görür ama detay önemli. Şöyle yazalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json
import hashlib
from functools import wraps

def cached(ttl: int = 300, prefix: str = &apos;fn&apos;):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            raw = f&apos;{func.__name__}:{args}:{sorted(kwargs.items())}&apos;
            key = f&apos;{prefix}:{hashlib.sha1(raw.encode()).hexdigest()}&apos;
            hit = r.get(key)
            if hit is not None:
                return json.loads(hit)
            result = func(*args, **kwargs)
            r.setex(key, ttl, json.dumps(result, default=str))
            return result
        return wrapper
    return decorator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada birkaç şey kasıtlı: &lt;code&gt;md5&lt;/code&gt; yerine &lt;code&gt;sha1&lt;/code&gt; (çarpışma riski daha düşük), &lt;code&gt;json.dumps&lt;/code&gt;&apos;a &lt;code&gt;default=str&lt;/code&gt; (datetime gibi tipler patlatmasın), TTL&apos;i &lt;code&gt;setex&lt;/code&gt; ile aynı çağrıda veriyoruz (&lt;code&gt;set&lt;/code&gt; + &lt;code&gt;expire&lt;/code&gt; ayrı yazmak yarış durumuna sebep olabilir).&lt;/p&gt;
&lt;h2 id=&quot;pipeline-ag-gidis-donusunden-tasarruf&quot;&gt;Pipeline: ag gidis donusunden tasarruf&lt;a href=&quot;#pipeline-ag-gidis-donusunden-tasarruf&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;Birden fazla komutu peşpeşe çağırıyorsanız her biri ayrı bir round-trip demek. Pipeline ile bunları tek pakette gönderirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with r.pipeline(transaction=False) as pipe:
    for user_id in user_ids:
        pipe.hgetall(f&apos;user:{user_id}&apos;)
    profiles = pipe.execute()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;transaction=False&lt;/code&gt; parametresi MULTI/EXEC&apos;i kapatır; sadece komutları batch&apos;lemek istiyorsanız bu yeterli ve daha hızlı. Atomiklik gerekiyorsa &lt;code&gt;transaction=True&lt;/code&gt; (varsayılan) bırakın.&lt;/p&gt;
&lt;h2 id=&quot;async-tarafi&quot;&gt;Async tarafi&lt;a href=&quot;#async-tarafi&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;Async uygulama yazıyorsanız &lt;code&gt;redis.asyncio&lt;/code&gt; arayüzü neredeyse birebir aynı. Tek fark &lt;code&gt;await&lt;/code&gt; koymanız:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import asyncio
import redis.asyncio as aioredis

async def fetch_user(user_id: int) -&amp;gt; dict:
    r = aioredis.Redis(host=&apos;redis-cache&apos;, decode_responses=True)
    data = await r.hgetall(f&apos;user:{user_id}&apos;)
    await r.aclose()
    return data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Production&apos;da her çağrıda yeni client açmazsınız tabii; uygulama startup&apos;ında bir tane oluşturup paylaşırsınız. Ama burada minimum örnek için böyle gösteriyorum.&lt;/p&gt;
&lt;h2 id=&quot;sik-karsilasilan-tuzaklar&quot;&gt;Sik karsilasilan tuzaklar&lt;a href=&quot;#sik-karsilasilan-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;KEYS *&lt;/code&gt; komutu&lt;/strong&gt;: Tek thread&apos;li Redis&apos;te tüm anahtar tablosunu tarar, on binlerce anahtar varsa sunucuyu birkaç saniye bloke eder. Yerine &lt;code&gt;SCAN&lt;/code&gt; cursor&apos;ı kullanın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTL vermeyi unutmak&lt;/strong&gt;: &lt;code&gt;set&lt;/code&gt; çağrısının &lt;code&gt;ex&lt;/code&gt; parametresi olmadan yazdığınız her anahtar ölümsüz olur. Bellek sessizce şişer, fark ettiğinizde geç olur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;json.dumps&lt;/code&gt; default&apos;unu vermemek&lt;/strong&gt;: Datetime, Decimal, UUID gibi tipler &lt;code&gt;TypeError&lt;/code&gt; atar. &lt;code&gt;default=str&lt;/code&gt; koymak en pratik kurtuluş.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection pool&apos;u her istek başı yeniden yaratmak&lt;/strong&gt;: Flask/FastAPI uygulamalarında her request&apos;te &lt;code&gt;redis.Redis(...)&lt;/code&gt; çağırırsanız her seferinde yeni TCP bağlantısı kurulur. Module seviyesinde tek instance yeter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pickle&lt;/code&gt; ile cache yazmak&lt;/strong&gt;: Bir tarafta Python 3.11, başka tarafta 3.12 olunca uyumsuzluk çıkabiliyor; ayrıca güvenlik riski de var. JSON tercih edilir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanis&quot;&gt;Kapanis&lt;a href=&quot;#kapanis&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 &lt;code&gt;redis-py&lt;/code&gt;&apos;nin bağlantı kurulumundan cache decorator&apos;a, pipeline&apos;dan async kullanıma kadar pratik desenleri özetledik. Bana sorarsanız Redis&apos;in gücünün yarısı kütüphanenin kendisinde değil, doğru TTL ve doğru anahtar şemasında saklı; o yüzden tuzak listesini iki kere okumanızı öneriyorum. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-14-python-node-js-ve-go-ile-clickhouse-baglantisi/</id>
    <title>Python, Node.js ve Go ile ClickHouse Bağlantısı</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-14-python-node-js-ve-go-ile-clickhouse-baglantisi/"/>
    <published>2026-06-14T00:00:00Z</published>
    <updated>2026-06-14T00:00:00Z</updated>
    <author><name>Tarık Aksoy</name></author>
    <summary>ClickHouse&#039;a Python, Node.js ve Go&#039;dan baglanmak icin resmi istemcilerin temel kullanimi, sorgu desenleri ve toplu insert ipuclari.</summary>
    <content type="html">&lt;p&gt;Selam, bu yazımda ClickHouse&apos;a üç farklı dilden, yani Python, Node.js ve Go&apos;dan nasıl bağlandığımıza bakacağız. Konuyu sıfırdan açmaktan ziyade, her dilin resmi istemcisinin üzerinde bence en çok işe yarayan kalıbı bir araya toplamak istedim. Lafı çok uzatmadan başlayayım.&lt;/p&gt;
&lt;p&gt;ClickHouse iki ayrı protokol sunuyor: HTTP (8123) ve Native (9000). Native daha hızlı, ama dile özel sürücü istiyor. HTTP ise neredeyse her ortamla konuşur, küçük bir overhead&apos;i göze almanız yeter. Hangisini seçersek seçelim, üçünde de ortak bazı alışkanlıklar var: bağlantıyı tek seferde kurup tekrar kullanmak, sorguları parametreyle yazmak, insert&apos;leri toplu göndermek. Aşağıda her dilde bunu nasıl yaparız görelim.&lt;/p&gt;
&lt;h2 id=&quot;python-ile-clickhouse-connect&quot;&gt;Python ile clickhouse-connect&lt;a href=&quot;#python-ile-clickhouse-connect&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;Resmi Python istemcisi &lt;code&gt;clickhouse-connect&lt;/code&gt;. HTTP&apos;yi kullanıyor ama veri transferinde optimizasyonları var.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install clickhouse-connect
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basit bir bağlantı ve sorgu:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import clickhouse_connect

client = clickhouse_connect.get_client(
    host=&apos;localhost&apos;,
    port=8123,
    username=&apos;default&apos;,
    password=&apos;your_password&apos;
)

result = client.query(&apos;SELECT version()&apos;)
print(result.result_rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sorgularda parametreyi mutlaka kullanın. String&apos;i &lt;code&gt;f&apos;...&apos;&lt;/code&gt; ile birleştirip atmak ileride canınızı yakar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from datetime import datetime

result = client.query(
    &apos;SELECT * FROM events WHERE event_type = {etype:String} AND created_at &amp;gt; {since:DateTime}&apos;,
    parameters={&apos;etype&apos;: &apos;page_view&apos;, &apos;since&apos;: datetime(2026, 1, 1)}
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Büyük sonuç kümeleri için streaming şart, yoksa Python süreci şişirilir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with client.query_rows_stream(&apos;SELECT * FROM events&apos;) as stream:
    for row in stream:
        process_row(row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Insert&apos;i de bilerek toplu yapın. Tek tek satır göndermek ClickHouse için neredeyse en kötü desen:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;data = [
    [&apos;evt-1&apos;, &apos;page_view&apos;, 100, datetime.now()],
    [&apos;evt-2&apos;, &apos;click&apos;, 101, datetime.now()],
]
client.insert(&apos;events&apos;, data, column_names=[&apos;event_id&apos;, &apos;event_type&apos;, &apos;user_id&apos;, &apos;created_at&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;nodejs-ile-clickhouseclient&quot;&gt;Node.js ile @clickhouse/client&lt;a href=&quot;#nodejs-ile-clickhouseclient&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;Node tarafında resmi istemci &lt;code&gt;@clickhouse/client&lt;/code&gt;. HTTP üzerinden çalışır, hem normal hem stream sorguyu rahat handle eder.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @clickhouse/client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tipik kurulum:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const { createClient } = require(&apos;@clickhouse/client&apos;);

const client = createClient({
  url: &apos;http://localhost:8123&apos;,
  username: &apos;default&apos;,
  password: &apos;your_password&apos;,
  database: &apos;default&apos;,
  max_open_connections: 10,
});

async function getVersion() {
  const result = await client.query({
    query: &apos;SELECT version()&apos;,
    format: &apos;JSONEachRow&apos;,
  });
  console.log(await result.json());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Parametreli sorgu Node tarafında da aynı mantıkla:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function queryByType(eventType) {
  const result = await client.query({
    query: &apos;SELECT * FROM events WHERE event_type = {etype:String} LIMIT 100&apos;,
    query_params: { etype: eventType },
    format: &apos;JSONEachRow&apos;,
  });
  return result.json();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Express içinde tek bir &lt;code&gt;client&lt;/code&gt; örneğini uygulama ömrü boyunca paylaşın; her istek için yeni client açmayın. Kapanışta &lt;code&gt;await client.close()&lt;/code&gt; çağırmayı unutmayın.&lt;/p&gt;
&lt;p&gt;Insert tarafında objelerden oluşan bir dizi göndermek en pratik yol:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;await client.insert({
  table: &apos;events&apos;,
  values: [
    { event_id: &apos;evt-1&apos;, event_type: &apos;page_view&apos;, user_id: 100 },
    { event_id: &apos;evt-2&apos;, event_type: &apos;click&apos;, user_id: 101 },
  ],
  format: &apos;JSONEachRow&apos;,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;go-ile-clickhouse-go-v2&quot;&gt;Go ile clickhouse-go v2&lt;a href=&quot;#go-ile-clickhouse-go-v2&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;Go tarafında hem &lt;code&gt;database/sql&lt;/code&gt; arayüzünü hem de native API&apos;yi sunan tek bir paket var: &lt;code&gt;clickhouse-go/v2&lt;/code&gt;. Bence native API&apos;yi tercih edin, performansı gerçekten farklı.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go get github.com/ClickHouse/clickhouse-go/v2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Native bağlantı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;conn, err := clickhouse.Open(&amp;amp;clickhouse.Options{
    Addr: []string{&amp;quot;localhost:9000&amp;quot;},
    Auth: clickhouse.Auth{
        Database: &amp;quot;default&amp;quot;,
        Username: &amp;quot;default&amp;quot;,
        Password: &amp;quot;your_password&amp;quot;,
    },
    DialTimeout:     10 * time.Second,
    MaxOpenConns:    10,
    MaxIdleConns:    5,
    ConnMaxLifetime: time.Hour,
})
if err != nil {
    log.Fatal(err)
}
defer conn.Close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yüksek hızlı insert için &lt;code&gt;PrepareBatch&lt;/code&gt; ile gidin. Tek tek &lt;code&gt;INSERT&lt;/code&gt; çalıştırmak ClickHouse&apos;da herkesin yaptığı ilk hata:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;batch, err := conn.PrepareBatch(ctx, &amp;quot;INSERT INTO events (event_id, event_type, user_id, created_at)&amp;quot;)
if err != nil {
    return err
}
for i := 0; i &amp;lt; count; i++ {
    if err := batch.Append(uuid.New().String(), &amp;quot;page_view&amp;quot;, uint64(i), time.Now()); err != nil {
        return err
    }
}
return batch.Send()
&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;Tek tek INSERT atmak&lt;/strong&gt;: ClickHouse her insert&apos;i bir part olarak yazar. Saniyede yüzlerce küçük insert atarsanız &lt;code&gt;Too many parts&lt;/code&gt; hatasıyla tanışırsınız. Çözüm: dilinize özel batch API&apos;sini kullanmak veya &lt;code&gt;Buffer&lt;/code&gt; engine ile arabelleğe almak.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bağlantıyı her istekte yeniden açmak&lt;/strong&gt;: Web uygulamasında her request&apos;te &lt;code&gt;get_client&lt;/code&gt; veya &lt;code&gt;createClient&lt;/code&gt; çağırmak istemcinin pool&apos;unu sıfırlar. Tek bir client&apos;ı process ömrü boyunca paylaşın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parametre yerine string concat&lt;/strong&gt;: Native protokol parametreyi binary olarak gönderir, HTTP istemciler ise sunucu tarafında parse eder. Manuel concat hem hatalı tip dönüşümüne hem de injection&apos;a kapı açar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeout ayarlamamak&lt;/strong&gt;: ClickHouse aggregate sorguları uzun sürebilir. &lt;code&gt;connect_timeout&lt;/code&gt;, &lt;code&gt;send_receive_timeout&lt;/code&gt; veya Go tarafında &lt;code&gt;context.WithTimeout&lt;/code&gt; ile sınırlamak şart, yoksa deadlock&apos;a benzeyen bekleyişlerle uğraşırsınız.&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;Yerel ortamda Docker ile bir ClickHouse kaldırın:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d --name ch-test -p 8123:8123 -p 9000:9000 clickhouse/clickhouse-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra istediğiniz dilden basit bir &lt;code&gt;SELECT version()&lt;/code&gt; sorgusu atın. Versiyon string&apos;i geldiyse bağlantı tamam demektir. Insert testleri için önce küçük bir &lt;code&gt;events&lt;/code&gt; tablosu oluşturun, sonra batch ile 10 bin satır gönderip &lt;code&gt;SELECT count() FROM events&lt;/code&gt; ile doğrulayın.&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 ClickHouse&apos;a Python, Node.js ve Go&apos;dan resmi istemcilerle bağlanmanın temel kalıbına baktık. Şahsi kanaatim, hangi dili kullanıyorsanız kullanın üç tane şey size hep kazandırır: client&apos;ı bir kere açmak, sorguları parametreyle yazmak ve insert&apos;leri toplu göndermek. Üstüne retry ve timeout&apos;u eklediniz mi production&apos;da rahat ediyorsunuz. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-14-kubernetes-dagitimi-secimi-hangi-tat-hangi-ekibe-yakisir/</id>
    <title>Kubernetes Dağıtımı Seçimi: Hangi Tat Hangi Ekibe Yakışır</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-14-kubernetes-dagitimi-secimi-hangi-tat-hangi-ekibe-yakisir/"/>
    <published>2026-06-14T00:00:00Z</published>
    <updated>2026-06-14T00:00:00Z</updated>
    <author><name>Defne Erkan</name></author>
    <summary>Kubernetes dağıtımları arasında nasıl seçim yaparız? Upstream, managed bulut, kurumsal platformlar ve edge dağıtımları için pratik bir karar rehberi.</summary>
    <content type="html">&lt;p&gt;Merhaba arkadaşlar, bu yazıda Kubernetes dünyasının dağıtım (distribution) çeşitliliğine bakacağız. &lt;code&gt;kubeadm&lt;/code&gt;&apos;den OpenShift&apos;e, EKS&apos;ten K3s&apos;e kadar onlarca seçenek var; ekipler de hangisine yaslanacaklarını tartışmakla çeyrekleri yakıyor. Lafı uzatmadan başlayalım, ama önce küçük bir uyarı: bence buradaki asıl mesele &apos;hangisi en iyi&apos; değil, &apos;sizin ekibinize hangisi en az acı veriyor&apos;.&lt;/p&gt;
&lt;p&gt;Türkiye&apos;deki ekiplerin çoğu önce managed bir bulut servisiyle tanışıyor, sonra fatura ya da uyumluluk derdi yüzünden başka kapılar çalmaya başlıyor. O kapıların ardında ne var, ona bakalım.&lt;/p&gt;
&lt;h2 id=&quot;dort-ana-tat&quot;&gt;Dort ana tat&lt;a href=&quot;#dort-ana-tat&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;Kubernetes dağıtımlarını bence dört kategoride toplamak en temizi. Aşağıdaki tabloyu yanınızda tutun, çoğu tartışma şu üç soruya iniyor: Control plane&apos;i kim yamalıyor? Network ve storage default&apos;larını kim getiriyor? etcd hıçkırınca SLA&apos;yı kim imzalıyor?&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tat&lt;/th&gt;
&lt;th&gt;Örnekler&lt;/th&gt;
&lt;th&gt;Ne vaat ediyor&lt;/th&gt;
&lt;th&gt;Ne zaman tutar&lt;/th&gt;
&lt;th&gt;Sizde kalan iş&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upstream / DIY&lt;/td&gt;
&lt;td&gt;kubeadm, Talos, Kubespray&lt;/td&gt;
&lt;td&gt;Tam kontrol, sıfır lisans&lt;/td&gt;
&lt;td&gt;Özel network/storage ve maliyet kontrolü&lt;/td&gt;
&lt;td&gt;Her şey: upgrade, etcd, CNI, yedek&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed Bulut&lt;/td&gt;
&lt;td&gt;GKE, EKS, AKS&lt;/td&gt;
&lt;td&gt;Control plane servis olarak&lt;/td&gt;
&lt;td&gt;Hyperscaler&apos;da yaşıyorsanız&lt;/td&gt;
&lt;td&gt;Worker image, addon, autoscaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kurumsal&lt;/td&gt;
&lt;td&gt;OpenShift, Tanzu, RKE2&lt;/td&gt;
&lt;td&gt;GitOps, policy, destek&lt;/td&gt;
&lt;td&gt;Uyumluluk + çoklu cluster&lt;/td&gt;
&lt;td&gt;Donanım/bulut faturası, app guardrail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hafif / Edge&lt;/td&gt;
&lt;td&gt;K3s, MicroK8s, k0s&lt;/td&gt;
&lt;td&gt;Küçük ayak izi&lt;/td&gt;
&lt;td&gt;&amp;lt;1 GB RAM, kopuk ortam&lt;/td&gt;
&lt;td&gt;OS yaması, air-gap registry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;upstream-diy-zorlu-ama-ozgur&quot;&gt;Upstream DIY: zorlu ama ozgur&lt;a href=&quot;#upstream-diy-zorlu-ama-ozgur&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;kubeadm&lt;/code&gt;, Talos veya Kubespray ile yapılan kurulumlar. İşletim sistemini siz seçiyorsunuz, etcd&apos;yi siz besliyorsunuz, sertifikaları siz döndürüyorsunuz.&lt;/p&gt;
&lt;p&gt;Bunu tercih eden ekipler genellikle şu üç şeyi istiyor: bare metal&apos;de Ceph veya MinIO&apos;yla ucuz storage, vendor onayı beklemeden Cilium / Calico / eBPF kullanma özgürlüğü, ve per-core lisans tax&apos;ından kaçınma. Ama uyarayım: upgrade&apos;ler çok adımlı (drain, kubelet rotasyonu, CRD update). Bu işi erkenden pipeline&apos;a almazsanız her yamadan korkar hale geliyorsunuz.&lt;/p&gt;
&lt;p&gt;Açıkçası bu yolu sadece etcd, Linux network ve storage konusunda rahat SRE&apos;leriniz varsa seçin. Yoksa sabaha karşı 03:00&apos;te ayakta olursunuz.&lt;/p&gt;
&lt;h2 id=&quot;managed-bulut-en-hizli-baslangic&quot;&gt;Managed bulut: en hizli baslangic&lt;a href=&quot;#managed-bulut-en-hizli-baslangic&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;GKE, EKS, AKS, DigitalOcean Kubernetes - çoğu şirket buradan başlıyor, mantıklı da. Bulut sağlayıcı control plane&apos;i çalıştırıyor, size SLA&apos;lı bir API endpoint&apos;i veriyor.&lt;/p&gt;
&lt;p&gt;Otomatik olarak ne geliyor? Control plane yaması ve self-healing. IAM entegrasyonu (RBAC zaten kullandığınız kimliklerle eşleşiyor). Bulut-native load balancer, node pool, managed node upgrade ve autoscaler.&lt;/p&gt;
&lt;p&gt;Aralarında küçük farklar var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GKE:&lt;/strong&gt; Upstream feature&apos;lara en hızlı erişim, multi-cluster servisler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EKS:&lt;/strong&gt; AWS entegrasyonu derin ama worker AMI&apos;ı hâlâ siz yönetiyorsunuz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AKS:&lt;/strong&gt; Node auto-upgrade en sade, Windows node desteği güçlü.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eğer iş yükleriniz zaten RDS, Pub/Sub, S3 gibi PaaS&apos;lara bağlıysa, network egress&apos;i ucuza tutmak için bulut içinde kalmak en doğal seçim. İnsan gücünü cluster tesisatına değil uygulamalara harcayabiliyorsunuz. Tabii node ölçekleme, ingress controller, service mesh, gözlemlenebilirlik hâlâ size düşüyor.&lt;/p&gt;
&lt;h2 id=&quot;kurumsal-platformlar-paragrafli-yollar&quot;&gt;Kurumsal platformlar: paragrafli yollar&lt;a href=&quot;#kurumsal-platformlar-paragrafli-yollar&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;OpenShift, VMware Tanzu, Rancher RKE2 gibi dağıtımlar Kubernetes&apos;i policy, pipeline ve multi-cluster dashboard&apos;larla sarıyor. Az kişiyle çok cluster yönetmek ya da denetim raporlarına hazır olmak isteyen ekipler için biçilmiş kaftan.&lt;/p&gt;
&lt;p&gt;Ne kazanıyorsunuz? Hazır GitOps ve registry, FIPS-sertifikalı build&apos;ler, admission policy, tek tıkla rolling upgrade. Ama esneklikten fedakarlık edersiniz: CNI veya CSI sürücüsünü değiştirmek istediğinizde &apos;sertifikalı seçenekler&apos; dışına çıkmak zorlaşıyor. Per-core lisans maliyetleri de overprovision yaparsanız managed bulut faturasıyla yarışabilir.&lt;/p&gt;
&lt;p&gt;Bana sorarsanız: regülasyonlu bir sektördeyseniz (banka, sigorta, sağlık) veya 30+ cluster&apos;ı 5 kişilik platform ekibiyle yönetiyorsanız, bu kategori parasını fazlasıyla çıkarır.&lt;/p&gt;
&lt;h2 id=&quot;hafif-ve-edge-dagitimlari&quot;&gt;Hafif ve edge dagitimlari&lt;a href=&quot;#hafif-ve-edge-dagitimlari&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;Fabrikaya, gemiye, perakende mağazasına Kubernetes götürdüğünüzde ağır bağımlılıklar yük olur. K3s tek binary, SQLite veya etcd backend&apos;iyle IoT&apos;ye ve CI runner&apos;larına hitap ediyor. MicroK8s laptop lab&apos;ı için ideal. k0s ve Talos Edge immutable OS + Kubernetes kombosuyla kilitli appliance&apos;lar için tasarlanmış.&lt;/p&gt;
&lt;p&gt;Şu durumlarda mantıklı: ağ bağlantısı güvenilmez ve workload&apos;ları periyodik senkronluyorsunuz; donanım sınırlı (Raspberry Pi, Intel NUC) ama hâlâ declarative deploy istiyorsunuz; binlerce minik cluster&apos;ı tek tek SRE ekibiyle değil filo yöneticisiyle yönetmek istiyorsunuz.&lt;/p&gt;
&lt;p&gt;Tasarım notları: pull-tabanlı GitOps şart, çünkü merkezden push yapmak kopuk ortamlarda her zaman çalışmıyor. Container&apos;ları kendi registry&apos;nizden (Harbor, Dragonfly) senkronlamak için otomasyon kurun.&lt;/p&gt;
&lt;h2 id=&quot;pilot-calismadan-karar-vermeyin&quot;&gt;Pilot calismadan karar vermeyin&lt;a href=&quot;#pilot-calismadan-karar-vermeyin&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 en kritik adım bu. Karar masasına oturmadan önce şunları yapın:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Aynı workload&apos;u iki tatta deneyin.&lt;/strong&gt; Ana revenue API&apos;nızı hem GKE&apos;de hem bare metal kubeadm&apos;de döndürün. Autoscaler davranışı ve on-call gürültüsü ortaya çıksın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control plane MTTR&apos;ını ölçün.&lt;/strong&gt; Vendor&apos;a sorun: &apos;etcd bozulduğunda kim onarıyor, SLA ne?&apos; DIY cluster&apos;larda recovery runbook&apos;unuz olmalı (snapshot frekansı, quorum restore adımları).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bütçeyi gerçek rakamlarla yapın.&lt;/strong&gt; Managed control plane saatlik, kurumsal dağıtımlar core başına, bare metal capex + tesis. Üçünü de aynı tabloya koyun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage&apos;ı ilk günden planlayın.&lt;/strong&gt; Vendor CSI&apos;sine kilitlenmemek ileride kendi donanımınıza geçtiğinizde size çok zaman kazandırır.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;sik-karsilasilan-tuzaklar&quot;&gt;Sik karsilasilan tuzaklar&lt;a href=&quot;#sik-karsilasilan-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;&apos;En popülerini seçelim&apos; refleksi&lt;/strong&gt;: Topluluk büyüklüğü değil, sizin ekibinizin hangi failure mode&apos;ları kaldırabildiği önemli. EKS yaygın diye seçip sonra IAM modeline savaş açıyorsanız yanlış kapıdasınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lisansı sonra düşünmek&lt;/strong&gt;: Per-core fiyatlandırma overprovision durumunda hızla katlanır. POC sırasında 3 node kullandığınız için sözleşme imzalayıp prod&apos;da 30 node&apos;a çıkınca şok yaşanır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge için &apos;mini bulut&apos; beklemek&lt;/strong&gt;: K3s&apos;i fabrikaya kurup HQ&apos;dan push etmeye çalışmak. Pull-tabanlı GitOps&apos;a geçmezseniz drift kaçınılmaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Upgrade&apos;i sonraya bırakmak&lt;/strong&gt;: Upstream DIY&apos;da ilk upgrade&apos;i otomatize etmezseniz, sonraki her yamada ekip korkudan ertelemeye başlar. CVE biriktikçe risk artar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;hizli-oneriler&quot;&gt;Hizli oneriler&lt;a href=&quot;#hizli-oneriler&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;Küçük ekip + tamamen AWS/GCP/Azure: Managed servisle başla, autoscaler veya Karpenter aç, kazandığın zamanı gözlemlenebilirliğe yatır.&lt;/li&gt;
&lt;li&gt;Regülasyonlu kurum veya çok-bulutlu SRE ekibi: OpenShift veya Rancher; denetim kapıyı çaldığında destek sözleşmesi rahatlatır.&lt;/li&gt;
&lt;li&gt;Maliyet odaklı ve donanım zaten elinizde: Upstream kubeadm veya Talos + Ceph; opex düşük, egemenlik yüksek.&lt;/li&gt;
&lt;li&gt;Perakende, edge, IoT filosu: K3s veya MicroK8s + GitOps pull agent; manuel drift&apos;i öldürür.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanis&quot;&gt;Kapanis&lt;a href=&quot;#kapanis&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;Doğru Kubernetes tatı, failure mode&apos;larını anladığınız ve upgrade ritmi ekibinizin kapasitesine uyan tat. Tartışmayı vendor FUD&apos;una değil sahiplik çizgisine çekin: kim yamalıyor, kim incident&apos;ta nöbette, kim faturayı ödüyor? Bu üçünü netleştirdiğinizde dağıtım neredeyse kendi kendini seç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-06-14-postgresql-okuma-replikalari-ile-olcekleme-rehberi/</id>
    <title>PostgreSQL okuma replikaları ile ölçekleme rehberi</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-14-postgresql-okuma-replikalari-ile-olcekleme-rehberi/"/>
    <published>2026-06-14T00:00:00Z</published>
    <updated>2026-06-14T00:00:00Z</updated>
    <author><name>Hüseyin Arslan</name></author>
    <summary>PostgreSQL streaming replication ile okuma replikası kurulumunu, bağlantı yönlendirmeyi ve replikasyon gecikmesini pratik notlarla anlatır.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazıda PostgreSQL&apos;de okuma replikalarını (read replica) nasıl kurduğumuza, trafiği nasıl yönlendirdiğimize ve gecikme (lag) konusunda neye dikkat ettiğimize bakacağız. Konu yazılı bilgi olarak kafa karıştırıcı durabiliyor; ben de ilk başlarda &apos;streaming replication mu, logical mi?&apos; karmaşasına çok düşmüştüm. Hadi adım adım gidelim.&lt;/p&gt;
&lt;p&gt;Bir noktada veritabanınız tek başına yetmemeye başlar. Genelde önce yazma değil, &lt;strong&gt;okuma&lt;/strong&gt; yorulur: raporlar, listeler, dashboard sorguları, herkes aynı primary&apos;i kemiriyor. Okuma replikaları tam burada işe yarıyor. Yazmaları primary&apos;de tutuyorsunuz, okumaları birden fazla replikaya dağıtıyorsunuz, primary nefes alıyor.&lt;/p&gt;
&lt;h2 id=&quot;okuma-replikası-nedir&quot;&gt;Okuma replikası nedir?&lt;a href=&quot;#okuma-replikası-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;Okuma replikası, primary sunucudan WAL (write-ahead log) kayıtlarını streaming yoluyla alan ve aynı veriyi salt-okunur (read only) modda servis eden bir PostgreSQL örneğidir. Bunlara teknik dilde &lt;strong&gt;hot standby&lt;/strong&gt; de deniyor; çünkü ayakta dururken sorgu kabul ediyor. Yazmayı kabul etmezler, sadece &lt;code&gt;SELECT&lt;/code&gt; çalıştırırsınız.&lt;/p&gt;
&lt;p&gt;Mantığı kabaca şöyle: primary her commit&apos;te WAL üretir, replikalar bu WAL&apos;ı ağ üzerinden alıp kendi diskine uygular. Yani replika her zaman primary&apos;nin biraz gerisindedir; bu farka &apos;replikasyon gecikmesi&apos; diyoruz. Sıfır olmaz, kabul edilebilir bir aralıkta tutulur.&lt;/p&gt;
&lt;h2 id=&quot;primary-tarafında-ayar&quot;&gt;Primary tarafında ayar&lt;a href=&quot;#primary-tarafında-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;PostgreSQL 14 ve üstü için tipik &lt;code&gt;postgresql.conf&lt;/code&gt; ayarları aşağıdaki gibi. Bence bu kısımları kopyala-yapıştır yerine satır satır anlamak iyi olur, çünkü production&apos;da bunları sonradan değiştirmek genelde restart gerektiriyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1GB
hot_standby = on
hot_standby_feedback = on
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra replikasyon için ayrı bir kullanıcı açıyoruz. Primary kullanıcısıyla aynı şeyi yapmayın, ayrıştırın:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD &apos;guclu_parola&apos;;
GRANT pg_read_all_data TO replicator;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pg_hba.conf&lt;/code&gt; içine de replika IP&apos;lerini ekliyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;host    replication     replicator      10.0.0.12/32    scram-sha-256
host    replication     replicator      10.0.0.13/32    scram-sha-256
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart edip primary&apos;de replication slot&apos;larını oluşturuyoruz. Slot kullanmak şart değil ama şiddetle tavsiye ederim - replika kısa süre offline kalsa bile primary onun WAL&apos;ını silmez:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT pg_create_physical_replication_slot(&apos;replica1&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;replika-kurulumu&quot;&gt;Replika kurulumu&lt;a href=&quot;#replika-kurulumu&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;Replika tarafında veriyi sıfırdan &lt;code&gt;pg_basebackup&lt;/code&gt; ile çekiyoruz. Var olan data dizinini temizleyip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo -u postgres pg_basebackup \
    -h primary.example.com \
    -D /var/lib/postgresql/16/main \
    -U replicator \
    -P -v -R -X stream \
    -S replica1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-R&lt;/code&gt; parametresi &lt;code&gt;standby.signal&lt;/code&gt; dosyasını ve &lt;code&gt;postgresql.auto.conf&lt;/code&gt; içine bağlantı bilgilerini otomatik yazıyor. Servisi başlattığınızda replika streaming moduna geçer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl start postgresql
sudo -u postgres psql -c &apos;SELECT pg_is_in_recovery();&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cevap &lt;code&gt;t&lt;/code&gt; (true) ise iş tamam.&lt;/p&gt;
&lt;h2 id=&quot;trafiği-yönlendirme&quot;&gt;Trafiği yönlendirme&lt;a href=&quot;#trafiği-yönlendirme&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ı işin uygulama tarafına dokunan yeri. Üç yol var, ben kısaca üçünü de söyleyeyim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HAProxy / PgBouncer&lt;/strong&gt;: 5432 yazma, 5433 okuma gibi ayrı portlar verirsiniz. Uygulama hangi havuza bağlanacağına bağlantı string&apos;inden karar verir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;target_session_attrs&lt;/code&gt;&lt;/strong&gt;: psycopg ve &lt;code&gt;pg&lt;/code&gt; (Node.js) sürücüleri &lt;code&gt;prefer-standby&lt;/code&gt; veya &lt;code&gt;read-only&lt;/code&gt; modunu destekliyor. Birden fazla host yazıyorsunuz, sürücü uygun olanı seçiyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uygulama düzeyinde iki havuz&lt;/strong&gt;: &lt;code&gt;writePool&lt;/code&gt; ve &lt;code&gt;readPool&lt;/code&gt; diye iki ayrı bağlantı havuzu açıp &lt;code&gt;INSERT/UPDATE/DELETE&lt;/code&gt; için birini, &lt;code&gt;SELECT&lt;/code&gt; için diğerini kullanmak. En esnek yol bu.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bence küçük-orta ölçekte uygulama düzeyinde ayrım en sağlıklısı. HAProxy işin altyapı tarafına ek yük getiriyor, küçük ekipler için fazla.&lt;/p&gt;
&lt;h2 id=&quot;replikasyon-gecikmesi&quot;&gt;Replikasyon gecikmesi&lt;a href=&quot;#replikasyon-gecikmesi&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;Gecikmenin sıfır olmayacağını kabul edin. Önemli olan ne kadar olduğunu bilmek ve uygulamanın bunu hesaba katması.&lt;/p&gt;
&lt;p&gt;Primary&apos;de &lt;code&gt;pg_stat_replication&lt;/code&gt;, replikada &lt;code&gt;pg_last_xact_replay_timestamp()&lt;/code&gt; ile bakabilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    EXTRACT(EPOCH FROM NOW() - pg_last_xact_replay_timestamp())
    AS lag_seconds;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kullanıcının az önce yazdığı bir kaydı hemen okuması gereken senaryolar var (read-your-writes). Bu durumda son N saniye içinde yazma yaptıysa o kullanıcının okumasını primary&apos;ye yönlendiriyoruz; aksi durumda replikadan okuyor. Basit ama hayat kurtaran bir desen.&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;Replication slot vermemek&lt;/strong&gt;: Replika 30 dakika offline kaldığında primary WAL&apos;ı atar, replika bir daha yetişemez ve baştan kurmak zorunda kalırsınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tüm okumayı replikaya yıkmak&lt;/strong&gt;: Hesap bakiyesi gibi anlık tutarlılık isteyen sorgular replikadan okunmamalı. Listeleme, arama, dashboard - bunlar replikaya gider; kritik görüntüler primary&apos;de kalır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hot_standby_feedback&lt;/code&gt; ihmal etmek&lt;/strong&gt;: Replikada uzun süren bir analitik sorgu varsa primary VACUUM&apos;u onun ihtiyacı bittikten sonra yapar. Bu açıksa gecikme artar ama replikadaki sorgular &lt;code&gt;canceling statement due to conflict with recovery&lt;/code&gt; hatası almaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aynı bağlantı string&apos;inden hem yazma hem okuma yapmak&lt;/strong&gt;: Application kodunda iki ayrı havuz tutun; tek bir DSN ile ikisini birden hallederim demek başınızı ağrıtır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Promosyon prosedürünü hiç test etmemek&lt;/strong&gt;: &lt;code&gt;pg_promote()&lt;/code&gt; çalışıyor mu, DNS değişimi nasıl olacak, uygulama yeni primary&apos;i nasıl bulacak - bunları felaket gününde değil, bugün deneyin.&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;Kurulumu çalıştırıp primary&apos;de şu sorguyu atın:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT client_addr, state, sent_lsn, replay_lsn,
    pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) AS lag
FROM pg_stat_replication;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Her replika için bir satır görmelisiniz, &lt;code&gt;state&lt;/code&gt; sütunu &lt;code&gt;streaming&lt;/code&gt;, &lt;code&gt;lag&lt;/code&gt; ise birkaç kilobayt mertebesinde. Sonra primary&apos;de bir test tablosuna &lt;code&gt;INSERT&lt;/code&gt; atın, replikada birkaç saniye sonra &lt;code&gt;SELECT&lt;/code&gt; ile çıkıp çıkmadığına bakın. Çıkıyorsa replikasyon canlı.&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 okuma replikalarını streaming replication ile nasıl kurduğumuza, trafiği nasıl ayırdığımıza ve gecikmeyle nasıl yaşadığımıza baktık. Bana sorarsanız, replika eklemek mimari olarak ucuz görünür ama operasyonel maliyeti gerçek; bu yüzden ihtiyaç gerçekten doğmadan replika atmayın, doğduğunda da slot&apos;sız kurmayın. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-14-python-list-comprehension-ile-daha-temiz-kod/</id>
    <title>Python List Comprehension ile Daha Temiz Kod</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-14-python-list-comprehension-ile-daha-temiz-kod/"/>
    <published>2026-06-14T00:00:00Z</published>
    <updated>2026-06-14T00:00:00Z</updated>
    <author><name>Aslı Yavuz</name></author>
    <summary>Python list comprehension yapılarını ne zaman kullanmak gerekir, nerede klasik döngü daha mantıklı olur, sık düşülen tuzaklar nelerdir.</summary>
    <content type="html">&lt;p&gt;Selam, bu yazımda Python&apos;un en sevilen özelliklerinden biri olan list comprehension üzerine konuşalım. Çok yazılan, çok da yanlış yazılan bir konu; o yüzden sentaks türlerini tek tek görmekle kalmayacağız, hangi durumda doğal hangi durumda zorlama olduğunu da konuşacağız. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;Bir for döngüsü yazıp listeye &lt;code&gt;append&lt;/code&gt; etmek bir süre sonra göz tırmalıyor. Python&apos;da aynı işi tek satırda yapan, hem de çoğu zaman daha hızlı çalışan bir alternatif var: list comprehension. Yapı basit, kapasitesi ise tahminden çok daha geniş.&lt;/p&gt;
&lt;h2 id=&quot;list-comprehension-nedir&quot;&gt;List Comprehension Nedir?&lt;a href=&quot;#list-comprehension-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;Genel kalıbı şu şekilde: &lt;code&gt;[ifade for eleman in iterable]&lt;/code&gt;. Soldaki ifade her elemana uygulanır, sağdaki for kısmı nereden geçtiğini söyler. Karşılaştıralım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Klasik dongu
kareler = []
for x in range(10):
    kareler.append(x ** 2)

# List comprehension
kareler = [x ** 2 for x in range(10)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İki kod da &lt;code&gt;[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]&lt;/code&gt; üretir. Comprehension sürümü daha kısa, ama sadece kısa olması tek başına yeterli sebep değil. Aşağıda göreceğimiz gibi performans tarafında da net bir farkı var.&lt;/p&gt;
&lt;h2 id=&quot;filtreleme-ve-dönüşüm&quot;&gt;Filtreleme ve Dönüşüm&lt;a href=&quot;#filtreleme-ve-dönüşü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;if&lt;/code&gt; ekleyerek elemanları filtreleriz, üçlü operatörle (&lt;code&gt;x if koşul else y&lt;/code&gt;) şartlı dönüşüm yaparız:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sayilar = range(1, 11)

ciftler = [n for n in sayilar if n % 2 == 0]
# [2, 4, 6, 8, 10]

etiketler = [&apos;cift&apos; if n % 2 == 0 else &apos;tek&apos; for n in sayilar]
# [&apos;tek&apos;, &apos;cift&apos;, &apos;tek&apos;, &apos;cift&apos;, ...]

negatifler = [-5, 3, -1, 7]
sifirlanmis = [n if n &amp;gt;= 0 else 0 for n in negatifler]
# [0, 3, 0, 7]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada ufak bir tuzak var: &lt;code&gt;if&lt;/code&gt; for&apos;dan &lt;strong&gt;sonra&lt;/strong&gt; geldiğinde filtre, for&apos;dan &lt;strong&gt;önce&lt;/strong&gt; geldiğinde dönüşüm koşulu. Karıştırmaya çok müsait, dikkat edin.&lt;/p&gt;
&lt;h2 id=&quot;i̇ç-i̇çe-döngüler&quot;&gt;İç İçe Döngüler&lt;a href=&quot;#i̇ç-i̇çe-döngüler&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;Birden fazla &lt;code&gt;for&lt;/code&gt; da yazılabilir. Soldan sağa okumak, iç içe yazılmış döngülere denk düştüğünü hatırlamak yeterli:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;matris = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

duz = [n for satir in matris for n in satir]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradan öteye, örneğin matris transpozu ya da kartezyen çarpım için de çalışır. Ama bence iki seviyeden derinleşince comprehension&apos;ı bırakmak gerekiyor, aşağıda konuşalım.&lt;/p&gt;
&lt;h2 id=&quot;dict-ve-set-comprehension&quot;&gt;Dict ve Set Comprehension&lt;a href=&quot;#dict-ve-set-comprehension&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;Aynı mantık süslü parantezle çalışır:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;isimler = [&apos;ali&apos;, &apos;ayse&apos;, &apos;mehmet&apos;]

uzunluk = {ad: len(ad) for ad in isimler}
# {&apos;ali&apos;: 3, &apos;ayse&apos;: 4, &apos;mehmet&apos;: 6}

skorlar = {&apos;ali&apos;: 85, &apos;ayse&apos;: 72, &apos;mehmet&apos;: 91}
gecenler = {ad: s for ad, s in skorlar.items() if s &amp;gt;= 75}
# {&apos;ali&apos;: 85, &apos;mehmet&apos;: 91}

benzersiz = {len(w) for w in [&apos;hello&apos;, &apos;world&apos;, &apos;hello&apos;]}
# {5}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;generator-expression-hafıza-dostu-akrabası&quot;&gt;Generator Expression: Hafıza Dostu Akrabası&lt;a href=&quot;#generator-expression-hafıza-dostu-akrabası&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öşeli parantez yerine yuvarlak parantez kullanınca elimizde generator olur. Tüm listeyi belleğe almaz, değer üretir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Listeyi bellege alir, milyonlarca eleman icin agir
toplam = sum([x ** 2 for x in range(1_000_000)])

# Generator: deger deger uretir, sabit hafiza
toplam = sum(x ** 2 for x in range(1_000_000))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Büyük veri setlerinde varsayılan tercihim generator. Listeyi gerçekten saklayacaksanız comprehension, sadece bir kez gezecekseniz generator yeterli.&lt;/p&gt;
&lt;h2 id=&quot;performans&quot;&gt;Performans&lt;a href=&quot;#performans&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;Comprehension, klasik for döngüsünden tipik olarak %20-40 daha hızlıdır. Sebebi sihir değil: &lt;code&gt;append&lt;/code&gt; çağrısının her seferinde method lookup yapmaması, döngünün C seviyesinde dönmesi ve uygun durumda boyutun önceden ayrılması. Yine de mikro-optimizasyon uğruna kodu okunmaz hale getirmeyin; comprehension&apos;ın asıl kazanımı okunabilirlik.&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;Aşırı iç içe yazma&lt;/strong&gt;: İki seviyeden fazla &lt;code&gt;for&lt;/code&gt; ya da iki koşulu olan dev bir comprehension kimsenin keyifle okuduğu kod değildir. Böyle bir noktaya geldiyseniz döngü yazın, kimse üzülmez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yan etki üretmek&lt;/strong&gt;: &lt;code&gt;[print(x) for x in items]&lt;/code&gt; çalışır ama gereksiz bir liste üretir ve niyeti gizler. Yan etki istiyorsanız &lt;code&gt;for&lt;/code&gt; döngüsü kullanın, comprehension değer üretmek içindir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exception yakalamak&lt;/strong&gt;: Comprehension içinde &lt;code&gt;try/except&lt;/code&gt; yazamazsınız. Hata olabilecek dönüşümlerde (&lt;code&gt;int(x)&lt;/code&gt; gibi) klasik döngüye geçin, validasyonu açıkça yapın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Walrus&apos;i unutmak&lt;/strong&gt;: Tekrar eden bir hesap varsa &lt;code&gt;:=&lt;/code&gt; operatörü ile bir kere hesaplayıp yeniden kullanmak okunabilirliği artırır, performansa da yarar. Sık kullanılmıyor ama uygun yerde altın değerinde.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generator&apos;i iki kez gezmek&lt;/strong&gt;: Generator bir kez tükenir. İkinci &lt;code&gt;list(gen)&lt;/code&gt; çağrısı boş liste döner. Tekrar gerekiyorsa ya &lt;code&gt;tee&lt;/code&gt; ile çoğaltın ya da listeye alı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;Şahsi kanaatim, list comprehension Python&apos;un karakter özelliklerinden biri ve doğru yerde kullanıldığında kodun kalitesini gerçekten artırıyor. Ama her şeyi tek satıra sığdırmak gibi bir hedef varsa iş tersine dönüyor; o yüzden iki seviyeden derin gidince ya da yan etki gerekince döngüye dönmek doğru tercih. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-13-pythonda-json-dosyalarini-okumak-ve-yazmak/</id>
    <title>Python&#039;da JSON Dosyalarını Okumak ve Yazmak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-13-pythonda-json-dosyalarini-okumak-ve-yazmak/"/>
    <published>2026-06-13T00:00:00Z</published>
    <updated>2026-06-13T00:00:00Z</updated>
    <author><name>Levent Güneş</name></author>
    <summary>Python&#039;un yerleşik json modülü ile dosya okuma, yazma, hata yakalama ve datetime gibi karmaşık türlerle başa çıkmanın pratik yollari.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda Python&apos;da JSON dosyalarıyla nasıl çalıştığımıza biraz farklı bir açıdan bakacağız. Konuyu en baştan anlatmaya çalışmayacağım; bunun yerine her geliştirici bir şekilde takılır deyip geçtiğim tuzaklara ve &lt;code&gt;json&lt;/code&gt; modülünün gerçekten işine yarayan parçalarına odaklanacağım. Lafı uzatmadan başlayayım.&lt;/p&gt;
&lt;p&gt;JSON (JavaScript Object Notation) artık veri taşımanın ortak dili gibi. API yanıtlarını parse ederken, konfigürasyon tutarken, hatta küçük bir veri deposu olarak bile karşımıza sürekli çıkıyor. Python tarafında işin güzel yanı, JSON&apos;un yapısı dict ve list&apos;e neredeyse bire bir oturuyor. Yani başka bir kütüphaneye ihtiyacımız yok; standart kütüphanedeki &lt;code&gt;json&lt;/code&gt; modülü çoğu durumda yeter.&lt;/p&gt;
&lt;h2 id=&quot;json-modülünün-dört-kapısı&quot;&gt;json modülünün dört kapısı&lt;a href=&quot;#json-modülünün-dört-kapısı&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;json&lt;/code&gt; modülünü ilk öğrenirken çoğumuzu kafa karıştıran şey, dört tane benzer isimli fonksiyonun olması: &lt;code&gt;load&lt;/code&gt;, &lt;code&gt;loads&lt;/code&gt;, &lt;code&gt;dump&lt;/code&gt;, &lt;code&gt;dumps&lt;/code&gt;. Aslında işin mantığı çok basit: sondaki &lt;code&gt;s&lt;/code&gt; &apos;string&apos; anlamına geliyor. Yani &lt;code&gt;loads&lt;/code&gt; string&apos;den okur, &lt;code&gt;load&lt;/code&gt; ise dosyadan. Aynı şekilde &lt;code&gt;dumps&lt;/code&gt; string&apos;e yazar, &lt;code&gt;dump&lt;/code&gt; dosyaya. Karıştırırsanız da Python size güzelce hata verir, korkmayın.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json

# Dosyadan oku
with open(&apos;config.json&apos;, &apos;r&apos;, encoding=&apos;utf-8&apos;) as file:
    data = json.load(file)

# String&apos;den oku
raw = &apos;{&amp;quot;name&amp;quot;: &amp;quot;Ayşe&amp;quot;, &amp;quot;active&amp;quot;: true}&apos;
parsed = json.loads(raw)
print(parsed[&apos;active&apos;])  # True (Python boolean&apos;una donustu)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki kritik nokta: JSON&apos;daki &lt;code&gt;true&lt;/code&gt;/&lt;code&gt;false&lt;/code&gt;/&lt;code&gt;null&lt;/code&gt; değerleri Python&apos;da otomatik olarak &lt;code&gt;True&lt;/code&gt;/&lt;code&gt;False&lt;/code&gt;/&lt;code&gt;None&lt;/code&gt;&apos;a dönüşüyor. Bu küçük detay, özellikle API yanıtlarını işlerken düşülen çukurların başında geliyor.&lt;/p&gt;
&lt;h2 id=&quot;yazarken-biraz-nezaket-indent-ve-ensureascii&quot;&gt;Yazarken biraz nezaket: indent ve ensure_ascii&lt;a href=&quot;#yazarken-biraz-nezaket-indent-ve-ensureascii&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 dict&apos;i &lt;code&gt;json.dump&lt;/code&gt; ile yazdığınızda varsayılan olarak tek satırlık, sıkıştırılmış bir çıktı alırsınız. Bu, makineler için iyi; ama git diff&apos;te gördüğünüzde gözünüzü kanatır. İnsanın da okuyacağı dosyalar için &lt;code&gt;indent&lt;/code&gt; parametresini ekleyin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json

user = {
    &apos;isim&apos;: &apos;Ayşe&apos;,
    &apos;roller&apos;: [&apos;admin&apos;, &apos;editor&apos;],
    &apos;ayarlar&apos;: {&apos;tema&apos;: &apos;koyu&apos;}
}

with open(&apos;user.json&apos;, &apos;w&apos;, encoding=&apos;utf-8&apos;) as file:
    json.dump(user, file, indent=2, ensure_ascii=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ensure_ascii=False&lt;/code&gt; çoğu kişinin atladığı bir parametre. Varsayılan &lt;code&gt;True&lt;/code&gt; olduğu için Türkçe karakterleriniz &lt;code&gt;ç&lt;/code&gt; gibi escape edilmiş hâlde dosyaya yazılır. Çalışır, evet, ama dosyayı bir editörde açtığınızda ne olduğunu anlamak imkansız hâle gelir. Bence Türkçe içerikle uğraşıyorsanız bu parametreyi neredeyse her zaman &lt;code&gt;False&lt;/code&gt; yapın.&lt;/p&gt;
&lt;h2 id=&quot;hatalari-ciddiye-almak&quot;&gt;Hatalari ciddiye almak&lt;a href=&quot;#hatalari-ciddiye-almak&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 tip hatayla sık karşılaşırsınız: dosya yok ya da içeriği bozuk. Birincisi &lt;code&gt;FileNotFoundError&lt;/code&gt;, ikincisi &lt;code&gt;json.JSONDecodeError&lt;/code&gt;. Üretim kodunda ikisini de yakalamadan geçmeyin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json
from pathlib import Path

def load_config(filepath: str) -&amp;gt; dict:
    path = Path(filepath)
    if not path.exists():
        return {}

    try:
        with open(path, &apos;r&apos;, encoding=&apos;utf-8&apos;) as file:
            content = file.read()
            if not content.strip():
                return {}
            return json.loads(content)
    except json.JSONDecodeError as exc:
        print(f&apos;Bozuk JSON {filepath}: satir {exc.lineno}, sutun {exc.colno}&apos;)
        return {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;JSONDecodeError&lt;/code&gt;&apos;ün üzerinde durmak istiyorum çünkü hata mesajı gerçekten yardımcıdır. &lt;code&gt;exc.lineno&lt;/code&gt; ve &lt;code&gt;exc.colno&lt;/code&gt; size bozuk yerin satır-sütun bilgisini verir. Bir keresinde 200 satırlık config&apos;in 137. satırındaki sondaki virgülü bulmak için tam beş dakika harcamıştım, sonra fark ettim ki istisna nesnesi zaten söylüyormuş.&lt;/p&gt;
&lt;h2 id=&quot;datetime-gibi-yabancilar&quot;&gt;datetime gibi yabancilar&lt;a href=&quot;#datetime-gibi-yabancilar&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;JSON tipi olarak sadece string, sayı, boolean, null, dizi ve nesne anlar. &lt;code&gt;datetime&lt;/code&gt; yok. Bu yüzden bir datetime&apos;ı dump etmeye kalkarsanız &lt;code&gt;TypeError&lt;/code&gt; yer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json
from datetime import datetime

class IsoDatetimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

event = {&apos;isim&apos;: &apos;Konferans&apos;, &apos;tarih&apos;: datetime.now()}
print(json.dumps(event, cls=IsoDatetimeEncoder, ensure_ascii=False))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ISO format tercih etmemin sebebi standart olması ve &lt;code&gt;datetime.fromisoformat&lt;/code&gt; ile geri okumanın kolay olması. Şahsi tercihim, datetime alanlarını her zaman bu şekilde tutmak; epoch saniye saklayanlar olur, anlarım, ama bir dosyayı bir yıl sonra açıp &lt;code&gt;1735689600&lt;/code&gt; görmek pek hoş bir deneyim değil.&lt;/p&gt;
&lt;h2 id=&quot;buyuk-dosyalar-icin-streaming&quot;&gt;Buyuk dosyalar icin streaming&lt;a href=&quot;#buyuk-dosyalar-icin-streaming&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;Dosya gigabyte boyutlarına ulaşıyorsa &lt;code&gt;json.load&lt;/code&gt; her şeyi belleğe yükler ve makineniz inler. Bu noktada &lt;code&gt;ijson&lt;/code&gt; kütüphanesi devreye girer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import ijson

with open(&apos;users.json&apos;, &apos;rb&apos;) as file:
    for user in ijson.items(file, &apos;users.item&apos;):
        print(user[&apos;name&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatif olarak veriyi her satırda bir JSON nesnesi olacak şekilde &lt;strong&gt;JSON Lines&lt;/strong&gt; (jsonl) formatında saklayabilirsiniz. Log dosyaları için ideal: append etmek &lt;code&gt;O(1)&lt;/code&gt;, okurken &lt;code&gt;for line in file&lt;/code&gt; ile satır satır gidersiniz, hafıza problemi yok.&lt;/p&gt;
&lt;h2 id=&quot;sik-karsilasilan-tuzaklar&quot;&gt;Sik karsilasilan tuzaklar&lt;a href=&quot;#sik-karsilasilan-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;encoding=&apos;utf-8&apos;&lt;/code&gt; yazmamak&lt;/strong&gt;: Linux&apos;ta sorun çıkarmaz; Windows&apos;ta Türkçe karakterler bozuk gelebilir, dosya yazarken sistem kodlamasıyla yazılır. Açık yazın, bağımlılığı kapatın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ensure_ascii&lt;/code&gt; varsayılanını unutmak&lt;/strong&gt;: Türkçe içerikli dosyalarınızda &lt;code&gt;ç&lt;/code&gt; görüyorsanız sebebi budur. &lt;code&gt;ensure_ascii=False&lt;/code&gt; çözer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;eval()&lt;/code&gt; ile parse etmek&lt;/strong&gt;: İnternette eski kod örneklerinde görebilirsiniz. Yapmayın. Güvenilmez kaynaklı string kod yürütür. &lt;code&gt;json.loads&lt;/code&gt; her zaman güvenli.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON&apos;da yorum beklemek&lt;/strong&gt;: JSON spec&apos;inde yorum yok. &lt;code&gt;// comment&lt;/code&gt; yazılı bir dosyayı &lt;code&gt;json.loads&lt;/code&gt; reddeder. Konfigürasyon için yorum lazımsa YAML, TOML ya da JSON5 düşünün.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sondaki virgul&lt;/strong&gt;: &lt;code&gt;{&amp;quot;a&amp;quot;: 1,}&lt;/code&gt; geçerli JSON değildir, JavaScript objesi gibi davranır diye varsaymayın.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanis&quot;&gt;Kapanis&lt;a href=&quot;#kapanis&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;un &lt;code&gt;json&lt;/code&gt; modülü gündelik işleri rahatça halleder; ihtiyacınızın yüzde doksanına yeter. Bence kritik nokta dört fonksiyonu (&lt;code&gt;load&lt;/code&gt;/&lt;code&gt;loads&lt;/code&gt;/&lt;code&gt;dump&lt;/code&gt;/&lt;code&gt;dumps&lt;/code&gt;) net ayırt etmek, &lt;code&gt;encoding&lt;/code&gt; ile &lt;code&gt;ensure_ascii&lt;/code&gt; parametrelerini bilinçli kullanmak ve datetime gibi tipleri bir custom encoder ile evcilleştirmek. Performans gerçekten dert olduğunda &lt;code&gt;orjson&lt;/code&gt; veya &lt;code&gt;ujson&lt;/code&gt;&apos;a bakabilirsiniz, ama o noktaya gelene kadar standart kütüphane sizi yormayacaktı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-06-13-terratest-ile-gcp-terraform-modullerini-gercekten-test-etmek/</id>
    <title>Terratest ile GCP Terraform Modullerini Gercekten Test Etmek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-13-terratest-ile-gcp-terraform-modullerini-gercekten-test-etmek/"/>
    <published>2026-06-13T00:00:00Z</published>
    <updated>2026-06-13T00:00:00Z</updated>
    <author><name>Cem Karadağ</name></author>
    <summary>GCP icin yazilmis Terraform modullerini Terratest ile gercek kaynak yaratip dogrulayan entegrasyon testleri yazma rehberi.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazimda Terraform modullerinizi Terratest ile nasil ciddi anlamda test edersiniz, ona bakacagiz. Konuyu sevenlerden biriyim cunku &apos;plan tertemiz, apply patladi&apos; hikayesi insanin uretimde gormesi gereken son sey. Hadi baslayalim.&lt;/p&gt;
&lt;p&gt;Test yazmadan modul yayinlamak, testsiz uygulama kodu yazmak gibi: bir gun calismayi durduruyor ve genelde o gun production&apos;da. Terratest, bu noktada devreye giren bir Go kutuphanesi (library). Mock ile is yapmiyor; gercek GCP kaynaklarini kaldiriyor, kontrolleri calistiriyor, sonra hepsini yikiyor. Yavas mi? Evet. Ama static analysis&apos;in yakalayamadigi seyleri yakaliyor.&lt;/p&gt;
&lt;h2 id=&quot;terratest-neden&quot;&gt;Terratest neden?&lt;a href=&quot;#terratest-neden&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 tarafinda birkac test araci var. Terratest&apos;i one cikaran sey su:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gercek altyapi&lt;/strong&gt; - Mock degil, gercek kaynak. Ne deploy ediyorsaniz onu test ediyorsunuz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Programlama dili&lt;/strong&gt; - HCL&apos;in dar kalibinda kalmiyorsunuz; Go ile istediginiz assertion&apos;i yazabiliyorsunuz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retry mantigi&lt;/strong&gt; - Eventual consistency icin hazir helper&apos;lar var.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP yardimcilari&lt;/strong&gt; - Olusturdugunuz endpoint&apos;i test etmek icin kullanissiz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paralel calisma&lt;/strong&gt; - Testler eszamanli kosabiliyor, isim catismasini &lt;code&gt;random.UniqueId()&lt;/code&gt; cozuyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bence en buyuk avantaji, &apos;gercekten ne oluyor?&apos; sorusuna cevap vermesi. Plan ciktisina guvenmek bir noktada yetmiyor; Cloud Run servisi gerekirken paylasilan bir VPC connector&apos;a yanlis CIDR vermissiniz, plan bunu fark etmiyor, ama uygulama acilmiyor.&lt;/p&gt;
&lt;h2 id=&quot;iskelet-kurmak&quot;&gt;Iskelet kurmak&lt;a href=&quot;#iskelet-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;Modulun yanina bir test klasoru aciyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;modules/
  cloud-run/
    main.tf
    variables.tf
    outputs.tf
    test/
      cloud_run_test.go
      go.mod
      go.sum
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonra Go modulunu baslatip gerekli paketleri cekiyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd modules/cloud-run/test
go mod init test
go get github.com/gruntwork-io/terratest/modules/terraform
go get github.com/gruntwork-io/terratest/modules/gcp
go get github.com/gruntwork-io/terratest/modules/http-helper
go get github.com/stretchr/testify/assert
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buraya kadar her sey hazirlik. Asil is, Go testinde basliyor.&lt;/p&gt;
&lt;h2 id=&quot;cloud-run-modulu-icin-gercek-bir-test&quot;&gt;Cloud Run modulu icin gercek bir test&lt;a href=&quot;#cloud-run-modulu-icin-gercek-bir-test&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 bir ornek gorelim. Asagidaki test, &lt;code&gt;cloud-run&lt;/code&gt; modulunu apply ediyor, output&apos;lari topluyor ve servisin gerekten 200 dondurdugunu retry&apos;li bir HTTP cagrisiyla dogruluyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package test

import (
    &amp;quot;fmt&amp;quot;
    &amp;quot;os&amp;quot;
    &amp;quot;strings&amp;quot;
    &amp;quot;testing&amp;quot;
    &amp;quot;time&amp;quot;

    httpHelper &amp;quot;github.com/gruntwork-io/terratest/modules/http-helper&amp;quot;
    &amp;quot;github.com/gruntwork-io/terratest/modules/random&amp;quot;
    &amp;quot;github.com/gruntwork-io/terratest/modules/terraform&amp;quot;
    &amp;quot;github.com/stretchr/testify/assert&amp;quot;
)

func TestCloudRunServiceCreation(t *testing.T) {
    t.Parallel()

    uniqueID := random.UniqueId()
    serviceName := fmt.Sprintf(&amp;quot;test-service-%s&amp;quot;, strings.ToLower(uniqueID))
    projectID := os.Getenv(&amp;quot;GCP_PROJECT_ID&amp;quot;)

    opts := terraform.WithDefaultRetryableErrors(t, &amp;amp;terraform.Options{
        TerraformDir: &amp;quot;../&amp;quot;,
        Vars: map[string]interface{}{
            &amp;quot;project_id&amp;quot;:            projectID,
            &amp;quot;region&amp;quot;:                &amp;quot;us-central1&amp;quot;,
            &amp;quot;service_name&amp;quot;:          serviceName,
            &amp;quot;image&amp;quot;:                 &amp;quot;gcr.io/cloudrun/hello&amp;quot;,
            &amp;quot;port&amp;quot;:                  8080,
            &amp;quot;allow_unauthenticated&amp;quot;: true,
        },
        NoColor: true,
    })

    defer terraform.Destroy(t, opts)
    terraform.InitAndApply(t, opts)

    serviceURL := terraform.Output(t, opts, &amp;quot;service_url&amp;quot;)
    assert.NotEmpty(t, serviceURL)

    httpHelper.HttpGetWithRetryWithCustomValidation(
        t, serviceURL, nil, 30, 10*time.Second,
        func(status int, body string) bool { return status == 200 },
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki kritik nokta &lt;code&gt;defer terraform.Destroy&lt;/code&gt;. Test ortada patlasa bile bu satir cagriliyor; yani &lt;code&gt;t.Fatal&lt;/code&gt; ile dussek de kaynaklar yikiliyor. Bunu unutursaniz GCP fatura ekraninda hosgelmeyen bir surpriz oluyor.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HttpGetWithRetryWithCustomValidation&lt;/code&gt; da onemli: Cloud Run apply&apos;dan hemen sonra hazir olmuyor, bir-iki saniye lazim. 30 deneme ve 10 saniyelik aralik benim icin makul; siz kendi ortaminiza gore ayarlayin.&lt;/p&gt;
&lt;h2 id=&quot;hizli-testler-validate-ve-plan&quot;&gt;Hizli testler: validate ve plan&lt;a href=&quot;#hizli-testler-validate-ve-plan&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;Her test gercek kaynak yaratmasin. Validate ve plan testleri, modulu deploy etmeden konfigurasyonu kontrol ediyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func TestTerraformValidate(t *testing.T) {
    t.Parallel()

    opts := &amp;amp;terraform.Options{
        TerraformDir: &amp;quot;../&amp;quot;,
        Vars: map[string]interface{}{
            &amp;quot;project_id&amp;quot;:   &amp;quot;test-project&amp;quot;,
            &amp;quot;service_name&amp;quot;: &amp;quot;test-service&amp;quot;,
            &amp;quot;image&amp;quot;:        &amp;quot;gcr.io/test/image:latest&amp;quot;,
        },
    }

    terraform.Init(t, opts)
    terraform.Validate(t, opts)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bunlari &lt;code&gt;-short&lt;/code&gt; flag&apos;i ile koruyup &lt;code&gt;go test -short&lt;/code&gt; calistirdiginizda saniyeler icinde bitiyor. CI&apos;da pre-commit niteligindeki bir sasama cizgisi olarak iyi is goruyor; gercek deploy&apos;lu testleri gece veya manuel tetikle birakirsiniz.&lt;/p&gt;
&lt;h2 id=&quot;sik-karsilasilan-tuzaklar&quot;&gt;Sik karsilasilan tuzaklar&lt;a href=&quot;#sik-karsilasilan-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 bir GCP projesi kullanmak&lt;/strong&gt;: Production projesinde test calistirmayin. Adi &apos;test&apos; olan ayri bir proje acin, IAM&apos;ini sikilastirin. Bir keresinde firma icinde &apos;sadece staging-like ortam&apos; diye paylasilan bir projede testler patlatildi, manuel cleanup yapan ekip iki saatini verdi.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Destroy&apos;u defer&apos;lememek&lt;/strong&gt;: &lt;code&gt;terraform.InitAndApply&lt;/code&gt;&apos;dan sonra &lt;code&gt;defer terraform.Destroy&lt;/code&gt; yazmazsaniz panic olusursa kaynak orada kaliyor. Buna ek olarak periyodik cleanup script&apos;i sart - Terratest %100 idempotent degil; iptal edilen test&apos;ten kalan artiklari haftalik bir job temizlesin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sabit isim kullanmak&lt;/strong&gt;: Iki test ayni isimde Cloud Run servisi yaratmaya calisinca biri 409 alir. &lt;code&gt;random.UniqueId()&lt;/code&gt; zaten cozuyor, kullanmamak icin sebep yok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeout&apos;u kucuk tutmak&lt;/strong&gt;: GKE Autopilot cluster&apos;i 10-15 dakika alabiliyor. &lt;code&gt;go test -timeout 30m&lt;/code&gt; minimum; varsayilan 10 dakika cluster testlerinde ortada keser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maliyeti gormezden gelmek&lt;/strong&gt;: Bu testler para harciyor. Her local kayitta kosturmayin; CI&apos;da nightly veya PR-uzerine sinirli kosun.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;dogrulama&quot;&gt;Dogrulama&lt;a href=&quot;#dogrulama&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 calistirip dogrulamak icin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd modules/cloud-run/test
GCP_PROJECT_ID=my-test-project go test -v -timeout 30m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sadece hizli testleri calistirmak isterseniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go test -v -short -timeout 5m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ciktida &lt;code&gt;PASS&lt;/code&gt; ve &lt;code&gt;--- PASS: TestCloudRunServiceCreation&lt;/code&gt; satirini goruyorsaniz, hem apply hem assertion hem de destroy bitmis demektir.&lt;/p&gt;
&lt;h2 id=&quot;kapanis&quot;&gt;Kapanis&lt;a href=&quot;#kapanis&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 yazida Terratest ile GCP Terraform modullerini gercek kaynak yaratip yikan entegrasyon testlerine baktik. Sahsi kanaatim, validate testleri her zaman, integration testleri ise haftalik nightly veya release oncesi calismali; her commit&apos;te tum suite&apos;i acmak hem yavas hem pahaliya patliyor. Umarim faydali olur, gorusmek uzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-13-clickhouse-bitmap-araliklarini-bitmapmin-ve-bitmapmax-ile-bulmak/</id>
    <title>ClickHouse Bitmap Aralıklarını bitmapMin ve bitmapMax ile Bulmak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-13-clickhouse-bitmap-araliklarini-bitmapmin-ve-bitmapmax-ile-bulmak/"/>
    <published>2026-06-13T00:00:00Z</published>
    <updated>2026-06-13T00:00:00Z</updated>
    <author><name>İrem Tan</name></author>
    <summary>ClickHouse roaring bitmap yapilarinda en kucuk ve en buyuk degeri bitmapMin ve bitmapMax ile cekme, kohort ve oturum analizinde pratik kullanim.</summary>
    <content type="html">&lt;p&gt;Selam, bu yazımda ClickHouse&apos;un az konuşulan ama segment ve kohort analizinde işi gerçekten kolaylaştıran iki fonksiyonuna, &lt;code&gt;bitmapMin()&lt;/code&gt; ve &lt;code&gt;bitmapMax()&lt;/code&gt;, yakından bakacağız. Roaring bitmap&apos;lerle ilk tanışan arkadaşlar için biraz bağlam vereceğim, sonra doğrudan örneklere geçeceğim. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;ClickHouse&apos;da roaring bitmap, bir sürü &lt;code&gt;UInt32&lt;/code&gt; ya da &lt;code&gt;UInt64&lt;/code&gt; değerini sıkıştırılmış biçimde tutmanın çok ucuz bir yolu. Milyonlarca kullanıcı kimliğini bir satırın bir hücresinde tutabiliyorsunuz, üstelik birleşim, kesişim, fark gibi küme operasyonları neredeyse bedava. Ben ilk başlarda &apos;tamam, kümeyi koyduk, ama bu kümeden minimum ve maksimum değeri nasıl alacağız?&apos; diye uzun süre dokümantasyona bakmıştım. Cevap aslında çok basit: &lt;code&gt;bitmapMin()&lt;/code&gt; ve &lt;code&gt;bitmapMax()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;bitmapmin-ve-bitmapmax-nedir&quot;&gt;bitmapMin ve bitmapMax nedir?&lt;a href=&quot;#bitmapmin-ve-bitmapmax-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;İki fonksiyon da tek iş yapar: bitmap&apos;in içindeki en küçük ve en büyük tamsayıyı döndürür. Karşılığı kabaca şöyle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    bitmapMin(bitmapBuild([3, 7, 1, 42, 15])) AS min_val,
    bitmapMax(bitmapBuild([3, 7, 1, 42, 15])) AS max_val;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Çıktı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;min_val | max_val
--------+--------
1       | 42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yani &lt;code&gt;MIN()&lt;/code&gt; ve &lt;code&gt;MAX()&lt;/code&gt; gibi düşünebilirsiniz, ama &lt;code&gt;Array&lt;/code&gt; ya da satır kümeleri yerine bitmap üzerinde çalışıyor. Aradaki performans farkı da küçümsenecek gibi değil; bitmap üzerinde min/max O(1) civarı.&lt;/p&gt;
&lt;h2 id=&quot;segmentin-en-eski-ve-en-yeni-kullanıcısı&quot;&gt;Segmentin en eski ve en yeni kullanıcısı&lt;a href=&quot;#segmentin-en-eski-ve-en-yeni-kullanıcısı&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;Kullanıcı kimlikleri monoton artıyorsa (auto-increment ya da Snowflake ID gibi), &lt;code&gt;bitmapMin()&lt;/code&gt; size segmentin en eski üyesini, &lt;code&gt;bitmapMax()&lt;/code&gt; ise en yeni üyesini verir. Şahsi kanaatim, bu sorgu kohort dashboard&apos;larının %80&apos;inde işe yarıyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    segment_name,
    bitmapMin(user_bitmap) AS oldest_user_id,
    bitmapMax(user_bitmap) AS newest_user_id,
    bitmapCardinality(user_bitmap) AS segment_size
FROM user_segments
GROUP BY segment_name;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tek tablo taraması, üç toplulaştırma. JOIN yok.&lt;/p&gt;
&lt;h2 id=&quot;oturum-içi-olay-aralığı&quot;&gt;Oturum içi olay aralığı&lt;a href=&quot;#oturum-içi-olay-aralığı&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;Her oturuma ait olay sıra numaralarını bir bitmap&apos;te tutuyorsanız, ilk ve son olayı tek seferde çekebilirsiniz. Aşağıdaki sorgu en uzun olay aralığına sahip on oturumu sıralar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    session_id,
    bitmapMin(event_seq_bitmap) AS first_event_seq,
    bitmapMax(event_seq_bitmap) AS last_event_seq,
    bitmapMax(event_seq_bitmap) - bitmapMin(event_seq_bitmap) AS sequence_span
FROM session_events
GROUP BY session_id
ORDER BY sequence_span DESC
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sequence_span&lt;/code&gt; değeri olay sayısı değil, ilk ve son olay arasındaki numara farkı. İkisinin arasındaki uçurum büyükse oturumun bazı olayları atlamış olduğunu (ya da numaralandırma boşluk bıraktığını) anlarsınız.&lt;/p&gt;
&lt;h2 id=&quot;seyrek-kohortları-bulmak&quot;&gt;Seyrek kohortları bulmak&lt;a href=&quot;#seyrek-kohortları-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;Bir kohort 100 milyon ile 100 milyon 50 bin arasında 50 bin kullanıcıyı tutuyorsa, kimlik aralığı dar; toplulaşmış. Aynı 50 bin kullanıcı 1 milyon ile 100 milyon arasına dağılmışsa kohort seyrek. &lt;code&gt;bitmapMin/Max&lt;/code&gt; ile &lt;code&gt;bitmapCardinality()&lt;/code&gt; arasındaki oranı kullanarak doluluk yüzdesini çıkarmak çok kolay:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    cohort_id,
    bitmapMin(user_bitmap)                              AS min_uid,
    bitmapMax(user_bitmap)                              AS max_uid,
    bitmapMax(user_bitmap) - bitmapMin(user_bitmap) + 1 AS id_range,
    bitmapCardinality(user_bitmap)                      AS actual_count,
    round(
        100.0 * bitmapCardinality(user_bitmap)
              / (bitmapMax(user_bitmap) - bitmapMin(user_bitmap) + 1),
        1
    ) AS fill_pct
FROM cohorts;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Düşük &lt;code&gt;fill_pct&lt;/code&gt; seyrek kohortu işaret eder. Pazarlama tarafında &apos;aynı kohort ama hangileri yeni katılım, hangileri eski&apos; tartışmasını bu tek sorgu çözüyor.&lt;/p&gt;
&lt;h2 id=&quot;groupbitmap-toplulastirmasi-ile-birlikte&quot;&gt;groupBitmap toplulastirmasi ile birlikte&lt;a href=&quot;#groupbitmap-toplulastirmasi-ile-birlikte&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;Birden fazla satırın bitmap&apos;ini birleştirip min/max almak istiyorsanız, &lt;code&gt;groupBitmapState&lt;/code&gt; ile zincirlemek doğal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    toDate(event_date)                      AS date,
    bitmapMin(groupBitmapState(user_id))    AS min_uid_active,
    bitmapMax(groupBitmapState(user_id))    AS max_uid_active
FROM daily_active_users
GROUP BY date
ORDER BY date DESC;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada günlük olarak aktif olan kullanıcıların kimlik aralığını çıkarıyoruz. ClickHouse bitmap&apos;leri arka planda birleştirir, sonra üzerine min/max uygular.&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;Boş bitmap&apos;i unutmak&lt;/strong&gt;: Boş bir bitmap&apos;e &lt;code&gt;bitmapMin()&lt;/code&gt; çağırdığınızda 4294967295 (&lt;code&gt;UINT32_MAX&lt;/code&gt;), &lt;code&gt;bitmapMax()&lt;/code&gt; çağırdığınızda ise 0 döner. Sorguya bu sayıyı geçerli bir kullanıcı kimliği gibi yansıtırsanız dashboard&apos;unuz tuhaf görünür. Her zaman önce &lt;code&gt;bitmapCardinality(b) &amp;gt; 0&lt;/code&gt; kontrolü yapın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UInt64 ve UInt32 karışımı&lt;/strong&gt;: Bitmap&apos;i &lt;code&gt;UInt64&lt;/code&gt; değerlerle kurduysanız boş durumda &lt;code&gt;bitmapMin()&lt;/code&gt; &lt;code&gt;UINT64_MAX&lt;/code&gt; döner. İki bitmap&apos;i birleştirirken tipler tutmuyorsa beklemediğiniz cast hataları alırsınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;bitmapMax - bitmapMin&lt;/code&gt; ile aralığı sayı zannetmek&lt;/strong&gt;: Aralık iki ucu kapsadığı için doğru formül &lt;code&gt;bitmapMax - bitmapMin + 1&lt;/code&gt;. Bunu yazmadığınız zaman %1 - %2 oranında küçük bir sapma yaşarsınız; küçük gibi görünür, ama eşik değerlerinde ciddi farklar yaratır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filtre uygulamadan tüm bitmap&apos;i kaldırmak&lt;/strong&gt;: Çok büyük bitmap&apos;lerde min/max ucuz olsa da, gereksiz &lt;code&gt;bitmapBuild&lt;/code&gt; çağrısı pahalı. Mümkünse bitmap&apos;i &lt;code&gt;AggregatingMergeTree&lt;/code&gt; ya da materialize edilmiş bir kolonda hazır tutun.&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 ClickHouse&apos;un &lt;code&gt;bitmapMin()&lt;/code&gt; ve &lt;code&gt;bitmapMax()&lt;/code&gt; fonksiyonlarını segment, kohort ve oturum analizi için nasıl kullanacağımıza baktık. Bana sorarsanız, kullanıcı kimliklerini bitmap&apos;te tutuyorsanız bu iki fonksiyon olmadan analizi tamamlamak çok zor; toplam kardinalite ile birleştirince elinize hem &apos;kim&apos; hem &apos;ne kadar yayılmış&apos; bilgisi geçiyor. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-13-mysql-json-replace-ile-guvenli-alan-guncelleme/</id>
    <title>MySQL JSON_REPLACE ile Güvenli Alan Güncelleme</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-13-mysql-json-replace-ile-guvenli-alan-guncelleme/"/>
    <published>2026-06-13T00:00:00Z</published>
    <updated>2026-06-13T00:00:00Z</updated>
    <author><name>Tolga Özkan</name></author>
    <summary>MySQL JSON_REPLACE fonksiyonunun nasil calistigini, JSON_SET ve JSON_INSERT ile farkini ve gercek senaryolarda nerede ise yaradigini anlatir.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda MySQL&apos;in &lt;code&gt;JSON_REPLACE()&lt;/code&gt; fonksiyonu üzerinde duracağız. JSON kolonlarıyla çalışmaya başladığınızda er ya da geç karşınıza çıkan bir ihtiyaç var: &apos;sadece var olan alanı güncelle, yeni alan ekleme&apos;. &lt;code&gt;JSON_SET()&lt;/code&gt; her şeyi yapar gibi durur ama bazen tam da bunu istemezsiniz. Hadi farkları somutlaştıralım.&lt;/p&gt;
&lt;h2 id=&quot;jsonreplace-nedir&quot;&gt;JSON_REPLACE Nedir?&lt;a href=&quot;#jsonreplace-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;JSON_REPLACE()&lt;/code&gt;, bir JSON dokümanı içindeki &lt;strong&gt;var olan&lt;/strong&gt; path&apos;in değerini güncelleyen bir fonksiyon. Yani path zaten dokümanda yoksa, fonksiyon herhangi bir şey eklemez; dokümanı olduğu gibi geri verir. Bu davranış kulağa kısıtlayıcı gelebilir, ama bence işin güzel tarafı bu. Şemasız bir alanda kazara yeni anahtar yaratmak, ileride &apos;bu alan nereden geldi?&apos; diye uzun uzun loglara bakmaya neden oluyor.&lt;/p&gt;
&lt;p&gt;Söz dizimi sade:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;JSON_REPLACE(json_doc, path, val [, path, val] ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Birden fazla path-değer çiftini aynı çağrıda geçebilirsiniz. Eğer &lt;code&gt;json_doc&lt;/code&gt; veya path&apos;lerden biri &lt;code&gt;NULL&lt;/code&gt; ise sonuç da &lt;code&gt;NULL&lt;/code&gt; olur, dikkat edin.&lt;/p&gt;
&lt;h2 id=&quot;basit-örnekler&quot;&gt;Basit Örnekler&lt;a href=&quot;#basit-örnekler&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 kuru kuru birkaç örnek görelim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Var olan bir alani guncelle
SELECT JSON_REPLACE(&apos;{&amp;quot;ad&amp;quot;:&amp;quot;Ayse&amp;quot;,&amp;quot;yas&amp;quot;:25}&apos;, &apos;$.yas&apos;, 30);
-- Sonuc: {&amp;quot;ad&amp;quot;: &amp;quot;Ayse&amp;quot;, &amp;quot;yas&amp;quot;: 30}

-- Path yok: dokuman degismez
SELECT JSON_REPLACE(&apos;{&amp;quot;ad&amp;quot;:&amp;quot;Ayse&amp;quot;}&apos;, &apos;$.yas&apos;, 30);
-- Sonuc: {&amp;quot;ad&amp;quot;: &amp;quot;Ayse&amp;quot;}  (yas EKLENMEDI)

-- Birden fazla alani tek seferde guncelle
SELECT JSON_REPLACE(&apos;{&amp;quot;ad&amp;quot;:&amp;quot;Ayse&amp;quot;,&amp;quot;yas&amp;quot;:25,&amp;quot;sehir&amp;quot;:&amp;quot;Izmir&amp;quot;}&apos;,
  &apos;$.yas&apos;, 30, &apos;$.sehir&apos;, &apos;Ankara&apos;);
-- Sonuc: {&amp;quot;ad&amp;quot;: &amp;quot;Ayse&amp;quot;, &amp;quot;yas&amp;quot;: 30, &amp;quot;sehir&amp;quot;: &amp;quot;Ankara&amp;quot;}

-- Ic ice degeri guncelle
SELECT JSON_REPLACE(&apos;{&amp;quot;kullanici&amp;quot;:{&amp;quot;ad&amp;quot;:&amp;quot;Ayse&amp;quot;,&amp;quot;yas&amp;quot;:25}}&apos;,
  &apos;$.kullanici.yas&apos;, 30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki kritik nokta şu: ikinci sorguda &lt;code&gt;$.yas&lt;/code&gt; path&apos;i mevcut değil, dolayısıyla doküman değişmeden geliyor. &lt;code&gt;JSON_SET()&lt;/code&gt; olsaydı &lt;code&gt;yas: 30&lt;/code&gt; olarak eklenecekti.&lt;/p&gt;
&lt;h2 id=&quot;jsonreplace-jsonset-ve-jsoninsert-farkı&quot;&gt;JSON_REPLACE, JSON_SET ve JSON_INSERT Farkı&lt;a href=&quot;#jsonreplace-jsonset-ve-jsoninsert-farkı&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;Üç fonksiyon birbirine benzer, ama davranışları üç farklı niyete denk düşer. Aynı dokümana üçünü uygulayalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SET @doc = &apos;{&amp;quot;ad&amp;quot;:&amp;quot;Ayse&amp;quot;,&amp;quot;yas&amp;quot;:25}&apos;;

-- JSON_REPLACE: var olani gunceller, eksigi gormezden gelir
SELECT JSON_REPLACE(@doc, &apos;$.yas&apos;, 30, &apos;$.sehir&apos;, &apos;Ankara&apos;);
-- {&amp;quot;ad&amp;quot;: &amp;quot;Ayse&amp;quot;, &amp;quot;yas&amp;quot;: 30}   sehir EKLENMEDI

-- JSON_SET: var olani gunceller, eksigi ekler (upsert)
SELECT JSON_SET(@doc, &apos;$.yas&apos;, 30, &apos;$.sehir&apos;, &apos;Ankara&apos;);
-- {&amp;quot;ad&amp;quot;: &amp;quot;Ayse&amp;quot;, &amp;quot;yas&amp;quot;: 30, &amp;quot;sehir&amp;quot;: &amp;quot;Ankara&amp;quot;}

-- JSON_INSERT: sadece eksigi ekler, var olana dokunmaz
SELECT JSON_INSERT(@doc, &apos;$.yas&apos;, 30, &apos;$.sehir&apos;, &apos;Ankara&apos;);
-- {&amp;quot;ad&amp;quot;: &amp;quot;Ayse&amp;quot;, &amp;quot;yas&amp;quot;: 25, &amp;quot;sehir&amp;quot;: &amp;quot;Ankara&amp;quot;}   yas DEGISMEDI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Şahsi kanaatim, çoğu update senaryosunda &lt;code&gt;JSON_SET()&lt;/code&gt; &apos;genel amaçlı&apos; diye seçiliyor ama asıl güvenli olan &lt;code&gt;JSON_REPLACE()&lt;/code&gt;. Çünkü kodda yazım hatasıyla &lt;code&gt;&apos;$.statu&apos;&lt;/code&gt; yerine &lt;code&gt;&apos;$.status&apos;&lt;/code&gt; yazıp bir gün milyonlarca satırınıza sessizce yeni bir alan açmış olmazsınız.&lt;/p&gt;
&lt;h2 id=&quot;tablo-üzerinde-pratik-kullanım&quot;&gt;Tablo Üzerinde Pratik Kullanım&lt;a href=&quot;#tablo-üzerinde-pratik-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;Şimdi gerçek bir senaryo. Diyelim ki bir &lt;code&gt;urunler&lt;/code&gt; tablomuz var, JSON kolonu içinde marka, RAM, depolama gibi öznitelikler tutuluyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE urunler (
  id INT AUTO_INCREMENT PRIMARY KEY,
  ad VARCHAR(100),
  ozellikler JSON
);

INSERT INTO urunler (ad, ozellikler) VALUES
(&apos;Laptop&apos;, &apos;{&amp;quot;marka&amp;quot;:&amp;quot;Dell&amp;quot;,&amp;quot;ram&amp;quot;:16,&amp;quot;depolama&amp;quot;:512}&apos;),
(&apos;Telefon&apos;, &apos;{&amp;quot;marka&amp;quot;:&amp;quot;Apple&amp;quot;,&amp;quot;ram&amp;quot;:8,&amp;quot;depolama&amp;quot;:256}&apos;);

-- Tum Dell urunlerinin depolamasini 1024 yap
UPDATE urunler
SET ozellikler = JSON_REPLACE(ozellikler, &apos;$.depolama&apos;, 1024)
WHERE JSON_UNQUOTE(JSON_EXTRACT(ozellikler, &apos;$.marka&apos;)) = &apos;Dell&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada bilinçli tercih: bazı kayıtlarda &lt;code&gt;depolama&lt;/code&gt; alanı eksik olabilir, biz &lt;code&gt;JSON_REPLACE()&lt;/code&gt; kullandığımız için onlara yanlışlıkla yeni alan eklemiyoruz. &lt;code&gt;JSON_SET()&lt;/code&gt; kullansaydık eksiklerin hepsi 1024 ile dolardı, ki bu büyük ihtimalle istemediğimiz bir &apos;shadow migration&apos; olurdu.&lt;/p&gt;
&lt;h2 id=&quot;koşullu-güncelleme&quot;&gt;Koşullu Güncelleme&lt;a href=&quot;#koşullu-güncelleme&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;JSON_REPLACE()&lt;/code&gt;&apos;i &lt;code&gt;WHERE&lt;/code&gt; ile birleştirip &apos;optimistic update&apos; tarzı bir akış da kurabilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Sadece su an &apos;aktif&apos; olan urunu &apos;pasif&apos; yap
UPDATE urunler
SET ozellikler = JSON_REPLACE(ozellikler, &apos;$.durum&apos;, &apos;pasif&apos;)
WHERE id = 5
  AND JSON_UNQUOTE(JSON_EXTRACT(ozellikler, &apos;$.durum&apos;)) = &apos;aktif&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eğer kayıt zaten &apos;pasif&apos;se güncelleme satır etkilemez. Bunu uygulamada kontrol edip &apos;beklediğim durumdan farklıydı&apos; diye dönebilirsiniz.&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;JSON_SET&lt;/code&gt; ile karıştırmak&lt;/strong&gt;: Klavyenizden &lt;code&gt;JSON_SET&lt;/code&gt; daha kolay çıkıyor diye refleksle yazıp yeni anahtarlar yaratıyorsunuz. Kod review sırasında bu farkı özellikle gözden geçirin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt; path tuzağı&lt;/strong&gt;: Path argümanı &lt;code&gt;NULL&lt;/code&gt; olursa fonksiyon doğrudan &lt;code&gt;NULL&lt;/code&gt; döner ve &lt;code&gt;UPDATE&lt;/code&gt; ifadesinde tüm JSON kolonu &lt;code&gt;NULL&lt;/code&gt; olabilir. Path&apos;leri sabit string olarak yazın ya da &lt;code&gt;COALESCE()&lt;/code&gt; ile koruyun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;String tırnak&apos;ı yanlış kaçırmak&lt;/strong&gt;: &lt;code&gt;JSON_REPLACE(@doc, &apos;$.ad&apos;, &amp;quot;Ayse&amp;quot;)&lt;/code&gt; MySQL ayarınıza göre &lt;code&gt;&amp;quot;Ayse&amp;quot;&lt;/code&gt; bir kolon adı sanılabilir. Tek tırnak kullanın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;JSON_EXTRACT&lt;/code&gt; ile karşılaştırmada &lt;code&gt;JSON_UNQUOTE&lt;/code&gt; unutmak&lt;/strong&gt;: &lt;code&gt;JSON_EXTRACT(...) = &apos;Dell&apos;&lt;/code&gt; çalışmaz; çünkü extract sonucu &lt;code&gt;&amp;quot;Dell&amp;quot;&lt;/code&gt; olur, tırnaklarla. &lt;code&gt;JSON_UNQUOTE()&lt;/code&gt; ile sarı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;&lt;code&gt;JSON_REPLACE()&lt;/code&gt;, MySQL&apos;in JSON araç kutusundaki en sade ama en disiplinli üyelerinden biri. Bana sorarsanız, JSON kolonlarına yazarken varsayılan tercihiniz bu olmalı; &lt;code&gt;JSON_SET()&lt;/code&gt; sadece gerçekten upsert davranışı istediğiniz yerlerde devreye girsin. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-13-opentofuda-ic-ice-dynamic-bloklar-karmasik-kaynaklari-yonetmek/</id>
    <title>OpenTofu&#039;da iç içe dynamic bloklar: karmaşık kaynakları evcilleştirmek</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-13-opentofuda-ic-ice-dynamic-bloklar-karmasik-kaynaklari-yonetmek/"/>
    <published>2026-06-13T00:00:00Z</published>
    <updated>2026-06-13T00:00:00Z</updated>
    <author><name>Tarık Aksoy</name></author>
    <summary>OpenTofu&#039;da ic ice dynamic bloklarla IAM policy, ECS service ve Kubernetes deployment gibi derin yapidaki kaynaklari nasil temiz tutariz, bir bakalim.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda OpenTofu (eski adıyla Terraform tarafının topluluk fork&apos;u) içindeki iç içe &lt;code&gt;dynamic&lt;/code&gt; blokları konuşacağız. Konu özünde basit ama insanı belaya sokmakta üstüne yok: bir kaynağın içinde başka bir blok, onun içinde başka bir blok... Ve siz statik HCL ile bunu tek tek yazmaya kalkarsanız, hem dosya şişer hem de değişiklik geldiğinde elinizde bir tomar tekrar kalır.&lt;/p&gt;
&lt;p&gt;Hadi lafı çok uzatmadan dalalım.&lt;/p&gt;
&lt;h2 id=&quot;dynamic-blok-nedir-ne-işe-yarar&quot;&gt;Dynamic blok nedir, ne işe yarar?&lt;a href=&quot;#dynamic-blok-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;OpenTofu kaynakları çoğunlukla iki şeyden oluşur: argümanlar (&lt;code&gt;name = &amp;quot;...&amp;quot;&lt;/code&gt;) ve &lt;strong&gt;bloklar&lt;/strong&gt; (&lt;code&gt;tags { ... }&lt;/code&gt;, &lt;code&gt;lifecycle { ... }&lt;/code&gt; gibi). Bloklar genellikle birden fazla kez tekrarlanabilir. Mesela bir IAM policy&apos;nin birden fazla &lt;code&gt;statement&lt;/code&gt;&apos;ı, bir ECS service&apos;in birden fazla &lt;code&gt;placement_strategy&lt;/code&gt;&apos;si olabilir.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dynamic&lt;/code&gt; bloğu, bir bloğu &lt;strong&gt;veriden türetmenizi&lt;/strong&gt; sağlar. Yani &apos;şu listedeki her eleman için bir tane bu bloktan üret&apos; diyorsunuz, OpenTofu da onu açıyor. Listeniz boşsa hiç blok üretilmiyor; üç eleman varsa üç tane.&lt;/p&gt;
&lt;p&gt;İç içe &lt;code&gt;dynamic&lt;/code&gt; ise tam tahmin ettiğiniz şey: blok içinde blok. IAM &lt;code&gt;statement&lt;/code&gt; içinde &lt;code&gt;condition&lt;/code&gt;, Kubernetes &lt;code&gt;container&lt;/code&gt; içinde &lt;code&gt;port&lt;/code&gt; ve &lt;code&gt;env&lt;/code&gt; gibi.&lt;/p&gt;
&lt;h2 id=&quot;iam-policy-statement-içinde-condition&quot;&gt;IAM policy: statement içinde condition&lt;a href=&quot;#iam-policy-statement-içinde-condition&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 örnekle başlayalım. AWS IAM policy&apos;si yazıyoruz, her statement&apos;ın istediği kadar koşulu olsun istiyoruz.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;variable &amp;quot;policy_statements&amp;quot; {
  type = list(object({
    sid       = string
    effect    = string
    actions   = list(string)
    resources = list(string)
    conditions = list(object({
      test     = string
      variable = string
      values   = list(string)
    }))
  }))
}

data &amp;quot;aws_iam_policy_document&amp;quot; &amp;quot;policy&amp;quot; {
  dynamic &amp;quot;statement&amp;quot; {
    for_each = var.policy_statements
    content {
      sid       = statement.value.sid
      effect    = statement.value.effect
      actions   = statement.value.actions
      resources = statement.value.resources

      dynamic &amp;quot;condition&amp;quot; {
        for_each = statement.value.conditions
        content {
          test     = condition.value.test
          variable = condition.value.variable
          values   = condition.value.values
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dış &lt;code&gt;dynamic&lt;/code&gt; her statement için bir blok açıyor; iç &lt;code&gt;dynamic&lt;/code&gt; ise o statement&apos;ın &lt;code&gt;conditions&lt;/code&gt; listesini gezerek alt blokları üretiyor. Şahsi kanaatim, bu yapı &lt;code&gt;var.policy_statements&lt;/code&gt; listesini bir JSON dosyasından okuduğunuzda asıl meyvesini veriyor: HCL dosyaları sade kalıyor, veri ayrı duruyor.&lt;/p&gt;
&lt;h2 id=&quot;ecs-service-birden-fazla-placement-stratejisi&quot;&gt;ECS service: birden fazla placement stratejisi&lt;a href=&quot;#ecs-service-birden-fazla-placement-stratejisi&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;ECS tarafında da benzer hikaye. Service&apos;in &lt;code&gt;ordered_placement_strategy&lt;/code&gt; ve &lt;code&gt;placement_constraints&lt;/code&gt; blokları sıfırdan N&apos;e kadar olabiliyor.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;variable &amp;quot;placement_strategies&amp;quot; {
  type = list(object({
    type  = string
    field = string
  }))
  default = [
    { type = &amp;quot;spread&amp;quot;,  field = &amp;quot;attribute:ecs.availability-zone&amp;quot; },
    { type = &amp;quot;binpack&amp;quot;, field = &amp;quot;cpu&amp;quot; }
  ]
}

resource &amp;quot;aws_ecs_service&amp;quot; &amp;quot;app&amp;quot; {
  name            = var.service_name
  cluster         = var.cluster_arn
  task_definition = var.task_definition_arn
  desired_count   = var.desired_count

  dynamic &amp;quot;ordered_placement_strategy&amp;quot; {
    for_each = var.placement_strategies
    content {
      type  = ordered_placement_strategy.value.type
      field = ordered_placement_strategy.value.field
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada iç içe değil, sadece düz &lt;code&gt;dynamic&lt;/code&gt; var; ama gerçek hayatta &lt;code&gt;placement_constraints&lt;/code&gt; ile birlikte kullanılınca aynı kaynakta birden fazla &lt;code&gt;dynamic&lt;/code&gt; görüyorsunuz - bu da hızla okunmaz hale gelebiliyor.&lt;/p&gt;
&lt;h2 id=&quot;kubernetes-deployment-container-içinde-port-ve-env&quot;&gt;Kubernetes deployment: container içinde port ve env&lt;a href=&quot;#kubernetes-deployment-container-içinde-port-ve-env&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;Belki de en sık karşılaştığım örnek. Bir deployment&apos;ta birden fazla container, her container&apos;ın kendi port ve env listesi olsun istiyoruz.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;resource &amp;quot;kubernetes_deployment&amp;quot; &amp;quot;app&amp;quot; {
  spec {
    template {
      spec {
        dynamic &amp;quot;container&amp;quot; {
          for_each = var.containers
          content {
            name  = container.value.name
            image = container.value.image

            dynamic &amp;quot;port&amp;quot; {
              for_each = container.value.ports
              content {
                container_port = port.value.container_port
                protocol       = port.value.protocol
              }
            }

            dynamic &amp;quot;env&amp;quot; {
              for_each = container.value.env
              content {
                name  = env.value.name
                value = env.value.value
              }
            }
          }
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Üç katmanlı bir veri modelini iki katmanlı &lt;code&gt;dynamic&lt;/code&gt; ile çözüyoruz. Aynı &lt;code&gt;container&lt;/code&gt; bloğu içinde iki ayrı iç &lt;code&gt;dynamic&lt;/code&gt; (&lt;code&gt;port&lt;/code&gt; ve &lt;code&gt;env&lt;/code&gt;) bulunması gayet doğal; her biri farklı alt listeyi gezdiği için karışmıyor.&lt;/p&gt;
&lt;h2 id=&quot;blok-değil-map-argümanı-cloudwatch-örneği&quot;&gt;Blok değil, map argümanı: CloudWatch örneği&lt;a href=&quot;#blok-değil-map-argümanı-cloudwatch-örneği&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 tuzağa dikkat: her tekrar eden şey blok değildir. &lt;code&gt;aws_cloudwatch_metric_alarm&lt;/code&gt; kaynağındaki &lt;code&gt;dimensions&lt;/code&gt; aslında bir &lt;strong&gt;map argümanı&lt;/strong&gt;. Yani &lt;code&gt;dynamic&lt;/code&gt; ile değil, &lt;code&gt;for&lt;/code&gt; ifadesiyle inşa ediliyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;resource &amp;quot;aws_cloudwatch_metric_alarm&amp;quot; &amp;quot;alarms&amp;quot; {
  for_each = { for a in var.metric_alarms : a.name =&amp;gt; a }

  alarm_name          = each.value.name
  metric_name         = each.value.metric_name
  namespace           = each.value.namespace
  comparison_operator = each.value.comparison_operator
  threshold           = each.value.threshold
  evaluation_periods  = each.value.evaluation_periods

  dimensions = {
    for dim in each.value.dimensions : dim.name =&amp;gt; dim.value
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dimensions = { ... }&lt;/code&gt; eşitliğine dikkat edin. Blok olsaydı &lt;code&gt;dimensions { ... }&lt;/code&gt; yazardık, &lt;code&gt;dynamic&lt;/code&gt; kullanırdık. Bu ayrım &lt;code&gt;tofu plan&lt;/code&gt; çıktısı garipleşince ilk bakacağınız yer olsun.&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;Blok ile argümanı karıştırmak&lt;/strong&gt;: Yukarıdaki CloudWatch örneği klasik. Provider dokümanına bakıp &lt;code&gt;Argument Reference&lt;/code&gt; mı yoksa &lt;code&gt;Block&lt;/code&gt; mı, önce ona karar verin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Üç kattan fazla iç içe gitmek&lt;/strong&gt;: Teknik olarak mümkün, ama insan gözü için cehennem. Bence iki kat yeterli; daha derine ihtiyaç duyduğunuzda veri modelinizi sadeleştirmek ya da kaynağı bölmek daha sağlıklı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boş listeyi unutmak&lt;/strong&gt;: &lt;code&gt;for_each = []&lt;/code&gt; durumunda hiç blok üretilmiyor, ki bu çoğu zaman istediğiniz şey. Ama bazı kaynaklar &apos;en az bir blok&apos; bekliyor olabilir; bu durumda plan değil, apply hata veriyor. Provider belgelerinde &apos;Required: Yes&apos; yazan blokları boş bırakmayın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;statement.value&lt;/code&gt; yerine &lt;code&gt;value&lt;/code&gt; yazmak&lt;/strong&gt;: İç içe dynamic&apos;lerde değişken adı, blok adıyla aynı oluyor (&lt;code&gt;statement&lt;/code&gt;, &lt;code&gt;condition&lt;/code&gt;, &lt;code&gt;container&lt;/code&gt; vs.). İçeride &lt;code&gt;value&lt;/code&gt; diye tek başına yazmak yasak; her seferinde &lt;code&gt;&amp;lt;blok-adı&amp;gt;.value&lt;/code&gt; yazmanız gerek. Hatayı genelde iç bloktayken yapıyoruz, çünkü hangi blok hangisi karışıyor.&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;İç içe &lt;code&gt;dynamic&lt;/code&gt; bloklar, derin yapılı kaynakları statik HCL&apos;in kombinatorik patlamasından kurtaran sade bir araç. Bana sorarsanız bu özelliği abartmadan, iki kat ile sınırlı tutarak kullanmak en sağlıklısı; daha öteye geçmek istediğinizde ya veriyi düzleştirin ya da kaynağı parçalayı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-06-13-python-ile-aws-lambda-uzerinde-serverless-fonksiyon/</id>
    <title>Python ile AWS Lambda üzerinde serverless fonksiyon</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-13-python-ile-aws-lambda-uzerinde-serverless-fonksiyon/"/>
    <published>2026-06-13T00:00:00Z</published>
    <updated>2026-06-13T00:00:00Z</updated>
    <author><name>Can Acar</name></author>
    <summary>Python ile AWS Lambda fonksiyonlarinda handler tasarimi, cold start optimizasyonu ve API Gateway entegrasyonu uzerine pratik notlar.</summary>
    <content type="html">&lt;p&gt;Selamlar arkadaşlar, bu yazıda Python ile AWS Lambda üzerinde serverless fonksiyon yazarken sıkça unuttuğumuz bir kaç ayrıntıyı toparlayacağız. Konuya tamamen yabancı olanlar için &apos;Lambda nedir?&apos; bölümünden başlıyorum, ama yazının asıl kıymeti handler düzeninde, cold start ile warm start arasındaki farkta ve API Gateway&apos;den gelen event&apos;i nasıl ehlileştireceğimizde. Hadi başlayalım.&lt;/p&gt;
&lt;h2 id=&quot;lambda-nedir-neden-python&quot;&gt;Lambda nedir, neden Python?&lt;a href=&quot;#lambda-nedir-neden-python&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;AWS Lambda kısaca &apos;sunucu provizyonu yapmadan kod çalıştırmak&apos; demek. Siz bir handler yazıyorsunuz, AWS sizin yerinize runtime&apos;ı ayağa kaldırıyor, çalıştırıyor, indiriyor. Faturada da yalnızca fonksiyonun çalıştığı milisaniyeler var. Boş duran sunucuya para yok.&lt;/p&gt;
&lt;p&gt;Python bu işin en sevilen runtime&apos;larından biri. Soğuk başlatma süresi makul, ekosistem zengin ve &lt;code&gt;boto3&lt;/code&gt; zaten standart kütüphane gibi çalışıyor. Bence basit bir webhook ya da event işleyici için Python + Lambda kombinasyonunu geçmek zor.&lt;/p&gt;
&lt;h2 id=&quot;cold-start-ve-warm-start&quot;&gt;Cold start ve warm start&lt;a href=&quot;#cold-start-ve-warm-start&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;Lambda&apos;nın yaşam döngüsünü anlamadan optimizasyon yapmak biraz karanlıkta el yordamı. İki temel durum var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cold start&lt;/strong&gt;: Yeni bir execution environment ayağa kalkıyor. Kod indiriliyor, runtime başlatılıyor, handler dışındaki tüm init kodu bir kez çalışıyor. Bu en pahalı senaryo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Warm start&lt;/strong&gt;: Mevcut environment yeniden kullanılıyor. Sadece handler tekrar çağrılıyor, init kodu atlanıyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Buradaki kritik nokta şu: &lt;strong&gt;handler dışında yazdığınız kod her cold start&apos;ta bir kez çalışır, sonra environment yaşadığı sürece bedava nimet olur.&lt;/strong&gt; Bu yüzden boto3 client&apos;larını, DynamoDB tablo nesnelerini, SSM&apos;den çekilen sırları handler&apos;ın dışında tanımlıyoruz.&lt;/p&gt;
&lt;h2 id=&quot;temiz-bir-handler-iskeleti&quot;&gt;Temiz bir handler iskeleti&lt;a href=&quot;#temiz-bir-handler-iskeleti&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;Şimdi minimum bir örnekle başlayalım. Aşağıdaki handler hem cold start&apos;ta hem warm start&apos;ta mantıklı davranır:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json
import logging
import os
import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Bu satirlar cold start&apos;ta bir kez calisir, warm start&apos;ta tekrar etmez
dynamodb = boto3.resource(&apos;dynamodb&apos;)
table = dynamodb.Table(os.environ.get(&apos;USERS_TABLE&apos;, &apos;users&apos;))


def handler(event: dict, context) -&amp;gt; dict:
    user_id = event.get(&apos;pathParameters&apos;, {}).get(&apos;userId&apos;)
    if not user_id:
        return _response(400, {&apos;error&apos;: &apos;userId zorunlu&apos;})

    item = table.get_item(Key={&apos;userId&apos;: user_id}).get(&apos;Item&apos;)
    if not item:
        return _response(404, {&apos;error&apos;: &apos;Kullanici bulunamadi&apos;})

    return _response(200, item)


def _response(status: int, body: dict) -&amp;gt; dict:
    return {
        &apos;statusCode&apos;: status,
        &apos;headers&apos;: {&apos;Content-Type&apos;: &apos;application/json&apos;},
        &apos;body&apos;: json.dumps(body, default=str),
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada &lt;code&gt;dynamodb&lt;/code&gt; ve &lt;code&gt;table&lt;/code&gt; değişkenleri modül seviyesinde. İlk istekte oluşturuldular, sonraki yüzlerce istekte aynı bağlantı havuzu üzerinden çalışıyorlar. Eğer bu üç satırı &lt;code&gt;handler()&lt;/code&gt; içine taşırsanız, her warm invocation&apos;da bile yeniden client kuracaksınız - bu hem yavaş hem de gereksiz.&lt;/p&gt;
&lt;h2 id=&quot;api-gateway-entegrasyonunda-dikkat-edilenler&quot;&gt;API Gateway entegrasyonunda dikkat edilenler&lt;a href=&quot;#api-gateway-entegrasyonunda-dikkat-edilenler&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 Gateway proxy entegrasyonu kullanıyorsanız Lambda&apos;ya gelen event şişman bir sözlük olur. İçinde &lt;code&gt;httpMethod&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;pathParameters&lt;/code&gt;, &lt;code&gt;queryStringParameters&lt;/code&gt;, &lt;code&gt;headers&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt; alanları var. &lt;code&gt;body&lt;/code&gt; her zaman string&apos;tir; JSON ise siz parse etmek zorundasınız:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def parse_body(event: dict) -&amp;gt; dict | None:
    raw = event.get(&apos;body&apos;)
    if not raw:
        return None
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bir başka tuzak: &lt;code&gt;queryStringParameters&lt;/code&gt; istek hiç query string içermiyorsa &lt;code&gt;None&lt;/code&gt; gelir, boş sözlük değil. Doğrudan &lt;code&gt;.get()&lt;/code&gt; çağırmadan önce &lt;code&gt;or {}&lt;/code&gt; koyun:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;qs = event.get(&apos;queryStringParameters&apos;) or {}
limit = int(qs.get(&apos;limit&apos;, 20))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CORS kontrolü için OPTIONS isteklerini erkenden kısa devre yapmak da klasik:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if event.get(&apos;httpMethod&apos;) == &apos;OPTIONS&apos;:
    return _response(200, {})
&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;Init kodunu handler&apos;ın içine yazmak&lt;/strong&gt;: Her warm invocation&apos;da boto3 client&apos;ı yeniden kuruyorsunuz. Hem yavaş hem de connection pool reset oluyor. Modül seviyesine taşıyın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeout&apos;u API Gateway&apos;in 29 saniyesinden uzun tutmak&lt;/strong&gt;: API Gateway 29 saniyede zaten 504 döner. Lambda 5 dakika çalışıyor olsa bile kullanıcı beklemiyor; gereksiz fatura.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory&apos;yi 128 MB&apos;da bırakmak&lt;/strong&gt;: Lambda&apos;da CPU bellek miktarına bağlı tahsis edilir. 1024 MB altı genelde CPU kıtlığı yaşatır; ölçüp artırmak çoğu zaman daha ucuza çıkar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSM Parameter Store&apos;u her invocation&apos;da çağırmak&lt;/strong&gt;: Cold start&apos;ta bir kez çekip modül seviyesinde önbelleğe alın, warm start&apos;larda bedava.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;event[&apos;body&apos;]&lt;/code&gt;&apos;nin string olduğunu unutmak&lt;/strong&gt;: Direkt &lt;code&gt;event[&apos;body&apos;][&apos;email&apos;]&lt;/code&gt; yazıp &lt;code&gt;TypeError: string indices must be integers&lt;/code&gt; ile karşılaşmak Lambda klasiklerindendir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;lokal-test-ve-doğrulama&quot;&gt;Lokal test ve doğrulama&lt;a href=&quot;#lokal-test-ve-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;SAM CLI ile lokalde event göndererek handler&apos;ı çalıştırabilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sam local invoke ApiFunction --event events/get-user.json
sam local start-api --port 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Birim test tarafında ise &lt;code&gt;moto&lt;/code&gt; kütüphanesi AWS servislerini taklit eder; &lt;code&gt;boto3&lt;/code&gt; çağrılarını gerçek bir AWS hesabına gitmeden test edebilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import boto3
import pytest
from moto import mock_aws

@mock_aws
def test_handler_returns_user():
    dynamodb = boto3.resource(&apos;dynamodb&apos;, region_name=&apos;eu-west-1&apos;)
    dynamodb.create_table(
        TableName=&apos;users&apos;,
        KeySchema=[{&apos;AttributeName&apos;: &apos;userId&apos;, &apos;KeyType&apos;: &apos;HASH&apos;}],
        AttributeDefinitions=[{&apos;AttributeName&apos;: &apos;userId&apos;, &apos;AttributeType&apos;: &apos;S&apos;}],
        BillingMode=&apos;PAY_PER_REQUEST&apos;,
    )
    # ... handler import ve cagri
&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;Bu yazıda Python ile Lambda fonksiyonu yazarken handler dışı init&apos;in neden hayati olduğunu, API Gateway event&apos;ini nasıl güvenli işleyeceğimizi ve lokal testin pratik araçlarını gördük. Şahsi kanaatim, küçük bir webhook için Lambda fazlasıyla yeterli; ama uzun süreli işlemler ya da WebSocket gibi senaryolar için Fargate veya ECS düşünün, zorlamayı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-06-12-clickhouse-kullanan-python-kodunu-nasil-test-ederiz/</id>
    <title>ClickHouse kullanan Python kodunu nasıl test ederiz?</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-12-clickhouse-kullanan-python-kodunu-nasil-test-ederiz/"/>
    <published>2026-06-12T00:00:00Z</published>
    <updated>2026-06-12T00:00:00Z</updated>
    <author><name>Volkan Kaplan</name></author>
    <summary>ClickHouse&#039;a bağımlı Python kodunu test etmek icin mock, Testcontainers ve materialized view senaryolarini pratik bir bakisla anlatir.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda ClickHouse&apos;a bağlı Python kodunu nasıl test ettiğimize bakacağız. Konu kulağa basit gelse de iş başa düşünce &apos;şu sorgu prod&apos;da gerçekten doğru sonucu mu döndürüyor?&apos; sorusu epey sinir bozucu olabiliyor. Lafı uzatmadan başlayayım.&lt;/p&gt;
&lt;p&gt;ClickHouse, OLAP tarafında biz Python&apos;cuların sevdiği bir veritabanı. Ama test yazma kültürü hâlâ çoğunlukla &apos;mock&apos;la geç, hayatına devam et&apos; tarafında. Bence bu eksik bir yaklaşım. Çünkü ClickHouse&apos;un asıl değerli olduğu yerler (aggregate fonksiyonlar, materialized view&apos;lar, &lt;code&gt;uniq&lt;/code&gt;, &lt;code&gt;quantile&lt;/code&gt; gibi yapılar) mock&apos;larla test edildiğinde aslında hiç test edilmemiş oluyor.&lt;/p&gt;
&lt;h2 id=&quot;üç-katmanlı-bir-yaklaşım&quot;&gt;Üç katmanlı bir yaklaşım&lt;a href=&quot;#üç-katmanlı-bir-yaklaşı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;Bana sorarsanız ClickHouse bağımlı kodu üç farklı seviyede test etmek lazım:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Birim testler&lt;/strong&gt;: ClickHouse istemcisini mock&apos;larız, sadece iş mantığını test ederiz. Hızlı ama sorgunun kendisini garanti etmez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entegrasyon testleri&lt;/strong&gt;: Testcontainers ile gerçek bir ClickHouse instance&apos;ı kaldırırız. Yavaş ama dürüst.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kontrat testleri&lt;/strong&gt;: Sorgu çıktısının beklenen şemaya uyduğunu doğrularız.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Üçü birbirini tamamlıyor; birini diğerine tercih etmeyin.&lt;/p&gt;
&lt;h2 id=&quot;mock-ile-birim-test&quot;&gt;Mock ile birim test&lt;a href=&quot;#mock-ile-birim-test&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;Diyelim ki günlük aktif kullanıcı sayısını hesaplayan basit bir fonksiyonumuz var:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# app.py
def get_daily_active_users(client, date: str) -&amp;gt; int:
    result = client.query(
        f&amp;quot;SELECT uniq(user_id) FROM events WHERE toDate(event_time) = &apos;{date}&apos;&amp;quot;
    )
    return result.first_row[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test tarafında istemciyi &lt;code&gt;MagicMock&lt;/code&gt; ile sahteliyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# test_app.py
from unittest.mock import MagicMock
from app import get_daily_active_users

def test_daily_active_users_returns_int():
    mock_client = MagicMock()
    mock_result = MagicMock()
    mock_result.first_row = [42]
    mock_client.query.return_value = mock_result

    count = get_daily_active_users(mock_client, &apos;2026-03-31&apos;)

    assert count == 42
    assert &apos;2026-03-31&apos; in mock_client.query.call_args[0][0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada test ettiğimiz şey çok dar: fonksiyon &lt;code&gt;first_row[0]&lt;/code&gt;&apos;ı doğru döndürüyor mu, tarihi sorguya gerçekten yerleştiriyor mu? Bu kadar. &lt;code&gt;uniq&lt;/code&gt; fonksiyonunun doğru çalışıp çalışmadığını test etmiyoruz. Açıkçası ben bu seviyeyi çoğunlukla SQL injection riskini görmek için kullanıyorum.&lt;/p&gt;
&lt;h2 id=&quot;testcontainers-ile-gerçek-clickhouse&quot;&gt;Testcontainers ile gerçek ClickHouse&lt;a href=&quot;#testcontainers-ile-gerçek-clickhouse&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;Şimdi işin gerçek kısmı. &lt;code&gt;testcontainers&lt;/code&gt; paketiyle her test oturumu için tek bir ClickHouse container&apos;ı kaldırıyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install testcontainers[clickhouse] clickhouse-connect pytest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;conftest.py&lt;/code&gt; içinde fixture&apos;larımızı tanımlıyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# conftest.py
import pytest
import clickhouse_connect
from testcontainers.clickhouse import ClickHouseContainer

@pytest.fixture(scope=&apos;session&apos;)
def ch_client():
    with ClickHouseContainer(&apos;clickhouse/clickhouse-server:latest&apos;) as ch:
        client = clickhouse_connect.get_client(
            host=ch.get_container_host_ip(),
            port=int(ch.get_exposed_port(8123)),
        )
        client.command(&apos;&apos;&apos;
            CREATE TABLE events (
                user_id UInt64,
                event_time DateTime DEFAULT now(),
                event_type String
            ) ENGINE = MergeTree() ORDER BY (user_id, event_time)
        &apos;&apos;&apos;)
        yield client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;scope=&apos;session&apos;&lt;/code&gt; olması kritik. Her test için container kaldırırsanız test süitiniz yarım saatte biter; oturum başına bir kez kaldırırsanız 5-10 saniyelik bir sabit maliyetiniz olur, sonrası akar gider.&lt;/p&gt;
&lt;p&gt;Testin kendisi sade:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# test_integration.py
def test_insert_and_count(ch_client):
    ch_client.insert(
        &apos;events&apos;,
        [[1, &apos;login&apos;], [2, &apos;click&apos;]],
        column_names=[&apos;user_id&apos;, &apos;event_type&apos;],
    )
    result = ch_client.query(&apos;SELECT count() FROM events&apos;)
    assert result.first_row[0] == 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;materialized-viewları-test-etmek&quot;&gt;Materialized view&apos;ları test etmek&lt;a href=&quot;#materialized-viewları-test-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;İşin sinsi tarafı burası. Materialized view&apos;lar arka planda merge bekliyor; insert sonrası hemen sorgu atarsanız beklenen sonucu göremeyebilirsiniz. &lt;code&gt;OPTIMIZE ... FINAL&lt;/code&gt; ile merge&apos;ü zorlamak gerekiyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_event_counts_materialized_view(ch_client):
    ch_client.command(&apos;&apos;&apos;
        CREATE MATERIALIZED VIEW event_counts
        ENGINE = SummingMergeTree() ORDER BY event_type
        AS SELECT event_type, count() AS cnt
        FROM events GROUP BY event_type
    &apos;&apos;&apos;)
    ch_client.insert(&apos;events&apos;, [[3, &apos;login&apos;]], column_names=[&apos;user_id&apos;, &apos;event_type&apos;])
    ch_client.command(&apos;OPTIMIZE TABLE event_counts FINAL&apos;)

    result = ch_client.query(
        &amp;quot;SELECT cnt FROM event_counts WHERE event_type = &apos;login&apos;&amp;quot;
    )
    assert result.first_row[0] &amp;gt;= 1
&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;Her test için yeni container kaldırmak&lt;/strong&gt;: &lt;code&gt;scope=&apos;function&apos;&lt;/code&gt; kullanırsanız süit dakikalarca akar. Oturum bazlı fixture kullanın, test başında &lt;code&gt;TRUNCATE TABLE&lt;/code&gt; ile temizleyin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Materialized view&apos;da merge&apos;ü beklememek&lt;/strong&gt;: Insert sonrası hemen &lt;code&gt;SELECT&lt;/code&gt; atarsanız sonuç boş gelebilir. &lt;code&gt;OPTIMIZE TABLE &amp;lt;View&amp;gt; FINAL&lt;/code&gt; çağırmayı unutmayın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;f-string ile sorgu kurmak&lt;/strong&gt;: Yukarıdaki örnekte tarihi f-string ile yerleştirdik; gerçek hayatta &lt;code&gt;parameters=&lt;/code&gt; argümanını kullanın, yoksa SQL injection kapısı açıyorsunuz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sadece mock ile yetinmek&lt;/strong&gt;: &lt;code&gt;uniq&lt;/code&gt;, &lt;code&gt;quantile&lt;/code&gt;, &lt;code&gt;argMax&lt;/code&gt; gibi ClickHouse&apos;a özgü fonksiyonların doğru sonuç verdiğini ancak gerçek bir instance ile doğrulayabilirsiniz.&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 ClickHouse&apos;a bağımlı Python kodunu mock&apos;tan Testcontainers&apos;a, oradan da materialized view senaryolarına kadar üç farklı seviyede nasıl test ettiğimize baktık. Şahsi kanaatim, mock&apos;lara fazla yaslanmadan en azından kritik sorguları gerçek bir container&apos;a karşı çalıştırmak şart; oturum kapsamlı fixture&apos;larla bunun maliyeti de görünenden çok daha düşük. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-12-python-ile-ipv6-ag-sagligini-izleyen-kucuk-bir-arac-yazmak/</id>
    <title>Python ile IPv6 ağ sağlığını izleyen küçük bir araç yazmak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-12-python-ile-ipv6-ag-sagligini-izleyen-kucuk-bir-arac-yazmak/"/>
    <published>2026-06-12T00:00:00Z</published>
    <updated>2026-06-12T00:00:00Z</updated>
    <author><name>Burak Aslan</name></author>
    <summary>Python ve asyncio ile IPv6 hostlarinin erisilebilirligini, gecikmesini ve paket kaybini izleyen pratik bir izleme aracini bastan sona kuruyoruz.</summary>
    <content type="html">&lt;p&gt;Selamlar arkadaşlar, bu yazımızda Python ile küçük ama gerçekten faydalı bir IPv6 izleme aracı yazacağız. Adres dağıtımı, prefix delegation falan değil derdimiz; konumuz daha sıradan ama bir o kadar can sıkıcı bir mesele: IPv6 tarafı ne durumda, kimse fark etmeden çökmüş olabilir mi? Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;İşin aslı şu: çoğu şirkette IPv4 izlemesi yıllardır oturmuş, IPv6 ise ya yarı kurulu durumda ya da &apos; çalışıyor herhalde&apos; modunda. Tecrübeyle sabittir, IPv6 routing&apos;i bir gece sessizce düşer ve bunu fark eden ilk kişi genellikle bir kullanıcı olur. Bu yüzden ufak bir Python script&apos;i bile büyük fark yaratıyor.&lt;/p&gt;
&lt;h2 id=&quot;neden-python-ve-neden-asyncio&quot;&gt;Neden Python ve neden asyncio?&lt;a href=&quot;#neden-python-ve-neden-asyncio&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;ping6&lt;/code&gt; veya &lt;code&gt;ping -6&lt;/code&gt; zaten her Linux kutusunda var; biz onun üzerine ince bir katman yazıyoruz. Tek bir host için &lt;code&gt;subprocess&lt;/code&gt; yeter, ama gerçekçi bir senaryoda 20-30 host paralel kontrol edilmek istenir. Sıralı ping yapsanız her hedef için 2-3 saniye bekler, toplam tur 60 saniyeye dayanır. Asyncio ile bunu birkaç saniyeye indiriyoruz.&lt;/p&gt;
&lt;p&gt;Şahsi kanaatim, bu tür araçlarda framework kullanmaya gerek yok; standart kütüphane fazlasıyla yetiyor.&lt;/p&gt;
&lt;h2 id=&quot;tek-host-için-ping-fonksiyonu&quot;&gt;Tek host için ping fonksiyonu&lt;a href=&quot;#tek-host-için-ping-fonksiyonu&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 başlayalım. Aşağıdaki fonksiyon &lt;code&gt;ping6&lt;/code&gt; çıktısını parse edip yapılandırılmış bir sonuç döndürüyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import asyncio
import re
from dataclasses import dataclass

@dataclass
class PingSonuc:
    host: str
    ulasilabilir: bool
    rtt_ms: float | None
    paket_kaybi_yuzde: float

async def ping6_async(host: str, sayi: int = 3) -&amp;gt; PingSonuc:
    proc = await asyncio.create_subprocess_exec(
        &apos;ping&apos;, &apos;-6&apos;, &apos;-c&apos;, str(sayi), &apos;-W&apos;, &apos;2&apos;, host,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.DEVNULL,
    )
    stdout, _ = await proc.communicate()
    cikti = stdout.decode()

    kayip = re.search(r&apos;(\d+(?:\.\d+)?)% packet loss&apos;, cikti)
    paket_kaybi = float(kayip.group(1)) if kayip else 100.0

    rtt = re.search(r&apos;min/avg/max.*= [\d.]+/([\d.]+)/&apos;, cikti)
    rtt_ms = float(rtt.group(1)) if rtt else None

    return PingSonuc(host, proc.returncode == 0, rtt_ms, paket_kaybi)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada &lt;code&gt;create_subprocess_exec&lt;/code&gt; ile &lt;code&gt;ping&lt;/code&gt; komutunu non-blocking şekilde çağırıyoruz. &lt;code&gt;-W 2&lt;/code&gt; parametresi her bir paket için en fazla 2 saniye bekleyeceğimizi söylüyor; donmuş hedefler bütün döngüyü tıkamasın diye bu önemli.&lt;/p&gt;
&lt;h2 id=&quot;paralel-toplu-kontrol&quot;&gt;Paralel toplu kontrol&lt;a href=&quot;#paralel-toplu-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;Tek host&apos;a bakmak ısınma turu. Şimdi gerçek senaryoya geçelim. Bir hedef listesini paralel pingleyip sonuçları toplayalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;async def topla(hedefler: list[str]) -&amp;gt; list[PingSonuc]:
    gorevler = [ping6_async(h) for h in hedefler]
    return await asyncio.gather(*gorevler, return_exceptions=False)

async def main():
    hedefler = [
        &apos;2001:4860:4860::8888&apos;,   # Google DNS
        &apos;2606:4700:4700::1111&apos;,   # Cloudflare DNS
        &apos;2001:db8:cafe::1&apos;,       # ic gateway
    ]
    sonuclar = await topla(hedefler)
    for s in sonuclar:
        durum = &apos;UP&apos; if s.ulasilabilir else &apos;DOWN&apos;
        rtt = f&apos;{s.rtt_ms:.1f}ms&apos; if s.rtt_ms else &apos;N/A&apos;
        print(f&apos;[{durum}] {s.host} rtt={rtt} kayip={s.paket_kaybi_yuzde:.0f}%&apos;)

asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;20 hedef için bu kod yaklaşık 3 saniyede toplam sonucu veriyor; sıralı versiyonun yanından bile geçmiyor. Tabii burada ICMP rate limit&apos;lere de dikkat etmek lazım, ağ ekibi sizi düşman zannetmesin.&lt;/p&gt;
&lt;h2 id=&quot;slo-bütçesi-sadece-updown-değil&quot;&gt;SLO bütçesi: sadece UP/DOWN değil&lt;a href=&quot;#slo-bütçesi-sadece-updown-değil&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;Bana sorarsanız, gerçek değer UP/DOWN&apos;da değil, gecikme dağılımında saklı. Bir host hâlâ ping&apos;e cevap veriyor olabilir ama RTT 12 ms&apos;den 180 ms&apos;ye fırladıysa kullanıcı zaten yandı. Basit bir SLO sınıfı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def slo_durumu(s: PingSonuc, esik_ms: float = 50.0) -&amp;gt; str:
    if not s.ulasilabilir:
        return &apos;KRITIK&apos;
    if s.paket_kaybi_yuzde &amp;gt; 0:
        return &apos;BOZULDU&apos;
    if s.rtt_ms and s.rtt_ms &amp;gt; esik_ms:
        return &apos;YAVAS&apos;
    return &apos;SAGLIKLI&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu üç-dört durumlu çıktı, alert kuralları yazmayı çok rahatlatıyor. Tek bir gauge yerine bir histogram tutarsanız Grafana&apos;da p95&apos;i takip etmek de kolay olur ama bu yazının kapsamı dışına taşar.&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;-W&lt;/code&gt; parametresini unutmak&lt;/strong&gt;: Hedef sessizce paket düşürüyorsa varsayılan timeout dakikalara çıkabilir. Tüm async döngünüz tek bir DOWN host yüzünden kilitlenir. &lt;code&gt;-W 2&lt;/code&gt; veya &lt;code&gt;-W 3&lt;/code&gt; koymak zorunlu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sadece DNS adı denemek&lt;/strong&gt;: Hostname AAAA kaydı dönmüyorsa Linux sessizce IPv4&apos;e fallback yapabilir; siz IPv6&apos;yı izlediğinizi sanırken aslında IPv4 ölçüyorsunuzdur. Mümkünse hedefi doğrudan IPv6 literali olarak verin, &lt;code&gt;requests&lt;/code&gt; kullanıyorsanız URL&apos;de köşeli parantezle (&lt;code&gt;https://[2001:db8::1]/health&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek thread&apos;de senkron &lt;code&gt;subprocess.run&lt;/code&gt;&lt;/strong&gt;: 30 hedef için döngü kuruyorsanız asyncio&apos;ya geçmek üç katı performans değil, on katı performans demek. &lt;code&gt;asyncio.gather&lt;/code&gt; burada hayat kurtarıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loopback üzerinde test edip iyi sandığınız kod&lt;/strong&gt;: &lt;code&gt;::1&lt;/code&gt; her zaman ulaşılabilir; gerçek bir test ortamı için harici bir IPv6 hedefi şart, yoksa script&apos;iniz sahada ilk ICMP filtresinde patlar.&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;Local&apos;de bir tail komutuyla manuel doğrulamak en hızlısı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python ipv6_monitor.py | grep -E &apos;DOWN|YAVAS&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beklediğiniz çıktı yoksa, yani her şey UP ve hızlı görünüyorsa, bilerek olmayan bir adres ekleyin (&lt;code&gt;2001:db8:dead::1&lt;/code&gt; gibi). DOWN satırı görüyor musunuz? Görüyorsanız hata yolu çalışıyor demektir.&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 Python ve asyncio ile basit ama işine yarayan bir IPv6 sağlık izleme aracı yazdık; UP/DOWN&apos;un ötesine geçip RTT ve paket kaybı eşikleriyle SLO mantığı ekledik. Bence bu küçük script kurumsal izleme yığınınızın yerine geçmez ama IPv6 tarafının gerçekten ne durumda olduğunu öğrenmenin en hızlı yolu hâlâ böyle 80 satırlık bir Python dosyası. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-12-fastapi-ve-postgresql-stackini-portainer-ile-yayina-almak/</id>
    <title>FastAPI ve PostgreSQL stack&#039;ini Portainer ile yayına almak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-12-fastapi-ve-postgresql-stackini-portainer-ile-yayina-almak/"/>
    <published>2026-06-12T00:00:00Z</published>
    <updated>2026-06-12T00:00:00Z</updated>
    <author><name>Damla Sezer</name></author>
    <summary>FastAPI ve PostgreSQL&#039;i Portainer üzerinden yayına alırken karşılaştığım tuzakları, healthcheck ve migration tarafını paylaşıyorum.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda FastAPI tarafında bir API&apos;yi PostgreSQL ile beraber Portainer&apos;in stack özelliği üzerinden nasıl ayağa kaldırdığıma bakacağız. Konuya çok yabancı olmayan ama &apos;compose dosyasını Portainer&apos;a yapıştırınca neden patlıyor?&apos; diyen arkadaşlar için yazdım. Lafı uzatmadan başlayalım.&lt;/p&gt;
&lt;p&gt;Açıkçası ben de ilk denemelerimde container&apos;ları ayağa kaldıran &lt;code&gt;depends_on&lt;/code&gt; kuralının yeterli olduğunu zannetmiştim. Sonra görüyorsunuz ki API, PostgreSQL daha hazır olmadan migration çalıştırmaya başlıyor ve &lt;code&gt;connection refused&lt;/code&gt; ile düşüyor. Buradaki kritik nokta &lt;code&gt;service_healthy&lt;/code&gt; koşulu ve &lt;code&gt;pg_isready&lt;/code&gt; ile doğru bir healthcheck yazmak.&lt;/p&gt;
&lt;h2 id=&quot;stack-dosyası&quot;&gt;Stack dosyası&lt;a href=&quot;#stack-dosyası&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;Portainer&apos;da Stacks &amp;gt; Add stack diyip aşağıdaki compose dosyasını yapıştırmanız yeterli. Ben üretim için aşağıdaki gibi bir şablon kullanıyorum:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: siparis
      POSTGRES_USER: siparis_app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
    healthcheck:
      test: [&apos;CMD-SHELL&apos;, &apos;pg_isready -U siparis_app -d siparis&apos;]
      interval: 10s
      timeout: 5s
      retries: 5

  api:
    build: ./app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - &apos;8000:8000&apos;
    environment:
      DATABASE_URL: postgresql+asyncpg://siparis_app:${DB_PASSWORD}@db:5432/siparis
      APP_SECRET: ${APP_SECRET}
    command: &amp;gt;
      bash -lc &apos;alembic upgrade head &amp;amp;&amp;amp; uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4&apos;

volumes:
  pg_data:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada birkaç noktanın altını çizmek isterim. &lt;code&gt;depends_on.condition: service_healthy&lt;/code&gt; olmadan API container&apos;ı, veritabanı daha hazır değilken ayağa kalkar. Healthcheck&apos;in &lt;code&gt;pg_isready -U siparis_app -d siparis&lt;/code&gt; şeklinde hem kullanıcı hem de veritabanı parametresiyle yazılması sık gözden kaçan bir detay. Sadece &lt;code&gt;pg_isready&lt;/code&gt; derseniz socket&apos;i kontrol eder, asıl DB hazır olmadan da yeşil yanabilir.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;${DB_PASSWORD}&lt;/code&gt; ve &lt;code&gt;${APP_SECRET}&lt;/code&gt; değerlerini Portainer&apos;in &apos;Environment variables&apos; kısmından girin. Stack içine düz metin şifre yapıştırmak istemezsiniz; hem versiyon kontrolüne kaçar hem de Portainer ekranı üzerinden herkes okur.&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;API tarafında elimden geldiğince ince bir şablon tutuyorum. SQLAlchemy 2.x&apos;in async desteği artık gerçekten oturdu; asyncpg ile beraber kullanınca performans gayet tatmin edici.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# app/main.py
from fastapi import FastAPI
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
import os

app = FastAPI(title=&apos;Siparis API&apos;)
engine = create_async_engine(os.environ[&apos;DATABASE_URL&apos;], pool_pre_ping=True)

@app.get(&apos;/health&apos;)
async def health():
    try:
        async with engine.connect() as conn:
            await conn.execute(text(&apos;SELECT 1&apos;))
        return {&apos;status&apos;: &apos;ok&apos;}
    except Exception as exc:
        return {&apos;status&apos;: &apos;down&apos;, &apos;detail&apos;: str(exc)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pool_pre_ping=True&lt;/code&gt; parametresi tecrübeyle sabittir; gece yarısı PostgreSQL&apos;in bakım için kısa bir kapanmasından sonra havuzdaki ölü bağlantıların temizlenmesini sağlıyor. Bu küçük parametre olmadan sabaha karşı birçok &apos;OperationalError: connection invalid&apos; alırsınız.&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;Healthcheck&apos;i unutmak&lt;/strong&gt;: &lt;code&gt;depends_on&lt;/code&gt; tek başına yeterli değildir. Container ayaktadır ama PostgreSQL hazır olmayabilir; migration patlar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;asyncpg&lt;/code&gt; yerine &lt;code&gt;psycopg&lt;/code&gt; driver yazmak&lt;/strong&gt;: &lt;code&gt;DATABASE_URL&lt;/code&gt;&apos;de &lt;code&gt;postgresql+asyncpg://&lt;/code&gt; yazmak zorundasınız, yoksa SQLAlchemy async driver bulamayınca anlamsız bir hata verir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migration&apos;ı &lt;code&gt;command&lt;/code&gt; içinde çalıştırmak&lt;/strong&gt;: Tek replica için bu kabul. Birden fazla replica açınca her instance aynı anda &lt;code&gt;alembic upgrade head&lt;/code&gt; çalıştırır ve advisory lock kullanmazsanız migration tablosunda bozulma riski çıkar. Bu durumda ayrı bir &lt;code&gt;migrate&lt;/code&gt; job&apos;ı açın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volume&apos;u dış bir path&apos;e bind etmek&lt;/strong&gt;: &lt;code&gt;./pg_data:/var/lib/postgresql/data&lt;/code&gt; gibi bir bind kullanınca SELinux veya UID/GID sorunları yaşarsınız. Portainer host&apos;unda named volume kalsın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt; Portainer environment alanında ama compose&apos;da değişken referansı olmamak&lt;/strong&gt;: Boş string ile container ayağa kalkar, sonra trust authentication zannedip yanılırsınız.&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;Stack ayağa kalkınca şu komutlarla durumu hızlıca gözden geçirebilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -s http://localhost:8000/health
docker exec -it $(docker ps -qf name=db) pg_isready -U siparis_app -d siparis
docker logs --tail 50 $(docker ps -qf name=api)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/health&lt;/code&gt; endpoint&apos;i &lt;code&gt;{&apos;status&apos;: &apos;ok&apos;}&lt;/code&gt; dönmüyorsa önce API loglarına bakın; çoğunlukla &lt;code&gt;DATABASE_URL&lt;/code&gt; içindeki host adı yanlış ya da şifre escape edilmemiş oluyor. Bende bir keresinde &lt;code&gt;@&lt;/code&gt; karakteri içeren bir şifre yüzünden yarım saat kaybetmiştim, URL-encode etmek lazım.&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 FastAPI ve PostgreSQL stack&apos;ini Portainer üzerinden ayağa kaldırırken healthcheck, async driver ve migration konularında dikkat edilmesi gereken noktalara baktık. Bence Portainer GUI&apos;si küçük ve orta ölçekli takımlar için kubectl ihtiyacı doğmadan yeterince güçlü; asıl mesele compose dosyasını olabildiğince açık yazmak. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-12-mongodb-projeksiyonlarinda-dizi-elemanlarini-slice-ile-sinirlamak/</id>
    <title>MongoDB projeksiyonlarinda dizi elemanlarini $slice ile sinirlamak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-12-mongodb-projeksiyonlarinda-dizi-elemanlarini-slice-ile-sinirlamak/"/>
    <published>2026-06-12T00:00:00Z</published>
    <updated>2026-06-12T00:00:00Z</updated>
    <author><name>Murat Erol</name></author>
    <summary>MongoDB sorgularinda dizi alanlarinin tamamini cekmeden $slice projection ile ilk N, son N veya sayfalanmis dilim almanin yollari.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda MongoDB tarafında çok sık ihtiyaç duyduğumuz ama bir o kadar da gözden kaçan bir konuya bakacağız: bir doküman içindeki uzun dizinin &lt;strong&gt;tamamını çekmeden&lt;/strong&gt; sadece istediğimiz kısmını almak. Konunun adı &lt;code&gt;$slice&lt;/code&gt; projection operatörü. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;İlk başlarda MongoDB ile çalışırken, &lt;code&gt;comments&lt;/code&gt; veya &lt;code&gt;notifications&lt;/code&gt; gibi alanları sorgudan çekerken dizinin &lt;strong&gt;hepsi geliyordu&lt;/strong&gt; ve buna dokunmadan rahatça yaşıyorduk. Sonra bir kullanıcının üstüne otuz bin tane bildirim biriktiğinde işler değişti. Tek bir &lt;code&gt;findOne&lt;/code&gt; çağrısı bile listede gözle görülür şekilde ağırlaşıyordu. İşte &lt;code&gt;$slice&lt;/code&gt; tam burada işe yarıyor.&lt;/p&gt;
&lt;h2 id=&quot;slice-projection-nedir&quot;&gt;$slice projection nedir?&lt;a href=&quot;#slice-projection-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;Kısaca: bir sorguda dizi alanının tamamı yerine sadece bir kısmını döndürmemizi sağlayan projeksiyon operatörü. Üç temel formu var:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Ilk N eleman
{ comments: { $slice: 3 } }

// Son N eleman (negatif deger)
{ comments: { $slice: -3 } }

// skip kadar atla, limit kadar al
{ comments: { $slice: [skip, limit] } }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada dikkat edilecek nokta, &lt;code&gt;$slice&lt;/code&gt; filtreleme değil, &lt;strong&gt;projeksiyon&lt;/strong&gt; yapıyor. Yani sorgu yine eşleşen tüm dokümanları taramaya devam ediyor, ama sonuçta dizinin sadece belirttiğin parçası dönüyor. Bunu sık karıştıranı gördüm; benim de ilk başlarda kafam karışmıştı.&lt;/p&gt;
&lt;h2 id=&quot;pratikte-nasil-kullaniyoruz&quot;&gt;Pratikte nasil kullaniyoruz?&lt;a href=&quot;#pratikte-nasil-kullaniyoruz&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;Diyelim elimizde şöyle bir koleksiyon var:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;{
  _id: 1,
  title: &apos;MongoDB ipuclari&apos;,
  comments: [&apos;c1&apos;, &apos;c2&apos;, &apos;c3&apos;, &apos;c4&apos;, &apos;c5&apos;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Son iki yorumu istiyorsan:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;db.posts.find({}, { comments: { $slice: -2 } })
// donus: comments: [&apos;c4&apos;, &apos;c5&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sayfa numarasıyla yorum çekmek istiyorsan, &lt;code&gt;[skip, limit]&lt;/code&gt; formu cuk oturuyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function getCommentPage(postId, page, pageSize) {
  return db.posts.findOne(
    { _id: postId },
    { comments: { $slice: [(page - 1) * pageSize, pageSize] } }
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tabii burada şuna dikkat: dizinin &lt;strong&gt;tamamı&lt;/strong&gt; belleğe alındıktan sonra dilimleniyor. Yani 100 bin elemanlı bir dizide ortadaki 10&apos;u istiyorsanız MongoDB diziyi yine de okuyor. Eğer dizilerin gerçekten patlama riski varsa, yorumları ayrı koleksiyona almak daha sağlıklı; bana sorarsanız üç haneli sayıları geçen yerlerde &lt;code&gt;$slice&lt;/code&gt;&apos;a güvenmek yanıltıcı.&lt;/p&gt;
&lt;h2 id=&quot;diger-projeksiyonlarla-birlestirmek&quot;&gt;Diger projeksiyonlarla birlestirmek&lt;a href=&quot;#diger-projeksiyonlarla-birlestirmek&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;$slice&lt;/code&gt;&apos;ı klasik dahil/hariç (&lt;code&gt;1&lt;/code&gt; / &lt;code&gt;0&lt;/code&gt;) projeksiyonuyla rahatça birlikte kullanabiliyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;db.posts.find(
  { status: &apos;published&apos; },
  {
    title: 1,
    author: 1,
    comments: { $slice: 5 },
    _id: 0
  }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ama aynı alan üzerinde &lt;code&gt;$elemMatch&lt;/code&gt; veya pozisyonel &lt;code&gt;$&lt;/code&gt; operatörüyle birleştiremiyoruz. Bu da Mongo dokümantasyonunun arka sayfalarında gizli bir kural; staging&apos;de hiç sorun çıkarmadan geçen bir sorgunun production&apos;da &lt;code&gt;Cannot specify positional operator and $slice&lt;/code&gt; hatasını verdiğini görmek mümkün.&lt;/p&gt;
&lt;h2 id=&quot;aggregation-icindeki-slice&quot;&gt;Aggregation icindeki $slice&lt;a href=&quot;#aggregation-icindeki-slice&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 de aggregation pipeline&apos;ında ayrı bir &lt;code&gt;$slice&lt;/code&gt; ifade operatörü var, ki bu çok daha esnek:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;db.posts.aggregate([
  {
    $project: {
      title: 1,
      recentComments: { $slice: [&apos;$comments&apos;, -5] },
      commentCount: { $size: &apos;$comments&apos; }
    }
  }
])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada hem son 5 yorumu alıyoruz hem de &lt;code&gt;$size&lt;/code&gt; ile toplam yorum sayısını döndürüyoruz. Projection formu bunu yapamaz; aggregation formu yapar. Şahsi kanaatim, dinamik dilimleme ya da hesaplama gerekiyorsa doğrudan aggregation&apos;a geçmek daha temiz.&lt;/p&gt;
&lt;h2 id=&quot;sik-karsilasilan-tuzaklar&quot;&gt;Sik karsilasilan tuzaklar&lt;a href=&quot;#sik-karsilasilan-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;Tüm diziyi taradığını unutmak&lt;/strong&gt;: &lt;code&gt;$slice&lt;/code&gt; ağ trafiğini azaltır, disk okumasını değil. Çok büyük dizilerde performans hâlâ sıkıntılı olabilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;[skip, limit]&lt;/code&gt; formunda negatif skip&lt;/strong&gt;: &lt;code&gt;[-3, 2]&lt;/code&gt; ifadesi sondan 3 atlar ve 2 alır. İlk gördüğümde &lt;code&gt;[3, 2]&lt;/code&gt; ile karıştırmıştım; doküman dikkatli okunmalı.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pozisyonel &lt;code&gt;$&lt;/code&gt; ile birlikte kullanmak&lt;/strong&gt;: Aynı alanda iki array operatörünü çakıştıramazsınız. Çakıştırırsanız sorgu hata verir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dizi olmayan alana uygulamak&lt;/strong&gt;: Hata vermez ama alan olduğu gibi döner. Sessiz bir aldatmaca; testinizde fark etmeniz zor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanis&quot;&gt;Kapanis&lt;a href=&quot;#kapanis&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;$slice&lt;/code&gt; küçük ama aklımızda tutmamız gereken bir araç; özellikle bildirim, yorum, son aktiviteler gibi &apos;sadece son N tanesi lazım&apos; senaryolarında yapay bir aggregation&apos;a girmeden işi halledebiliyoruz. Bence tek ipucu şu: dizilerin gerçekten büyüdüğü yerlerde &lt;code&gt;$slice&lt;/code&gt; çözüm değil, ayrı koleksiyona geçmenin habercisidir. Umarım faydalı olur, görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-12-rhel-uzerinde-tox-ile-python-test-otomasyonu/</id>
    <title>RHEL üzerinde Tox ile Python test otomasyonu</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-12-rhel-uzerinde-tox-ile-python-test-otomasyonu/"/>
    <published>2026-06-12T00:00:00Z</published>
    <updated>2026-06-12T00:00:00Z</updated>
    <author><name>Mehmet Demir</name></author>
    <summary>RHEL uzerinde tox kurulumu, birden fazla Python surumu icin izole ortamlar ve CI hattina baglama icin pratik bir rehber.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazıda RHEL sunucu üzerinde tox&apos;u kurup, birden fazla Python sürümüne karşı testlerimizi nasıl koşturduğumuza bakacağız. Konu basit ama bir-iki yerde insanın canını sıkan ufak detaylar var, ben de tam o detaylara değineceğim. Hadi başlayalım.&lt;/p&gt;
&lt;p&gt;Bir Python kütüphanesi yazıyorsanız ya da farklı sunucularda farklı Python sürümleriyle karşılaşacak bir uygulama paketliyorsanız, &apos;benim makinemde çalışıyordu&apos; cümlesi er ya da geç başınıza patlıyor. Tox tam burada devreye giriyor: izole virtualenv&apos;ler kuruyor, her ortamda paketinizi sıfırdan yüklüyor, testleri çalıştırıyor ve sonucu net bir özetle veriyor. Yani aslında küçük bir CI&apos;yi yerel makineye taşıyorsunuz.&lt;/p&gt;
&lt;h2 id=&quot;tox-aslında-ne-yapıyor&quot;&gt;Tox aslında ne yapıyor?&lt;a href=&quot;#tox-aslında-ne-yapı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;Tox bir &lt;code&gt;tox.ini&lt;/code&gt; ya da &lt;code&gt;pyproject.toml&lt;/code&gt; dosyası okur. Tanımladığınız her ortam (env) için şu adımları sırayla yapar:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Temiz bir virtualenv açar.&lt;/li&gt;
&lt;li&gt;Paketinizi ve tanımladığınız bağımlılıkları kurar.&lt;/li&gt;
&lt;li&gt;Belirttiğiniz komutları çalıştırır (genelde &lt;code&gt;pytest&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Geçti / kaldı diye raporlar.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bu kadar. Sihir yok; ama bu kadarı bile elle yapmaya kalktığınızda kafa şişiriyor.&lt;/p&gt;
&lt;h2 id=&quot;birden-fazla-python-sürümü-kurmak&quot;&gt;Birden fazla Python sürümü kurmak&lt;a href=&quot;#birden-fazla-python-sürümü-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;RHEL 9 varsayılan olarak Python 3.9 ile geliyor. Test matrisini büyütmek için AppStream&apos;den ek sürümleri çekiyoruz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo dnf install -y python3 python3-pip python3-devel
sudo dnf install -y python3.11 python3.11-pip python3.11-devel
sudo dnf install -y python3.12 python3.12-pip python3.12-devel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada &lt;code&gt;python3.12-devel&lt;/code&gt; paketini atlamak ilk anda işe yaramış gibi görünür, ama bağımlılıklarınız içinde C uzantısı derleyen bir şey çıktığında (mesela &lt;code&gt;psycopg2&lt;/code&gt;&apos;nin binary olmayan sürümü) hata yer. Bence baştan &lt;code&gt;-devel&lt;/code&gt;&apos;i de kurun, dert etmeyin.&lt;/p&gt;
&lt;h2 id=&quot;toxu-kurmak&quot;&gt;Tox&apos;u kurmak&lt;a href=&quot;#toxu-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;Tox&apos;u sistem geneline değil, kullanıcı seviyesinde kurmak benim tercihim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip3 install --user tox
tox --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eğer &lt;code&gt;tox&lt;/code&gt; komutu bulunamıyorsa &lt;code&gt;~/.local/bin&lt;/code&gt; dizini &lt;code&gt;PATH&lt;/code&gt;&apos;inizde olmayabilir. Bunu &lt;code&gt;.bashrc&lt;/code&gt;&apos;ye eklemek bir kerelik bir iş.&lt;/p&gt;
&lt;h2 id=&quot;ornek-bir-toxini&quot;&gt;Ornek bir tox.ini&lt;a href=&quot;#ornek-bir-toxini&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;Kafamızda somutlaşması için küçük bir paket düşünelim. &lt;code&gt;src/mypackage/__init__.py&lt;/code&gt; içinde iki fonksiyon olsun:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError(&apos;Sifira bolme yok&apos;)
    return a / b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test dosyamız &lt;code&gt;tests/test_math.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pytest
from mypackage import add, divide

def test_add():
    assert add(2, 3) == 5

def test_divide_by_zero():
    with pytest.raises(ValueError):
        divide(1, 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Şimdi &lt;code&gt;tox.ini&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[tox]
envlist = py39, py311, py312, lint
isolated_build = True

[testenv]
deps =
    pytest
    pytest-cov
commands =
    pytest tests/ -v --cov=mypackage --cov-report=term-missing

[testenv:lint]
deps =
    flake8
    black
commands =
    flake8 src/ tests/
    black --check src/ tests/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tox&lt;/code&gt; komutunu çalıştırdığınızda dört ortam birden açılır, paketinizi her birine ayrı ayrı kurar ve testleri koşar. &lt;code&gt;tox -e py311&lt;/code&gt; derseniz tek ortam çalışır.&lt;/p&gt;
&lt;h2 id=&quot;paralel-kosma-ve-yeniden-olusturma&quot;&gt;Paralel kosma ve yeniden olusturma&lt;a href=&quot;#paralel-kosma-ve-yeniden-olusturma&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 sürümü ardı ardına kurulup testler çalışınca süre uzayabiliyor. Paralel modu deneyin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tox -p auto
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bağımlılık değişip ortamlar bayatladığında ise:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tox -r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bütün ortamları siler, sıfırdan kurar. CI dışında yerelde sık ihtiyaç duyduğum bir komut, çünkü &lt;code&gt;requirements&lt;/code&gt;&apos;a yeni bir paket eklediğinizde tox bunu otomatik fark etmiyor her zaman.&lt;/p&gt;
&lt;h2 id=&quot;bizim-takildigimiz-bir-nokta&quot;&gt;Bizim takildigimiz bir nokta&lt;a href=&quot;#bizim-takildigimiz-bir-nokta&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;Şahsi tecrübeden söyleyeyim: bir keresinde RHEL 9 üzerinde &lt;code&gt;py312&lt;/code&gt; ortamı tox listesinde &apos;görünmüyor&apos; diye uğraşmıştık. Sebebi gayet basitti aslında - paket kurulu değildi ama dnf sessizce başka bir paketi çözmüştü. &lt;code&gt;python3.12 --version&lt;/code&gt; denediğimizde &apos;command not found&apos; aldık. AppStream module setlerinde sürüm bazen gizli kalıyor; &lt;code&gt;dnf module list python&lt;/code&gt; ile aktif modülü görmek bu durumda iş kurtarır. Üzerinde bir kahve harcadıktan sonra alışkanlık oldu, ilk iş onu çağırıyorum şimdi.&lt;/p&gt;
&lt;h2 id=&quot;ci-hattina-baglamak&quot;&gt;CI hattina baglamak&lt;a href=&quot;#ci-hattina-baglamak&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;GitHub Actions tarafında matris tanımı şu kadar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [&apos;3.9&apos;, &apos;3.11&apos;, &apos;3.12&apos;]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install tox
      - run: tox -e py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tox -e py&lt;/code&gt; o anki Python sürümünün ortamını seçiyor; matris bunu üç farklı versiyon için ayrı ayrı tetikliyor. Yerel &lt;code&gt;tox.ini&lt;/code&gt;&apos;yi değiştirmeden CI&apos;yi büyütebiliyorsunuz, en hoş tarafı bu.&lt;/p&gt;
&lt;h2 id=&quot;sik-karsilasilan-tuzaklar&quot;&gt;Sik karsilasilan tuzaklar&lt;a href=&quot;#sik-karsilasilan-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;isolated_build = True&apos;yi unutmak&lt;/strong&gt;: &lt;code&gt;pyproject.toml&lt;/code&gt; ile çalışıyorsanız bu satır olmazsa olmaz. Yoksa tox eski stil &lt;code&gt;setup.py&lt;/code&gt; davranışına düşer ve modern build backend&apos;lerinizi tanımaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deps icindeki surumleri sabitlememek&lt;/strong&gt;: CI&apos;da yeşil olan tox koşusu ertesi gün kırmızı dönerse, büyük ihtimalle &lt;code&gt;pytest&lt;/code&gt; ya da &lt;code&gt;flake8&lt;/code&gt; minor sürüm zıplaması yapmıştır. Kritik araçlar için &lt;code&gt;pytest&amp;gt;=8,&amp;lt;9&lt;/code&gt; gibi bir aralık koyun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;passenv&apos;i bos birakmak&lt;/strong&gt;: Tox güvenlik için ortam değişkenlerinizi temizler. CI&apos;nin koyduğu &lt;code&gt;CI&lt;/code&gt;, &lt;code&gt;GITHUB_ACTIONS&lt;/code&gt; gibi değişkenler de gider. Lazımsa açıkça &lt;code&gt;passenv = CI HOME GITHUB_*&lt;/code&gt; deyin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sistem Python&apos;u ile karistirmak&lt;/strong&gt;: &lt;code&gt;pip install tox&lt;/code&gt; derken root olmayın. Sistem paketleriyle çakışırsa toparlamak yorucu olur; &lt;code&gt;pip3 install --user tox&lt;/code&gt; ya da &lt;code&gt;pipx install tox&lt;/code&gt; çok daha temiz.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kapanis&quot;&gt;Kapanis&lt;a href=&quot;#kapanis&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 tox ile RHEL üzerinde çoklu Python sürümü test ortamını nasıl kurduğumuza, paralel koşmaya ve CI&apos;a bağlamaya kısaca baktık. Bana sorarsanız tox, kütüphane yazan herkes için artık opsiyonel bir araç değil; matris testi olmadan farklı sürümlerde kırılan bir paketi yayınlamanın bedeli her zaman daha ağır oluyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-11-mysql-innodb-coktukten-sonra-veri-kurtarma/</id>
    <title>MySQL InnoDB çöktükten sonra veri kurtarma</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-11-mysql-innodb-coktukten-sonra-veri-kurtarma/"/>
    <published>2026-06-11T00:00:00Z</published>
    <updated>2026-06-11T00:00:00Z</updated>
    <author><name>Selin Çetin</name></author>
    <summary>MySQL InnoDB crash recovery nasıl calisir, otomatik kurtarma basarisiz oldugunda ne yapariz ve binary log ile point-in-time recovery nasil isler.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda MySQL&apos;in en sevilen ama en az anlaşılan konularından birine, InnoDB&apos;nin crash recovery mekanizmasına bakacağız. Konuyu hem teorik hem de pratik tarafıyla ele alacağız; çünkü production&apos;da bir gece yarısı MySQL ayağa kalkmıyorsa, dokümantasyonu o anda okumak biraz geç oluyor. Lafı çok uzatmadan başlayalım.&lt;/p&gt;
&lt;p&gt;InnoDB aslında çökmelerden tek başına kurtulmak için tasarlanmıştır. Yani sunucu fişten çekildiğinde bile, redo log dosyaları sayesinde commit edilmiş işlemleri geri oynatır, yarım kalanları rollback eder. Çoğu zaman siz farkına bile varmazsınız. Ama bazen otomatik kurtarma yetmez ve iş başa düşer.&lt;/p&gt;
&lt;h2 id=&quot;innodb-crash-recovery-nasıl-çalışır&quot;&gt;InnoDB crash recovery nasıl çalışır?&lt;a href=&quot;#innodb-crash-recovery-nasıl-çalışı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;InnoDB, write-ahead logging (kısaca WAL) yaklaşımını kullanır. Şöyle ki, veri sayfaları diske yazılmadan önce değişiklikler önce redo log&apos;a yazılır. MySQL 8.0.30 öncesinde bu dosyalar &lt;code&gt;ib_logfile0&lt;/code&gt; ve &lt;code&gt;ib_logfile1&lt;/code&gt; idi; sonrasında &lt;code&gt;#innodb_redo/&lt;/code&gt; klasörü altına taşındı.&lt;/p&gt;
&lt;p&gt;Çökme sonrası başlangıçta InnoDB üç şey yapar:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Son checkpoint&apos;ten itibaren redo log&apos;u okur.&lt;/li&gt;
&lt;li&gt;Commit edilmiş işlemleri ileri sarar (roll forward).&lt;/li&gt;
&lt;li&gt;Yarım kalan işlemleri undo log ile geri alır (rollback).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Recovery&apos;nin gerçekten çalışıp çalışmadığını şuradan kontrol edebilirsiniz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM performance_schema.global_status
WHERE VARIABLE_NAME LIKE &apos;Innodb_redo_log%&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;otomatik-kurtarma-ve-error-log&quot;&gt;Otomatik kurtarma ve error log&lt;a href=&quot;#otomatik-kurtarma-ve-error-log&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;Vakaların büyük çoğunluğunda MySQL kendi kendine ayağa kalkar. Bizim ilk işimiz error log&apos;a bakmak olmalı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo tail -100 /var/log/mysql/error.log | grep -i &apos;recovery\|innodb\|crash&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tipik olarak şuna benzer satırlar görürsünüz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Restoring possible half-written data pages
InnoDB: Completed initialization of buffer pool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bu satırları gördüyseniz rahat nefes alabilirsiniz; InnoDB işini yapmış demektir.&lt;/p&gt;
&lt;h2 id=&quot;mysql-ayağa-kalkmıyorsa&quot;&gt;MySQL ayağa kalkmıyorsa&lt;a href=&quot;#mysql-ayağa-kalkmıyorsa&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;İşler çığrından çıktığında, yani MySQL servisi başlamıyorsa, error log yine ilk durağımız:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo journalctl -u mysql --since &apos;1 hour ago&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Genelde şu iki hata mesajı yön gösterir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[ERROR] InnoDB: Corruption in the InnoDB tablespace - use innodb_force_recovery
[ERROR] InnoDB: Unable to lock ./ibdata1 - check if another mysqld is running
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;İkincisi aslında masum bir durum; çoğu zaman zombi bir &lt;code&gt;mysqld&lt;/code&gt; process&apos;i takılı kalmıştır. İlki ise daha can sıkıcı, çünkü &lt;code&gt;innodb_force_recovery&lt;/code&gt; parametresine başvurmamız gerekecek.&lt;/p&gt;
&lt;h2 id=&quot;yedekten-geri-dönmek&quot;&gt;Yedekten geri dönmek&lt;a href=&quot;#yedekten-geri-dönmek&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;Açıkçası bana sorarsanız, ciddi bir bozulma varsa en sağlıklı yol yedekten dönmektir. &lt;code&gt;innodb_force_recovery=6&lt;/code&gt; ile veriyi kurtarabilirsiniz, ama o noktada veri tutarlılığı zaten yarım yamalaktır.&lt;/p&gt;
&lt;p&gt;Mantıksal yedek (mysqldump) için:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysql -u root -p &amp;lt; /backup/full_backup_2026-03-31.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fiziksel yedek (Xtrabackup) için MySQL durdurulmalı ve data dizini boşaltılmalı:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl stop mysql
xtrabackup --prepare --target-dir=/backup/xtrabackup_2026-03-31/
sudo rm -rf /var/lib/mysql/*
xtrabackup --copy-back --target-dir=/backup/xtrabackup_2026-03-31/
sudo chown -R mysql:mysql /var/lib/mysql/
sudo systemctl start mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;binary-log-ile-point-in-time-recovery&quot;&gt;Binary log ile point-in-time recovery&lt;a href=&quot;#binary-log-ile-point-in-time-recovery&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;Yedeği geri yüklediniz; tamam ama yedek sabah 03:00&apos;a ait, çökme öğlen 13:00&apos;da oldu. Aradaki on saatlik veri ne olacak? Eğer binary log açıksa, o on saati &lt;code&gt;mysqlbinlog&lt;/code&gt; ile geri oynatabiliriz:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysqlbinlog --start-datetime=&apos;2026-03-31 03:05:00&apos; \
            --stop-datetime=&apos;2026-03-31 12:55:00&apos; \
            /var/lib/mysql/mysql-bin.000001 | mysql -u root -p
&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;Binary log&apos;u kapalı bırakmak&lt;/strong&gt;: Yedeğiniz olsa bile son N saatin verisi gider. &lt;code&gt;log_bin&lt;/code&gt; production&apos;da pazarlığa açık değil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tek replica&apos;yı yedek sanmak&lt;/strong&gt;: Replica master&apos;ın bozulmasını da seve seve replike eder. Yedek dediğimiz şey ayrı, periyodik ve geri yüklemesi test edilmiş olandır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;innodb_force_recovery=6&lt;/code&gt; ile uzun süre çalışmak&lt;/strong&gt;: Bu mod read-only&apos;dir ve veri çıkarmak içindir. Veriyi alır almaz yeni bir instance kurun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redo log boyutunu küçük bırakmak&lt;/strong&gt;: Yoğun yazma altında checkpoint baskısı recovery süresini de uzatır. MySQL 8.0.30+ için &lt;code&gt;innodb_redo_log_capacity&lt;/code&gt; ile ayarlayın:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[mysqld]
innodb_redo_log_capacity=2G
log_bin=mysql-bin
binlog_expire_logs_seconds=604800
&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;Bu yazıda InnoDB&apos;nin redo log üzerinden nasıl kendi kendine toparlandığına, otomatik kurtarma yetmediğinde error log&apos;dan başlayarak yedek ve binary log&apos;a uzanan yola baktık. Şahsi kanaatim, crash recovery&apos;nin asıl çözümü çökme anında değil, çökmeden çok önce alınan kararlardadır: düzenli yedek, açık binary log ve geri yükleme tatbikatı. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://pythonvesql.com/makale/2026-06-11-python-da-ozel-context-manager-yazmak/</id>
    <title>Python&#039;da özel context manager yazmak</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-11-python-da-ozel-context-manager-yazmak/"/>
    <published>2026-06-11T00:00:00Z</published>
    <updated>2026-06-11T00:00:00Z</updated>
    <author><name>Cem Karadağ</name></author>
    <summary>Python&#039;da __enter__/__exit__ ve contextlib ile kendi context manager&#039;larinizi nasil yazacaginizi pratik orneklerle anlatir.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda Python&apos;ın en zarif özelliklerinden biri olan context manager&apos;lara, özellikle de kendi context manager&apos;larınızı nasıl yazacağınıza bakacağız. &lt;code&gt;with open(...)&lt;/code&gt; kalıbını hepimiz biliyoruz; ama aynı mantığı veritabanı bağlantıları, kilitler, zamanlayıcılar ve genel olarak setup-teardown isteyen her kaynak için kullanabilirsiniz. Hadi dalalım.&lt;/p&gt;
&lt;h2 id=&quot;context-manager-neden-lazım&quot;&gt;Context manager neden lazım?&lt;a href=&quot;#context-manager-neden-lazı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;Açıkça söyleyeyim, eğer kodunuzda her yerde &lt;code&gt;try/finally&lt;/code&gt; tekrar eden satırlar varsa, orada büyük ihtimalle gizli bir context manager bekliyor. &lt;code&gt;with&lt;/code&gt; bloğu üç şeyi bir arada veriyor: garantili temizlik (exception olsa bile), tekrar kullanılabilir kalıp ve kaynak kapsamını net görsel olarak işaretleyen bir blok. Bu üçü olmadan kod hızla &apos;try&apos;lı, dağınık&apos; bir hale geliyor.&lt;/p&gt;
&lt;p&gt;Buradan ne çıkarmalıyız? Aynı setup-teardown desenini iki yerde gördüğünüzde durup düşünün; muhtemelen bir context manager&apos;a kavuşturmanın zamanı gelmiştir.&lt;/p&gt;
&lt;h2 id=&quot;sınıf-tabanlı-yaklaşım&quot;&gt;Sınıf tabanlı yaklaşım&lt;a href=&quot;#sınıf-tabanlı-yaklaşı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 esnek yol, &lt;code&gt;__enter__&lt;/code&gt; ve &lt;code&gt;__exit__&lt;/code&gt; metotlarını içeren bir sınıf yazmak. Şablonu şöyle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class OrnekContext:
    def __enter__(self):
        # Hazirlik kodu burada
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # Temizlik kodu burada, her durumda calisir
        # True donerseniz exception bastirilir, False ile yukari iletilir
        return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;__enter__&lt;/code&gt;&apos;ın döndürdüğü değer &lt;code&gt;as&lt;/code&gt; ile bağlanan değişken olur. &lt;code&gt;__exit__&lt;/code&gt; ise bloktan çıkarken çalışır; eğer içeride exception fırladıysa parametreler dolu gelir, fırlamadıysa hepsi &lt;code&gt;None&lt;/code&gt; olur.&lt;/p&gt;
&lt;p&gt;Şimdi bir veritabanı bağlantısı örneği görelim. Aşağıdaki sınıf, blok normal biterse &lt;code&gt;commit&lt;/code&gt;, exception olursa &lt;code&gt;rollback&lt;/code&gt; çağırıyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sqlite3
from typing import Optional


class DbConnection:
    def __init__(self, db_path: str):
        self.db_path = db_path
        self.connection: Optional[sqlite3.Connection] = None
        self.cursor: Optional[sqlite3.Cursor] = None

    def __enter__(self) -&amp;gt; sqlite3.Cursor:
        self.connection = sqlite3.connect(self.db_path)
        self.cursor = self.connection.cursor()
        return self.cursor

    def __exit__(self, exc_type, exc_val, exc_tb) -&amp;gt; bool:
        if exc_type is not None:
            self.connection.rollback()
        else:
            self.connection.commit()
        self.cursor.close()
        self.connection.close()
        return False


with DbConnection(&apos;app.db&apos;) as cursor:
    cursor.execute(&apos;INSERT INTO users VALUES (1, ?)&apos;, (&apos;Ayse&apos;,))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kullanırken sadece &lt;code&gt;with&lt;/code&gt; yazıyoruz; &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;rollback&lt;/code&gt; ve &lt;code&gt;close&lt;/code&gt;&apos;u tek tek hatırlamak zorunda değiliz. Bence bu yaklaşımın en güzel tarafı bu: tek bir yerde doğru yaptıktan sonra her yerde doğru.&lt;/p&gt;
&lt;h2 id=&quot;contextlib-ile-daha-kısa-yol&quot;&gt;contextlib ile daha kısa yol&lt;a href=&quot;#contextlib-ile-daha-kısa-yol&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;Her seferinde sınıf yazmak istemeyebilirsiniz. Bu durumda &lt;code&gt;contextlib.contextmanager&lt;/code&gt; dekoratörü işinizi görür. Generator fonksiyonu yazıyorsunuz; &lt;code&gt;yield&lt;/code&gt; öncesi setup, sonrası teardown:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from contextlib import contextmanager
import os


@contextmanager
def working_directory(path: str):
    original_dir = os.getcwd()
    try:
        os.chdir(path)
        yield path
    finally:
        os.chdir(original_dir)


with working_directory(&apos;/tmp&apos;):
    # Burada gecici olarak /tmp icindeyiz
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Buradaki kritik nokta &lt;code&gt;try/finally&lt;/code&gt;. &lt;code&gt;yield&lt;/code&gt;&apos;i çıplak bırakırsanız, blok içinde exception fırladığında temizlik kodunuz çalışmaz. Şahsi kanaatim, dekoratör yaklaşımı sadece basit setup-teardown için; durum tutmanız gerekiyorsa sınıfa geçin.&lt;/p&gt;
&lt;h2 id=&quot;birden-fazla-contexti-birlikte-kullanmak&quot;&gt;Birden fazla context&apos;i birlikte kullanmak&lt;a href=&quot;#birden-fazla-contexti-birlikte-kullanmak&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 3.10 ile parantez sözdizimi geldi:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with (
    open(&apos;input.txt&apos;) as infile,
    open(&apos;output.txt&apos;, &apos;w&apos;) as outfile,
):
    outfile.write(infile.read())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sayı dinamikse &lt;code&gt;ExitStack&lt;/code&gt; daha pratik:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from contextlib import ExitStack


def process_files(filenames: list):
    with ExitStack() as stack:
        files = [stack.enter_context(open(fname)) for fname in filenames]
        for f in files:
            print(f.readline())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ExitStack&lt;/code&gt; her dosyayı otomatik kapatır, sıraya da kendisi karar verir.&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;Generator&apos;da &lt;code&gt;try/finally&lt;/code&gt; unutmak&lt;/strong&gt;: &lt;code&gt;yield&lt;/code&gt;&apos;den sonraki temizlik kodu, exception olursa çalışmaz. &lt;code&gt;finally&lt;/code&gt; bloğuna almazsanız sızıntı kaçınılmazdır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;__exit__&lt;/code&gt;&apos;te &lt;code&gt;True&lt;/code&gt; döndürmek&lt;/strong&gt;: Bu, blok içindeki tüm exception&apos;ları sessizce yutar. Hata ayıklamayı imkansız hale getirir. Sadece gerçekten &apos;bu exception&apos;ı buradan öteye iletme&apos; demek istiyorsanız kullanın, o da çoğu zaman istemediğiniz şeydir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;__enter__&lt;/code&gt;&apos;da exception fırlatmak ama temizliği yapmamak&lt;/strong&gt;: Eğer &lt;code&gt;__enter__&lt;/code&gt; ortasında patlarsa &lt;code&gt;__exit__&lt;/code&gt; çağrılmaz. Bu yüzden &lt;code&gt;__enter__&lt;/code&gt; içinde de kendi &lt;code&gt;try/except&lt;/code&gt;&apos;inizle yarım kalmış kaynakları kapatın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aynı context&apos;i birden fazla kez girmek&lt;/strong&gt;: Sınıfı yeniden girilebilir (reentrant) yazmadıysanız ikinci &lt;code&gt;with&lt;/code&gt; durumu bozar. Gerekiyorsa &lt;code&gt;threading.RLock&lt;/code&gt; benzeri bir yapıya 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;Bu yazıda hem sınıf tabanlı hem de &lt;code&gt;contextlib&lt;/code&gt; ile context manager yazmaya baktık. Bana sorarsanız, kodunuzdaki her tekrar eden &lt;code&gt;try/finally&lt;/code&gt; deseni aslında &apos;beni bir context manager&apos;a çevir&apos; diye bağırı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-06-10-amazon-keyspaces-ile-yonetimsiz-cassandra-deneyimi/</id>
    <title>Amazon Keyspaces ile yönetimsiz Cassandra deneyimi</title>
    <link rel="alternate" href="https://pythonvesql.com/makale/2026-06-10-amazon-keyspaces-ile-yonetimsiz-cassandra-deneyimi/"/>
    <published>2026-06-10T00:00:00Z</published>
    <updated>2026-06-10T00:00:00Z</updated>
    <author><name>Berk Avcı</name></author>
    <summary>Amazon Keyspaces uzerinde keyspace olusturma, SigV4 ile baglanma, kapasite modlari ve Cassandra ile uyumsuz noktalari pratik ornek esliginde anlatir.</summary>
    <content type="html">&lt;p&gt;Selamlar, bu yazımda Amazon Keyspaces&apos;e bir göz atacağız. Cassandra&apos;yı kendi sunucularında çalıştırmış olanlar bilir; compaction ayarları, gossip protokolü, tombstone temizliği derken ortaya neredeyse tam zamanlı bir iş çıkar. Keyspaces, AWS&apos;nin Cassandra uyumlu yönetilen servisi; CQL&apos;i konuşuyor ama altta cluster yönetimi yok. Lafı uzatmadan başlayayım.&lt;/p&gt;
&lt;h2 id=&quot;keyspaces-cassandradan-ne-kadar-farklı&quot;&gt;Keyspaces, Cassandra&apos;dan ne kadar farklı?&lt;a href=&quot;#keyspaces-cassandradan-ne-kadar-farklı&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;Keyspaces, Cassandra Query Language&apos;i (CQL) ve resmi Apache sürücülerini destekliyor. Yani mevcut sorgularınızın büyük çoğunluğu hiç dokunmadan çalışır. Ama bazı özelliklerde duvara toslayabilirsiniz:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lightweight transaction (LWT)&lt;/strong&gt; desteği kısıtlı; bazı operasyonlarda &lt;code&gt;IF NOT EXISTS&lt;/code&gt; çalışmıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User-defined type (UDT)&lt;/strong&gt;, &lt;strong&gt;user-defined function&lt;/strong&gt; ve &lt;strong&gt;materialized view&lt;/strong&gt; yok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secondary index&lt;/strong&gt; desteklenmiyor (bunun yerine custom indexler var).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BATCH&lt;/strong&gt; ifadesi tek tabloya sınırlı; çok tablolu batch yazılamaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tunable consistency&lt;/strong&gt; yok. Yazmalarda &lt;code&gt;LOCAL_QUORUM&lt;/code&gt;, okumalarda &lt;code&gt;LOCAL_ONE&lt;/code&gt; veya &lt;code&gt;LOCAL_QUORUM&lt;/code&gt; ile yetiniyorsunuz.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Karşılığında node yönetimi, replikasyon faktörü, kapasite planlaması gibi başlıkları unutuyorsunuz. Bence ekip küçükse bu trade-off neredeyse her zaman mantıklı.&lt;/p&gt;
&lt;h2 id=&quot;keyspace-ve-tablo-oluşturma&quot;&gt;Keyspace ve tablo oluşturma&lt;a href=&quot;#keyspace-ve-tablo-oluşturma&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;Cluster provision etmeye gerek yok. Bir keyspace açıyorsunuz, içine tabloları ekliyorsunuz. AWS CLI ile şöyle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;aws keyspaces create-keyspace \
  --keyspace-name my_application

aws keyspaces get-keyspace --keyspace-name my_application
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tablo tarafında JSON şema verebilir ya da CQL ile gidebilirsiniz. Cassandra&apos;dan geliyorsanız ikincisi daha tanıdık gelir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE my_application.users (
    user_id uuid,
    email text,
    name text,
    created_at timestamp,
    status text,
    PRIMARY KEY (user_id)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sigv4-ile-pythondan-bağlanmak&quot;&gt;SigV4 ile Python&apos;dan bağlanmak&lt;a href=&quot;#sigv4-ile-pythondan-bağlanmak&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;Keyspaces&apos;e iki yoldan bağlanabilirsiniz: IAM&apos;de oluşturulan service-specific credential ya da SigV4 plugin&apos;i. Production için bence SigV4 daha temiz; uzun ömürlü parola tutmuyorsunuz, IAM rolünüzü olduğu gibi kullanıyorsunuz.&lt;/p&gt;
&lt;p&gt;Bağlanmadan önce Starfield kök sertifikasını indirmeniz gerekiyor; Keyspaces TLS şart koşuyor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl https://certs.secureserver.net/repository/sf-class2-root.crt -O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sonrası standart Cassandra sürücüsü:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from cassandra.cluster import Cluster
from ssl import SSLContext, PROTOCOL_TLSv1_2, CERT_REQUIRED
from cassandra_sigv4.auth import SigV4AuthProvider
import boto3

ssl_context = SSLContext(PROTOCOL_TLSv1_2)
ssl_context.load_verify_locations(&apos;sf-class2-root.crt&apos;)
ssl_context.verify_mode = CERT_REQUIRED

boto_session = boto3.Session(region_name=&apos;us-east-1&apos;)
auth_provider = SigV4AuthProvider(boto_session)

cluster = Cluster(
    [&apos;cassandra.us-east-1.amazonaws.com&apos;],
    ssl_context=ssl_context,
    auth_provider=auth_provider,
    port=9142,
)

session = cluster.connect()
print(&apos;Keyspaces bagli&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Burada dikkat: port 9142, klasik Cassandra&apos;nın 9042&apos;sinden farklı. Hata alıyorsanız ilk bakacağınız yer bu.&lt;/p&gt;
&lt;h2 id=&quot;veriyle-çalışmak&quot;&gt;Veriyle çalışmak&lt;a href=&quot;#veriyle-çalışmak&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ğlantıyı kurduk, şimdi tipik bir zaman serisi tablosuna bir-iki kayıt yazalım:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;session.execute(&amp;quot;&amp;quot;&amp;quot;
    CREATE KEYSPACE IF NOT EXISTS my_application
    WITH REPLICATION = {&apos;class&apos;: &apos;SingleRegionStrategy&apos;}
&amp;quot;&amp;quot;&amp;quot;)
session.set_keyspace(&apos;my_application&apos;)

session.execute(&amp;quot;&amp;quot;&amp;quot;
    CREATE TABLE IF NOT EXISTS events (
        device_id text,
        event_date date,
        event_time timestamp,
        event_type text,
        payload text,
        PRIMARY KEY ((device_id, event_date), event_time)
    ) WITH CLUSTERING ORDER BY (event_time DESC)
&amp;quot;&amp;quot;&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Partition key&apos;e &lt;code&gt;event_date&lt;/code&gt; koymamızın sebebi şu: tek bir cihazın yıllarca biriken kaydı tek partition&apos;a düşerse partition şişer. Tarihle parçalayınca her gün için ayrı bir partition oluyor, bu da Keyspaces&apos;in 1 GB altı partition önerisine uyuyor.&lt;/p&gt;
&lt;h2 id=&quot;kapasite-modları&quot;&gt;Kapasite modları&lt;a href=&quot;#kapasite-modları&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 seçenek var. &lt;strong&gt;On-demand&lt;/strong&gt;, isteğe göre ölçekleniyor; öngöremediğiniz, ani yükselen iş yükleri için iyi. &lt;strong&gt;Provisioned&lt;/strong&gt; ise saniyede beklediğiniz okuma/yazma birimini siz tanımlıyorsunuz, auto-scaling de açabiliyorsunuz. Tahmin edilebilir trafiklerde provisioned belirgin biçimde ucuza geliyor; şahsi kanaatim, gündüzleri sabit yük taşıyan API&apos;ler için doğrudan provisioned&apos;a gitmek mantıklı.&lt;/p&gt;
&lt;p&gt;PITR (point-in-time recovery) son 35 güne kadar restore vermenizi sağlıyor; tabloyu oluştururken ya da sonra &lt;code&gt;update-table&lt;/code&gt; ile açabiliyorsunuz. Açık olsun, kötü bir gün hayat kurtarır.&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;9042 portuyla bağlanmaya çalışmak&lt;/strong&gt;: Keyspaces 9142&apos;yi dinliyor. Yanlış port verirseniz timeout alırsınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS&apos;siz bağlantı denemek&lt;/strong&gt;: Keyspaces düz TCP kabul etmiyor. Sertifika dosyasını yüklemeden cluster &lt;code&gt;connect()&lt;/code&gt; patlar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Büyük IN listeleri kullanmak&lt;/strong&gt;: Arka planda her değer için ayrı partition okuması yapılır, throttle yersiniz. Sürücü zaten paralel çalışıyor, tek tek sorgu atın.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LWT&apos;ye güvenmek&lt;/strong&gt;: Cassandra&apos;da çalışan &lt;code&gt;IF NOT EXISTS&lt;/code&gt; mantığı Keyspaces&apos;te her yerde geçerli değil. Tasarımı LWT etrafına kurmadan önce hedef tabloyla deneyin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CloudWatch&apos;a bakmamak&lt;/strong&gt;: &lt;code&gt;ThrottledRequests&lt;/code&gt; ve &lt;code&gt;SystemErrors&lt;/code&gt; metrikleri &lt;code&gt;AWS/Cassandra&lt;/code&gt; altında yayınlanıyor; bu ikisine alarm koymadan production&apos;a almayı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;Keyspaces, ekibiniz Cassandra biliyor ama operasyon yükünü taşımak istemiyorsa oldukça rahatlatıcı bir servis. Ben olsam yine de UDT, materialized view ve LWT kullanan kod yollarını listeleyip, geçişten önce bunları test ortamında bir tarayışla doğrulardım. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.&lt;/p&gt;
</content>
  </entry>
</feed>
