PostgreSQL'in “cannot execute CREATE TABLE in a read-only transaction” hatası, birçok ekibin migrasyon süreçlerini aksatıyor, şemaların yarım uygulanmasına ve migrasyon geçmişi tablolarının kilitlenmesine neden oluyor. Bu hata genellikle bir yazma işleminin birincil (primary) sunucu yerine bir replikaya (replica) yönlendirilmesiyle ortaya çıkar ve Flyway, Liquibase, Django ORM ve benzeri şema değişikliklerine dayanan tüm araçları durdurur.
Hatanın ortaya çıkma nedenleri
Bir işlem (transaction) salt okunur (read-only) olarak işaretlendiğinde, PostgreSQL tüm yazma işlemlerini, şema değişikliklerini ve sequence güncellemelerini devre dışı bırakır. Bir migrasyonun bu duruma düşmesinin en yaygın yolları şunlardır:
- Bağlantı havuzlayıcılar (örneğin PgBouncer) gibi, migrasyon bağlantısını yanlışlıkla bir okuma replikasına (read replica) gönderen araçlar.
- Varsayılan olarak
default_transaction_read_onlyparametresi on olarak ayarlanmış roller. - Ayrı okuyucu (reader) ve yazıcı (writer) URL'leri sunan bulut uç noktaları; okuyucu URL'sinin kullanılması (AWS RDS veya Aurora'da yaygın olduğu gibi), yazma işlemlerini bir replikaya yönlendirir.
Bu koşullardan herhangi biri geçerli olduğunda, migrasyon süreci tablolar oluşturabilir, sütun ekleyebilir veya sequence'ları güncelleyebilir; ancak sonunda bir engelle karşılaşarak veritabanını kısmen migre edilmiş bir durumda bırakır.
İşlem (transaction) içinde anlık çözüm
Eğer hatayı zaten aldıysanız, oturumun (session) geri kalanını etkilemeden mevcut işlem için salt okunur bayrağını geçersiz kılabilirsiniz; bu, bir bağlantı havuzunun aynı oturumu diğer işler için yeniden kullandığı durumlarda kritik öneme sahiptir.
BEGIN;
SET LOCAL default_transaction_read_only = off;
SET TRANSACTION READ WRITE;
CREATE TABLE orders (id SERIAL PRIMARY KEY, total NUMERIC);
COMMIT;
SET LOCAL, ayarı yalnızca işlemin süresi boyunca değiştirir. Düz SET kullanmak, değişikliği tüm oturum boyunca kalıcı hale getirir; bu da varsayılan olarak salt okunur olmaya meşru bir şekilde ihtiyaç duyan diğer işlemleri bozabilir.
Önleyici tedbirler
1. Birincil (primary) düğümde olduğunuzu doğrulayın
Herhangi bir migrasyon çalışmadan önce hızlı bir kontrol ekleyin:
SELECT CASE WHEN pg_is_in_recovery() THEN 'REPLICA' ELSE 'PRIMARY' END;
Sonuç REPLICA ise migrasyonu iptal edin. pg_is_in_recovery() fonksiyonu, bir standby sunucuda true döner ve böylece salt okunur bir kopyaya yazmaya çalışmadığınızı garanti eder.
2. Özel migrasyon rolleri kullanın
Varsayılan işlem modu yazmaya izin verecek şekilde ayarlanmış bir rol oluşturun ve ona yalnızca ihtiyacı olan yetkileri verin:
- Hedef veritabanı üzerinde
CREATEyetkisi. - Migrasyon aracının bir oturum açmasına izin vermek için
CONNECTyetkisi.
Bu role, bazen genel amaçlı kullanıcılar için ayarlanan default_transaction_read_only = on özniteliğini vermekten kaçının.
3. Altyapıyı yazıcı (writer) uç noktasına yönlendirin
IaC betiklerinde (Terraform, CloudFormation vb.), migrasyon çalıştırıcılarını okuyucu uç noktası yerine kümenin writer uç noktasını kullanacak şekilde yapılandırın. Writer uç noktası birincil düğüme çözümlenirken, reader uç noktası yazma işlemlerini reddedecek bir replikaya çözümlenir.
4. Bir CI/CD kontrol noktası (gate) ekleyin
Pipeline'ınıza (GitHub Actions, GitLab CI vb.) pg_is_in_recovery() sorgusunu çalıştıran bir shell adımı ekleyin. Eğer sonuç true dönerse, dağıtımı erkenden durdurmak için işten (job) sıfır olmayan bir durum koduyla çıkın.
if psql $DATABASE_URL -c "SELECT pg_is_in_recovery()" | grep -q t; then
echo "Connected to replica – aborting migration"
exit 1
fi
5. Migrasyon aracı yeniden denemelerini optimize edin
Flyway gibi araçlar genellikle bağlantıları otomatik olarak yeniden dener. Flyway 9+ için flyway.connectRetries=0 ayarını yapın. Bu, aracın tekrar tekrar bir replikaya bağlanmaya çalışmasını önler; aksi takdirde bu durum replikasyon gecikmesini (lag) artırabilir ve kaynak israfına yol açabilir.
Bundan sonra nelere dikkat edilmeli
- Replikasyon gecikmesi (lag) metrikleri: Artan bir gecikme, bir migrasyonun yanlışlıkla bir replikayı hedeflediğini ve yazma girişimlerinin kuyruğa alınmasına neden olduğunu gösterebilir.
- Bağlantı havuzlayıcı yönlendirme kuralları: Havuzlayıcı yapılandırmalarının migrasyon trafiğini açıkça birincil (primary) ana makineye yönlendirdiğinden emin olun.
- Yükseltmelerden sonraki rol varsayılanları: Veritabanı yükseltmeleri bazen rol parametrelerini sıfırlar; ana sürüm değişikliklerinden sonra
default_transaction_read_onlyayarını tekrar denetleyin.
Özetle: salt okunur işlem hatası nadiren bir PostgreSQL hatasıdır; bu, trafiğin yanlış düğüme gönderilmesinin veya bir rolün yanlış yapılandırılmasının bir belirtisidir. Düğüm rolünü kontrol ederek, özel migrasyon hesapları kullanarak ve CI/CD hattınızı güçlendirerek, migrasyonların sorunsuz çalışmasını sağlayabilir ve sonraki geliştirme süreçlerini felç eden yarım uygulanmış şemalardan kaçınabilirsiniz.
