<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tarık Anafarta</title>
    <description>The latest articles on DEV Community by Tarık Anafarta (@tarikanafarta).</description>
    <link>https://dev.to/tarikanafarta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3794394%2Faa68f549-a9ff-4768-beb9-ec304b3e86d3.png</url>
      <title>DEV Community: Tarık Anafarta</title>
      <link>https://dev.to/tarikanafarta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tarikanafarta"/>
    <language>en</language>
    <item>
      <title>TimescaleDB'deki verileri okuyup alarm yapısına dönüştürmek</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 16 Mar 2026 10:23:02 +0000</pubDate>
      <link>https://dev.to/tarikanafarta/timescaledbdeki-verileri-okuyup-alarm-yapisina-donusturmek-jfa</link>
      <guid>https://dev.to/tarikanafarta/timescaledbdeki-verileri-okuyup-alarm-yapisina-donusturmek-jfa</guid>
      <description>&lt;p&gt;Alarm üretim mantığı genelde manuel yazılıyor. Bu ya PostgreSQL tarafında bir trigger + NOTIFY akışıyla başlatılıyor, ya da uygulama tarafında çalışan bir servis ile okuyup dönüştürülüyor.&lt;/p&gt;

&lt;p&gt;Elimizde TimescaleDB üzerinde bir hypertable olduğunu düşünelim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sensör verisi geliyor&lt;/li&gt;
&lt;li&gt;bazı kayıtlar alarm üretmeli&lt;/li&gt;
&lt;li&gt;bazı alarmlar tek satırdan çıkıyor, bazıları ise pencere bazlı hesap gerektiriyor, yani son 5 dakikanın ortalaması %80'i geçti mi? gibi&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TimescaleDB bu tip akışlar için ne kadar uygun?
&lt;/h2&gt;

&lt;p&gt;TimescaleDB, PostgreSQL'in üzerinde çalıştığı için PostgreSQL'in trigger mekanizmasını destekliyor. Resmi dokümana göre hypertable üzerinde oluşturulan trigger'lar alttaki chunk'lara da yayılıyor.&lt;/p&gt;

&lt;p&gt;Ayrıca TimescaleDB'de job mekanizması var ve continuous aggregate desteği de var. Yani alarmı doğrudan her satır insert edildiğinde üretmek zorunda değiliz. Özellikle pencere bazlı kurallarda önce aggregate üretmek, sonra alarm çıkarmak daha mantıklı.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yaklaşım 1: PostgreSQL LISTEN/NOTIFY ile alarm akışı
&lt;/h2&gt;

&lt;p&gt;Bu modelin mantığı basit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TimescaleDB hypertable'ına veri gelir.&lt;/li&gt;
&lt;li&gt;'AFTER INSERT' trigger çalışır.&lt;/li&gt;
&lt;li&gt;Trigger, uygun gördüğü kayıt için &lt;code&gt;pg_notify(...)&lt;/code&gt; çağırır.&lt;/li&gt;
&lt;li&gt;Uygulamadaki bir listener servis ilgili kanalı dinler.&lt;/li&gt;
&lt;li&gt;Listener, payload'daki anahtar bilgiyle gerçek kaydı okur.&lt;/li&gt;
&lt;li&gt;Alarm nesnesini oluşturur ve &lt;code&gt;alarms&lt;/code&gt; tablosuna yazar.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Artıları nelerdir?
&lt;/h3&gt;

&lt;p&gt;Öncelikle gecikme düşük. NOTIFY, commit sonrası dinleyicilere sinyal gönderir. Yani veri başarılı şekilde commit olduktan hemen sonra uygulaman uyanabilir. Ayrı bir queue ürünü kurmadan, veritabanının içinden olay sinyali alabiliyorsun.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dikkat edilmesi gereken noktalar:
&lt;/h3&gt;

&lt;p&gt;PostgreSQL dokümanına göre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bildirimler transaction commit edilmeden teslim edilmiyor&lt;/li&gt;
&lt;li&gt;dinleyici transaction içindeyse mesaj ona da transaction bitince teslim ediliyor&lt;/li&gt;
&lt;li&gt;aynı transaction içinde aynı kanal ve aynı payload tekrar tekrar gönderilirse tek bildirime indirgenebiliyor&lt;/li&gt;
&lt;li&gt;payload varsayılan yapılandırmada 8000 bayttan kısa olmak zorunda&lt;/li&gt;
&lt;li&gt;queue dolarsa NOTIFY çağıran transaction commit anında hata alabiliyor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yani kısaca NOTIFY veri taşımıyor. Sadece olay sinyali veriyor.&lt;/p&gt;

&lt;h3&gt;
  
  
  NOTIFY için basit örnek
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sensor_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;device_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metric&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="nb"&gt;double&lt;/span&gt; &lt;span class="nb"&gt;precision&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;create_hypertable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sensor_events'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ts'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;notify_alarm_candidate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'temperature'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;pg_notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'alarm_candidates'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_notify_alarm_candidate&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sensor_events&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;notify_alarm_candidate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu örnekte trigger sadece bu kayıt alarm adayı olabilir diyor. Alarmın gerçekten açılıp açılmayacağına uygulama karar veriyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yaklaşım 2: Yazılımla okumak ve alarma dönüştürmek
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Uygulama tarafında bir worker veya servis çalışır.&lt;/li&gt;
&lt;li&gt;Bu servis TimescaleDB'den yeni verileri belli bir mantıkla okur.&lt;/li&gt;
&lt;li&gt;Okuduğu veriyi alarm modeline dönüştürür.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bu modelde raw hypertable ayrı kalır, alarm işleme için ayrı bir &lt;code&gt;alarm_candidates&lt;/code&gt; tablosu tutulur.&lt;/p&gt;

&lt;p&gt;Uygulama worker'ları bu tablodan kayıt çekip işler. Çoklu worker varsa PostgreSQL'in &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; özelliği ile çakışmadan paralel tüketim yapılabilir. PostgreSQL dokümanı da &lt;code&gt;SKIP LOCKED&lt;/code&gt; kullanımının queue benzeri yapılarda lock çakışmasını azaltmak için uygun olduğunu söylüyor.&lt;/p&gt;

&lt;p&gt;Örnek:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;picked&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;alarm_outbox&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;alarm_outbox&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'processing'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;picked_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;picked&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;picked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sonuç
&lt;/h2&gt;

&lt;p&gt;NOTIFY, iyi bir tetikleme sinyali, uygulama worker'ı ise daha iyi bir işleme motoru.&lt;/p&gt;

</description>
      <category>database</category>
      <category>dataengineering</category>
      <category>monitoring</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Elasticsearch vs RDBMS, Logstash vs Fluentd, Elasticsearch vs Opensearch</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 09 Mar 2026 11:29:07 +0000</pubDate>
      <link>https://dev.to/tarikanafarta/elasticsearch-vs-rdbms-logstash-vs-fluentd-elasticsearch-vs-opensearch-2263</link>
      <guid>https://dev.to/tarikanafarta/elasticsearch-vs-rdbms-logstash-vs-fluentd-elasticsearch-vs-opensearch-2263</guid>
      <description>&lt;h2&gt;
  
  
  Elasticsearch vs RDBMS
&lt;/h2&gt;

