2 puan yazan GN⁺ 2023-12-14 | 1 yorum | WhatsApp'ta paylaş
  • Knock, bildirim iş akışı motorunun çekirdek veri deposu olan Postgres'i AWS RDS Aurora 11.9'dan 15.3'e müşteri etkisi olmadan taşımak için bir prosedür oluşturdu
  • Amazon RDS'de Postgres 11.9'un kullanım ömrü sonu olan 29 Şubat 2024 öncesinde harekete geçilmezse, zorunlu yükseltme ve kesinti riski vardı
  • In-place yükseltme ile pg_dump/pg_restore uzun kesinti süreleri gerektirdiği için elendi; bunun yerine yeni DB üzerinde PUBLICATION/SUBSCRIPTION tabanlı mantıksal çoğaltma kuruldu
  • Tablo boyutu ve yazma desenine göre çoğaltma stratejileri ayrıldı; küçük tablolar doğrudan çoğaltıldı, büyük append-only tablolar için ise copy_data = false ile snapshot backfill birlikte kullanıldı
  • Son geçiş, iki DB bağlantısı korunurken bir flag değiştirilerek, çalışan sorgulara 500ms tanınıp ardından 1 saniye boyunca yeni DB istekleri duraklatılarak stale read riskini azaltacak şekilde birkaç saniyede tamamlandı

Yükseltme hedefleri ve kısıtlar

  • Knock, bildirim iş akışı motorunda Postgres'e bağımlı; iş akışı yapılandırmaları, mesaj şablonları, milyonlarca log kaydı ve arka plan iş kuyruğu için Postgres kullanıyor
  • Postgres, ilişkisel veritabanı yapısı gereği yükseltme sırasında en az yeniden başlatma gerektirir; major sürüm yükseltmeleri ise diskteki veri ve indeks saklama biçimi değiştiği için birkaç dakika veya daha uzun tam duruş gerektirebilir
  • Şirketin başlangıcından beri kullanılan Postgres 11.9, Amazon RDS'de kullanım dışı bırakılacaktı ve ek önlem alınmazsa zorunlu yükseltme ile zorunlu kesinti olasılığı vardı
  • Yükseltme koşulları, operasyonel riski azaltmaya odaklandı
    • Mümkün olan en güncel sürüm olan Aurora için Postgres 15.3'e atlamak
    • 60 saniyeyi aşan kesintiyi kabul etmemek, ideal olarak 0 sistem kesintisi
    • Amazon'un Şubat 2024 son tarihinden önce tamamlamak
    • Müşteri etkisini en aza indirmek; örneğin API hata yanıtlarını 0'da tutmak
    • Bir sonraki yükseltmede yeniden kullanılabilmesi için prosedürü runbook haline getirmek
  • 11.9'dan 15.3'e geçiş, 4 major sürüm yükseltmesi anlamına geldiği için in-place yükseltmeyi 4 kez tekrarlamak seçenek dışı bırakıldı

Ön hazırlık: riski azaltma ve gözlemlenebilirlik

  • Postgres yükseltmesi için önce risk listesi çıkarıldı ve etkisi büyük, önceden kaldırılması kolay riskler önceliklendirildi
    • Uzun kesinti
    • Veri kaybı
    • Uygulama iş yükünde DB performans değişimi
    • VACUUM sıklığı veya davranışındaki değişimler
    • Çoğaltma slotlarının taşınma gereksinimi
  • Postgres sürüm notları incelenerek sürümler arası değişiklikler doğrulandı; VACUUM davranış değişimleri veya belirli yükseltmelerde yeniden indeksleme gereksinimi gibi riskler belirlendi
  • Yükseltme boyunca sistem ve veritabanı metrikleri sürekli izlenmeliydi
    • Transaction wraparound'u önlemek için Max TXN ID
    • DB CPU kullanımı
    • Writer instance üzerindeki bekleyen oturumlar
    • Sorgu gecikmesi
    • Uygulama API yanıt gecikmesi
  • Knock ayrıca API isteğinin bildirime dönüşme süresi gibi uygulamaya özgü metrikleri de izledi
  • Zamanında görülebilen metrikler yoksa yükseltme sürecinde adeta gözler bağlı kalınır

Elenen yöntemler: in-place yükseltme ve dump/restore

  • AWS RDS'nin in-place yükseltmesi, AWS konsolu üzerinden başlatılır; AWS DB'yi durdurur, yükseltme betiklerini çalıştırır ve sonra tekrar çevrimiçi hale getirir
  • Bu süreç, veri miktarına ve sürümler arası değişim kapsamına bağlı olarak birkaç dakikadan saatlerce sürebilir
  • DB yeniden çevrimiçi olduktan sonra bile VACUUM veya REINDEX gibi bakım işleri gerekebilir; bu nedenle sistem hemen tam kullanılabilir olmayabilir
  • pg_dump ve pg_restore yaklaşımı, güvenilir bir yedek almak için tüm uygulamaların eski DB'den ayrılmasını gerektirir; büyük DB'lerde dump ve restore işlemlerinin kendisi de uzun sürer
  • Knock'un kesinti sınırını büyük ölçüde aşma ihtimali yüzünden iki yöntem de elendi

