PostgreSQL'de partitioning (bölümleme), büyük tabloları daha küçük, yönetilebilir fiziksel birimlere ayırarak veritabanı performansını ve yönetimini optimize etmek için kullanılan temel bir veri organizasyon yöntemidir. Bu strateji, mantıksal olarak tek bir tablo gibi görünen yapının, fiziksel olarak "child table" adı verilen alt tablolara bölünmesi ilkesine dayanır. Bu ayırma işlemi, belirli bir sütun veya sütun grubunun değerlerine göre gerçekleştirilir ve her bir bölüm, tanımlanan aralık, liste veya hash değerine uygun satırları barındırır.
Bölümleme mekanizmasının merkezinde, kullanıcı sorguları için tek bir giriş noktası sağlayan ana tablo (parent table) ve gerçek verilerin depolandığı alt tablolar (child tables) bulunur. PostgreSQL'in sorgu planlayıcısı, WHERE koşulunda belirtilen bölümleme anahtarı değerini analiz ederek, ilgili alt tabloları hariç tutabilir (partition pruning). Bu işlem, tam tablo taramalarını önleyerek I/O operasyonlarını ve bellek kullanımını önemli ölçüde azaltır. Temel avantajları arasında, arşivleme işlemlerinin hızlanması, indekslerin daha verimli kullanılması ve paralel sorgu yürütme için daha iyi fırsatların oluşması yer alır.
Uygulamada, bölümlemenin doğru şekilde yapılandırılması kritiktir. Bölüm anahtarının seçimi, veri erişim desenleri, veri hacmi ve sorgu yükü dikkate alınarak yapılmalıdır. Yanlış bir anahtar seçimi, veri dağılımının dengesiz olmasına ("partition skew") ve performansın beklenenin aksine düşmesine neden olabilir. Ayrıca, PostgreSQL'in native bölümlemesi, her bir bölümün kendi indekslerne, kısıtlamalarına ve depolama parametrelerine sahip olabilmesine olanak tanır, bu da ince ayar yapmayı mümkün kılar.
| Kavram | Tanım | Amacı |
|---|---|---|
| Ana Tablo (Parent Table) | Bölümlenmiş tablonun mantıksal tanımını içeren, veri barındırmayan şema nesnesi. | Sorgular için bir arayüz sağlamak ve bölümleme stratejisini tanımlamak. |
| Alt Tablo (Child/Partition Table) | Gerçek verilerin fiziksel olarak depolandığı, ana tabloya inheritance ile bağlı tablo. | Verileri fiziksel olarak ayırmak, yönetimi ve performansı iyileştirmek. |
| Bölümleme Anahtarı | Satırların hangi bölüme ait olduğunu belirleyen sütun veya sütun grubu. | Veri dağılımını ve sorgu optimizasyonunu yönlendirmek. |
Range ve List Partitioning
PostgreSQL'in sunduğu en yaygın bölümleme yöntemleri olan Range (Aralık) ve List (Liste) partitioning, verileri değer tabanlı mantıksal gruplara ayırır. Range partitioning, sürekli ve sıralanabilir bir veri alanını (tarih, sayısal ID, vb.) önceden tanımlanmış aralıklara böler. Bu yöntem, zaman serisi verileri veya artan sıralı kimlikler için idealdir. Örneğin, `sales_date` sütununa göre aylık veya yıllık bölümler oluşturulabilir. Her bölüm, `FROM 2024-01-01 TO 2024-02-01` gibi bir aralık tanımına sahiptir. PostgreSQL 14 ve sonrası ile gelen "NULLS LAST/FIRST" desteği ve çok sütunlu aralık anahtarları, bu yöntemin esnekliğini önemli ölçüde artırmıştır.
List partitioning ise, bölüm anahtarının aldığı ayrık değerlerin bir listesini kullanır. Belirli bir bölge kodu (`region_id`), kategori etiketi veya durum değeri gibi sütunlar için uygundur. Örneğin, bir müşteri tablosunu ülke kodlarına göre bölümlemek istenirse, `PARTITION OF parent_table FOR VALUES IN ('TR', 'US', 'DE')` şeklinde bölümler tanımlanabilir. Bu yöntemin avantajı, doğrudan ve açık bir eşleme sağlamasıdır; bir satırın hangi bölüme ait olduğu kesin bir listeden belirlenir. Bununla birlikte, `DEFAULT` bölümü tanımlanarak listede olmayan değerler için bir "catch-all" bölümü oluşturulabilir, ancak bu bölümün dikkatli yönetilmesi gereklidir.
Her iki yöntemin seçimi, veri modelinin doğasına ve sorgu desenlerine bağlıdır. Aralık bölümlemesi, aralık sorgularında (`BETWEEN`, `<`, `>`) mükemmel performans gösterirken, liste bölümlemesi eşitlik (`=`) veya `IN` sorgularında çok etkilidir. Bu yöntemlerin hibrit kullanımı da mümkündür; önce aralık, sonra liste ile alt bölümleme (subpartitioning) yapılarak daha granüler bir kontrol sağlanabilir. Bu karma strateji, veri hacmi çok büyük olduğunda ve birden fazla boyutta filtreleme yapıldığında faydalıdır.
| Özellik | Range Partitioning | List Partitioning |
|---|---|---|
| Anahtar Tipi | Sıralanabilir, sürekli (tarih, integer). | Ayrık, kategorik (kod, enum, metin). |
| Tanım Şekli | FOR VALUES FROM (min) TO (max) |
FOR VALUES IN (value_list) |
| İdeal Kullanım | Zaman serileri, sıralı ID'ler, aralık sorguları. | Coğrafi bölgeler, ürün kategorileri, durum kodları. |
| Veri Dağılımı | Zamana veya aralığa bağlı olarak dengeli/dengesiz olabilir. | Listedeki değerlerin sıklığına bağlıdır. |
| Esneklik | Yeni aralıklar eklenebilir. Mevcut bölümler bölünebilir. | Listeye yeni değerler eklenebilir veya DEFAULT kullanılır. |
Bölümlemenin başarısı, ilgili operasyonların doğru otomasyonuna bağlıdır. Yeni veri aralıkları veya kategori değerleri ortaya çıktıkça, bölümlerin dinamik olarak oluşturulması gerekir. PostgreSQL'de bu, genellikle zamanlanmış işler (cron jobs) veya tetikleyiciler (triggers) aracılığıyla `CREATE TABLE ... PARTITION OF ...` ve `ATTACH PARTITION` komutlarının çalıştırılmasıyla sağlanır. Özellikle `RANGE` bölümlemede, en son bölümün üzerine yazma riskini önlemek için, bölüm oluşturmanın veri eklemeden önce gerçekleştiğinden emin olmak kritik bir yönetim görevidir.
- Range Partitioning Senaryosu: Bir ölçüm tablosunda, her gün için ayrı bir bölüm (`measurements_y2024m01d15`) oluşturulur. Sorgu planlayıcısı, `WHERE timestamp BETWEEN '2024-01-15' AND '2024-01-16'` sorgusu geldiğinde yalnızca ilgili bölümü tarar.
- List Partitioning Senaryosu: Çok kiracılı (multi-tenant) bir SaaS uygulamasında, her müşteri (tenant) için ayrı bir bölüm (`data_tenant_xyz`) oluşturulur. `WHERE tenant_id = 'xyz'` sorgusu, sadece o müşterinin bölümüne erişir, veri güvenliği ve performansı artar.
- Hibrit Strateji: Önce yıla göre `RANGE` bölümleme, ardından her yıl bölümünü bölgeye göre `LIST` alt bölümlemeye tabi tutma. Bu, hem tarih hem de coğrafya bazlı filtrelemelerde hızlı sonuç verir.
Hash Partitioning ve Detaylar
Hash partitioning, verilerin bölümlere dağıtılması için deterministik bir hash fonksiyonu kullanan ve dağılımı dengelemeyi (load balancing) hedefleyen bir bölümleme türüdür. Bu yöntem, bölüm anahtarının değeri bir hash algoritmasından (genellikle PostgreSQL'in dahili hash fonksiyonu) geçirilir ve elde edilen hash değerinin modülü alınarak satırın ait olacağı bölüm belirlenir. Bu yaklaşım, aralık veya liste bölümlemesinde karşılaşılabilecek "partition skew" riskini azaltır ve verilerin bölümler arasında nispeten eşit dağılmasını sağlamayı amaçlar.
Hash bölümlemenin temel kullanım alanı, doğal bir sıralı veya kategorik gruplama anahtarı bulunmayan, ancak yüksek oranda paralel okuma/yazma işlemlerine tabi olan büyük tablolardır. Örneğin, oturum verileri, önbellek tabloları veya yük dengeli iş kuyrukları için ideal bir seçim olabilir. Bölüm sayısı, `PARTITION BY HASH (key_column)` ifadesi ile oluşturulurken, `CREATE TABLE ... PARTITION OF ... FOR VALUES WITH (MODULUS N, REMAINDER M)` şeklinde tanımlanır; burada `N` toplam bölüm sayısını, `M` ise 0 ile N-1 arasında bir bölüm indeksini temsil eder.
Hash bölümlemenin performans avantajları önemlidir. Veriler eşit dağıldığında, sorgu yükü bölümler arasında dengelenir, bu da paralel sorgu yürütme (`parallel sequential scan`) sırasında daha etkin kaynak kullanımı sağlar. Ancak, hash bölümlemenin en önemli sınırlaması, sorgu optimizasyonu bağlamındadır. Sorgu planlayıcısı, yalnızca bölüm anahtarı üzerinde doğrudan eşitlik (`=`) filtresi kullanıldığında partition pruning gerçekleştirebilir. Aralık sorguları (`BETWEEN`, `>`, `<`) veya `IN` listeleri gibi koşullar, hash bölümleme için etkin bir şekilde ele alınamaz ve tüm bölümlerin taranmasına yol açabilir.
Hash bölümleme stratejisini tasarlarken dikkat edilmesi gereken kritik bir nokta, bölüm sayısının (`MODULUS`) sabit kalması gerekliliğidir. Bölüm sayısını değiştirmek (artırmak veya azaltmak) tüm verilerin yeniden dağıtılmasını gerektirir, bu da maliyetli bir veri taşıma operasyonu anlamına gelir. Bu nedenle, ilk kurulumda gelecek yük artışını ve ölçeklenme ihtiyacını öngören, 2'nin katları gibi bir bölüm sayısı seçmek, gelecekteki yeniden bölümleme maliyetini azaltabilir. Ayrıca, seçilen anahtar sütununun yüksek kardinaliteye sahip olması, dağılımın daha uniform olmasını garantilemeye yardımcı olur.
-- Hash Partitioning Örneği
CREATE TABLE sensor_logs (
log_id BIGSERIAL,
sensor_id INTEGER NOT NULL,
log_data JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
) PARTITION BY HASH (sensor_id);
-- 4 bölümlük bir hash bölümleme şeması oluşturma
CREATE TABLE sensor_logs_p0 PARTITION OF sensor_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE sensor_logs_p1 PARTITION OF sensor_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE sensor_logs_p2 PARTITION OF sensor_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE sensor_logs_p3 PARTITION OF sensor_logs
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
-- Sorgu: Sadece belirli bir sensor_id'ye ait bölüm taranır (pruning çalışır).
-- SELECT * FROM sensor_logs WHERE sensor_id = 12345;
Performans Avantajları
PostgreSQL bölümlemesinin sağladığı en belirgin performans avantajı, partition pruning (bölüm budama) mekanizmasıdır. Bu mekanizma, sorgu planlayıcısının, WHERE koşulundaki bölümleme anahtarı değerini analiz ederek, ilgilenilen satırları içermeyen tüm bölümleri sorgu yürütme planından tamamen çıkarmasını sağlar. Sonuç olarak, fiziksel I/O operasyonları yalnızca gerekli bölümlerle sınırlanır ve bu da sorgu sürelerinde dramatik düşüşler sağlar. Özellikle zaman serisi verilerinde tarih bazlı aralık sorguları, pruning sayesinde saniyeler yerine milisaniyeler içinde tamamlanabilir.
Bölümleme aynı zamanda paralel sorgu yürütme için ideal bir zemin hazırlar. Her bir bölüm, bağımsız bir depolama birimi olarak ele alınabildiğinden, PostgreSQL'in paralel sıralı tarayıcıları (parallel sequential scan) her bölüm üzerinde aynı anda çalışabilir. Bu, büyük toplu veri analizi (OLAP) sorgularının performansını doğrudan artırır. Ayrıca, indeksler bölüm düzeyinde yönetildiği için, her bölümn kendi, daha küçük ve daha verimli B-Tree indeksi olur. Daha küçük indeksler, daha hızlı arama süreleri, daha az bellek kullanımı ve daha yüksek önbellek isabet oranları anlamına gelir.
Yönetimsel operasyonların hızlanması da kritik bir avantajdır. Büyük bir tabloda eski verileri silmek (`DELETE`), VACUUM üzerinde büyük bir yük oluşturur ve uzun kilitlenmelere neden olabilir. Bölümlenmiş bir tabloda ise, eski bir veri aralığını temizlemek için, ilgili bölümün basitçe `DROP TABLE` ile silinmesi veya `DETACH PARTITION` ile çıkarılıp arşivlenmesi yeterlidir. Bu işlemler, saniyeler içinde tamamlanabilen metaveri operasyonlarıdır ve tablonun geri kalanını etkilemez. Benzer şekilde, yeni veri aralıkları için bölüm eklemek de anında gerçekleşir.
Performans optimizasyonunun bir diğer boyutu, depolama katmanıdır. Farklı bölümler, erişim sıklıklarına göre farklı depolama türlerine (SSD vs. HDD) yerleştirilebilir. Sık erişilen güncel bölümler hızlı SSD'lerde, nadiren sorgulanan arşiv bölümleri ise daha ucuz HDD'lerde tutulabilir. PostgreSQL'in tablespace özelliği bu esnekliği sağlar. Bu sayede, toplam depolama maliyeti optimize edilirken, sıcak verilere erişim performansı da korunmuş olur.
Yönetim ve Planlama Stratejileri
Etkili bir bölümleme stratejisi, sadece teknik uygulama değil, aynı zamanda kapsamlı bir planlama ve sürekli yönetim gerektirir. Planlamanın ilk ve en kritik adımı, bölümleme anahtarının doğru seçilmesidir. Bu seçim, uygulamanın birincil sorgu desenlerine dayanmalıdır. WHERE cümlesinde en sık filtreleme yapılan sütun, genellikle en güçlü adaydır. Yanlış anahtar seçimi, sorgularda partition pruning'ın devreye girmesini engelleyerek bölümlemenin tüm faydalarını ortadan kaldırabilir.
Bölüm boyutu ve yaşam döngüsü yönetimi otomasyonu zorunludur. Range bölümlemede, bölümlerin (örneğin, günlük veya aylık) önceden ve düzenli olarak oluşturulması, veri ekleme sırasında hataların önüne geçer. Benzer şekilde, veri saklama politikaları (retention policies) gereği, eski bölümlerin düzenli olarak arşivlenmesi veya silinmesi gerekir. Bu süreçler, genellikle pg_cron veya dış zamanlayıcılar kullanılarak otomatikleştirilmelidir. PostgreSQL 13 ile gelen `ALTER TABLE ... DETACH PARTITION CONCURRENTLY` özelliği, bir bölümü ana tablodan ayırırken eşzamanlı erişime izin vererek bu operasyonları daha da güvenli hale getirmiştir.
Bölümlenmiş tablolarda ikincil indekslerin tasarımı da dikkat gerektirir. Global bir indeks oluşturulamaz; her indeks bölüm bazlıdır. Bu, bölüm anahtarı dışındaki sütunlarda sık filtreleme yapılıyorsa, her bölümde aynı indeksin oluşturulması gerektiği anlamına gelir. İndeks oluşturma işlemleri, yeni bölümler için otomasyon script'lerine dahil edilmelidir. Ayrıca, `UNIQUE` kısıtlamaları tanımlanırken, kısıtlamanın bölümleme anahtarını da içermesi gerekebilir, aksi takdirde kısıtlama yalnızca bölüm içinde geçerli olur.
Son olarak, izleme ve bakım süreklilik arz eder. `pg_stat_user_tables` sistem kataloğu bölüm düzeyinde sorgulanarak, her bölümün boyutu, tarama sayısı ve indeks kullanım istatistikleri takip edilmelidir. Bu veriler, veri dağılımındaki dengesizlikleri (skew) ve performans sorunlarını erken tespit etmeye yarar. VACUUM ve ANALYZE işlemleri de bölüm bazında planlanabilir, böylece bakım pencereleri daha kısa ve daha az yıkıcı olur. Doğru planlanmış ve yönetilen bir bölümleme mimarisi, veritabanının ölçeklenebilirliğini ve uzun vadeli sürdürülebilirliğini garanti altına alan temel taşıdır.