PostgreSQL veritabanı yönetiminde en güçlü araçlardan biri olan logical replication, klasik fiziksel replikasyonun ötesine geçerek sana inanılmaz bir esneklik sunuyor. Tüm veritabanını kopyalamak zorunda kalmadan, sadece ihtiyacın olan tabloları, hatta belirli satırları bile seçerek başka bir sunucuya senkronize edebilirsin. Bu yazıda logical replication’ı gerçek dünya senaryolarıyla ele alacağız ve nasıl kurulup yönetileceğini adım adım göreceğiz.
Logical Replication Nedir ve Fiziksel Replikasyondan Farkı Ne?
Fiziksel (streaming) replikasyon, WAL (Write-Ahead Log) dosyalarını blok seviyesinde kopyalar. Yani primary sunucundaki her değişiklik, standby sunucusuna aynen yansır. Bu yaklaşım basit ve güvenilirdir, ancak çok kısıtlayıcıdır. Standby sunucu read-only modda çalışır, farklı PostgreSQL majör versiyonları arasında çalışmaz ve tablo bazında seçim yapamazsın.
Logical replication ise WAL’ı mantıksal değişiklikler olarak yorumlar. “Şu tablodaki şu satır eklendi, şu kayıt güncellendi” şeklinde anlamlı işlemler olarak iletir değişiklikleri. Bu sayede:
- Sadece belirli tabloları replikalayabilirsin
- Hedef sunucu write işlemi yapabilir (kendi tablolarına)
- Farklı PostgreSQL majör versiyonları arasında çalışır
- Farklı veritabanı isimleri ve şema yapıları kullanabilirsin
- Row-level filtering ile sadece belirli satırları bile seçebilirsin (PostgreSQL 15+)
Temel Kavramlar: Publication ve Subscription
Logical replication iki ana kavram üzerine kurulu:
Publication (Yayın): Kaynak (publisher) sunucuda tanımlarsın. “Hangi tabloları, hangi operasyonlarla yayınlıyorum?” sorusunun cevabıdır.
Subscription (Abonelik): Hedef (subscriber) sunucuda tanımlarsın. “Hangi yayına abone oluyorum ve nereye bağlanıyorum?” sorusunun cevabıdır.
Bu iki kavramı anladıktan sonra geri kalan her şey mantıklı yerine oturuyor.
Ortam Hazırlığı ve Ön Gereksinimler
Önce her iki sunucuda da postgresql.conf ayarlarını yapman gerekiyor. Publisher sunucuda WAL level’ı logical olarak ayarlamalısın:
# Publisher sunucuda postgresql.conf düzenle
sudo nano /etc/postgresql/16/main/postgresql.conf
# Şu parametreleri ayarla
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
Publisher sunucuda pg_hba.conf dosyasına da replication bağlantısına izin vermelisin:
# /etc/postgresql/16/main/pg_hba.conf dosyasına ekle
# Subscriber sunucunun IP adresini kullan
host replication replicator_user 192.168.1.20/32 md5
host uygulama_db replicator_user 192.168.1.20/32 md5
Değişiklikleri uygulamak için PostgreSQL’i yeniden yükle:
sudo systemctl reload postgresql
Publisher Sunucuda Kurulum
Publisher sunucuda gerekli kullanıcıyı oluşturup hakları ver:
# Publisher sunucuda psql'e bağlan
sudo -u postgres psql
-- Replikasyon kullanıcısı oluştur
CREATE USER replicator_user WITH REPLICATION LOGIN PASSWORD 'guclu_sifre_buraya';
-- Tablolara SELECT hakkı ver
GRANT SELECT ON ALL TABLES IN SCHEMA public TO replicator_user;
-- Yeni oluşacak tablolar için de otomatik grant
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO replicator_user;
Şimdi publication’ı oluşturalım. Diyelim ki bir e-ticaret platformun var ve sadece urunler, kategoriler ve stok tablolarını başka bir sunucuya senkronize etmek istiyorsun:
-- Sadece belirli tabloları yayınla
CREATE PUBLICATION urun_katalogu_pub
FOR TABLE urunler, kategoriler, stok
WITH (publish = 'insert, update, delete');
-- Mevcut publication'ları listele
SELECT pubname, puballtables, pubinsert, pubupdate, pubdelete
FROM pg_publication;
-- Bir publication'daki tabloları gör
SELECT tablename
FROM pg_publication_tables
WHERE pubname = 'urun_katalogu_pub';
Subscriber Sunucuda Kurulum
Subscriber sunucuda önce hedef tabloları oluşturman gerekiyor. Logical replication şema oluşturmaz, tabloların zaten var olması lazım:
# Subscriber sunucuda psql'e bağlan
sudo -u postgres psql -d hedef_veritabani
-- Hedef tablolar publisher ile aynı yapıda olmalı
CREATE TABLE urunler (
id SERIAL PRIMARY KEY,
urun_adi VARCHAR(255) NOT NULL,
fiyat DECIMAL(10,2),
kategori_id INT,
olusturma_tarihi TIMESTAMP DEFAULT NOW()
);
CREATE TABLE kategoriler (
id SERIAL PRIMARY KEY,
kategori_adi VARCHAR(100) NOT NULL,
ust_kategori_id INT
);
CREATE TABLE stok (
id SERIAL PRIMARY KEY,
urun_id INT,
miktar INT,
son_guncelleme TIMESTAMP
);
-- Subscription oluştur
CREATE SUBSCRIPTION urun_katalogu_sub
CONNECTION 'host=192.168.1.10 port=5432 dbname=uygulama_db user=replicator_user password=guclu_sifre_buraya'
PUBLICATION urun_katalogu_pub
WITH (copy_data = true);
copy_data = true seçeneği ilk kurulumda mevcut veriyi de kopyalar. Sadece yeni değişiklikleri almak istiyorsan false yapabilirsin.
Subscription Durumunu İzleme
Her şey çalışıyor mu? Bunu nasıl anlarsın?
# Publisher sunucuda replikasyon slotlarını kontrol et
sudo -u postgres psql -c "
SELECT slot_name, plugin, slot_type, active, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots;
"
# Publisher'da WAL sender durumunu gör
sudo -u postgres psql -c "
SELECT pid, usename, application_name, client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication;
"
# Subscriber'da subscription durumunu kontrol et
sudo -u postgres psql -d hedef_veritabani -c "
SELECT subname, pid, received_lsn, last_msg_send_time, last_msg_receipt_time, latest_end_lsn, latest_end_time
FROM pg_stat_subscription;
"
Eğer her şey doğruysa subscriber’da worker process’in çalıştığını göreceksin. latest_end_time değerinin güncel olması replikasyonun aktif olduğunu gösterir.
Gerçek Dünya Senaryosu 1: Raporlama Veritabanı
En yaygın kullanım senaryolarından biri raporlama. Diyelim ki ana üretim veritabanın var ve raporlama ekibi ağır sorgularla üretim sunucusunu çökertmesin diye ayrı bir sunucuya sadece rapor tablolarını senkronize etmek istiyorsun.
Publisher sunucuda:
-- Sadece raporlama için gereken tabloları yayınla
-- Hassas finansal tablolar burada yok
CREATE PUBLICATION raporlama_pub
FOR TABLE siparisler, musteri_ozeti, urun_performans, aylik_satis
WITH (publish = 'insert, update, delete, truncate');
-- Abonelik oluşturulduğunda raporlama sunucusu bu tabloları alacak
-- Ama müşteri_kisisel_bilgi veya odeme_bilgileri gibi hassas tablolar gitmeyecek
Subscriber (raporlama sunucusu) tarafında ek indeksler ekleyebilirsin:
-- Raporlama sunucusunda ek indeksler tanımla
-- Bunlar publisher'ı etkilemez
CREATE INDEX idx_siparisler_tarih ON siparisler(siparis_tarihi);
CREATE INDEX idx_siparisler_musteri ON siparisler(musteri_id, siparis_tarihi);
-- Materialized view'lar oluşturabilirsin
CREATE MATERIALIZED VIEW aylik_ozet AS
SELECT
DATE_TRUNC('month', siparis_tarihi) as ay,
COUNT(*) as siparis_sayisi,
SUM(toplam_tutar) as toplam_ciro
FROM siparisler
GROUP BY 1;
Gerçek Dünya Senaryosu 2: Row-Level Filtering (PostgreSQL 15+)
PostgreSQL 15 ile gelen muhteşem özellik: sadece belirli satırları filtrele. Diyelim ki birden fazla ülkede hizmet veren bir şirketin var ve her ülkenin kendi veri merkezine sadece kendi verisini göndermek istiyorsun:
-- Turkey bölgesindeki siparişleri sadece TR sunucusuna gönder
CREATE PUBLICATION tr_siparisler_pub
FOR TABLE siparisler (id, musteri_id, toplam_tutar, siparis_tarihi)
WHERE (bolge = 'TR')
WITH (publish = 'insert, update, delete');
-- Almanya bölgesindeki siparişleri DE sunucusuna gönder
CREATE PUBLICATION de_siparisler_pub
FOR TABLE siparisler (id, musteri_id, toplam_tutar, siparis_tarihi)
WHERE (bolge = 'DE')
WITH (publish = 'insert, update, delete');
Bu özellik hem satır hem de kolon bazında filtreleme yapmanı sağlıyor. Hassas kolonları publication’dan çıkartabilirsin, belirli koşulları taşıyan satırları yayınlayabilirsin.
Publication Yönetimi ve Dinamik Değişiklikler
Gerçek hayatta ihtiyaçlar değişir. Mevcut publication’a tablo ekleyip çıkartabilirsin:
-- Publication'a yeni tablo ekle
ALTER PUBLICATION urun_katalogu_pub ADD TABLE yeni_tablo;
-- Publication'dan tablo çıkar
ALTER PUBLICATION urun_katalogu_pub DROP TABLE eski_tablo;
-- Publication'ı tamamen yeniden tanımla
ALTER PUBLICATION urun_katalogu_pub SET TABLE urunler, kategoriler, stok, kampanyalar;
-- Sadece INSERT ve UPDATE yayınla, DELETE'leri yayınlama
ALTER PUBLICATION urun_katalogu_pub SET (publish = 'insert, update');
Subscriber tarafında da değişiklik yapabilirsin:
-- Subscription'ı geçici olarak devre dışı bırak
ALTER SUBSCRIPTION urun_katalogu_sub DISABLE;
-- Tekrar aktif et
ALTER SUBSCRIPTION urun_katalogu_sub ENABLE;
-- Subscription'ın yeni publication değişikliklerini algılamasını sağla
ALTER SUBSCRIPTION urun_katalogu_sub REFRESH PUBLICATION;
Sorun Giderme ve Yaygın Hatalar
Logical replication kurulumunda karşılaşılan en sık sorunları ve çözümlerini paylaşayım:
REPLICA IDENTITY hatası: UPDATE veya DELETE replike edilirken tablo primary key’e veya replica identity’e ihtiyaç duyar:
-- Tabloda primary key yoksa replica identity ayarla
ALTER TABLE log_tablosu REPLICA IDENTITY FULL;
-- Ya da belirli bir unique index kullan
ALTER TABLE baska_tablo REPLICA IDENTITY USING INDEX unique_idx_adi;
-- Mevcut tabloların replica identity durumunu kontrol et
SELECT relname, relreplident
FROM pg_class
WHERE relkind = 'r' AND relname IN ('siparisler', 'urunler', 'stok');
-- d=default (primary key), f=full, i=index, n=nothing
Replikasyon gecikmesini izle:
#!/bin/bash
# replication_lag_check.sh
# Bu scripti cron'a ekleyip düzenli çalıştırabilirsin
PUBLISHER_HOST="192.168.1.10"
SUBSCRIBER_HOST="192.168.1.20"
DB_NAME="uygulama_db"
ALERT_LAG_SECONDS=300 # 5 dakikadan fazla gecikme varsa alarm ver
LAG=$(sudo -u postgres psql -h $PUBLISHER_HOST -d $DB_NAME -t -c "
SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::INT;
" 2>/dev/null)
if [ -z "$LAG" ]; then
# Subscriber tarafından kontrol et
LAG=$(sudo -u postgres psql -h $SUBSCRIBER_HOST -d $DB_NAME -t -c "
SELECT EXTRACT(EPOCH FROM (now() - last_msg_receipt_time))::INT
FROM pg_stat_subscription
LIMIT 1;
")
fi
if [ "$LAG" -gt "$ALERT_LAG_SECONDS" ]; then
echo "UYARI: Replikasyon gecikmesi ${LAG} saniye!" | mail -s "Replikasyon Alarmı" [email protected]
fi
echo "Mevcut gecikme: ${LAG} saniye"
Büyük tabloların initial copy’si takılırsa:
-- Subscription durumunu detaylı gör
SELECT * FROM pg_subscription_rel;
-- srsubstate: i=initialize, d=data copy, s=synchronized, r=ready
-- Eğer data copy aşamasında takılıysa, slot'u kontrol et
SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as wal_behind
FROM pg_replication_slots;
Majör Versiyon Yükseltme Senaryosu
Logical replication’ın en değerli kullanım alanlarından biri PostgreSQL majör versiyon yükseltmesi. Sıfır downtime ile PostgreSQL 14’ten 16’ya geçmek mümkün:
# 1. Yeni sunucuya PostgreSQL 16 kur
sudo apt install postgresql-16
# 2. Yeni sunucuda veritabanı oluştur ve şemayı aktar
# pg_dump ile sadece şemayı al (--schema-only)
sudo -u postgres pg_dump --schema-only uygulama_db > schema.sql
# Yeni sunucuya aktar
sudo -u postgres psql -h yeni_sunucu -d uygulama_db < schema.sql
# 3. Eski sunucudan (publisher) yeni sunucuya (subscriber) subscription kur
# Tüm tabloları yayınla
sudo -u postgres psql -d uygulama_db -c "
CREATE PUBLICATION full_migration_pub FOR ALL TABLES;
"
# 4. Yeni sunucuda subscription oluştur
sudo -u postgres psql -h yeni_sunucu -d uygulama_db -c "
CREATE SUBSCRIPTION migration_sub
CONNECTION 'host=eski_sunucu port=5432 dbname=uygulama_db user=replicator_user password=sifre'
PUBLICATION full_migration_pub
WITH (copy_data = true);
"
# 5. Veriler senkronize olduktan sonra uygulamayı yeni sunucuya yönlendir
# 6. Test et, sorun yoksa eski sunucuyu kapat
Circular Replication ve Conflict Yönetimi
Subscriber tarafında da yazma işlemi yapıyorsan, çakışma durumlarını yönetmen gerekir. Conflict genellikle duplicate key hatası olarak ortaya çıkar:
-- Subscriber'da conflict davranışını ayarla
ALTER SUBSCRIPTION urun_katalogu_sub
SET (conflict_resolution = 'apply_error');
-- apply_error: işlemi durdur ve hata logla (default)
-- skip: çakışan değişikliği atla
-- Conflict loglarını kontrol et
SELECT * FROM pg_stat_subscription_stats;
-- Manuel olarak bir LSN'i atlamak için (dikkatli kullan!)
SELECT pg_replication_origin_advance('pg_16395', '0/15E3210');
Performans İpuçları
Logical replication yoğun yazma altında performans sorunları yaratabilir. Birkaç pratik ipucu:
wal_sender_timeoutdeğerini ağ gecikmene göre ayarla (default 60s)wal_receiver_timeoutsubscriber tarafında benzer şekilde ayarla- Çok sayıda küçük tablo yerine, mümkünse az sayıda büyük tablo yayınla
synchronous_commit = remote_writereplikasyon güvenilirliğini artırır ama yazma performansını düşürür. Kullanım senaryona göre karar ver- Publication’larda gereksiz operasyonları yayınlama: sadece INSERT gerekiyorsa
publish = 'insert'yeterli
# WAL birikiyor mu kontrol et
sudo -u postgres psql -c "
SELECT slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as gecikme_boyutu,
active
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;
"
# Kullanılmayan replikasyon slotları disk şişirmesine yol açar, temizle
-- DROP SUBSCRIPTION kullanamazsan doğrudan slot'u sil
SELECT pg_drop_replication_slot('slot_adi');
Sonuç
Logical replication, modern PostgreSQL ortamlarında vazgeçilmez bir araç haline geldi. Seçici veri senkronizasyonu ihtiyacı olan her senaryoda, fiziksel replikasyonun sınırlamalarını aşmana olanak tanıyor.
Özetle logical replication şu senaryolarda sana büyük avantaj sağlar:
- Raporlama veritabanı oluştururken üretim sunucusunu izole etmek
- Majör PostgreSQL versiyon yükseltmelerinde sıfır downtime sağlamak
- Çok bölgeli mimarilerde bölgesel veri filtreleme yapmak
- Hassas veri içeren tabloları replikasyonun dışında tutmak
- Microservis mimarisinde belirli servislere sadece ihtiyaç duydukları tabloları göndermek
Kurulum sırasında en çok dikkat etmen gereken nokta: replication slot’larının birikmesi. Eğer subscriber beklenmedik şekilde kapanırsa ve slot aktif kalmaya devam ederse, publisher tarafında WAL dosyaları disk dolana kadar birikir. Monitoring scriptini mutlaka kurar ve disk alarmlarını düzenlersin.
PostgreSQL 15 ve 16 ile gelen row-level filtering ve kolon bazlı filtreleme özellikleri logical replication’ı çok daha güçlü hale getirdi. Eski versiyonlarda view veya trigger hilelerine başvururken, artık doğrudan publication tanımında halledebiliyorsun. Versiyon yükseltme zamanı geldiyse bu da iyi bir neden.