&lt;p&gt;Elasticsearch, büyük veri içinde özellikle metin tabanlı arama (full-text search) yapmak için kullanılan, açık kaynaklı ve dağıtık mimariye sahip bir arama motorudur. Temel amacı çok büyük veri kümeleri içinde belirli kelime veya bilgileri çok hızlı bulabilmektir. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2exxft8nzhgtxnuvcr7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2exxft8nzhgtxnuvcr7p.png" alt="full-text search" width="720" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Klasik bir veritabanında veri doğrudan satırlar üzerinden taranırken, Elasticsearch verileri index üzerinden arar. Bu yaklaşım sayesinde milyarlarca kayıt içinde arama işlemleri neredeyse gerçek zamanlı olarak gerçekleştirilebilir.&lt;/p&gt;

&lt;p&gt;Elasticsearch indeksleri, ilişkisel veritabanlarında (RDBMS) bulunan indekslerle aynı değildir. Bir Elasticsearch cluster'ı, içinde birçok indeks barındırabilen bir veritabanı gibidir. Her indeks bir tabloya benzer, ve her indeksin içinde birçok doküman bulunur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RDBMS =&amp;gt; Databases =&amp;gt; Tables =&amp;gt; Columns/Rows
Elasticsearch =&amp;gt; Clusters =&amp;gt; Indices =&amp;gt; Shards =&amp;gt; Documents (key-value pairs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elasticsearch temelde Apache Lucene üzerine kuruludur. Lucene, metinlerin indekslenmesi ve aranması için kullanılan güçlü bir kütüphanedir. Elasticsearch ise bu altyapıyı kullanarak hem structured hem de unstructured verilerin indekslenmesini ve aranmasını sağlar. Elasticsearch'te tutulan her veri JSON formatında bir document olarak saklanır hepsinin bir id'si vardır.&lt;/p&gt;

&lt;p&gt;Bir veri Elasticsearch'e eklendiğinde sistem bu veriyi doğrudan taranabilir halde saklamaz. Bunun yerine belirlenen alanlar indekslenir. İndeksleme sırasında her kelimenin hangi dokümanlarda geçtiği bir liste halinde tutulur. Buna inverted index mantığı denir. Bu yapı Elasticsearch’ün çok hızlı çalışmasının temel sebebidir.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lx87kkdiw5efpqyvaxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lx87kkdiw5efpqyvaxc.png" alt="Inverted index" width="638" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Elasticsearch'ün dağıtık çalışmasını sağlayan önemli yapılardan biri shard ve replica kavramlarıdır. Büyük veri kümelerini tek bir sunucuda tutmak zor olabileceği için bir index birden fazla shard'a bölünür. Shard'lar farklı node'lara dağıtılarak paralel işlem yapılması sağlanır. Böylece hem performans artar hem de sistem yatay olarak ölçeklenebilir.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslqb56miwr4j3ze7jglv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslqb56miwr4j3ze7jglv.png" alt="Scaling" width="714" height="456"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Logstash vs Fluentd
&lt;/h2&gt;

&lt;p&gt;Logstash'ın çalışabilmesi için JVM gereklidir. Bu bağımlılık yüksek bellek tüketiminin temel nedeni haline gelmiştir. Logstash daha ağırdır ama güçlü transform yapar, Fluentd ise daha hafif ve yüksek ölçekli log toplama için tercih edilir.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfxx5qxprwliz3rlxgfn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfxx5qxprwliz3rlxgfn.png" alt="ban-java" width="594" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ayrıca Fluent Bit yalnızca log değil, aynı zamanda metrik verilerini de toplayabilir.&lt;/p&gt;

&lt;p&gt;Fluentd + Elasticsearch genelde DaemonSet olarak çalışan bir log collector modeliyle kullanılır; logları pod dosyalarından okuyup Kubernetes metadata ekleyerek direkt Elasticsearch'e yollar. Logstash + Elasticsearch tarafında ise Logstash da Kubernetes'te çalıştırılabilir ama JVM tabanlı olduğu için kaynak ayarı ve operasyonu daha ağırdır.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Sonuçları
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fierm9xyfijclm5w49fpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fierm9xyfijclm5w49fpf.png" alt="workflow" width="720" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Düşük hacimli veri akışlarında Logstash ve Fluentd sistem üzerinde benzer bir yük oluşturur. Ancak bu durum 16 thread / iş yükü düğümü (workload nodes) noktasına gelindiğinde değişir ve Logstash'ın aynı miktarda olayı işleyebilmek için daha fazla CPU kullandığı açıkça görülür. Ortalama olarak CPU kullanımı %25 daha yüksektir.&lt;/p&gt;

&lt;p&gt;Performans elbette kullanılan senaryoya bağlı olsa da Logstash'ın Fluentd'ye kıyasla daha fazla bellek tükettiği bilinmektedir. Fluentd verimli bir log toplayıcıdır ve ölçeklenebilirliği oldukça iyidir. &lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Desteği
&lt;/h3&gt;

&lt;p&gt;Docker'ın Fluentd için yerleşik bir logging driver'ı vardır, ancak Logstash için böyle bir driver bulunmaz. Fluentd kullanıldığında container üzerinde logları Fluentd'ye göndermek için ekstra bir agent çalıştırmaya gerek yoktur. Loglar ek bir log dosyasına ihtiyaç duymadan doğrudan STDOUT üzerinden Fluentd servisine gönderilir. Logstash'ta ise uygulama loglarının okunabilmesi ve Logstash'a gönderilebilmesi için bir plugin'e (filebeat) ihtiyaç vardır.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sonuç
&lt;/h3&gt;

&lt;p&gt;Kubernetes ortamlarında da Fluentd, yerleşik Docker logging driver'ı ve parser'ı sayesinde ideal bir aday gibi görünmektedir. Bu yapı sayesinde container üzerinde logları Fluentd'ye göndermek için ek bir agent çalıştırmaya gerek kalmaz. Logstash ile karşılaştırıldığında bu durum mimarinin daha az karmaşık olmasını sağlar ve loglama hataları oluşma riskini de azaltır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Elasticsearch vs OpenSearch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Benchmark Sonuçları
&lt;/h3&gt;

&lt;p&gt;"Çeşitli testlerin sonuçları dikkate alındığında, Elasticsearch'ün OpenSearch'e kıyasla sürekli olarak daha iyi performans gösterdiği açıkça görülmektedir. Basit sorguların çalıştırılması, verilerin sıralanması, histogram oluşturulması, terim veya aralık sorgularının işlenmesi ya da kaynakların daha verimli kullanılması gibi durumların hepsinde Elasticsearch öne çıkmaktadır." (Kobar &amp;amp; Sangiorgi, 2023)&lt;/p&gt;

&lt;p&gt;Elasticsearch'ün arama özellikleri Opensearch'e göre daha gelişmiştir ve AI/ML alanında da geliştirmeler yapmaya başlamıştır.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>distributedsystems</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Kubernetes Objeleri</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Fri, 06 Mar 2026 11:05:58 +0000</pubDate>
      <link>https://dev.to/tarikanafarta/kubernetes-objeleri-37ei</link>
      <guid>https://dev.to/tarikanafarta/kubernetes-objeleri-37ei</guid>
      <description>&lt;h2&gt;
  
  
  Pod
&lt;/h2&gt;

&lt;p&gt;Çalışan en küçük birim. İçerisinde bir veya birden fazla konteyner bulunur. Buradaki konteynerlar kendi içerisinde localhost üzerinden iletişim kurabilirler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;İstenen replica sayısını izler, eksikse yeni pod oluşturur fazlaysa siler.&lt;/p&gt;

&lt;h2&gt;
  
  
  ReplicaSet
&lt;/h2&gt;

&lt;p&gt;İstenilen pod sayısının çalışır olmasını garantileyen objedir. Deployment tarafından arka planda yönetilir.&lt;/p&gt;

&lt;h2&gt;
  
  
  StatefulSet
&lt;/h2&gt;

&lt;p&gt;Podlara pod-postgres-56945869d5-2kgp8, pod-postgres-75ff6f9cd-4w5xd gibi kimlik verir ve pod yeniden başlasa bile bu değişmez. Her birinin kendi kalıcı volume'ü vardır.&lt;/p&gt;

&lt;h2&gt;
  
  
  DaemonSet
&lt;/h2&gt;

&lt;p&gt;Cluster'daki her Node üzerinde tam olarak bir pod çalıştırılmasını sağlar. Cluster'a yeni bir node eklendiğinde ilgili pod otomatik olarak o node'a da yerleştirilir. Prometheus Node Exporter gibi altyapı servislerinde de kullanılır.&lt;/p&gt;

&lt;h2&gt;
  
  
  Job
&lt;/h2&gt;

&lt;p&gt;Tek seferlik ve sonlanması beklenen görevleri çalıştırmak için kullanılır. Pod görevini tamamlarsa durur, hata alırsa başarılı olana kadar yeniden çalıştırılır.&lt;/p&gt;

&lt;h2&gt;
  
  
  CronJob
&lt;/h2&gt;

&lt;p&gt;İstenilen zamanda job objesi ve buna bağlı pod oluşturulur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service
&lt;/h2&gt;

&lt;p&gt;Podların ip ve dns üzerinden erişilebilir olmasını sağlar. NodePort, ClusterIP ve LoadBalancer gibi tipleri vardır.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingress
&lt;/h2&gt;

&lt;p&gt;Cluster dışından gelen HTTP ve HTTPS trafiğini service'lere yönlendirir.&lt;/p&gt;

&lt;h2&gt;
  
  
  NetworkPolicy
&lt;/h2&gt;

&lt;p&gt;Podlar arasındaki ağ trafiğini kontrol eder.&lt;/p&gt;

&lt;h2&gt;
  
  
  PV
&lt;/h2&gt;

&lt;p&gt;Kalıcı depolama kaynağıdır. Podlar silinse bile veriler durur.&lt;/p&gt;

&lt;h2&gt;
  
  
  PVC
&lt;/h2&gt;

&lt;p&gt;Pod'ların ihtiyacı olan depolamayı talep etmek için kullandığı objedir.&lt;/p&gt;

&lt;h2&gt;
  
  
  StorageClass
&lt;/h2&gt;

&lt;p&gt;PVC oluşturulduğunda otomatik olarak PV sağlar.&lt;/p&gt;

&lt;h2&gt;
  
  
  ConfigMap
&lt;/h2&gt;

&lt;p&gt;Konfigürasyon verilerini saklar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret
&lt;/h2&gt;

&lt;p&gt;Şifre ve token gibi hassas verileri encoded şekilde saklar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Namespace
&lt;/h2&gt;

&lt;p&gt;Farklı ortamlar için alan oluşturur. Namespace üzerinde RBAC uygulanabilir.&lt;/p&gt;

&lt;h2&gt;
  
  
  ServiceAccount
&lt;/h2&gt;

&lt;p&gt;Pod'ların Kubernetes API ile kimlik doğrulaması yapmasını sağlayan hesaptır.&lt;/p&gt;

&lt;h2&gt;
  
  
  Role/ClusterRole
&lt;/h2&gt;

&lt;p&gt;Kaynaklar üzerinde neler yapılabileceğini tanımlayan yetki kurallarıdır. Role yalnızca tanımlandığı namespace içinde geçerliyken cluster role tüm cluster genelinde geçerlidir.&lt;/p&gt;

&lt;h2&gt;
  
  
  RoleBinding/ClusterRoleBinding
&lt;/h2&gt;

&lt;p&gt;Tanımlanmış bir role'ü kullanıcı, grup veya service account'a bağlayan objedir. Kimin neye erişebileceği açıkça tanımlar. &lt;/p&gt;

&lt;h2&gt;
  
  
  ResourceQuota
&lt;/h2&gt;

&lt;p&gt;Bir namespace içinde kullanılabilecek kaynakların sınırını belirler.&lt;/p&gt;

&lt;h2&gt;
  
  
  LimitRange
&lt;/h2&gt;

&lt;p&gt;Namespace içindeki podlar için varsayılan ve maksimum kaynak değerlerini otomatik olarak uygular.&lt;/p&gt;

&lt;h2&gt;
  
  
  InitContainer
&lt;/h2&gt;

&lt;p&gt;Ana konteyner başlamadan önce çalışan, hazırlık görevi yapan konteynerdır.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Kubernetes - Keycloak OIDC Entegrasyonu</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Tue, 03 Mar 2026 13:11:44 +0000</pubDate>
      <link>https://dev.to/tarikanafarta/kubernetes-keycloak-oidc-entegrasyonu-829</link>
      <guid>https://dev.to/tarikanafarta/kubernetes-keycloak-oidc-entegrasyonu-829</guid>
      <description>&lt;p&gt;&lt;strong&gt;Amaç:&lt;/strong&gt; kube-apiserver'ı Keycloak'u OIDC Identity Provider olarak kullanacak şekilde yapılandırmak ve kubelogin tabanlı kubeconfig ile kubectl erişimini sağlamak.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ortam / Topoloji
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bileşen&lt;/th&gt;
&lt;th&gt;Değer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes VM (control-plane)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ip_adresi&amp;gt;&lt;/code&gt; (RKE2, single-node, static pod)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keycloak&lt;/td&gt;
&lt;td&gt;Helm ile &lt;code&gt;keycloak&lt;/code&gt; namespace'inde&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rancher&lt;/td&gt;
&lt;td&gt;Helm ile &lt;code&gt;cattle-system&lt;/code&gt; namespace'inde&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingress Controller&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kube-system&lt;/code&gt; içindeki &lt;code&gt;rke2-ingress-nginx&lt;/code&gt; (hostPort 80/443)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keycloak host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;keycloak.example.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rancher host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rancher.example.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;ip_adresi&amp;gt;:6443&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;keycloak.example.com&lt;/code&gt; ve &lt;code&gt;rancher.example.com&lt;/code&gt; gerçek DNS'te yoksa istemci makinede &lt;code&gt;/etc/hosts&lt;/code&gt;'a &lt;code&gt;&amp;lt;ip_adresi&amp;gt; keycloak.example.com&lt;/code&gt; ve &lt;code&gt;&amp;lt;ip_adresi&amp;gt; rancher.example.com&lt;/code&gt; olarak eklenmelidir.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Keycloak TLS Sertifikası
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Adım 1: CA oluştur&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;kc-ca.key ve kc-ca.crt üretildi. &lt;code&gt;basicConstraints: CA:TRUE&lt;/code&gt; ve &lt;code&gt;keyUsage: keyCertSign&lt;/code&gt; ile CA olarak işaretlendi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 2: SAN config&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;san.cnf oluşturuldu, &lt;code&gt;subjectAltName&lt;/code&gt; olarak &lt;code&gt;DNS.1 = keycloak.example.com&lt;/code&gt; eklendi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 3: Server sertifikası&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;keycloak.key ve keycloak.csr üretildi. CSR, kc-ca.crt ile imzalanarak keycloak.crt elde edildi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 4: Keycloak proxy/hostname ayarları&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RKE2 ingress hostPort 443 üzerinden yayın yaptığı için issuer'ın port içermemesi gerekiyor. Bu yüzden &lt;code&gt;KC_HOSTNAME&lt;/code&gt; şu şekilde set edildi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KC_PROXY_HEADERS=xforwarded&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_HOSTNAME=https://keycloak.example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;/.well-known/openid-configuration&lt;/code&gt; ile issuer URL doğrulandı.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Keycloak Realm / Client / User / Group
&lt;/h2&gt;

&lt;p&gt;Tüm işlemler Keycloak pod'u içinde kcadm.sh ile yapılır. Container FS read-only olabildiği için &lt;code&gt;HOME=/tmp&lt;/code&gt; set edilmeli.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Realm:&lt;/strong&gt; kubernetes adıyla oluşturuldu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client:&lt;/strong&gt; kubernetes client ID'siyle oluşturuldu. Browser'sız kubeconfig için &lt;code&gt;directAccessGrantsEnabled&lt;/code&gt; açık olmalı (password grant).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Groups claim:&lt;/strong&gt; Token içinde &lt;code&gt;groups&lt;/code&gt; claim'i gelmesi için &lt;code&gt;oidc-group-membership-mapper&lt;/code&gt; eklendi (claim name: &lt;code&gt;groups&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group, User, Membership:&lt;/strong&gt; k8s-admins grubu oluşturuldu. Kullanıcı oluşturuldu, şifresi set edildi (Temporary: Off) ve gruba eklendi.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. kube-apiserver OIDC Yapılandırması
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Yeni Kubernetes sürümlerinde &lt;code&gt;--oidc-ca-file&lt;/code&gt; yerine AuthenticationConfiguration daha stabil bir yol. Biz de AuthenticationConfiguration kullandık.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Adım 1: Auth config dosyası&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/kubernetes/oidc-auth-config.yaml&lt;/code&gt; oluşturuldu. &lt;code&gt;apiVersion: apiserver.config.k8s.io/v1beta1&lt;/code&gt;, &lt;code&gt;kind: AuthenticationConfiguration&lt;/code&gt; formatında; issuer URL, audiences, CA sertifikası ve claim mapping'leri (&lt;code&gt;preferred_username&lt;/code&gt;, &lt;code&gt;groups&lt;/code&gt;) içeriyor.&lt;/p&gt;

&lt;p&gt;Önemli nokta: issuer URL port içermemeli ve Keycloak'ın &lt;code&gt;.well-known&lt;/code&gt; içindeki issuer ile birebir aynı olmalı.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 2: RKE2'de apiserver'a bağlama&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RKE2 kube-apiserver manifest'ini direkt editlemek yerine &lt;code&gt;/etc/rancher/rke2/config.yaml&lt;/code&gt; kullanılır. Biz şu argümanı ekledik:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;authentication-config=/etc/kubernetes/oidc-auth-config.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RKE2 bunu static pod manifest'ine yansıtır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 3: Doğrulama&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;kube-apiserver args içinde &lt;code&gt;--authentication-config=/etc/kubernetes/oidc-auth-config.yaml&lt;/code&gt; göründüğünde ve loglarda OIDC/TLS hatası kalmadığında entegrasyon başarılı.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. İstemci Tarafı: kubelogin + kubeconfig
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;kubelogin kurulumu:&lt;/strong&gt; GitHub releases'ten Linux amd64 zip indirildi, &lt;code&gt;/usr/local/bin/kubelogin&lt;/code&gt; olarak kuruldu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CA kopyalama:&lt;/strong&gt; kc-ca.crt (Keycloak CA) istemci makinede &lt;code&gt;~/.kube/keycloak-ca.crt&lt;/code&gt; olarak tutulur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;kubeconfig:&lt;/strong&gt; Her kullanıcı için &lt;code&gt;~/.kube/&amp;lt;kullaniciadi&amp;gt;-kubeconfig&lt;/code&gt; oluşturulur. exec plugin olarak &lt;code&gt;kubelogin get-token&lt;/code&gt; tanımlanır.&lt;/p&gt;

&lt;p&gt;Kubernetes cluster CA'sı için istemciye &lt;code&gt;server-ca.crt&lt;/code&gt; kopyalanabilir ve kubeconfig'te &lt;code&gt;certificate-authority:&lt;/code&gt; ile kullanılabilir (base64 gömmek şart değil).&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Kullanıcı Bazlı kubeconfig Dağıtımı
&lt;/h2&gt;

&lt;p&gt;Her kullanıcı için Keycloak'ta hesap açılır, o kullanıcının bilgilerini içeren bir kubeconfig üretilir ve dosya kullanıcıya verilir. Kullanıcı sadece bu dosyayla kubectl kullanır, tarayıcı açılmaz, başka bir şey bilmesine de gerek yoktur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kullanıcı oluşturma:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keycloak UI'dan kubernetes realm'ı altında Users -&amp;gt; Add user ile oluşturulur. Credentials sekmesinden şifre set edilir (Temporary: Off). Required Actions boş olmalıdır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;kubeconfig üretme:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.kube/&amp;lt;kullaniciadi&amp;gt;-kubeconfig&lt;/code&gt; dosyası oluşturulur. exec plugin olarak &lt;code&gt;kubelogin get-token&lt;/code&gt; tanımlandı; &lt;code&gt;--grant-type=password&lt;/code&gt;, &lt;code&gt;--username&lt;/code&gt;, &lt;code&gt;--password&lt;/code&gt;, &lt;code&gt;--oidc-client-secret&lt;/code&gt; ve &lt;code&gt;--certificate-authority&lt;/code&gt; argümanları verilir. &lt;code&gt;interactiveMode: Never&lt;/code&gt; ile tarayıcı açılması engellenir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RBAC:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Her kullanıcı Kubernetes loglarında kendi adıyla görünür. Gruba göre ClusterRoleBinding veya namespace bazlı RoleBinding ile yetkilendirilir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;KUBECONFIG=~/.kube/&amp;lt;kullaniciadi&amp;gt;-kubeconfig kubectl auth whoami&lt;/code&gt; ile kullanıcının doğru kimlikle bağlandığı doğrulanır.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Kubernetes'te TimescaleDB Retention Testleri</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 02 Mar 2026 12:43:34 +0000</pubDate>
      <link>https://dev.to/tarikanafarta/kuberneteste-timescaledb-retention-testleri-3i8m</link>
      <guid>https://dev.to/tarikanafarta/kuberneteste-timescaledb-retention-testleri-3i8m</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/tarikanafarta/kubernetes-uzerinde-kucuk-veri-iceren-veritabani-icin-postgresql-ve-timescaledb-storage-kiyaslamasi-566p"&gt;Önceki yazımızda&lt;/a&gt; Kubernetes üzerinde iki ayrı kurulum (vanilla PostgreSQL ve TimescaleDB) yapıp az veriyle tablo + index boyutlarını kıyaslamıştık. Bu yazıda aynı environment üzerinde TimescaleDB'nin data retention (eski veriyi silme) yaklaşımını pratikte test ediyoruz.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ortam
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Namespace: database&lt;/li&gt;
&lt;li&gt;TimescaleDB pod: timescaledb-single chart (master pod)&lt;/li&gt;
&lt;li&gt;StorageClass: local-path (tek node)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Komutlarda iki değişken kullanacağız: NS ve TSPOD.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NS=database
TSPOD="$(kubectl get pod -n $NS -l release=timescaledb,role=master -o jsonpath='{.items[0].metadata.name}')"
echo "TSPOD=$TSPOD"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu blok sonraki komutlarda kullanacağımız değişkenleri hazırlar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deney 1 - Manuel chunk droplama (drop_chunks)
&lt;/h2&gt;

&lt;p&gt;Bu deneyde 48 saatlik veri bastık (5 dakikada bir), sonra 24 saatten eski chunk'ları manuel olarak dropladık.&lt;/p&gt;

&lt;h3&gt;
  
  
  1A) Kurulum ve veri basma
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE SCHEMA IF NOT EXISTS lab;

DROP TABLE IF EXISTS lab.sensor_ret_manual;
CREATE TABLE lab.sensor_ret_manual (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature DOUBLE PRECISION
);

SELECT create_hypertable(
  'lab.sensor_ret_manual',
  'time',
  chunk_time_interval =&amp;gt; INTERVAL '1 hour',
  create_default_indexes =&amp;gt; FALSE
);

CREATE INDEX sensor_ret_manual_time_idx ON lab.sensor_ret_manual(time);

INSERT INTO lab.sensor_ret_manual
SELECT
  generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'),
  (random() * 10)::int,
  random() * 100;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada lab schema'sını ve 1 saatlik chunk'larla hypertable'ı oluşturup sonra 48 saatlik örnek veri basıyoruz.&lt;/p&gt;

&lt;h3&gt;
  
  
  1B) Silmeden önce ölçüm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT count(*) AS rows_before
