# PostgreSQL tablo partitioning'e sıfır kesinti ile nasıl geçerim?

> `created_at` üzerinde aylık RANGE declarative partitioning kurun, geçmişi batch'lerle backfill edip isimleri tek transaction'da takaslayın.

- Soruldu: 2026-06-03
- Yanıtlandı: 2026-06-06
- Soran: Furkan
- Etiketler: veritabani, postgresql, olcekleme
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/postgresql-tablo-partitioning-sifir-kesinti-ile-gecis/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** SaaS uygulamamızda kullanıcı hareket loglarını tuttuğumuz `audit_logs` tablosu aylık 50M satır büyüyor; tablo büyüdükçe basit `select`'ler bile yavaşladı. PostgreSQL'in declarative partitioning'iyle bu tabloyu `created_at`'e göre aylık partition'lara ayırmak istiyorum.

Canlı veriyi sıfır kesinti ile bu yeni yapıya nasıl taşırım? İndeksler ve foreign key bağımlılıkları bundan nasıl etkilenir?


Kısa cevap: Aylık 50M satır gerçekten büyük — partitioning burada doğru hamle. `created_at` üzerinden **aylık RANGE declarative partitioning** kullanın; geçişi sıfır kesinti için aşamalı yapın.

Önce şu sağlamayı yapmakta fayda var: 50M/ay gerçek bir hacim, "verin gerçekten büyük mü yoksa kötü mü modellenmiş" sorusunun cevabı burada net — gerçekten büyük. Partition mantıklı.

1. **Native declarative RANGE ile parçalayın.** Yeni bir partitioned parent (`PARTITION BY RANGE (created_at)`) oluşturun ve aylık partition'ları `ATTACH` edin. Eski monolitik tablonun aksine, sorgu sadece ilgili aya değer; partition pruning sayesinde basit `select`'ler tekrar hızlanır.
2. **Geçişi sıfır kesinti ile yapın.** Yeni parent'ı kurun, partition'ları bağlayın, sonra geçmiş veriyi **batch'ler hâlinde** (örn. 50k'lık dilimlerle, off-peak) partition'lara backfill edin. Bu sırada uygulama yazmaya devam etsin: ya yeni yapıya yönlendiren bir trigger/view kullanın ya da hem eskiye hem yeniye **dual-write** yapın. Backfill bitince isimleri **tek bir transaction içinde** takaslayın — kesinti yok.
3. **İndeksler partition başına oluşur.** Parent üstünde tanımladığınız indeksi Postgres her partition'a otomatik yayar (propagate). Yani global tek bir B-tree değil, her ay için ayrı indeks; bu aslında işinize yarar, eski ayların indeksi sıcak veriyi yormaz.
4. **UNIQUE ve FK kuralını unutmayın.** Partitioned tabloda global bir UNIQUE/PK, **partition anahtarını içermek zorundadır** — yani PK'in `(id, created_at)` olur. Tabloya referans veren FK'ler de bundan etkilenir; modern PG'de partitioned tabloya FK kurmak çalışır ama sürümünüzü doğrulayın.
5. **Yaşam döngüsünü `pg_partman`'a bırakın.** Gelecek ayların partition'larını otomatik oluşturması için `pg_partman` kullanın; retention için eski partition'ları `DETACH` + `DROP` ile silin — `DELETE` taramasından çok daha ucuz, anında.

**Sonuç:** `created_at` üzerinde declarative RANGE, batch'li backfill, her unique key'de partition anahtarı, yaşam döngüsü için `pg_partman`. Geçişi dual-write + tek transaction'lık isim takasıyla kurarsanız canlı sistem hiç durmaz. Bu tür DB derinliklerine girmeden önce ölçeklenme ihtiyacının gerçekten "büyük veri" mi olduğunu netleştirin; bu vakada öyle.

## İlgili Yazılar

- [PostgreSQL Her Şeye Yeter mi?](https://sade.dev/tr/journal/postgresql-her-seye-yeter-mi) — sade.dev
- [Zaman serisi verisi için TimescaleDB mi, InfluxDB mu?](https://www.muhammetsafak.com.tr/sor-bakalim/zaman-serisi-verisi-timescaledb-mi-influxdb-mu/) — Sor Bakalım
- [Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/sadece-pending-satirlari-taranan-bir-kuyruk-tablosunda-partial-index-kullanmali-miyim/) — Sor Bakalım
- [WAL arşivleme ve PITR ile felaketten saniyeler öncesine nasıl dönerim?](https://www.muhammetsafak.com.tr/sor-bakalim/veritabani-pitr-ve-wal-arsivleme-ile-felaket-oncesine-donmek/) — Sor Bakalım
