TimescaleDB ile PostgreSQL’de Zaman Serisi Verisi Yönetimi

Zaman serisi verisi, modern altyapıların en kritik veri tiplerinden biri haline geldi. Sunucu metrikleri, IoT sensör ölçümleri, finansal tick verileri, uygulama logları… Bunların hepsi zamana bağlı, sürekli akan ve hacmi hızla büyüyen veriler. Klasik ilişkisel veritabanları bu yükü taşımakta zorlanırken, özel zaman serisi veritabanları da PostgreSQL ekosisteminden kopuk çalışmak zorunda kalıyor. TimescaleDB tam da bu noktada devreye giriyor: PostgreSQL üzerine inşa edilmiş, zaman serisi için optimize edilmiş bir uzantı.

TimescaleDB Nedir ve Neden PostgreSQL?

TimescaleDB, Timescale şirketi tarafından geliştirilen açık kaynaklı bir PostgreSQL uzantısıdır. Sıfırdan yeni bir veritabanı yazmak yerine PostgreSQL’i temel alması, birçok pratik avantaj sağlıyor. Mevcut PostgreSQL bilginiz geçerliliğini koruyor, standart SQL sorguları çalışıyor, pg_dump/pg_restore gibi araçlar kullanılabiliyor ve PostgreSQL ekosisteminin tüm araçlarıyla uyumlu çalışıyor.

Peki neden sadece PostgreSQL yetmiyor? Çünkü zaman serisi verisi bazı özel gereksinimlere sahip:

  • Veriler genellikle zaman damgasına göre sıralı olarak ekleniyor ve eski veriler nadiren güncelleniyor
  • Sorguların büyük çoğunluğu belirli zaman aralıklarını kapsıyor
  • Veri hacmi çok hızlı büyüyor, eski veriler sıkıştırılabilir veya silinebilir
  • Aggregation sorguları (ortalama, max, min, sum) çok sık kullanılıyor

TimescaleDB bu ihtiyaçlara yanıt vermek için hypertable adı verilen özel bir yapı kullanıyor. Hypertable, arka planda otomatik olarak yönetilen “chunk” adı verilen alt tablolara bölünmüş bir tablo yapısı. Bu sayede zaman bazlı sorgular sadece ilgili chunk’ları tarıyor, tüm veriyi dolaşmak zorunda kalmıyor.

Kurulum

Ubuntu/Debian Üzerinde Kurulum

# PostgreSQL ve TimescaleDB için gerekli repository'yi ekle
sudo apt install -y gnupg postgresql-common apt-transport-https lsb-release wget

# PostgreSQL resmi repository'sini ekle
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh

# TimescaleDB repository'sini ekle
echo "deb https://packagecloud.io/timescale/timescaledb/ubuntu/ $(lsb_release -c -s) main" | 
  sudo tee /etc/apt/sources.list.d/timescaledb.list

wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo apt-key add -

sudo apt update
sudo apt install -y timescaledb-2-postgresql-16

# TimescaleDB konfigürasyonunu otomatik optimize et
sudo timescaledb-tune --quiet --yes

# PostgreSQL'i yeniden başlat
sudo systemctl restart postgresql

RHEL/Rocky Linux Üzerinde Kurulum

# TimescaleDB repository dosyasını oluştur
sudo tee /etc/yum.repos.d/timescale_timescaledb.repo << EOF
[timescale_timescaledb]
name=timescale_timescaledb
baseurl=https://packagecloud.io/timescale/timescaledb/el/9/$basearch
repo_gpgcheck=1
gpgcheck=0
enabled=1
gpgkey=https://packagecloud.io/timescale/timescaledb/gpgkey
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
EOF

sudo dnf install -y timescaledb-2-postgresql-16

sudo timescaledb-tune --quiet --yes
sudo systemctl restart postgresql

Kurulum sonrasında PostgreSQL’e bağlanıp uzantıyı etkinleştirmeniz gerekiyor:

-- Veritabanına bağlan
psql -U postgres

-- Uzantıyı yükle
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- Versiyon kontrolü
SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';

İlk Hypertable’ı Oluşturma

Gerçek bir senaryo üzerinden gidelim: Sunucu izleme sistemi. Yüzlerce sunucudan CPU, RAM, disk ve ağ metriklerini topluyoruz.