FROM lab.sensor_ret_manual;

SELECT show_chunks('lab.sensor_ret_manual') AS chunk_before;

SELECT
  pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_before
FROM show_chunks('lab.sensor_ret_manual') AS chunk;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada ise silmeden önce satır sayısı, hangi chunk'ların oluştuğu ve chunk'ların toplam disk boyutunu görüyoruz.&lt;/p&gt;

&lt;p&gt;Benim makinemdeki çıktılar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows_before: 577&lt;/li&gt;
&lt;li&gt;chunk_before: 49 chunk&lt;/li&gt;
&lt;li&gt;total_before: 1176 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1C) 24 saatten eski chunk'ları dropla (manuel)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT drop_chunks('lab.sensor_ret_manual', older_than =&amp;gt; INTERVAL '24 hours');
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İşte şimdi 'now() - 24 hours' çizgisinin tamamen solunda kalan chunk tablolarını topluca DROP ettik. Satır satır silme yok. Chunk bazında hızlıca droplar.&lt;/p&gt;

&lt;h3&gt;
  
  
  1D) Silmeden sonra ölçüm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT count(*) AS rows_after
FROM lab.sensor_ret_manual;

SELECT show_chunks('lab.sensor_ret_manual') AS chunk_after;

SELECT
  pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after