Seçilen yaklaşım: mantıksal çoğaltma tabanlı yükseltme

  • Nihai seçim, Postgres'in PUBLICATION ve SUBSCRIPTION özelliklerini kullanan mantıksal çoğaltma oldu
  • Temel akış şöyleydi
    • Hedef Postgres sürümünde yeni bir DB ayağa kaldırmak
    • Yapılandırmaları, eklentileri, tablo düzenini ve kullanıcıları taşımak
    • Eski DB'de bir publication oluşturup yeni DB'de bir subscription kurmak
    • Tabloları publication'a eklemek
    • Çoğaltma tamamlanınca kalan riskleri doğrulayan testler yapmak
    • Yeni DB yapılandırması yeterince doğrulanınca uygulamayı yeni DB'ye geçirmek
    • Eski DB'yi kaldırmak
  • Büyük bir yükseltmeyi tek seferde yapmak yerine kademeli adımlarla ilerlemek mümkün oldu ve yeni DB gerçek veriyle, gerçek iş yükü altında test edilebildi
  • Yeni DB hazır olduktan sonra geçişin kendisi birkaç saniyede tamamlandı; böylece geçiş zamanı ve yöntemi daha iyi kontrol edildi

Çoğaltma kurulumunun temel noktaları

  • Postgres mantıksal çoğaltması, replication slot yapılandırması için gereken parametreleri kullanır; basit uygulamalarda ana değişiklik wal_level değerini logical yapmak olabilir
  • Zaten read replica, DB failover veya veri ambarı senkronizasyonu için replication slot kullanılıyorsa, max_replication_slots gibi parametreler belgelerde anlatıldığı şekilde ayarlanmalıdır
  • Yeni DB'deki tablo yapısı eski DB ile aynı olmalı, ancak boş olmalıdır
  • Şema snapshot'ı, pg_dumpall ile --schema-only, --no-role-passwords seçenekleri verilerek üretilebilir; ardından yeni DB için oluşturulan SQL ile karşılaştırılıp farklar düzeltilebilir
  • Eski DB'de publication, yeni DB'de subscription oluşturulurken başlıca seçenekler ayarlandı
    • enabled = false: baştan senkronizasyon başlamasın diye
    • create_slot = true: replication slot'u Postgres yönetsin diye
    • copy_data = true: varsayılan olarak tablo içeriği kopyalansın diye
    • disable_on_error = true: beklenmeyen hatalarda subscription dursun ve sorun giderildikten sonra devam edilebilsin diye
  • FOR ALL TABLES ile tüm tabloları tek seferde publication'a eklemek büyük DB'lerde performans sorunu yaratabileceği için Knock, tabloları ALTER PUBLICATION ... ADD TABLE ile tek tek ekledi

Tablo sınıflandırması ve çoğaltma stratejisi

  • Knock tabloları disk boyutu ve tuple sayısına göre ayırdı
    • Birkaç dakika içinde senkronize edilebilen küçük tablolar
    • Büyük ama append-only'ye yakın tablolar
    • Büyük ve satırlarının çoğu sık güncellenen tablolar
  • Knock'a göre “küçük” tablo, 50GB altı ve 10 milyon tuple altı olan tablolardı
  • Postgres'te tuple, insert veya update'in saklandığı birimdir; satır sayısı az olsa bile temizlenmemiş tuple sayısı yüksekse çoğaltma süresi uzayabilir
  • Çoğaltma öncesinde VACUUM çalıştırmak, kaynak DB'den hedefe kopyalanması gereken tuple sayısını azaltmaya yardımcı olabilir
  • Tablo senkronizasyon süresi disk boyutu ve tuple sayısıyla doğrudan ilişkilidir; uzun süren senkronizasyonlar primary DB'deki VACUUM işlemini engelleyerek performans düşüşüne ve transaction wraparound riskine yol açabilir

Küçük tabloların çoğaltılması

  • Küçük tablolar, eski DB'de publication'a tablo eklenip yeni DB'de subscription refresh edilerek taşındı
  • Tablo kopyalama, senkronizasyon ve sonraki değişikliklerin uygulanması Postgres tarafından yönetildi
  • Çok küçük tablolar 1 saniyeden kısa sürede senkronize olabildi

Büyük append-only tabloların çoğaltılması

  • Güncelleme almayan veya yalnızca yakın zamandaki satırları güncellenen büyük tablolar için copy_data = false ile ayrı publication/subscription kurulabilir
  • Knock, bunu normal çoğaltmadan ayırmak için adlarda _nocopy soneki kullandı
  • Önce sadece yeni değişiklikler çoğaltıldı, geçmiş veri ise yedek veya snapshot üzerinden ayrıca backfill edildi
  • AWS RDS Aurora üzerinde kullanılan süreç şöyleydi
    • Production DB snapshot'ı oluşturmak
    • Snapshot'ı yeni bir DB instance'ına geri yüklemek
    • Çoğaltılacak snapshot DB tablo adlarına _snapshot gibi bir sonek eklemek
    • Hedef DB'de de aynı şemaya sahip snapshot tabloları oluşturmak
    • Snapshot DB ile hedef DB arasında publication/subscription kurmak
    • Çoğaltma ilerleyişini izlemek
    • Çoğaltma yetiştiğinde INSERT ... ON CONFLICT DO NOTHING ile gerçek hedef tablolara birleştirmek
  • Çok büyük tablolar için bu süreç günler sürebilir; ancak arka planda yürütüldüğü için production ortamını etkilememelidir
  • Birleştirme sonrası satır sayıları karşılaştırılarak tutarlılık doğrulandı; ardından hedef DB'deki snapshot tabloları, snapshot subscription ve snapshot DB instance'ı kaldırıldı