-- Önce veritabanını oluştur
CREATE DATABASE monitoring;
c monitoring
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- Metrikler için standart bir tablo oluştur
CREATE TABLE server_metrics (
    time        TIMESTAMPTZ NOT NULL,
    host        TEXT NOT NULL,
    datacenter  TEXT NOT NULL,
    cpu_usage   DOUBLE PRECISION,
    mem_usage   DOUBLE PRECISION,
    disk_io     DOUBLE PRECISION,
    net_rx      BIGINT,
    net_tx      BIGINT
);

-- Bu tabloyu hypertable'a dönüştür
-- chunk_time_interval: Her chunk kaç günlük veri tutacak
SELECT create_hypertable(
    'server_metrics',
    'time',
    chunk_time_interval => INTERVAL '1 day'
);

-- Sık kullanılan sorgular için index oluştur
CREATE INDEX ON server_metrics (host, time DESC);
CREATE INDEX ON server_metrics (datacenter, time DESC);

Hypertable oluşturduktan sonra artık standart PostgreSQL INSERT/SELECT komutları çalışıyor. TimescaleDB arka planda chunk yönetimini otomatik yapıyor.

-- Örnek veri ekle (gerçekte bu işi metrik toplama ajanı yapıyor)
INSERT INTO server_metrics (time, host, datacenter, cpu_usage, mem_usage, disk_io, net_rx, net_tx)
VALUES
    (NOW(), 'web-01', 'istanbul', 45.2, 67.8, 12.3, 1024000, 512000),
    (NOW(), 'web-02', 'istanbul', 23.1, 55.4, 8.7, 890000, 445000),
    (NOW(), 'db-01', 'ankara', 78.9, 89.2, 45.6, 256000, 128000);

-- Chunk yapısını gör
SELECT chunk_schema, chunk_name, range_start, range_end
FROM timescaledb_information.chunks
WHERE hypertable_name = 'server_metrics'
ORDER BY range_start;

Zaman Serisi Sorgulama

TimescaleDB’nin en güçlü yanlarından biri, standart SQL’e ek olarak özel zaman serisi fonksiyonları sunması. Bunları gerçek sorgu senaryolarıyla görelim.

-- Son 1 saatteki ortalama CPU kullanımı (5 dakikalık aralıklarla)
SELECT
    time_bucket('5 minutes', time) AS bucket,
    host,
    AVG(cpu_usage) AS avg_cpu,
    MAX(cpu_usage) AS max_cpu,
    MIN(cpu_usage) AS min_cpu
FROM server_metrics
WHERE time > NOW() - INTERVAL '1 hour'
GROUP BY bucket, host
ORDER BY bucket DESC, host;

-- Son 24 saatte CPU kullanımı %80'i geçen sunucular
SELECT
    host,
    time_bucket('1 hour', time) AS hour,
    AVG(cpu_usage) AS avg_cpu,
    COUNT(*) FILTER (WHERE cpu_usage > 80) AS high_cpu_count
FROM server_metrics
WHERE time > NOW() - INTERVAL '24 hours'
GROUP BY host, hour
HAVING AVG(cpu_usage) > 70
ORDER BY avg_cpu DESC;

-- Veri merkezine göre saatlik ağ trafiği özeti
SELECT
    time_bucket('1 hour', time) AS hour,
    datacenter,
    SUM(net_rx) / 1024 / 1024 AS total_rx_mb,
    SUM(net_tx) / 1024 / 1024 AS total_tx_mb,
    COUNT(DISTINCT host) AS active_hosts
FROM server_metrics
WHERE time BETWEEN NOW() - INTERVAL '7 days' AND NOW()
GROUP BY hour, datacenter
ORDER BY hour DESC;

time_bucket fonksiyonu, PostgreSQL’in standart date_trunc fonksiyonuna benzer ama çok daha esnek. 5 dakika, 15 dakika, 1 saat, 3 saat gibi herhangi bir aralık tanımlayabilirsiniz.

Sürekli Agregasyonlar (Continuous Aggregates)

Gerçek dünyada binlerce sunucudan saniyede binlerce ölçüm geldiğini düşünün. Bu hacimde veriye her seferinde GROUP BY sorgusu yazmak ciddi performans sorunu yaratır. TimescaleDB’nin Continuous Aggregate özelliği bu problemi çözüyor: Arka planda otomatik olarak güncellenen materyalize görünümler oluşturuyorsunuz.