FROM show_chunks('lab.sensor_ret_manual') AS chunk;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada drop_chunks sonrasında satır/chunk/boyutun gerçekten azaldığını doğruladık.&lt;/p&gt;

&lt;p&gt;Benim makinemdeki çıktılar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows_after: 299&lt;/li&gt;
&lt;li&gt;chunk_after: 25 chunk&lt;/li&gt;
&lt;li&gt;total_after: 600 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Önemli not: 'older_than =&amp;gt; 24 hours' her zaman 'tam 24 saatin dışındaki tüm satırlar gider' demek değildir. Cutoff çizgisini kesen bir chunk içinde hem eski hem yeni veri varsa, o chunk komple droplanmaz. Bu nedenle az miktarda daha eski satır kalabilir.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Deney 2 - Otomatik veri saklama politikası (add_retention_policy + run_job)
&lt;/h2&gt;

&lt;p&gt;Bu deneyde ise TimescaleDB'nin dahili job mekanizması ile otomatik retention kurduk ve job'ı manuel tetikledik.&lt;/p&gt;

&lt;h3&gt;
  
  
  2A) Hypertable oluştur ve veri ekle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
DROP TABLE IF EXISTS lab.sensor_ret_policy;
CREATE TABLE lab.sensor_ret_policy (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature DOUBLE PRECISION
);