Büyük ve sık güncellenen tablolar

  • Büyük ve satırlarının çoğu sık güncellenen tablolar en zor olanlardı; uzun süren çoğaltma AUTOVACUUM çalışmasını engelleyebilirdi
  • Değerlendirilebilecek adımlar şunlardı
    • Housekeeping ile tablo boyutu azaltılabilir mi kontrol etmek
    • Yakın zamanda VACUUM yapılıp yapılmadığını görmek
    • Tabloyu daha küçük parçalara partition etmenin mümkün olup olmadığını incelemek
    • Bir süre sonra satır güncellemeleri duruyor mu bakıp append-only gibi ele alınıp alınamayacağını değerlendirmek
  • Kaynak DB PG 15'ten eskiyse seçenekler sınırlıdır; küçük tablolar yöntemiyle çoğaltıp izleme üzerinden hizmette bozulma olup olmadığı kontrol edilmelidir
  • Gerekirse publication'dan tablo çıkarılıp subscription refresh edilerek geri dönüş yapılabilir
  • Aşırı büyük tablolar için, yük ve yazma etkisini azaltmak amacıyla çoğaltma düşük trafik saatlerinde başlatılabilir

PG 15 ve sonrasında mümkün olan büyük tabloyu parçalayarak çoğaltma

  • Kaynak DB PG 15 veya üstündeyse, büyük tabloları küçük parçalara taşımak için çoğaltma birden çok publication'a bölünebilir
  • Bu yaklaşım partitioning veya sharding'e benzer çalışır; bedeli ise daha fazla replication slot kullanmasıdır
  • Knock, 11.9'dan 15.3'e geçtiği için bu yöntemi kullanamadı ve doğrudan test etmedi
  • Örnekte, primary key hash'i ve WHERE koşulu kullanılarak satırlar birden çok publication'a dağıtılıyordu
  • Knock'un yönetilebilir bulduğu parça boyutu, indeksler hariç veri için yaklaşık 100GB idi

Çoğaltma durumunu kontrol etme ve durdurma

  • Subscription'a tablo eklendiğinde durum, hedef DB'deki pg_subscription_rel.srsubstate üzerinden takip edilebilir
    • i: başlatma
    • d: tablo içeriğini kopyalama
    • f: kopya tamamlandı, son senkronizasyon bekleniyor
    • s: ilk senkronizasyon tamamlanıyor
    • r: normal çoğaltma çalışıyor
  • d aşaması eski Postgres transaction ID'lerini korumak zorunda olduğu için VACUUM'u fiilen engelleyebilir; bu da performans sorunlarına veya transaction ID wraparound'a yol açabilir
  • Wraparound sınırına yaklaşılıyorsa, migrasyonu durdurup daha küçük parçalara bölmek daha iyidir
  • Belirli bir tablonun çoğaltmasını durdurmak için eski DB'deki publication'dan tablo çıkarılır ve yeni DB'deki subscription refresh edilir
  • Sadece subscription'ı disable etmek, kaynak DB'nin eski transaction ID'lerini tutmaya devam etmesi yüzünden performans sorununu çözmeyebilir
  • Acil durumda publication ve subscription tamamen silinip en baştan başlanabilir; Postgres ilgili replication slot'ları temizler

Replication slot taşımanın kısıtları

  • Postgres replication slot'ları, başka DB'lerin veya uygulamaların tüketebileceği DB etkinlik loglarını saklar
  • Slot ilerleyişi Log Sequence Number yani LSN ile izlenir ve LSN primary Postgres DB'ye özgüdür
  • Eski DB'deki replication slot LSN'si aynen yeni DB'ye kopyalanamaz
  • Veri ambarı araçları gibi replication slot tüketen uygulamalar için, her aracın dokümantasyonuna göre ayrı bir taşıma stratejisi belirlenmelidir
  • Kendi uygulamanız replication slot kullanıyorsa, eski DB ile yeni DB'den gelen yinelenen transaction'ları ayıklayabilen bir idempotency mekanizması faydalı olur

Son doğrulama

  • Tüm tablolar publication'a eklendikten ve subscription yetiştikten sonra tabloların gerçekten uyuştuğu doğrulanmalıdır
  • Mantıksal çoğaltmadaki gecikme nedeniyle eski DB ile yeni DB'nin aynı anda tamamen birebir olması zor olsa da, satır sayısı karşılaştırması yeterince yakın olup olmadığını görmek için yeterlidir
  • Knock, her tablo için eski DB ve yeni DB'deki satır sayılarını sayan bir betik yazdı
  • inserted_at sütunu olan tablolarda yalnızca 10 saniyeden daha eski satırlar karşılaştırıldı; çünkü son 10 saniyedeki verilerin yakında çoğaltılacağı varsayıldı
  • Bazı tablolarda rastgele satır örnekleri karşılaştırılarak içerik eşleşmesi ayrıca doğrulandı