-- Saatlik CPU özeti için continuous aggregate oluştur
CREATE MATERIALIZED VIEW server_metrics_hourly
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 hour', time) AS bucket,
    host,
    datacenter,
    AVG(cpu_usage) AS avg_cpu,
    MAX(cpu_usage) AS max_cpu,
    MIN(cpu_usage) AS min_cpu,
    AVG(mem_usage) AS avg_mem,
    MAX(mem_usage) AS max_mem,
    SUM(net_rx) AS total_rx,
    SUM(net_tx) AS total_tx,
    COUNT(*) AS sample_count
FROM server_metrics
GROUP BY bucket, host, datacenter
WITH NO DATA;

-- İlk yenilemeyi manuel tetikle
CALL refresh_continuous_aggregate(
    'server_metrics_hourly',
    NOW() - INTERVAL '30 days',
    NOW()
);

-- Otomatik yenileme politikası ekle
SELECT add_continuous_aggregate_policy(
    'server_metrics_hourly',
    start_offset => INTERVAL '3 hours',
    end_offset   => INTERVAL '1 hour',
    schedule_interval => INTERVAL '1 hour'
);

Artık dashboard sorguları ham veriye değil, bu materyalize görünüme bakacak:

-- Hızlı dashboard sorgusu (saniyeler içinde döner)
SELECT bucket, host, avg_cpu, max_cpu, avg_mem
FROM server_metrics_hourly
WHERE bucket > NOW() - INTERVAL '7 days'
  AND datacenter = 'istanbul'
ORDER BY bucket DESC;

Veri Saklama Politikaları

Sysadmin hayatının gerçeği şu: Disk alanı sonsuz değil. Eski metriklerin granülaritesini düşürmek veya tamamen silmek kaçınılmaz bir ihtiyaç. TimescaleDB bu konuda da çok pratik çözümler sunuyor.

-- Ham veri için saklama politikası: 90 günden eski verileri sil
SELECT add_retention_policy(
    'server_metrics',
    INTERVAL '90 days'
);

-- Politikaları listele
SELECT * FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention';

-- Politikayı kaldır (gerekirse)
SELECT remove_retention_policy('server_metrics');

90 günden eski ham veri silinecek ama saatlik agregatlar ayrı bir hypertable’da tutulduğu için tarihsel trendleri görebilmeye devam ediyorsunuz. İşte bu kombinasyon, birçok izleme sisteminin temel mimarisi.

Veri Sıkıştırma

TimescaleDB’nin öne çıkan özelliklerinden biri, zaman serisi verisi için optimize edilmiş sıkıştırma algoritmaları kullanması. Gerçek dünya testlerinde ham verinin yüzde 90’a kadar sıkıştırılabildiği görülüyor.

-- Sıkıştırma politikası tanımla
-- Segment by: Aynı değerleri bir arada grupla (daha iyi sıkıştırma için)
-- Order by: Chunk içi sıralama (sıkıştırma etkinliği için kritik)
ALTER TABLE server_metrics SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'host, datacenter',
    timescaledb.compress_orderby = 'time DESC'
);

-- Otomatik sıkıştırma politikası: 7 günden eski chunk'ları sıkıştır
SELECT add_compression_policy(
    'server_metrics',
    INTERVAL '7 days'
);

-- Manuel sıkıştırma (belirli bir chunk için)
SELECT compress_chunk(chunk)
FROM show_chunks('server_metrics', older_than => INTERVAL '7 days');

-- Sıkıştırma istatistiklerini gör
SELECT
    hypertable_name,
    before_compression_total_bytes / 1024 / 1024 AS before_mb,
    after_compression_total_bytes / 1024 / 1024 AS after_mb,
    ROUND(
        (1 - after_compression_total_bytes::NUMERIC /
             before_compression_total_bytes) * 100, 2
    ) AS compression_ratio_pct
FROM chunk_compression_stats('server_metrics')
WHERE compression_status = 'Compressed'
ORDER BY before_compression_total_bytes DESC;

Sıkıştırılmış chunk’lara hala normal SQL sorguları yazabilirsiniz, TimescaleDB sorgu anında decompress ediyor. Ancak sıkıştırılmış chunk’lara INSERT veya UPDATE yapmak mümkün değil, bu nedenle son 7 günlük ham veriyi her zaman sıkıştırmasız bırakmak iyi bir pratik.

IoT Senaryosu: Fabrika Sensör Verisi