SELECT create_hypertable(
  'lab.sensor_ret_policy',
  'time',
  chunk_time_interval =&amp;gt; INTERVAL '1 hour',
  create_default_indexes =&amp;gt; FALSE
);

CREATE INDEX sensor_ret_policy_time_idx ON lab.sensor_ret_policy(time);

INSERT INTO lab.sensor_ret_policy
SELECT
  generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'),
  (random() * 10)::int,
  random() * 100;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu blok ile otomatik retention deneyi için ikinci bir hypertable oluşturduk ve aynı şekilde 48 saatlik örnek veriyi doldurduk.&lt;/p&gt;

&lt;h3&gt;
  
  
  2B) Retention policy ekle (24 saat tut)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT add_retention_policy('lab.sensor_ret_policy', INTERVAL '24 hours');
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada ise TimescaleDB içinde arka planda çalışacak bir retention job'u oluşturduk. Dönüş değeri job_id olur.&lt;/p&gt;

&lt;h3&gt;
  
  
  2C) Job'ı bul ve çalıştır
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT job_id, proc_name, schedule_interval, config
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention'
ORDER BY job_id DESC;

CALL run_job(&amp;lt;yukaridaki_job_id&amp;gt;);
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burası retention job'unun konfigürasyonunu gösterir ve beklemeden hemen çalıştırır. Normalde job schedule_interval'a göre otomatik çalışır.&lt;/p&gt;

&lt;h3&gt;
  
  
  2D) Sonucu doğrula
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT count(*) AS rows_after
FROM lab.sensor_ret_policy;

SELECT
  pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after
FROM show_chunks('lab.sensor_ret_policy') AS chunk;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu blok policy çalıştıktan sonra kalan satır sayısını ve chunk toplam boyutunu ölçer. Yani job'un gerçekten eski chunk'ları dropladığını kanıtlar.&lt;/p&gt;

&lt;p&gt;Benim makinemdeki çıktılar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows_after: 299&lt;/li&gt;
&lt;li&gt;total_after: 600 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bu, retention policy'nin temelde drop_chunks ile aynı chunk droplama mekanizmasını otomatik olarak çalıştırdığını gösteriyor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Temizlik
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT remove_retention_policy('lab.sensor_ret_policy');
DROP SCHEMA IF EXISTS lab CASCADE;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burası retention policy'yi kaldırır ve lab schema'sını CASCADE ile silerek tüm test tablolarını ve internal chunk tablolarını temizler.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL DELETE vs TimescaleDB retention
&lt;/h2&gt;

&lt;p&gt;TimescaleDB'nin sunduğu yerleşik, chunk tabanlı silme yöntemlerini kullanmak çoğu zaman en verimli yoldur. Yine de hangi yöntemin doğru olduğuna senaryo karar verir.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL DELETE (satır bazlı)
&lt;/h3&gt;

&lt;p&gt;Artıları:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;İnce ayar: Sadece belirli cihazın verisi, belirli aralık, GDPR gibi seçici silmeler için uygundur.&lt;/li&gt;
&lt;li&gt;Chunk sınırına bağlı kalmadan hedeflediğin satırları silersin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eksileri:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL MVCC nedeniyle satırlar diskten hemen silinmez; dead tuple olarak kalır.&lt;/li&gt;
&lt;li&gt;Büyük silmeler table bloat üretir; disk alanını gerçekten geri almak için VACUUM (bazen agresif vacuum) ve kimi zaman REINDEX gerekebilir.&lt;/li&gt;
&lt;li&gt;Çok miktarda veride satır satır silmek yavaş ve maliyetlidir (IO/CPU artışı).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  TimescaleDB retention (chunk bazlı)
&lt;/h3&gt;