Uygulama geçiş yöntemi

  • Son cutover için uygulama, iki DB'ye birden bağlanabilecek şekilde değiştirilebilir
  • Trafiği düşük DB'ler için ayarı yeni DB'ye çevirip uygulamayı yeniden başlatmak gibi basit bir yöntem yeterliydi
  • Eşzamanlı etkinliği yüksek uygulamalarda eski DB ile yeni DB arasında çakışan yazma işlemlerinden kaçınmak gerekiyordu
  • Knock'un cutover betiği şu sırayla çalıştı
    • Tüm uygulama instance'larına yeni sorguları yeni DB'ye göndermelerini söylemek
    • Çalışan DB sorgularına tamamlanmaları için 500ms vermek, sonra zorla iptal etmek
    • Flag değişiminden sonraki ilk 1 saniye boyunca yeni DB isteklerini yapay olarak duraklatmak; böylece bekleyen transaction'ların yeni DB'ye çoğaltılması için zaman tanımak
    • Sonrasında DB etkinliğini normale döndürmek ama yeni DB'yi kullanmak
    • Bazı özel DB iş yüklerini durdurup yeni DB'ye yeniden bağlanmaları için yeniden başlatmak
  • Knock, 500ms'nin çoğu DB sorgusundan çok daha uzun olduğunu ve zorunlu bağlantı kesme kaynaklı hata yaşanmadığını doğruladı

Sequence yönetimi

  • Postgres mantıksal çoğaltması sequence değerlerini senkronize etmez
  • Eski DB'de sequence değeri kullanılsa bile yeni DB'deki sequence değeri artmaz
  • Knock, feature flag geçişinden hemen önce iki DB'ye bağlanan bir betik çalıştırdı
    • Eski DB'deki tüm sequence'ler için SELECT nextval('sequence_name') ile sonraki değeri almak
    • Yeni DB'de SELECT setval('sequence_name', value::int4 + 100000) ile sequence'i ileri taşımak
  • Bu yöntem sequence içinde boşluk yaratır; ancak Knock'un sequence'leri bigint olduğu için 100 bin değer atlamak, kullanılabilir sequence alanının pratikte neredeyse %0'ına denk geliyordu
  • Gerçek cutover sırasında kullanılacak sequence hacmine göre boşluk boyutu ayarlanmalıdır

Cutover öncesi kontrol edilmesi gerekenler

  • Son geçiş öncesi kontrol listesi operasyonel hazırlığı geniş kapsamda ele aldı
    • Tüm tabloların satır sayıları beklendiği gibi uyuşuyor mu
    • Tüm subscription'lar enabled durumda mı ve hatasız çalışıyor mu
    • Şemalar eşleşiyor mu, migration release'leri dondurulabilir mi
    • Yeni DB iş yüküne uygun şekilde boyutlandırıldı mı
    • Eski DB ve yeni DB cluster topolojisini eşleştirmek için read replica gerekiyor mu
    • Yeni DB'de REINDEX ve temel VACUUM bakımı yapıldı mı
    • Postgres sürüm notlarında uygulama regresyon ihtimali yeniden kontrol edildi mi
    • Yeni sürümün staging DB'sinde otomatik ve manuel testler yapıldı mı
    • En ağır sorgular pg_bench ile yük testinden geçirildi mi
    • Hâlâ azaltılabilecek bir risk kaldı mı
    • Cutover prosedürü staging veya test ortamında defalarca pratik edildi mi
    • Cutover'dan hemen önce DB yedeği oluşturuldu mu

Gerçek geçişin sonucu

  • Knock, birkaç hafta boyunca tabloları tek tek çoğalttı; çoğu çalışma mesai sonrası ve trafiğin en düşük olduğu saatlerde yapıldı
  • Cutover, staging ortamında birçok kez prova edilerek fazla operatör müdahalesi olmadan çalışacak şekilde iyileştirildi
  • PG 15 replica ve uygulama geçiş kodu hazır olduktan sonra son kontroller yapıldı ve flag değiştirildi
  • Gerçek cutover birkaç saniye içinde tamamlandı; çoğaltmanın yetişmesini beklemek için eklenen kasıtlı kısa gecikme dışında uygulama çalışmaya devam etti
  • Sonrasında geçici uygulama değişiklikleri geri alındı, tüm bağlantılar kalıcı olarak yeni DB'ye geçirildi, yeni DB'deki subscription'lar ve eski DB kaldırıldı
  • Knock, Postgres'i 11.9'dan 15.3'e kesintisiz olarak taşımayı tamamladı

Sonuç

  • Postgres'te aynı anda 4 major sürümü atlamak zorlu ama mümkün bir iş
  • Mantıksal çoğaltma yaklaşımı, gerçek cutover öncesinde birçok kez prova, test ve yeniden çalışma imkânı sunduğu için planlı kesintiden daha güvenli olabilir
  • Süreç sırasında sorun çıkarsa eski DB'deki publication silinip yeniden başlanabildiğinden, hizmette bozulma olmadan prosedür geri alınabildi
  • Kusursuz %100 erişilebilirlik teknik olarak mümkün olmasa da, kesintisiz migrasyon büyük hizmet kesintileri olmadan sistemi çalışır tutmaya yardımcı olur