Farklı bir gerçek dünya örneği üzerinden TimescaleDB’nin esnekliğini gösterelim. 50 farklı fabrikada 500 sensör var, saniyede 2000 ölçüm geliyor.

-- Yeni veritabanı ve şema
CREATE DATABASE factory_iot;
c factory_iot
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- Sensör kayıt tablosu (metadata, normal PostgreSQL tablosu)
CREATE TABLE sensors (
    sensor_id   SERIAL PRIMARY KEY,
    factory_id  INTEGER NOT NULL,
    sensor_type VARCHAR(50) NOT NULL,
    location    TEXT,
    unit        VARCHAR(20),
    active      BOOLEAN DEFAULT true,
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- Ölçüm tablosu (hypertable olacak)
CREATE TABLE sensor_readings (
    time        TIMESTAMPTZ NOT NULL,
    sensor_id   INTEGER NOT NULL REFERENCES sensors(sensor_id),
    value       DOUBLE PRECISION NOT NULL,
    quality     SMALLINT DEFAULT 100  -- 0-100 arası veri kalitesi skoru
);

-- Hypertable oluştur, sensore göre de partition ekle (space partitioning)
SELECT create_hypertable(
    'sensor_readings',
    'time',
    partitioning_column => 'sensor_id',
    number_partitions => 4,
    chunk_time_interval => INTERVAL '1 day'
);

-- Sıkıştırma ayarları
ALTER TABLE sensor_readings SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'sensor_id',
    timescaledb.compress_orderby = 'time DESC'
);

SELECT add_compression_policy('sensor_readings', INTERVAL '3 days');
SELECT add_retention_policy('sensor_readings', INTERVAL '365 days');

-- Anlık anomali tespiti sorgusu
-- Son 10 dakikada ortalamanın 2 standart sapma dışındaki okumalar
WITH recent_stats AS (
    SELECT
        sensor_id,
        AVG(value) AS mean_val,
        STDDEV(value) AS std_val
    FROM sensor_readings
    WHERE time > NOW() - INTERVAL '1 hour'
    GROUP BY sensor_id
)
SELECT
    sr.time,
    sr.sensor_id,
    s.sensor_type,
    s.location,
    sr.value,
    rs.mean_val,
    ABS(sr.value - rs.mean_val) / NULLIF(rs.std_val, 0) AS z_score
FROM sensor_readings sr
JOIN recent_stats rs ON sr.sensor_id = rs.sensor_id
JOIN sensors s ON sr.sensor_id = s.sensor_id
WHERE sr.time > NOW() - INTERVAL '10 minutes'
  AND ABS(sr.value - rs.mean_val) > 2 * rs.std_val
ORDER BY z_score DESC;

Performans Tuning ve İzleme

TimescaleDB kullanırken sysadmin olarak dikkat etmeniz gereken birkaç kritik nokta var.

# timescaledb-tune otomatik öneriler sunar
# work_mem, shared_buffers, max_parallel_workers gibi parametreleri ayarlar
sudo timescaledb-tune --pg-config=/usr/bin/pg_config

# postgresql.conf'daki kritik parametreler
# shared_buffers: Toplam RAM'in yüzde 25'i önerilir
# work_mem: Paralel sorgular için, (RAM / max_connections) formülüyle hesapla
# max_parallel_workers_per_gather: CPU çekirdek sayısına göre ayarla
# effective_cache_size: Toplam RAM'in yüzde 75'i

# TimescaleDB arka plan işlerini izle
psql -U postgres -d monitoring -c "
SELECT job_id, proc_name, schedule_interval, 
       last_run_status, last_successful_finish
FROM timescaledb_information.jobs
ORDER BY job_id;
"
-- Hypertable boyutlarını izle
SELECT
    hypertable_name,
    pg_size_pretty(hypertable_size(format('%I.%I', hypertable_schema, hypertable_name)::regclass)) AS total_size,
    pg_size_pretty(hypertable_detailed_size(format('%I.%I', hypertable_schema, hypertable_name)::regclass) -> 'compressed_size') AS compressed,
    num_chunks
FROM timescaledb_information.hypertables;

-- Yavaş sorguları yakala (pg_stat_statements ile birlikte)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

SELECT
    ROUND(total_exec_time::NUMERIC, 2) AS total_ms,
    calls,
    ROUND(mean_exec_time::NUMERIC, 2) AS mean_ms,
    LEFT(query, 80) AS query_preview
