Partitioning (bölümleme)
Tablo, bir sütuna (çoğunlukla tarih) göre fiziksel parçalara ayrılır. WHERE event_date = '2024-12-01' yazdığında motor sadece o günün bölümünü okur (partition pruning).
-- BigQuery CREATE TABLE analytics.events PARTITION BY DATE(event_time) CLUSTER BY user_id, event_type AS SELECT * FROM raw.events;
Clustering / sıralama anahtarı
Bölüm içindeki veriyi belirli sütunlara göre sıralı tutar; filtrelenen bloklar atlanır (Snowflake micro-partition, Redshift sort key, Delta Z-order).
Dosya formatları
| Format | Yapı | Not |
|---|---|---|
| CSV / JSON | Satır, metin | İnsan okur, büyük ve yavaş |
| Parquet / ORC | Sütun tabanlı, sıkıştırılmış, şemalı | Analitik için standart |
| Avro | Satır tabanlı, şemalı | Akış ve şema evrimi |
Maliyet bilinci
- Sütun tabanlı ambarlarda SELECT * taranan veri miktarını (ve faturayı) katlar.
- Partition filtresi olmayan sorguları engelleyen ayarlar (require partition filter) kullan.
- Sık kullanılan ağır sorguları materialized view veya özet tabloya çevir.
Derinleş: bölüm budamasını gerçekten çalıştırmak
Bölüm budaması (partition pruning), sorgu motorunun filtreye bakarak okunması gereksiz bölümleri hiç açmamasıdır. Bulut ambarlarında taranan bayt faturalandığı için bu doğrudan para demektir: 3 yıllık olay tablosunda bir günlük sorgu, budama çalışırsa verinin yaklaşık 1/1000'ini okur.
Budamayı bozan yazımlar
Budama, filtre bölümleme sütununun kendisi üzerinde ve sabit değerlerle yazıldığında çalışır. Sütuna fonksiyon uygularsan motor çoğu durumda hangi bölümlere bakacağını çıkaramaz:
-- Budama dostu değil: sütun fonksiyonun içinde
SELECT COUNT(*) AS adet, ROUND(SUM(amount)) AS toplam
FROM bank_txns
WHERE strftime('%Y-%m', txn_time) = '2024-03';
-- Budama dostu: sütun yalın, sınırlar sabit
SELECT COUNT(*) AS adet, ROUND(SUM(amount)) AS toplam
FROM bank_txns
WHERE txn_time >= '2024-03-01' AND txn_time < '2024-04-01';
İki sorgu da aynı sonucu (3.510 işlem) verir; ama bölümlenmiş bir tabloda ilki tüm bölümleri tarar. Aynı ilke indeksler için de geçerlidir (sargable koşul). Benzer tuzaklar: CAST(tarih AS STRING) LIKE '2024%', alt sorgudan gelen dinamik tarih (bazı motorlar budayamaz), bölüm sütununu hiç filtrelememek.
Bölüm büyüklüğünü seçmek
SELECT COUNT(*) AS satir,
COUNT(DISTINCT date(txn_time)) AS gunluk_bolum,
COUNT(DISTINCT strftime('%Y-%m', txn_time)) AS aylik_bolum,
ROUND(1.0 * COUNT(*) / COUNT(DISTINCT date(txn_time))) AS gun_basina_satir
FROM bank_txns;
Ilgaz Bank'ta 2024'te 42.284 işlem var: günlük bölümlemede 366 bölüm ve bölüm başına yaklaşık 116 satır. Bu, gerçek bir ambar için fazla küçük bir bölümdür; her bölümün dosya ve meta veri maliyeti olduğundan çok sayıda küçük bölüm sorguyu yavaşlatır. Kural olarak bölüm başına yüzlerce MB – birkaç GB hedeflenir; küçük tablolarda aylık bölüm ya da hiç bölümlememek daha iyidir.
| Teknik | Ne yapar? | İyi olduğu filtre |
|---|---|---|
| Partition | Veriyi fiziksel parçalara böler | Tarih aralığı |
| Clustering / sort key | Bölüm içinde sıralı tutar, blok atlar | Yüksek kardinaliteli eşitlik (user_id) |
| İndeks (OLTP) | Satır konumuna hızlı erişim | Az sayıda satır döndüren arama |
| Materialized view | Sonucu önceden hesaplar | Sık tekrarlanan ağır toplama |
Bölüm eleme (partition pruning): gerçek çıktı
bank_txns verisini PostgreSQL'de çeyreklere bölünmüş bir tabloya aldık (tx_bolum). Aşağıdakiler PostgreSQL 16.4 üzerinde üretilen gerçek planlardır.
Bölüm sütunu yalın: tek bölüm okunur
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF) SELECT COUNT(*) FROM tx_bolum WHERE txn_time >= '2024-11-01' AND txn_time < '2024-12-01';
Gerçek çıktı (PostgreSQL 16.4, PGlite)
Aggregate (cost=270.09..270.10 rows=1 width=8) (actual rows=1 loops=1)
Buffers: shared hit=100
-> Seq Scan on tx_2024q4 tx_bolum (cost=0.00..261.09 rows=3600 width=0) (actual rows=3590 loops=1)
Filter: ((txn_time >= '2024-11-01 00:00:00'::timestamp without time zone) AND (txn_time < '2024-12-01 00:00:00'::timestamp without time zone))
Rows Removed by Filter: 7149
Buffers: shared hit=100
Sütun fonksiyonda: bütün bölümler okunur
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF) SELECT COUNT(*) FROM tx_bolum WHERE EXTRACT(MONTH FROM txn_time) = 11;
Gerçek çıktı (PostgreSQL 16.4, PGlite)
Aggregate (cost=1027.84..1027.85 rows=1 width=8) (actual rows=1 loops=1)
Buffers: shared hit=392
-> Append (cost=0.00..1027.32 rows=211 width=0) (actual rows=3590 loops=1)
Buffers: shared hit=392
-> Seq Scan on tx_2024q1 tx_bolum_1 (cost=0.00..251.73 rows=52 width=0) (actual rows=0 loops=1)
Filter: (EXTRACT(month FROM txn_time) = '11'::numeric)
Rows Removed by Filter: 10382
Buffers: shared hit=96
-> Seq Scan on tx_2024q2 tx_bolum_2 (cost=0.00..254.42 rows=52 width=0) (actual rows=0 loops=1)
Filter: (EXTRACT(month FROM txn_time) = '11'::numeric)
Rows Removed by Filter: 10495
Buffers: shared hit=97
-> Seq Scan on tx_2024q3 tx_bolum_3 (cost=0.00..259.02 rows=53 width=0) (actual rows=0 loops=1)
Filter: (EXTRACT(month FROM txn_time) = '11'::numeric)
Rows Removed by Filter: 10668
Buffers: shared hit=99
-> Seq Scan on tx_2024q4 tx_bolum_4 (cost=0.00..261.09 rows=54 width=0) (actual rows=3590 loops=1)
Filter: (EXTRACT(month FROM txn_time) = '11'::numeric)
Rows Removed by Filter: 7149
Buffers: shared hit=100
Aynı sonuç, ama 100 yerine 392 sayfa.
Alt sorgudan gelen tarih: çalışma anında eleme
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF) SELECT COUNT(*) FROM tx_bolum WHERE txn_time >= (SELECT MAX(txn_time) - INTERVAL '30 days' FROM bank_txns);
Gerçek çıktı (PostgreSQL 16.4, PGlite)
Aggregate (cost=1945.83..1945.84 rows=1 width=8) (actual rows=1 loops=1)
Buffers: shared hit=491
InitPlan 1 (returns $0)
-> Aggregate (cost=919.55..919.56 rows=1 width=8) (actual rows=1 loops=1)
Buffers: shared hit=391
-> Seq Scan on bank_txns (cost=0.00..813.84 rows=42284 width=8) (actual rows=42284 loops=1)
Buffers: shared hit=391
-> Append (cost=0.00..991.02 rows=14095 width=0) (actual rows=3462 loops=1)
Buffers: shared hit=491
-> Seq Scan on tx_2024q1 tx_bolum_1 (cost=0.00..225.78 rows=3461 width=0) (never executed)
Filter: (txn_time >= $0)
-> Seq Scan on tx_2024q2 tx_bolum_2 (cost=0.00..228.19 rows=3498 width=0) (never executed)
Filter: (txn_time >= $0)
-> Seq Scan on tx_2024q3 tx_bolum_3 (cost=0.00..232.35 rows=3556 width=0) (never executed)
Filter: (txn_time >= $0)
-> Seq Scan on tx_2024q4 tx_bolum_4 (cost=0.00..234.24 rows=3580 width=0) (actual rows=3462 loops=1)
Filter: (txn_time >= $0)
Rows Removed by Filter: 7277
Buffers: shared hit=100
PostgreSQL değeri planlama sırasında bilmiyor, ama çalışırken bölümleri eliyor. Bu yüzden diğer bölümler never executed yazıyor. Motordan motora değişen konu budur: BigQuery belgesine göre alt sorgu sonucuyla karşılaştırma bölüm elemez, tarih sabit bir ifade olmalıdır.
BigQuery bölüm kuralları (belgeden)
- Filtre bölüm sütunu üzerinde olmalı, sütun karşılaştırmanın bir tarafında yalın kalmalı.
- Sütunu başka bir sütunla ya da alt sorguyla karşılaştırmak eleme yapmaz.
- Başka sütunlarla
ANDile birleşebilir,ORelemeyi bozar. - Bazı yerleşik zaman fonksiyonları desteklenir, diğer fonksiyonlar ve karmaşık matematik tam tarama ister.
- Elemeyi doğrulamak için kuru çalıştırma (dry run) ile tahmini baytı kontrol et.
SQLite'ın bölümleme özelliği yok. Bu yüzden görevlerde mantığı aralık koşulu ve indeksle çalıştırıyoruz, gerçek bölüm davranışını yukarıdaki çıktılardan okuyoruz.