1 yorum

 
GN⁺ 2023-12-14
Hacker News yorumları
  • Tablo içeriklerini tek tek tamamen kopyalama yöntemi G/Ç yükü açısından çok ağır ve çok büyük tablolarda işe yaramaz.
    Daha iyi yöntem bir replikasyon slotu oluşturmak, bir snapshot almak, bunu yeni instance’a geri yüklemek, LSN’i ilerletmek ve ardından oradan replikasyona başlamaktır. Böylece tüm verileri içeren mantıksal bir replika oluşur; sonra bu replikayı yükseltmek yeterlidir.
    Instacart yazısında yöntem anlatılıyor: https://archive.ph/K5ZuJ
    Yanlış hatırlamıyorsam yazıda birkaç küçük hata vardı, ama genel prosedür çalışıyordu ve TB ölçeğinde instance’ları bu şekilde birçok kez yükselttim.

    • Bu iyi bir tarif, ancak pg_upgrade’i araya yerleştirme sırası konusunda küçük ama önemli bir düzeltme gerekiyor.
      Önce mantıksal replikasyonu başlatıp sonra pg_upgrade çalıştırırsanız bozulma riski vardır. İlgili tartışma pgsql-hackers’ta: https://www.postgresql.org/message-id/flat/20230217075433.u5...
      Çözüm için önce mantıksal slotu oluşturmak, yeni cluster’ı slotun LSN konumuna kadar ilerletmek ama mantıksal replikasyonu henüz başlatmamak; ardından pg_upgrade’i çalıştırmak ve cluster yeni PostgreSQL sürümünde ayağa kalktıktan sonra mantıksal replikasyonu başlatmak gerekir.
      Postgres.ai yakın zamanda GitLab’in birkaç multi-TiB cluster’ını yüksek yük altındayken kesintisiz yükseltirken tam olarak bu yöntemi kullandı; PgBouncer’ın PAUSE/RESUME özelliğini de birlikte kullandılar. Alexander Sosna’nın sunumu bu haftanın ilerleyen günlerinde planlanıyor: https://www.postgresql.eu/events/pgconfeu2023/schedule/sessi...
    • OP olarak bu yöntemi de değerlendirdim, ancak önerilen şekilde LSN’i elle ilerletme konusunda emin değildim ve replikasyonda bir şeyi kaçırırsak tutarsızlığı tespit edebileceğime de güvenmiyordum.
      Tablo bazlı ilerleme çok daha zahmetliydi, ama daha güvenilir görünüyordu.
    • Yazı güncellenmiş: https://tech.instacart.com/zero-downtime-postgresql-cutovers...
    • O yazı Instacart’ın yükseltme yönteminin temelini ele alıyor, ama oldukça eski; aşağıdaki yazı mevcut prosedürü daha iyi gösteriyor.
      Bu yöntemle çok büyük ve aktif birçok veritabanını başarıyla yükselttik.
      https://www.instacart.com/company/how-its-made/zero-downtime...
  • Yaklaşım ilginç ve dokümantasyonu da iyi, ancak “modern müşteriler %100 erişilebilirlik bekler” cümlesi takılıyor.
    Bu ne bir müşteri olarak tercihim ne de bir sağlayıcı olarak deneyimim. Birçok iş yükünde tutarlılık, erişilebilirlikten çok daha önemlidir.
    Bir sağlayıcı kesinti penceresi duyurduğunda, çoğu zaman verilerimi dikkatle ele aldığının bir işareti gibi göründüğü için içim rahatlıyor.

    • OP olarak iyi bir geri bildirim.
      Hem ürünün güvenilirliğine hem de iş yükünün tutarlılığına güven oluşturmak istiyordum. Elbette tutarlıymış gibi görünüp kararsız olmaktansa, müşteri beklentilerini yönetmek ve uzun vadede daha iyi çalışma süresi için bilinçli olarak kesinti yapmak çok daha iyidir.
      Periyodik bakım pencerelerinin önceden beklenir hale getirilmesi genel olarak daha sağlam bir mimariye de yol açabilir. Müşteri kesintiye dayanacak güvenlik önlemleri oluşturursa dayanıklılık artar; ekip de müşteriye bu şekilde güvenebildiğinde daha iyi ürün için yatırım yapacak zaman kazanır.
      Belki bir sonraki majör sürüm yükseltmesinden sonra “Kesinti beklentilerini belirlemek, çok yüksek çalışma süresine giden yoldur” diye bir yazı yazarım.
    • Müşterinin kim olduğuna bağlı.
      AWS müşterisi olarak %100 erişilebilirlik beklerim. Çünkü kendi müşterilerim dünyanın her yerinde ve kesinti koyabileceğim bir zaman dilimi yok.
  • AWS artık blue/green deployment destekliyor: https://aws.amazon.com/about-aws/whats-new/2023/10/amazon-rd...

    • Birkaç hafta önce bizzat denedim; PostgreSQL için henüz güvenmemek daha iyi.
      AWS ile birkaç kez yazıştıktan sonra deney birkaç saat boyunca takılı kaldı ve AWS UI ancak daha sonra geçişin uygulanmadığını kabul etti. Neyse ki güvenli şekilde başarısız oldu, ancak GB ve üzeri veri kümelerinde gerçek geçiş anını tutturabileceğime dair güvenim yok.
    • Doğru. OP olarak o sırada biz Aurora 11.9 kullanıyorduk ve blue/green deployment desteği kapsamında değildik.
      Bir dahaki sefere mümkün olabilir.
  • Bu harika.
    Yaşadıklarınızın çoğunu otomatikleştiren bir araç yaptım; faydalı bulursanız ya da geri bildirim/fikirlerle genişletmek isterseniz her zaman memnun olurum: https://github.com/shayonj/pg_easy_replicate

    • Harika bir araç.
      Büyük tablolardan elde ettiğiniz bulgular böyle bir araç için ilginç olabilir. Tablo bazında doğru stratejiyi uygulamayı kolaylaştırırsa, gelecekte bu tür migration yapan ekipler için vazgeçilmez araç haline gelebilir.
  • “Knock gibi bir hizmette, planlı olup olmamasına bakılmaksızın hiçbir kesintiye izin verilmez” iddiası şüpheli
    Karmaşık bir sistemse arızalar da olur, kesintiler de olur. Önceden duyurulmuş 15 dakikalık kesinti neredeyse tüm SaaS işletmeleri için sorun değildir. Hastane de değil, elektrik santrali de değil
    Hizmetin gerçekte olduğundan daha önemli olduğu düşünülünce çok sayıda sahte iş ortaya çıkıyor. Buraya harcanan mühendislik zamanı ürünü ya da geliştirme ekibinin verimliliğini iyileştirmeye harcansaydı, kullanıcıların daha mutlu olma ihtimali daha yüksekti. Özellikle bildirimler kuyruğa alınıp kesinti sonrasında arayı kapatabiliyorsa bu daha da geçerli
    15 dakikalık kesinti için tazminat şartı içeren kurumsal bir SLA varsa bu gerekçelendirilebilir, ama çoğu durumda yoktur. Gerçekte, benzer ya da daha uzun birkaç arıza zaten yaşanmış olma ihtimali de yüksek
    Veritabanı geçişlerinde “kısa kesinti” ile “kesintisiz” arasındaki iş yükü farkı genelde oldukça büyük olduğundan bu daha da önemli. Bunun gibi tek seferlik bir işte ve RDS’in en yeni PostgreSQL sürümleri varsayılan olarak desteklediği durumlarda, bunu gerekçelendirmenin özellikle zor olduğunu düşünüyorum

    • OP olarak, tüm hizmetlerde herhangi bir nedenle kesinti olabileceği doğru
      Bakım penceresi ayarlamayı da tartıştık, ama sürekli düşündüğümüz konu, yükseltmeyi üretim verileriyle nasıl prova edebileceğimizdi. Üretim verileriyle senkronize PG 15 replikası, iş yüklerinin beklendiği gibi çalıştığını doğrulamak için çok önemliydi
      Canlı replika kullanınca üretim ortamındaki etkiyi en aza indirerek prova yapmak mümkün oluyor
      Bu geçişte çıkardığımız büyük ders, böyle projelerde akla gelebilecek tüm riskleri izleyip azaltmanın ne kadar faydalı olduğuydu. Sonuçta yerinde yükseltmenin riski, seçtiğimiz yolun riskinden daha büyük görünüyordu; bu da bakım penceresi olup olmamasından ayrı bir değerlendirmeydi
      Ayrıca ileride bu yaklaşıma ihtiyaç olursa bu blog yazısı bir başlangıç noktası olup haftalar kazandıracak. Benzer durumdaki başka ekiplere de yardımcı olmasını umarım
    • Bir doktor olarak, kesintiye tahammül edemeyecek sistemlere örnek diye “hastane de değil ya” denmesi ilginç
      ABD’nin en büyük elektronik sağlık kaydı sağlayıcılarından Epic bile yükseltmeler için en az ayda bir kez, her seferinde yaklaşık 30–60 dakikalık planlı kesinti yapıyor
    • Sorun, RDS’de bir PostgreSQL instance’ını planlı 15 dakikalık kesintiyle yükseltmenin bir yolu olmaması
      Yeniden başlatma anını kontrol edemiyorsunuz. Süreci başlattığınızda geçiş bir saat, iki saat, üç saat sonra başlayabilir; ne zaman yeniden başlatılacağını bilemez ve kontrol edemezsiniz
      Replikalar varsa paralel olarak yükseltilir ve rastgele bir anda yeniden başlatılır; bu da işi daha da zorlaştırır
      Dolayısıyla veritabanı boyutuna bağlı olarak birkaç saate kadar uzayabilecek bir zaman aralığında rastgele erişilemezliği kaldıramıyorsanız, RDS yükseltmeleri için mantıksal replikasyon fiilen tek yoldur
      Instance ne kadar büyükse sorun o kadar zorlaşır
    • Kesintinin asıl sorunu, tüm sistemlerin aynı anda kapanmasıdır
      Jira günde 15 dakika kapalı kalırsa genelde büyük bir etkisi olmaz. İş kuyruğunda başka işler vardır; en kötü durumda birden fazla arıza çakışsa bile birine söz verilmiş dokümantasyon işi vardır
      Ama Atlassian ürün ailesinin tamamı aynı anda çökerse işi sürdürebilecek tampon işler tutmak çok daha zorlaşır. Bir şirketin tüm uygulamalarının aynı storage array’i kullanmasını sağlarsanız verimlilik kaybı %5’ten %95’e fırlayabilir
    • “Önceden duyurulmuş 15 dakikalık kesinti neredeyse tüm SaaS işletmeleri için sorun değildir” denmesine karşın, her ay kesintisi olmayan bir rakip olabilir
      Böyle bir rakip, benim ihtiyaçlarımı kendi rahatının önüne koyuyor demektir
      Sizin arızanız aynı zamanda benim arızamdır
  • hava.io’da şu anda bu süreçten geçiyoruz
    AWS RDS PostgreSQL 11.13’ten 15.5’e yükseltiyoruz
    Sonuçta pglogical kullanan tek yönlü replikasyon gibi nispeten basit bir yaklaşımı seçtik. Google Cloud SQL’den AWS RDS’e kesintisiz taşınmayı aynı yöntemle daha önce yaptığımız için, müşteriye görünen bir etki olmadan çalışacağından emindik
    pglogical bu tür geçişleri oldukça basitleştiriyor. Her zaman hızlı değil, ama tüm veritabanının yeni instance’a kademeli olarak replike edilmesi için birkaç gün bekleyebiliyorsanız sorun değil
    Bu yöntem, storage türünü ve boyutunu değiştirme konusunda da daha fazla esneklik sağladı. IOPS elde etmek için storage’ı gereğinden fazla ayırmıştık; storage türünü değiştirip boyutu da küçültmek istiyorduk. Bu yüzden basit snapshot restore bizim için uygun değildi

  • “Sales engineering” aşamasında AWS’in söz verdiği o özellikten mi bahsediliyor diye düşündüm
    Gerçekte ise major version upgrade zorunlu hale geldiğinde bunu sağlayamadılar

  • Bir yedeği kullanarak replikanın başlatılamamasına şaşırdım.
    Mümkün olsaydı, kararlı durumdaki mevcut veritabanı içeriğini yeni sunucuya stream etmekle uğraşmayı azaltırdı.
    Ayrıca bu “kesintisiz” değil; servisi yeni sunucuya aktarmak için birkaç saniyelik kesinti var.
    Yazı, tutarlılığın nasıl korunduğunu atlamış. Örneğin uygulamayı bir süreliğine iki sunucuya birden öylece bağlayamazsınız. Okumalar belki ikisinden de sunulabilir ama bu da kusursuz değil; yazmaların mutlaka yalnızca tek bir sunucuya gitmesi gerekir.
    Son olarak rollback seçeneği de yok. Bu kadar büyük ölçekli veriyi tek seferde taşıma işleri, gece geç saatte ters gidebilir. Bu yüzden her zaman bir önceki adıma dönebileceğiniz ve sabah servis ayakta olacak diye içiniz rahat uyuyabileceğiniz bir plana ihtiyaç vardır.
    Özellikle de yeni sunucuya yazma transaction’ları gönderdikten sonra herhangi bir nedenle eski sunucuya dönmeniz gerekirse bu zordur; veriler artık zaten tutarsız hale gelmiştir.

    • OP olarak, bir yedekten replika başlatılabilir; ancak yedekleme sırasında devam eden yazmaları elde edemezsiniz.
      Bir tür replikasyon aracı yoksa ya da bunu uygulama katmanına taşımıyorsanız, geri yüklenen sistemde eksik yazmalar olur.
      Örneğin uygulamayı değiştirip çift yazma uygulayabilirsiniz. RDBMS’ten Apache Cassandra gibi tamamen farklı bir veritabanına tüm uygulamayı yeniden platformlayan ekiplerin de bunu yaptığını biliyorum.
      Bizim durumumuzda çift yazma, PostgreSQL’in yerleşik özellikleriyle streaming replication kurmaktan daha riskli görünüyordu. Ancak bazı ekipler için daha iyi bir seçenek olabilir.
      “Kesintisiz değil” ve “tutarlılığı koruma ayrıntıları eksik” kısımlarına gelirsek, yazıda tutarlılığı nasıl koruduğumuzu ve API kesintisinden nasıl kaçındığımızı ayrıntılı anlattık. Özetle uygulama iki veritabanına da bağlıydı ama yeni veritabanını varsayılan olarak kullanmıyordu.
      Ardından LaunchDarkly ile tüm uygulama instance’larına geçiş sinyali gönderdik; LaunchDarkly tüm instance’larla düşük gecikmeli bağlantılar tutuyor.
      Sinyalden sonraki ilk 1 saniye boyunca sunucu, replikasyonun yetişebilmesi için veritabanı isteklerini kuyruğa aldı. Bu yüzden kısa süreli bir gecikme sıçraması oldu, ama bu bilerek hesapladığımız kabul edilebilir sınırlar içindeydi. Bu kısa duraklamadan sonra istekler her zamanki gibi akmaya devam etti, ancak yeni veritabanını hedefledi ve geçiş tamamlandı.
      Eski veritabanına kalan trafik için 500 ms timeout ile zorunlu bağlantı kesme de ekledik. Bu değer p99 sorgu süresinden çok daha büyüktü, bu yüzden çalışmakta olan sorgular zorla sonlandırılmadı. Böylece eski veritabanı trafiği durdu ve replikasyonun yetişmesi için yeterli zaman oluştu.
      Rollback seçeneği blog yazısında yer almadı, ancak PG 11.9 üzerinde alternatif bir veritabanı oluşturup 15.3 veritabanını bu üçüncü veritabanına replike etme seçeneğini de değerlendirdik. Durdurmamız gerekseydi aynı sürümdeki bu veritabanına roll-forward yapabilirdik.
      Staging’de yükseltme prosedürünü birkaç kez prova edip başarı ihtimalini doğruladıktan sonra bu seçeneği kullanmamaya karar verdik. Birkaç kez prova yaptığımız için gerçek geçiş sırasında özgüvenimiz vardı. Production’da da canary dağıtımla salt okunur workload’un bir bölümünü 15.3 instance’ı üzerinde doğruladık ve onu bir read replica gibi ele aldık.
      Gece geç saatlerde sorun yaşamamak için özellikle hafta sonu erken akşam saatlerinde yaptık. Geçiş, insan hatası riskini azaltmak için titizlikle script’lendi ve prova edildi.
      Felaket bir hata olursa sistem eski veritabanına geri dönecek şekilde de hazırdı. Bu durumda yeni veritabanına giren verinin bir kısmı kaybolacaktı; kritik bölümler için uzlaştırma yapmaya hazırdık. Veri kaybı riskini azaltmak için geçiş sırasında bazı arka plan işlerini kısa süre durdurarak yazma sayısını azalttık.
      Bu ayrıntıları bloga koymadık çünkü Knock’a özel değerlendirmelerden çok PostgreSQL ile ilgili ayrıntılara odaklanmak istedik. Bu playbook’u uygulamak isteyen ekipler her zaman kendi bağlamlarında risk listesini çıkarmalı ve bunları azaltmalıdır.
  • Sequence ile ilgili kısım gerçekten ilginç.
    Bir süredir sequence’leri neredeyse hiç kullanmıyor, daha çok sıralı UUID veya UUID v7 ya da HiLo gibi yaklaşımlar kullanıyorum.
    https://en.wikipedia.org/wiki/Hi/Lo_algorithm

    • PostgreSQL yerel olarak destekleyene kadar veritabanı içinde UUID v7 üretimi sorumluluğunu korumak isteyenler için PL/pgSQL fonksiyonu yardımcı olabilir.
      IETF taslak spesifikasyonuna göre 12 bitlik bir sequence oluşturup mevcut UNIX epoch milisaniyesi ve 62 bit rastgele sayıyla UUID’yi oluşturma yaklaşımı.
      Temel fikir, uuidv7_seq bulundurup generate_uuidv7() fonksiyonunda clock_timestamp(), NEXTVAL, RANDOM() kullanarak UUID v7 biçiminde bir değer döndürmek.
    • OP olarak, bir uygulama noktası hariç bağımlılıklar nedeniyle sequence’lerden kaçınıyoruz.
      Birçok yerde KSUID ve UUID v4 kullanıyoruz. Bu “tuzak” tüm sequence’ler için geçerli olduğundan, bu tür bir migration yapılırken genel tavsiye olarak vurgulanmaya değer.
      [1]: https://segment.com/blog/a-brief-history-of-the-uuid/
  • Başarıyla tamamlanmış büyük bir işi küçümsemek istemem, ama yeni sürümler çıktıkça neden küçük küçük yükseltme yapılmadığını merak ediyorum.
    Okuması harika, ama büyük fırtınanın etrafından dolaşmak yerine trajediyle bitebileceğini bilerek doğrudan içinden geçmeye karar veren denizcilerin hikâyesi gibi hissettiriyor.
    Bu durumda küçük yükseltmeler seçenek dışı mıydı? “Tek bir küçük yükseltme de büyük yükseltme kadar kesinti maliyeti getirdiği için olabildiğince erteledik” gibi bir şey mi, merak ediyorum. Girişte buna dair bir ipucu var gibi, ama fazla yorumluyor da olabilirim.

    • OP olarak, minor yükseltmelerde de aynı yaklaşımı kullanırdık.
      “Erteleyip köşeye sıkıştık”tan ziyade, bir gün sıçrama yapmamız gerektiğini bilsek de “bozuk değilse tamir etme”ye daha yakındı.
    • N sürüm yükseltmek, N ister 1 ister 3 olsun, erişilebilirlik tehdidi açısından neredeyse aynıdır.
    • Her yükseltme kesinti getirir.
      Gerçek cevap 60 saniyenin altında olsa bile, 15’e giden yolda bu kesintiyi birkaç kez yaşamış olurduk.