Büyük veri setleri üzerinde çalışan PostgreSQL veritabanlarında sorgu performansı, çoğu zaman sistem yöneticilerinin en fazla vakit harcadığı konuların başında gelir. Özellikle raporlama sorgularında, analitik işlemlerde veya büyük tablo taramalarında tek bir CPU çekirdeğinin yetersiz kaldığını fark etmişsinizdir. İşte tam bu noktada PostgreSQL’in paralel sorgu yürütme mekanizması devreye giriyor. Doğru yapılandırıldığında, bir sorgunun çalışma süresini dakikalardan saniyelere indirebilecek bu özelliği derinlemesine ele alalım.
Paralel Sorgu Yürütme Nedir?
PostgreSQL 9.6 sürümüyle birlikte gelen paralel sorgu yürütme özelliği, tek bir SQL sorgusunun birden fazla CPU çekirdeği kullanarak eşzamanlı çalışmasını sağlar. Klasik yürütme modelinde bir sorgu tek bir işlemci çekirdeğinde sırayla işlenirken, paralel modda sorgu birden fazla worker process tarafından parçalara bölünerek işlenir.
Bunu somutlaştıralım: 100 milyon satırlık bir tabloda COUNT(*) çalıştırdığınızı düşünün. Tek çekirdekli yürütmede tüm satırları sırayla okumak gerekir. Ama 4 worker process ile çalıştırıldığında, her worker process yaklaşık 25 milyon satırı eşzamanlı olarak okur ve sonuçlar birleştirilir. Teorik olarak 4 kat hızlanma elde edilebilir.
Paralel Sorgu Mimarisi
PostgreSQL’de paralel yürütme şu bileşenlerden oluşur:
- Leader process: Ana sorgu sürecini yönetir, worker’ları koordine eder ve sonuçları birleştirir
- Worker processes:
parallel_workersayarıyla belirlenen sayıda arka plan işlemi, veriyi paralel olarak işler - Dynamic Background Workers: Her paralel sorgu için dinamik olarak oluşturulan geçici süreçlerdir
Kritik Konfigürasyon Parametreleri
PostgreSQL’de paralel sorgu davranışını kontrol eden birden fazla parametre bulunur. Bunları tek tek inceleyelim.
max_parallel_workers_per_gather
Bu parametre, tek bir sorgu için kullanılabilecek maksimum worker sayısını belirler. Varsayılan değeri PostgreSQL 10 ve sonrasında 2‘dir.
# Mevcut değeri kontrol et
psql -U postgres -c "SHOW max_parallel_workers_per_gather;"
# postgresql.conf içinde değiştir
# max_parallel_workers_per_gather = 4
max_parallel_workers
Aynı anda tüm paralel sorgular için kullanılabilecek toplam worker sayısı üst limitidir. max_worker_processes değerini geçemez.
# Mevcut ayarları görüntüle
psql -U postgres -c "
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name IN (
'max_parallel_workers_per_gather',
'max_parallel_workers',
'max_worker_processes',
'parallel_tuple_cost',
'parallel_setup_cost',
'min_parallel_table_scan_size',
'min_parallel_index_scan_size'
);
"
max_worker_processes
Tüm arka plan işlemcileri (paralel worker’lar dahil) için üst limit. Paralel worker’lar bu havuzdan beslenir. Varsayılan değer 8‘dir.
parallel_tuple_cost ve parallel_setup_cost
Planner’ın paralel plan seçip seçmeyeceğini etkileyen maliyet parametreleridir:
- parallel_tuple_cost: Worker’dan leader’a her tuple iletmenin maliyeti (varsayılan: 0.1)
- parallel_setup_cost: Paralel worker başlatmanın sabit maliyeti (varsayılan: 1000)
min_parallel_table_scan_size ve min_parallel_index_scan_size
Paralel tarama için tablo veya index’in minimum boyutu:
- min_parallel_table_scan_size: Varsayılan 8MB, bu boyutun altındaki tablolar paralel taranmaz
- min_parallel_index_scan_size: Varsayılan 512kB
parallel_workers Ayarı: Tablo Bazlı Yapılandırma
İşte çoğu kaynakta göremeyeceğiniz kritik bir nokta: PostgreSQL’de tablo başına parallel_workers değeri tanımlanabilir. Bu, global ayarlardan bağımsız olarak belirli tablolar için worker sayısını override etmenizi sağlar.
# Belirli bir tablo için parallel_workers ayarla
psql -U postgres -d mydb -c "
ALTER TABLE sales_transactions SET (parallel_workers = 8);
"
# Ayarı doğrula
psql -U postgres -d mydb -c "
SELECT relname, reloptions
FROM pg_class
WHERE relname = 'sales_transactions';
"
Bu ayar özellikle şu senaryolarda faydalıdır:
- Büyük fact tabloları gibi sık taranan tablolar için daha yüksek worker sayısı
- Küçük ve sık güncellenen tablolar için paralel yürütmeyi engellemek
- Farklı boyutlardaki tablolar için granüler kontrol sağlamak
PostgreSQL Paralel Sorgu Nasıl Karar Verir?
Planner, bir sorguyu paralel çalıştırmadan önce bazı kontrollerden geçirir. Bunu anlamak, neden bazı sorgularınızın paralel çalışmadığını anlamanıza yardımcı olur.
# EXPLAIN ile paralel plan kullanılıp kullanılmadığını kontrol et
psql -U postgres -d mydb -c "
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT product_category, SUM(amount), COUNT(*)
FROM sales_transactions
WHERE sale_date >= '2023-01-01'
GROUP BY product_category;
"
Çıktıda Gather veya Gather Merge node’u görüyorsanız paralel yürütme aktiftir. Workers Planned: 4 gibi bir satır kaç worker planlandığını gösterir.
Paralel Yürütmeyi Engelleyen Durumlar
PostgreSQL aşağıdaki durumlarda paralel plan oluşturmaz:
- Sorgu veri değiştirir (INSERT, UPDATE, DELETE, MERGE)
- Sorgu bir cursor üzerinden çalışır
max_parallel_workers_per_gathersıfıra ayarlanmış- Fonksiyon
PARALLEL UNSAFEolarak işaretli - Tablo
parallel_workers = 0olarak ayarlanmış
Gerçek Dünya Senaryosu: Raporlama Veritabanı Optimizasyonu
Diyelim ki bir e-ticaret firmasında çalışıyorsunuz. Aylık satış raporu sorgusu 20 dakika sürüyor ve iş birimi sabırsızlanıyor. Sisteminizde 32 çekirdekli bir sunucu var ama PostgreSQL sadece 2 worker kullanıyor.
# Mevcut durumu kontrol et
psql -U postgres -d ecommerce -c "
EXPLAIN (ANALYZE, FORMAT TEXT)
SELECT
DATE_TRUNC('month', sale_date) as month,
product_category,
SUM(amount) as total_sales,
COUNT(DISTINCT customer_id) as unique_customers
FROM sales_transactions
WHERE sale_date BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY 1, 2
ORDER BY 1, 2;
"
Şimdi adım adım optimizasyon yapalım:
# Adım 1: postgresql.conf'u düzenle
# /etc/postgresql/15/main/postgresql.conf
sudo nano /etc/postgresql/15/main/postgresql.conf
# Aşağıdaki ayarları 32 çekirdekli sunucu için düzenle
# max_worker_processes = 32
# max_parallel_workers = 24
# max_parallel_workers_per_gather = 8
# parallel_setup_cost = 500
# parallel_tuple_cost = 0.05
# min_parallel_table_scan_size = 4MB
# Değişiklikleri yükle (max_worker_processes restart gerektirir)
sudo systemctl restart postgresql
# Adım 2: Büyük tablo için özel ayar
psql -U postgres -d ecommerce -c "
ALTER TABLE sales_transactions SET (parallel_workers = 8);
"
# Adım 3: Sorgu sonrası planı tekrar kontrol et
psql -U postgres -d ecommerce -c "
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT
DATE_TRUNC('month', sale_date) as month,
product_category,
SUM(amount) as total_sales,
COUNT(DISTINCT customer_id) as unique_customers
FROM sales_transactions
WHERE sale_date BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY 1, 2
ORDER BY 1, 2;
"
Bu değişiklikler sonrasında sorgunun 20 dakikadan 3-4 dakikaya inmesini bekleyebilirsiniz. Teorik 8x hızlanmaya ulaşamazsınız çünkü I/O, memory bandwidth ve coordinator overhead devreye girer.
Session Bazlı Geçici Ayarlar
Production’da kalıcı değişiklik yapmadan önce session bazlı test yapmak her zaman iyi bir pratiktir:
psql -U postgres -d ecommerce -c "
-- Session için geçici olarak paralel worker sayısını artır
SET max_parallel_workers_per_gather = 6;
SET parallel_setup_cost = 200;
SET parallel_tuple_cost = 0.05;
-- Sorguyu çalıştır
EXPLAIN (ANALYZE, BUFFERS)
SELECT product_category, SUM(amount)
FROM sales_transactions
WHERE sale_date >= '2024-01-01'
GROUP BY product_category;
"
Geçici olarak paralel yürütmeyi tamamen devre dışı bırakmak istiyorsanız:
psql -U postgres -d ecommerce -c "
-- Bu session için paralel yürütmeyi kapat
SET max_parallel_workers_per_gather = 0;
-- veya
SET enable_parallel_hash = off;
SET enable_parallel_append = off;
"
Monitoring: Paralel Sorguları İzlemek
Paralel sorgu yürütmesini izlemek için birkaç farklı yöntem kullanabilirsiniz:
# Aktif paralel worker'ları görüntüle
psql -U postgres -c "
SELECT
pid,
query_start,
state,
wait_event_type,
wait_event,
LEFT(query, 80) as query_preview
FROM pg_stat_activity
WHERE backend_type = 'parallel worker'
ORDER BY query_start;
"
# pg_stat_statements ile paralel sorgu maliyetlerini analiz et
# Önce extension'ın yüklü olduğundan emin ol
psql -U postgres -d mydb -c "
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
"
psql -U postgres -d mydb -c "
SELECT
calls,
ROUND(mean_exec_time::numeric, 2) as avg_ms,
ROUND(total_exec_time::numeric, 2) as total_ms,
rows,
LEFT(query, 100) as query
FROM pg_stat_statements
WHERE mean_exec_time > 1000
ORDER BY mean_exec_time DESC
LIMIT 10;
"
Paralel Yürütme ve Fonksiyonlar
Kendi yazdığınız PostgreSQL fonksiyonları varsayılan olarak PARALLEL UNSAFE olarak işaretlenir. Bu, fonksiyonu kullanan sorgular paralel çalışamaz demektir. Güvenli fonksiyonlarınızı işaretleyerek bu kısıtlamayı aşabilirsiniz:
psql -U postgres -d mydb -c "
-- Fonksiyonu paralel güvenli olarak işaretle
CREATE OR REPLACE FUNCTION calculate_discount(price NUMERIC, rate NUMERIC)
RETURNS NUMERIC
LANGUAGE SQL
PARALLEL SAFE
AS $$
SELECT price * (1 - rate);
$$;
-- Mevcut fonksiyonu güncelle
ALTER FUNCTION calculate_discount(NUMERIC, NUMERIC) PARALLEL SAFE;
"
Paralel güvenlilik seviyeleri:
- PARALLEL SAFE: Paralel çalışabilir, global state’e dokunmaz
- PARALLEL RESTRICTED: Sadece leader process’te çalışabilir
- PARALLEL UNSAFE: Paralel yürütmeyi tamamen engeller
Donanım Kaynaklarıyla Uyumlu Ayar Yapımı
Paralel worker sayısını belirlerken birkaç faktörü göz önünde bulundurmanız gerekir.
Sunucu CPU çekirdeğine göre önerilen yapılandırma şöyle planlanabilir:
4 çekirdekli sunucu için:
- max_worker_processes: 8
- max_parallel_workers: 4
- max_parallel_workers_per_gather: 2
8 çekirdekli sunucu için:
- max_worker_processes: 16
- max_parallel_workers: 8
- max_parallel_workers_per_gather: 4
16 çekirdekli sunucu için:
- max_worker_processes: 24
- max_parallel_workers: 12
- max_parallel_workers_per_gather: 6
32+ çekirdekli sunucu için:
- max_worker_processes: 48
- max_parallel_workers: 24
- max_parallel_workers_per_gather: 8
Dikkat edilmesi gereken kritik nokta: max_parallel_workers_per_gather değerini çekirdek sayısının yarısını aşmamak. Paralel sorgular yoğun I/O ve CPU baskısı oluşturur, aşırı worker sayısı tüm sistemi yavaşlatabilir.
OLTP vs OLAP Workload Ayarları
İki farklı workload tipi için paralel sorgu ayarları ciddi farklılık gösterir.
OLTP (çok sayıda kısa sorgu) için:
# /etc/postgresql/15/main/postgresql.conf
# max_parallel_workers_per_gather = 0 # Paraleli kapat
# veya
# max_parallel_workers_per_gather = 1 # Minimal paralel
# OLTP'de paralel overhead genellikle zaman kaybettirir
# Kısa sorgular için paralel kurulum maliyeti çok yüksek
OLAP / Raporlama için:
# /etc/postgresql/15/main/postgresql.conf
# max_parallel_workers_per_gather = 8
# parallel_setup_cost = 200
# parallel_tuple_cost = 0.05
# min_parallel_table_scan_size = 2MB
# Büyük fact tabloları için tablo bazlı ayar
# ALTER TABLE fact_sales SET (parallel_workers = 8);
# ALTER TABLE fact_inventory SET (parallel_workers = 6);
JIT ile Paralel Sorguyu Birlikte Kullanmak
PostgreSQL 11+ ile gelen JIT (Just-In-Time) compilation, paralel sorgularla birlikte kullanıldığında önemli performans artışı sağlayabilir:
# JIT ayarlarını kontrol et
psql -U postgres -c "
SELECT name, setting
FROM pg_settings
WHERE name LIKE 'jit%';
"
# JIT'i etkinleştir (genellikle varsayılan açık)
# jit = on
# jit_above_cost = 100000
# jit_inline_above_cost = 500000
# jit_optimize_above_cost = 500000
Ancak JIT her zaman performans artışı sağlamaz. Kısa sorgularda JIT derleme süresi overhead oluşturur. Büyük analitik sorgularda ise jit_above_cost eşiğini düşürerek JIT’in daha sık devreye girmesini sağlayabilirsiniz.
Sık Karşılaşılan Sorunlar ve Çözümleri
Sorun 1: Paralel plan seçilmiyor
# Force parallel plan test et
psql -U postgres -d mydb -c "
SET force_parallel_mode = on;
EXPLAIN (ANALYZE)
SELECT COUNT(*) FROM büyük_tablo;
"
Eğer force_parallel_mode ile de paralel plan gelmiyorsa, tablonun parallel_workers = 0 olup olmadığını kontrol edin:
psql -U postgres -d mydb -c "
SELECT relname, reloptions
FROM pg_class
WHERE relname = 'büyük_tablo';
"
Sorun 2: Worker sayısı beklenden az
Planner, tablonun boyutuna göre dinamik olarak worker sayısını hesaplar. min_parallel_table_scan_size değerini düşürmek daha küçük tablolarda da paralel yürütme sağlar. Tablo başına parallel_workers ayarı ise bunu override eder.
Sorun 3: Paralel sorgular sistemi yavaşlatıyor
Çok yüksek max_parallel_workers_per_gather değeri, tek bir sorgunun tüm CPU’ları tüketmesine neden olabilir:
# Kullanıcı bazında kısıtlama
psql -U postgres -c "
ALTER ROLE rapor_kullanicisi SET max_parallel_workers_per_gather = 2;
"
# Veritabanı bazında kısıtlama
psql -U postgres -c "
ALTER DATABASE raporlama SET max_parallel_workers_per_gather = 4;
"
Sonuç
PostgreSQL’de paralel sorgu yürütme, doğru yapılandırıldığında büyük veri setleri üzerindeki analitik sorguların performansını dramatik biçimde iyileştirebilir. Ancak bu, dikkatli bir denge kurmayı gerektiren bir özelliktir.
Önemli çıkarımları şöyle özetleyelim:
- Her workload için aynı ayar işe yaramaz: OLTP ve OLAP ihtiyaçları birbirinden tamamen farklıdır
- Tablo bazlı parallel_workers: Global ayarları override eden bu özellik, granüler kontrol sağlar
- Donanım limitlerini aşmayın: Worker sayısını çekirdek sayısının yarısıyla sınırlamak iyi bir başlangıç noktasıdır
- Test edin: Session bazlı geçici ayarlarla production’a geçmeden önce ölçüm yapın
- İzleme şart:
pg_stat_activityvepg_stat_statementsile paralel sorgu davranışını düzenli takip edin
Paralel sorgu yürütme bir sihirli değnek değildir. I/O bound sorgularda CPU’yu ne kadar artırırsanız artırın depolama hızı darboğaz olmaya devam eder. CPU bound ve büyük tablo taramalarında ise bu özellik gerçek bir oyun değiştirici olabilir. Kendi ortamınızda ölçün, gözlemleyin ve ona göre ayarlayın.