Eski PostgreSQL Projelerini Okumak: 13'ten 17'ye Önemli Değişiklikler
14'ten 17'ye geçen bir projeyi bozan şey yeni özellikler değil, kaldırılan ve taşınanlardır: pg_start_backup 15'te, public schema'daki CREATE yetkisi 15'te, pg_stat_bgwriter'ın checkpoint kolonları 17'de gitti. Aynı SQL dosyası 13–17'de çalıştırılarak multirange, MERGE, SQL/JSON, JSON_TABLE ve builtin collation'ın geldiği sürümler, pg_upgrade'in public yetkisini taşıması ve destek dışı kalan 14'ten çıkış.
Sürüm notu: Bu yazıdaki örnekler resmî Docker image'larıyla PostgreSQL 13.23, 14.25, 15.20, 16.16 ve 17.12 üzerinde, aynı SQL dosyası her sürümde çalıştırılarak denenmiştir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) bir süredir eski bir tedarikçi entegrasyonunu da devraldı. Entegrasyonun veritabanı PostgreSQL 14'te çalışıyor ve repo'sunda şu üç parça var: gece yedeğini başlatan bir script satırı, bir izleme dashboard'unun sorgusu ve import job'unun ara tablosu. Uygulama kendi rolüyle bağlanıyor, superuser değil.
SELECT pg_start_backup('nightly'); /* yedek script'i */
SELECT checkpoints_timed FROM pg_stat_bgwriter; /* dashboard */
CREATE TABLE public.probe_scratch (id int); /* import job'u, uygulama rolüyle */
Ekip entegrasyonu yeni kurulmuş bir PostgreSQL 17 sunucusuna, şemayı migration'larla yeniden oluşturarak taşıyacak. Üç satırdan kaçı 17'de aynen çalışır?
ERROR: function pg_start_backup(unknown) does not exist
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
ERROR: column "checkpoints_timed" does not exist
ERROR: permission denied for schema public
Hiçbiri. Üçü de 14'te çalışıyordu; ikisi 15'te, biri 17'de değişti. Eski bir projeyi devraldığında ya da birkaç sürüm atlayarak upgrade ettiğinde karşına çıkacak olan bu: sürümler arasında kaldırılan, adı değişen ve yeni gelen şeyler. Bu yazı 14'ten 17'ye her sürümün önemli değişikliklerini, eski kodda gördüğünde ne anlama geldiğiyle birlikte özetliyor. Kaynak her sürümün resmî release notes'u; her madde, aşağıdaki SQL dosyası beş sürümde de çalıştırılarak denendi.
Destek Takvimi ve PostgreSQL 14'ün Sonu
PostgreSQL her yıl bir major sürüm çıkarır ve her major sürüm beş yıl boyunca minor sürümlerle (güvenlik ve hata düzeltmeleri) desteklenir. Takvim postgresql.org'un sürüm politikası sayfasından:
| Sürüm | İlk yayın | Destek sonu | Son minor (2026-11-14) |
|---|---|---|---|
| 13 | 2020-09-24 | 2025-11-13 | 13.23 |
| 14 | 2021-09-30 | 2026-11-12 | 14.25 |
| 15 | 2022-10-13 | 2027-11-11 | 15.20 |
| 16 | 2023-09-14 | 2028-11-09 | 16.16 |
| 17 | 2024-09-26 | 2029-11-08 | 17.12 |
| 18 | 2025-09-25 | 2030-11-14 | 18.7 |
14.25, 12 Kasım 2026'da 18.7 ile birlikte çıktı ve 14'ün son sürümü oldu; aynı duyuru 52 güvenlik açığının kapatıldığını söylüyor. Bundan sonra 14 için çıkacak bir açık kapatılmayacak. 13 ise bir yıldır destek dışı. Bu iki sürümde çalışan bir sistem upgrade listesinin en üstünde olmalı. Hedef olarak 17 yerine doğrudan 18'i seçmek, bir sonraki zorunlu upgrade'i bir yıl daha ileri atar; 14'teki pg_upgrade notları burada da geçerli.
Deneme Düzeneği
db/15-versions.sql boş bir sunucuda çalışan küçük bir dosya: iki satırlık bir books tablosu, bir fiyat güncelleme tablosu ve her özellik için tek satırlık bir deneme. Aynı dosyayı her sürümde, geçici bir container'da çalıştırdık:
foreach ($v in 13, 14, 15, 16, 17) {
docker run -d --name pg-old -e POSTGRES_PASSWORD=<password> postgres:$v-alpine
Start-Sleep 5
docker cp db/15-versions.sql pg-old:/tmp/15-versions.sql
docker exec pg-old psql -U postgres -X -f /tmp/15-versions.sql > "15-$v.out" 2>&1
docker rm -f pg-old
}
Sonuçların özeti; hücrede o sürümde aldığın sonuç var:
| Deneme | 13 | 14 | 15 | 16 | 17 |
|---|---|---|---|---|---|
datemultirange(...) | hata | çalışır | çalışır | çalışır | çalışır |
SHOW password_encryption | md5 | scram-sha-256 | scram-sha-256 | scram-sha-256 | scram-sha-256 |
MERGE | hata | hata | çalışır | çalışır | çalışır |
Uygulama rolüyle CREATE TABLE public.... | çalışır | çalışır | hata | hata | hata |
UNIQUE NULLS NOT DISTINCT | hata | hata | çalışır | çalışır | çalışır |
pg_start_backup() | çalışır | çalışır | hata | hata | hata |
Publication'da kolon listesi ve WHERE | hata | hata | çalışır | çalışır | çalışır |
JSON_OBJECT, IS JSON | hata | hata | hata | çalışır | çalışır |
JSON_TABLE, MERGE ... RETURNING | hata | hata | hata | hata | çalışır |
pg_stat_bgwriter.checkpoints_timed | çalışır | çalışır | çalışır | çalışır | hata |
provider = builtin collation | hata | hata | hata | hata | çalışır |
Tabloda iki tür satır var: yeni gelen özellikler (eski sürümde syntax hatası) ve kaldırılan ya da taşınan şeyler (yeni sürümde hata). Upgrade'de seni ikinci tür bozar; birinci tür ise eski kodda neden dolambaçlı bir çözüm gördüğünü açıklar.
PostgreSQL 14: Multirange, Pipeline Mode ve SCRAM Varsayılanı
14 ile her range tipinin bir multirange karşılığı geldi: birbirine bitişik olmayan aralıkları tek bir değerde tutmak için. 04'teki kampanya dönemleri gibi parçalı bir takvim artık tek kolonda:
SELECT datemultirange(daterange('2026-11-01', '2026-11-10'), daterange('2026-11-20', '2026-12-01')) AS campaign_days;
campaign_days
---------------------------------------------------
{[2026-11-01,2026-11-10),[2026-11-20,2026-12-01)}
13'te aynı satır function datemultirange(daterange, daterange) does not exist veriyor. Eski kodda daterange[] kolonu ve aralıkları uygulamada birleştiren kod görürsen sebebi bu. Npgsql 6'dan beri multirange'i NpgsqlRange<T>[] olarak eşliyor.
14'ün libpq'ya (PostgreSQL'in C istemci kütüphanesi; psql ve birçok sürücü onu kullanır) eklediği pipeline mode, birden çok sorguyu cevabını beklemeden göndermeyi sağlıyor. Bu yenilik libpq kullanan istemciler için; Npgsql kendi protokol katmanını yazdığı için batching'i zaten destekliyordu (11'deki NpgsqlBatch). Bir de varsayılan: password_encryption 14 ile md5'ten scram-sha-256'ya geçti. 13'ten taşınan role'lerin parolaları hâlâ md5 olabilir; 14. yazıdaki kontrol sorgusu bunu gösterir.
PostgreSQL 15: MERGE, public Schema ve Yedekleme Fonksiyonları
15'in en görünür yeniliği standart MERGE; 04'te ON CONFLICT ile farkını görmüştük. Eski kodda MERGE yerine ON CONFLICT ya da "önce UPDATE, satır yoksa INSERT" görmek normal; çoğu zaman yeniden yazmaya gerek yok, çünkü concurrent yazmada ON CONFLICT daha güvenli.
Upgrade'de asıl etkisi olan değişiklik public schema: 15'ten itibaren herkesin orada nesne oluşturma yetkisi kaldırıldı ve schema'nın sahibi veritabanının sahibi oldu. Tahmindeki üçüncü hata bu. Release notes, mevcut veritabanları için bu yetkiyi elle kaldırmayı öneriyor; çünkü pg_upgrade eski yetkiyi taşıyor. 14'ten 18'e pg_upgrade ile denedik (db/15-ops/public-schema.sh): public'in yetkisi {postgres=UC/postgres,=UC/postgres} olarak kaldı ve uygulama rolü tablo oluşturabildi. template1 de taşındığı için upgrade'den sonra oluşturduğumuz yeni bir veritabanında bile aynı sonuç çıktı. Yani aynı uygulama, upgrade edilmiş sunucuda çalışırken yeni kurulan test ortamında hata verebilir. Eski bir script'te GRANT CREATE ON SCHEMA public TO PUBLIC görürsen, birisi 15'in davranışını geri almış demektir; neden gerektiğini sor ve uygulamaya kendi schema'sını ver.
Yedek tarafında 15, uzun süredir deprecated olan exclusive backup modunu kaldırdı ve pg_start_backup()/pg_stop_backup()'ın adlarını pg_backup_start()/pg_backup_stop() yaptı. Tahmindeki ilk hata bu. Elle yazılmış bir yedek script'i görürsen onu 14'teki gibi pg_basebackup'a ya da bir yedekleme aracına taşı. Diğer iki yenilik: UNIQUE NULLS NOT DISTINCT ile NULL'ları da tekrar sayan unique constraint (eskiden partial index ve coalesce hileleri gerekiyordu) ve logical replication publication'larında kolon listesi ile WHERE filtresi. Bunun ayrıntıları Veritabanı Performansı ve Scaling serisinde; bir not: filtre kolonu replica identity'de değilse o tabloda UPDATE ve DELETE reddediliyor, denemede bunu da gördük.
PostgreSQL 16: SQL/JSON Constructor'ları ve Standby'dan Logical Replication
16, SQL standardının JSON constructor'larını getirdi: JSON_OBJECT, JSON_ARRAY, JSON_OBJECTAGG, JSON_ARRAYAGG ve IS JSON kontrolü:
SELECT JSON_OBJECT('isbn': isbn, 'title': title) AS doc FROM books WHERE book_id = 1;
SELECT '{"pages": 412}' IS JSON OBJECT AS is_json;
doc
----------------------------------------------
{"isbn" : "9780441013593", "title" : "Dune"}
is_json
---------
t
Eski kodda bunların yerine json_build_object ve json_agg göreceksin; ikisi de hâlâ çalışıyor ve değiştirmeye gerek yok. IS JSON ise metin kolonlarına JSON doğrulayan bir CHECK constraint yazmayı kolaylaştırıyor. Operasyon tarafında 16, standby sunucudan logical decoding yapmaya izin verdi: logical replication ya da CDC (Change Data Capture; veritabanındaki değişiklikleri akış olarak dışarı aktarma) için primary'yi yormadan replica'dan okumak. pg_stat_io view'ı da 16 ile geldi; 01'deki I/O sorularına bu view ile cevap verebilirsin.
PostgreSQL 17: JSON_TABLE, MERGE RETURNING, VACUUM ve Builtin Collation
17, SQL/JSON tarafını JSON_TABLE ile tamamladı (05'te kullandık) ve MERGE'e RETURNING ekledi. merge_action() her satırın hangi işlemle değiştiğini söylüyor:
MERGE INTO books b USING price_updates u ON b.book_id = u.book_id
WHEN MATCHED THEN UPDATE SET price = u.price + 1
RETURNING merge_action(), b.book_id, b.price;
merge_action | book_id | price
--------------+---------+-------
UPDATE | 2 | 42.50
UPDATE | 3 | 13.00
Eski kodda jsonb_to_recordset ya da jsonb_array_elements ile yazılmış dönüşümler JSON_TABLE'ın işini görüyor; çalışıyorsa dokunma. 14. yazıda gördüğümüz incremental backup da 17 ile geldi.
Görünmeyen ama önemli değişiklik VACUUM'un (dead tuple'ların yerini yeniden kullanıma açan bakım komutu, 08) belleği: 17, dead tuple listesi için yeni bir veri yapısına geçti, daha az bellek kullanıyor ve maintenance_work_mem 1 GB'tan büyük verildiğinde eskiden sessizce 1 GB'ta kalan sınır kalktı. 08'deki büyük tablo VACUUM'ları için bu, index'lerin daha az sayıda geçişle temizlenmesi demek. 03'te gördüğümüz builtin collation provider da 17'nin: işletim sisteminin libc'sinden bağımsız, sürümden sürüme sıralaması değişmeyen C.UTF-8.
17'de kırılan şey izleme sorguları: checkpoint sayaçları pg_stat_bgwriter'dan yeni pg_stat_checkpointer view'ına taşındı (01'de bu view'ı kullanmıştık). Tahmindeki dashboard sorgusu bu yüzden bozuldu; checkpoints_timed'ın yeni adı pg_stat_checkpointer.num_timed. Grafana ya da başka bir izleme aracının hazır dashboard'larını upgrade öncesi yeni sürümle dene.
Eski Kodda Görürsen Ne Demek?
serial ve bigserial: IDENTITY'den önceki yöntem (03). Çalışır, ama sequence'in sahipliği ve yetkileri ayrı yönetilir; yeni tablolarda GENERATED ... AS IDENTITY kullan.
CREATE EXTENSION "uuid-ossp" ya da yalnızca UUID (her sistemde benzersiz olacak şekilde üretilen 128 bitlik kimlik) için pgcrypto: 13'ten beri gen_random_uuid() çekirdekte, 18'de uuidv7() da var. Denemedeki core_uuid satırı 13'ten itibaren her sürümde t döndü.
GRANT CREATE ON SCHEMA public, pg_start_backup, pg_stat_bgwriter'daki checkpoint kolonları: yukarıdaki üç kırılma. wal_keep_segments ayarı: 13'te wal_keep_size oldu, eski bir configuration dosyasında görürsen sunucu açılmaz.
md5 parolalar ve pg_hba.conf'ta md5 yöntemi: 14'ten beri varsayılan SCRAM (Salted Challenge Response Authentication Mechanism; parolayı ağda açık göndermeden doğrulayan yöntem), 18'de md5 deprecated (14. yazı).
Üretimde Dikkat
Birden çok sürüm atlıyorsan arada kalan her sürümün release notes'undaki "Migration to Version N" bölümünü oku; uyumsuzluklar orada toplanıyor. Bu yazıdaki gibi bir deneme dosyası, uygulamanın kullandığı fonksiyonları ve izleme sorgularını hedef sürümde birkaç dakikada kontrol eder.
Upgrade'i önce bir kopyada pg_upgrade --check ile dene, sonra uygulamanın testlerini ve izleme sorgularını yeni sürüme karşı çalıştır. 14'ten çıkan bir sistemde ilk iş yedek script'lerine ve public schema yetkilerine bakmak.
Driver sürümlerini unutma: eski bir Npgsql, yeni sunucunun tiplerini (multirange gibi) bilmeyebilir. Sunucuyu yükseltmeden önce driver'ı güncel tut.
Sık Düşülen Tuzaklar
Yalnızca "yeni özellikler" listesini okuyup kaldırılanları atlamak: upgrade'i bozan, yeni gelenler değil, kaldırılan ve adı değişenler.
Test ortamını yeni kurup production'ı pg_upgrade ile taşımak: public schema örneğindeki gibi iki ortam farklı davranır. Mümkünse test ortamını da aynı yöntemle kur.
Destek dışı bir sürümde "çalışıyor, dokunma" demek: 14 artık güvenlik düzeltmesi almıyor; bir sonraki kritik açık kapanmayacak.
Küçük Bir Deneme
★☆☆ 15-versions.sql'e SELECT 'Dune' IS JSON ve SELECT pg_backup_start('nightly') satırlarını ekle; hangi sürümlerde ne döndüğüne bak.
★★☆ Deneme dosyasına, Bookstore'un kendi raporlarında kullandığı üç sorguyu ekle ve 13–17 arasında çalıştır. Hangi sürümde ilk kez çalışıyorlar?
★★★ public-schema.sh'ı çalıştır, sonra upgrade edilmiş cluster'da bookstore ve template1 için REVOKE CREATE ON SCHEMA public FROM PUBLIC uygula. Yeni oluşturduğun bir veritabanında uygulama rolü artık ne alıyor? template1'i düzeltmeyi neden unutmamak gerekiyor?
Bir sonraki yazıda bu serinin tabanı olan PostgreSQL 18'in yeniliklerini topluca ele alıyoruz: asynchronous I/O, skip scan, uuidv7(), virtual generated column, RETURNING OLD/NEW ve temporal constraint'ler, her biri için "17'de bunu nasıl yapıyorduk" karşılaştırmasıyla.