&lt;p&gt;Artıları:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chunk'lar komple droplandığı için genellikle çok hızlıdır (DROP TABLE benzeri).&lt;/li&gt;
&lt;li&gt;Relation size çoğu senaryoda hemen düşer (örneğin benim makinemde 1176 kB -&amp;gt; 600 kB).&lt;/li&gt;
&lt;li&gt;add_retention_policy ile cron/script yazmadan, DB içindeki job mekanizmasıyla otomatik yönetim sağlanır.&lt;/li&gt;
&lt;li&gt;Zaman serisi kullanımında en doğal model: 'ham veriyi X süre tut, eskisini at'.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eksileri / dikkat edilmesi gerekenler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Granularity: Retention satır bazlı değil chunk bazlıdır. Cutoff'u kesen chunk kalabilir; bu yüzden az miktarda daha eski satır tutulabilir.&lt;/li&gt;
&lt;li&gt;Seçici silme için uygun değildir (ör. sadece device_id=3 verisini silmek). Bu tip işlerde DELETE gerekir.&lt;/li&gt;
&lt;li&gt;Lock ihtiyacı: Chunk drop, ilgili chunk'lar üzerinde lock alır; uzun transaction varsa drop gecikebilir veya timeout görebiliriz.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sonuç
&lt;/h3&gt;

&lt;p&gt;Hedefiniz 'X süreden eski zaman serisi ham verisini otomatik ve verimli şekilde kaldırmak' ise TimescaleDB'nin retention policy (add_retention_policy) veya manuel drop_chunks yaklaşımı genellikle en doğru ve yönetimi en kolay çözümdür. Buna rağmen seçici ve ince ayarlı silme ihtiyaçlarında SQL DELETE hala gerekli bir araçtır.&lt;/p&gt;

</description>
      <category>database</category>
      <category>kubernetes</category>
      <category>postgres</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes Üzerinde Az Veri İçeren Veritabanı İçin PostgreSQL ve TimescaleDB Storage Kıyaslaması</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 02 Mar 2026 10:09:22 +0000</pubDate>
      <link>https://dev.to/tarikanafarta/kubernetes-uzerinde-kucuk-veri-iceren-veritabani-icin-postgresql-ve-timescaledb-storage-kiyaslamasi-566p</link>
      <guid>https://dev.to/tarikanafarta/kubernetes-uzerinde-kucuk-veri-iceren-veritabani-icin-postgresql-ve-timescaledb-storage-kiyaslamasi-566p</guid>
      <description>&lt;h2&gt;
  
  
  Amaç
&lt;/h2&gt;

&lt;p&gt;Kubernetes üzerinde &lt;strong&gt;iki ayrı kurulum&lt;/strong&gt; yapıp (vanilla PostgreSQL + TimescaleDB) küçük bir veri setiyle storage kullanımını kıyaslamak.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not: TimescaleDB zaten PostgreSQL üzerinde çalışır. Burada "TimescaleDB kurulumu" dediğimiz şey, içinde PostgreSQL + Timescale extension bulunan ayrı bir kurulumdur. "vanilla PostgreSQL" de kuruyoruz ki sonuçlar daha net olsun.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Ön koşullar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Çalışan bir Kubernetes cluster'ı ve &lt;code&gt;kubectl&lt;/code&gt; erişimi&lt;/li&gt;
&lt;li&gt;Helm&lt;/li&gt;
&lt;li&gt;Kurulum tek node'lu bir cluster'da yapılmıştır ve StorageClass local-path'tir.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Aşama: Kurulum
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Namespace
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create namespace database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.2 Helm repo'ları
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add timescaledb 'https://charts.timescale.com'
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Aşama: Vanilla PostgreSQL (bitnami) kurulumu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;postgres-values.yaml&lt;/code&gt; oluşturalım:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; postgres-values.yaml &amp;lt;&amp;lt; 'EOF'
auth:
  postgresPassword: "postgres123"

primary:
  persistence:
    storageClass: "local-path"
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kurulum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install postgres bitnami/postgresql   -n database   -f postgres-values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod'un ayağa kalkmasını bekleyelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pods -n database -w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Aşama: TimescaleDB kurulumu (Timescale chart)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;timescaledb-values.yaml&lt;/code&gt; oluşturalım:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; timescaledb-values.yaml &amp;lt;&amp;lt; 'EOF'
replicaCount: 1

image:
  repository: timescale/timescaledb-ha
  tag: pg14.6-ts2.9.1-p1

secrets:
  credentials:
    PATRONI_SUPERUSER_PASSWORD: "postgres123"
    PATRONI_REPLICATION_PASSWORD: "replication123"
    PATRONI_admin_PASSWORD: "admin123"

backup:
  enabled: false
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kurulum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install timescaledb timescaledb/timescaledb-single   -n database   -f timescaledb-values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod'un ayağa kalkmasını bekleyelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pods -n database -w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Aşama: Bağlantı (2 ayrı terminal açabilirsiniz)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 PostgreSQL’e bağlanma (Şifre: postgres123)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PGPOD="$(kubectl get pod -n database -l app.kubernetes.io/instance=postgres,app.kubernetes.io/name=postgresql -o name | head -n 1)"
kubectl exec -it -n database ${PGPOD} -- psql -U postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 TimescaleDB’ye bağlanma (Şifre: postgres123)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MASTERPOD="$(kubectl get pod -o name --namespace database -l release=timescaledb,role=master)"
kubectl exec -it --namespace database ${MASTERPOD} -- psql -U postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: İki ayrı pod'a bağlanıyoruz. Aynı işlemleri iki farklı DB kurulumunda da çalıştırıyoruz.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Aşama: Küçük veri setiyle test
&lt;/h2&gt;

&lt;p&gt;Aşağıdaki SQL'i &lt;strong&gt;iki tarafta da&lt;/strong&gt; uygulayacağız:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL'de &lt;code&gt;sensor_pg&lt;/code&gt; normal tablo olacak&lt;/li&gt;
&lt;li&gt;TimescaleDB'de &lt;code&gt;sensor_ts&lt;/code&gt; hypertable olacak&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Veri az olsun diye 30 gün yerine &lt;strong&gt;1 gün&lt;/strong&gt; üretiyoruz.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5.1 PostgreSQL:
&lt;/h3&gt;

&lt;p&gt;PostgreSQL pod'unda &lt;code&gt;psql&lt;/code&gt; içindeyken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE sensor_pg (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature FLOAT
);

CREATE INDEX sensor_pg_time_idx ON sensor_pg(time);

INSERT INTO sensor_pg
SELECT
  generate_series(NOW() - INTERVAL '1 day', NOW(), INTERVAL '1 minute'),
  (random() * 10)::int,
  random() * 100;

SELECT count(*) FROM sensor_pg;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5.2 TimescaleDB:
&lt;/h3&gt;

&lt;p&gt;TimescaleDB pod'unda &lt;code&gt;psql&lt;/code&gt; içindeyken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE EXTENSION IF NOT EXISTS timescaledb;

CREATE TABLE sensor_ts (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature FLOAT
);

SELECT create_hypertable('sensor_ts', 'time');

CREATE INDEX sensor_ts_time_idx ON sensor_ts(time);

INSERT INTO sensor_ts
SELECT
  generate_series(NOW() - INTERVAL '1 day', NOW(), INTERVAL '1 minute'),
  (random() * 10)::int,
  random() * 100;

SELECT count(*) FROM sensor_ts;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: TimescaleDB'de hypertable tek tablo gibi görünür ama arkada chunk'lara ayrılır. Az veri testinde bile en azından bir chunk oluşur.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. Aşama: Storage ölçümü
&lt;/h2&gt;