FROM pg_stat_statements
WHERE query LIKE '%server_metrics%'
ORDER BY mean_exec_time DESC
LIMIT 10;

-- Chunk durumunu detaylı gör
SELECT
    chunk_name,
    range_start,
    range_end,
    is_compressed,
    pg_size_pretty(chunk_size) AS chunk_size
FROM chunk_detailed_size('server_metrics')
ORDER BY range_start DESC
LIMIT 20;

Yedekleme Stratejisi

TimescaleDB tabanlı veritabanlarını yedeklemek için standart PostgreSQL araçları kullanılıyor, ancak dikkat edilmesi gereken bazı noktalar var.

# pg_dump ile yedekleme (küçük-orta ölçekli veritabanları için)
pg_dump -U postgres -h localhost -Fc monitoring > /backup/monitoring_$(date +%Y%m%d).dump

# Geri yükleme
pg_restore -U postgres -h localhost -d monitoring_restore /backup/monitoring_20240115.dump

# Büyük veritabanları için pg_basebackup (fiziksel yedek)
pg_basebackup -U replication_user -h localhost -D /backup/basebackup 
  --wal-method=stream --compress=9 --progress

# Yalnızca belirli bir zaman aralığındaki veriyi dışa aktar
psql -U postgres -d monitoring -c "COPY (
    SELECT * FROM server_metrics
    WHERE time BETWEEN '2024-01-01' AND '2024-01-31'
) TO '/backup/metrics_jan2024.csv' CSV HEADER"

Çok büyük hypertable’larla çalışırken, chunk bazlı yedekleme stratejisi daha mantıklı olabilir. Her chunk ayrı bir PostgreSQL tablosu olduğu için pg_dump’ı tabloya özel çalıştırabilirsiniz.

Grafana Entegrasyonu

Çoğu izleme altyapısında TimescaleDB, Grafana ile birlikte kullanılıyor. Grafana’da PostgreSQL datasource ekleyip doğrudan sorgu yazabilirsiniz:

-- Grafana için time series sorgusu
-- $__timeFilter() Grafana'nın otomatik zaman filtresi
-- $__timeGroupAlias() seçilen aralığa göre bucket boyutunu otomatik ayarlar

SELECT
    $__timeGroupAlias(time, $__interval),
    host AS metric,
    AVG(cpu_usage) AS value
FROM server_metrics
WHERE $__timeFilter(time)
  AND datacenter = '$datacenter'
GROUP BY 1, host
ORDER BY 1;

Bu sorguyu Grafana’nın panel editörüne yapıştırdığınızda, zaman aralığını değiştirdikçe bucket boyutu otomatik olarak uyum sağlıyor. Örneğin 1 saatlik görünümde 1 dakikalık bucket, 7 günlük görünümde 1 saatlik bucket kullanılıyor.

Sonuç

TimescaleDB, PostgreSQL dünyasında zaman serisi verisi yönetimini gerçek anlamda çözüme kavuşturan bir uzantı. Sıfırdan yeni bir veritabanı teknolojisi öğrenmek yerine mevcut PostgreSQL bilginizi ve araçlarınızı kullanarak InfluxDB veya Prometheus TSDB gibi özel zaman serisi veritabanlarının sunduğu özelliklere kavuşuyorsunuz.

Özellikle şu senaryolarda TimescaleDB güçlü bir tercih:

  • Mevcut PostgreSQL altyapısına zaman serisi yeteneği eklemek istiyorsanız
  • Hem zaman serisi hem de ilişkisel veriyi JOIN’lerle bir arada sorgulamanız gerekiyorsa
  • Standart SQL araçlarıyla ve mevcut ORM’lerle çalışmaya devam etmek istiyorsanız
  • Ekibinizin PostgreSQL deneyimi varsa ve yeni bir teknoloji öğrenme maliyetinden kaçınmak istiyorsanız

Pratik tavsiyem şu: Küçük bir proof-of-concept ile başlayın. Mevcut bir metrik kaynağınızı TimescaleDB’ye yönlendirin, continuous aggregate ve sıkıştırma politikalarını kurun, Grafana’ya bağlayın. Birkaç hafta sonra hem performans rakamlarını hem de disk tasarrufunu kendi altyapınızda görmüş olursunuz. O noktadan sonra ne kadar büyütmek istediğinize karar vermek çok daha kolay.

Yorum yapın