&lt;p&gt;Bu işi iki seviyede ölçmek daha somut sonuçlar sağlar:&lt;/p&gt;

&lt;p&gt;1) &lt;strong&gt;Relation size (SQL ile)&lt;/strong&gt;: tablo+index boyutları için&lt;br&gt;
2) &lt;strong&gt;Data directory (filesystem ile)&lt;/strong&gt;: gerçek disk kullanımı için&lt;/p&gt;


&lt;h3&gt;
  
  
  6.1 PostgreSQL: SQL ile tablo boyutu
&lt;/h3&gt;

&lt;p&gt;PostgreSQL pod'unda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  pg_size_pretty(pg_table_size('sensor_pg'))        AS table_only,
  pg_size_pretty(pg_indexes_size('sensor_pg'))      AS indexes,
  pg_size_pretty(pg_total_relation_size('sensor_pg')) AS total;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6.2 TimescaleDB: SQL ile hypertable boyutu (chunk bazında)
&lt;/h3&gt;

&lt;p&gt;TimescaleDB pod'unda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  pg_size_pretty(sum(pg_table_size(chunk.id::regclass))) AS table_only,
  pg_size_pretty(sum(pg_indexes_size(chunk.id::regclass))) AS indexes,
  pg_size_pretty(sum(pg_total_relation_size(chunk.id::regclass))) AS total
FROM show_chunks('sensor_ts') AS chunk(id);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Chunk bazında bakmamızın nedeni hypertable fiziksel olarak chunk tablolardan oluşur. Yani gerçek data+index boyutu chunk'ların toplamıdır.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Benim ortamımdaki çıktı
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres=# SELECT count(*) FROM sensor_pg;
 count 
-------
  1441
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TimescaleDB&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres=# SELECT count(*) FROM sensor_ts;
 count 
-------
  1441
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.1 SQL (relation) boyutları
&lt;/h3&gt;

&lt;p&gt;Tabloların ve indexlerin PostgreSQL içinde kapladığı alanlar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL (&lt;code&gt;sensor_pg&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; table_only | indexes | total  
------------+---------+--------
 112 kB     | 48 kB   | 160 kB
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TimescaleDB (&lt;code&gt;sensor_ts&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; table_only | indexes | total  
------------+---------+--------
 112 kB     | 72 kB   | 184 kB
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;table_only iki tarafta da aynı iken indexes kısmı Timescale tarafında daha fazla.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Neden Timescale küçük veride daha büyük çıkabiliyor?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TimescaleDB tarafında hypertable fiziksel olarak &lt;strong&gt;chunk tablolardan&lt;/strong&gt; oluşur. Her chunk kendi tablo/index yapısını taşır.&lt;/li&gt;
&lt;li&gt;Küçük veri setlerinde bu yapı "overhead" olarak daha görünür olur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sonuç olarak üzerinde çok veri olmayan bir tablo senaryosunda, Timescale hypertable'ın chunk/index overhead'i yüzünden PostgreSQL biraz daha küçük göründü.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>database</category>
    </item>
    <item>
      <title>Kubernetes'te StorageClass Nedir?</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Thu, 26 Feb 2026 11:49:45 +0000</pubDate>
      <link>https://dev.to/tarikanafarta/kuberneteste-storageclass-nedir-dha</link>
      <guid>https://dev.to/tarikanafarta/kuberneteste-storageclass-nedir-dha</guid>
      <description>&lt;h2&gt;
  
  
  PV, PVC ve Veri Gerçekten Nerede Saklanıyor?
&lt;/h2&gt;

&lt;p&gt;Bu yazıda şu konuları inceleyeceğim:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PV (Persistent Volume) nedir?&lt;/li&gt;
&lt;li&gt;PVC (Persistent Volume Claim) nedir?&lt;/li&gt;
&lt;li&gt;StorageClass ne işe yarar?&lt;/li&gt;
&lt;li&gt;Static ve Dynamic provisioning farkı nedir?&lt;/li&gt;
&lt;li&gt;Disk gerçekte nerede? (Local PV, NFS, Cloud Storage)&lt;/li&gt;
&lt;li&gt;Object storage (S3) nedir? MinIO ne işe yarar?&lt;/li&gt;
&lt;li&gt;Distributed storage örnekleri: Ceph ve Longhorn&lt;/li&gt;
&lt;li&gt;MariaDB / PostgreSQL gibi uygulamalarda disk nerede?&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Storage Akışının Mantığı
&lt;/h2&gt;

&lt;p&gt;Öncelikle Kubernetes'te storage zinciri şu şekildedir:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pod -&amp;gt; PVC -&amp;gt; StorageClass -&amp;gt; PV -&amp;gt; Physical Storage&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Persistent Volume (PV)
&lt;/h2&gt;

&lt;p&gt;PV, cluster içindeki gerçek disk kaynağını temsil eder.&lt;/p&gt;

&lt;p&gt;Bu kaynak şunlardan birisidir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node üzerindeki local disk&lt;/li&gt;
&lt;li&gt;NFS share&lt;/li&gt;
&lt;li&gt;Cloud block storage (AWS EBS, GCE PD, Azure Disk)&lt;/li&gt;
&lt;li&gt;Distributed storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yani aslında PV, storage'ın kendisidir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Persistent Volume Claim (PVC)
&lt;/h2&gt;

&lt;p&gt;PVC ise uygulamanın disk talebidir.&lt;/p&gt;

&lt;p&gt;Örneğin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10Gi storage&lt;/li&gt;
&lt;li&gt;Access mode: ReadWriteOnce&lt;/li&gt;
&lt;li&gt;storageClass: fast-storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PVC diski oluşturmaz, bir disk talep eder.&lt;/p&gt;




&lt;h2&gt;
  
  
  StorageClass Nedir?
&lt;/h2&gt;

&lt;p&gt;StorageClass, PVC oluşturulduğunda disk'in nasıl sağlanacağını belirler.&lt;/p&gt;

&lt;p&gt;Yani, disk local mi olacak? NFS mi olacak? Cloud block storage mı olacak? Otomatik mi üretilecek? Hangi performans sınıfı kullanılacak? Gibi sorulara cevap veren bir policy katmanıdır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Static ve Dynamic Provisioning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Static Provisioning
&lt;/h3&gt;

&lt;p&gt;Static provisioning modelinde PV manuel olarak oluşturulur. Yani önce storage kaynağı tanımlanır, ardından PVC bu PV'ye bağlanır.&lt;/p&gt;

&lt;p&gt;Bu yöntem daha fazla kontrol sağlar ancak her yeni disk ihtiyacında manuel işlem gerektirir. Genellikle test ortamlarında veya local/NFS kurulumlarında tercih edilir.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Provisioning
&lt;/h3&gt;

&lt;p&gt;Dynamic provisioning modelinde ise sadece PVC oluşturulur.&lt;br&gt;
Bağlı olduğu StorageClass, arka planda otomatik olarak uygun bir PV üretir ve PVC'ye bağlar.&lt;/p&gt;

&lt;p&gt;Bu süreç CSI (Container Storage Interface) driver'ları sayesinde çalışır.&lt;br&gt;
Production ortamlarında genellikle dynamic provisioning tercih edilir çünkü daha otomatik ve ölçeklenebilirdir.&lt;/p&gt;


&lt;h2&gt;
  
  
  Disk Gerçekte Nerede?
&lt;/h2&gt;

&lt;p&gt;Bu sorunun cevabı PV tanımındadır.&lt;/p&gt;
&lt;h3&gt;
  
  
  Local PV
&lt;/h3&gt;

&lt;p&gt;PV şu path'i gösteriyorsa: &lt;code&gt;/mnt/k8s/data&lt;/code&gt;, disk node üzerindedir. Node arızalanırsa veri kaybedilebilir. Pod başka node'a taşınırsa diske erişilemez.&lt;/p&gt;
&lt;h3&gt;
  
  
  NFS
&lt;/h3&gt;

&lt;p&gt;PV şu şekilde tanımlanmışsa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nfs:
  server: 192.168.1.10
  path: /srv/nfs/share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disk NFS server üzerindedir. Pod hangi node'da olursa olsun aynı veriye erişebilir.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Storage
&lt;/h3&gt;

&lt;p&gt;PV bir cloud volume'a bağlıysa, disk cloud provider tarafındadır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Object Storage (S3) ve MinIO
&lt;/h2&gt;

&lt;p&gt;Şimdiye kadar bahsettiğim storage türleri çoğunlukla disk mantığında (block/file) çalışıyordu. Ancak pratikte sıkça kullanılan bir model daha var: Object storage. S3/MinIO genellikle database'in primary storage'ı olarak kullanılmaz; daha çok backup, log ve dosya arşivi gibi senaryolarda tercih edilir.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 (Simple Storage Service) Nedir?
&lt;/h3&gt;

&lt;p&gt;S3 bir object storage yaklaşımıdır. Dosyalar filesystem gibi "klasör/dosya" mantığından ziyade "object" olarak saklanır. Genellikle şu senaryolarda kullanılır:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dosya yükleme (upload)&lt;/li&gt;
&lt;li&gt;Log arşivleme&lt;/li&gt;
&lt;li&gt;Backup (özellikle database backup)&lt;/li&gt;
&lt;li&gt;Media saklama (image/video)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;S3 çoğunlukla PV/PVC gibi mount edilerek kullanılmaz. Uygulama S3 API üzerinden erişir.&lt;/p&gt;

&lt;h3&gt;
  
  
  MinIO Nedir?
&lt;/h3&gt;

&lt;p&gt;MinIO, S3 uyumlu bir object storage çözümüdür. Özellikle cloud'dan bağımsız (on-premise) ortamlarda veya Kubernetes içinde S3 benzeri bir servis ihtiyacında kullanılır.&lt;/p&gt;

&lt;p&gt;MinIO genellikle PV üzerinde çalışır. Yani MinIO'nun kendi datası için altında yine PV/PVC bulunabilir. Uygulamalar ise MinIO’ya S3 API ile erişir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Distributed Storage: Ceph ve Longhorn
&lt;/h2&gt;

&lt;p&gt;Distributed storage, veriyi tek bir node'a bağımlı bırakmak yerine birden fazla node'a yayarak (replication) daha dayanıklı bir yapı sunmayı hedefler. Ceph/Longhorn gibi çözümler Kubernetes'e genellikle CSI driver ile entegre olur ve StorageClass üzerinden dynamic provisioning sağlar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ceph
&lt;/h3&gt;

&lt;p&gt;Ceph yaygın bir distributed storage sistemidir. Kubernetes ortamında çoğunlukla Rook-Ceph gibi operatörlerle yönetilir. Ceph ile farklı storage tipleri sağlanabilir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block storage&lt;/li&gt;
&lt;li&gt;File storage&lt;/li&gt;
&lt;li&gt;Object storage (S3-compatible)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ceph'in temel avantajı, verinin birden fazla node'a replike edilebilmesi ve node kayıplarında veri kaybı riskinin azalmasıdır.&lt;/p&gt;

&lt;h3&gt;
  
  
  Longhorn
&lt;/h3&gt;

&lt;p&gt;Longhorn, Kubernetes-native bir distributed block storage çözümüdür. Longhorn tarafında öne çıkan özellikler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Volume replication&lt;/li&gt;
&lt;li&gt;Snapshot / backup mekanizmaları&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Longhorn genellikle küçük veya orta ölçekli cluster'larda veya daha hızlı kurulup yönetilmesi gereken ortamlarda tercih edilir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access Modes
&lt;/h2&gt;

&lt;p&gt;RWO (ReadWriteOnce) -&amp;gt; Sadece tek node yazabilir&lt;br&gt;
RWX (ReadWriteMany) -&amp;gt; Birden fazla node yazabilir&lt;br&gt;
ROX (ReadOnlyMany)  -&amp;gt; Birden fazla node sadece okuyabilir&lt;/p&gt;


&lt;h2&gt;
  
  
  Reclaim Policy
&lt;/h2&gt;

&lt;p&gt;PVC silindiğinde disk'in nasıl davranacağını reclaim policy belirler. Kubernetes'te 3 farklı reclaim policy vardır (1 tanesi eski). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retain -&amp;gt; PVC silinse bile PV ve içindeki veri korunur.&lt;/li&gt;
&lt;li&gt;Delete -&amp;gt; PVC silindiğinde PV ve arkasındaki storage kaynağı silinir.&lt;/li&gt;
&lt;li&gt;Recycle (Deprecated) -&amp;gt; PV içindeki veriyi basitçe temizleyip tekrar kullanılabilir hale getirirdi.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Örneğin: MariaDB ve PostgreSQL Verisi Nerede?
&lt;/h2&gt;

&lt;p&gt;Bir database container'ı çalıştırdığınızda veri her zaman belirli bir data directory içine yazılır. Önemli olan, bu klasör container içinde mi kalıyor, yoksa bir PVC üzerinden gerçek diske mi bağlanıyor?&lt;/p&gt;
&lt;h3&gt;
  
  
  MariaDB
&lt;/h3&gt;

&lt;p&gt;MariaDB'nin varsayılan veri dizini: &lt;code&gt;/var/lib/mysql&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Eğer persistence yapılandırılmadıysa bu dizin container filesystem'i üzerindedir. Pod silindiğinde veri kaybolur.&lt;/p&gt;

&lt;p&gt;Eğer persistence enabled ise bu path bir volumeMount ile bir PVC'ye bağlanır.&lt;/p&gt;

&lt;p&gt;PVC -&amp;gt; PV -&amp;gt; Physical storage&lt;/p&gt;

&lt;p&gt;Pod silinse bile veri korunur.&lt;/p&gt;

&lt;p&gt;Pod içinden mount durumunu görmek için:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl describe pvc -n &amp;lt;namespace&amp;gt; &amp;lt;pvc-ismi&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount edilen path'leri burada görebilirsiniz.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL
&lt;/h3&gt;

&lt;p&gt;PostgreSQL’in varsayılan veri dizini: &lt;code&gt;/var/lib/postgresql/data&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Mantık MariaDB ile aynıdır. Persistence yoksa veri geçicidir. Pod restart edildiğinde veri kaybolabilir.&lt;/p&gt;

&lt;p&gt;Persistence varsa bu dizin bir PVC'ye mount edilir. Gerçek disk PV tarafındadır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gerçek Disk Yeri Nasıl Bulunur?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pvc -n &amp;lt;namespace&amp;gt; # PVC'yi bul
kubectl describe pvc &amp;lt;pvc-ismi&amp;gt; # Bağlı olduğu PV'yi bul
kubectl describe pv &amp;lt;pv-ismi&amp;gt; # PV detayını incele
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada şunu görürüz: Local path mi? NFS server mı? Cloud volume mu? Distributed storage mı?&lt;/p&gt;

&lt;p&gt;Kısaca, gerçek disk yeri PV tanımında yazar.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
