Kesintisiz Postgres yükseltmesi
(knock.app)- 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_restoreuzun kesinti süreleri gerektirdiği için elendi; bunun yerine yeni DB üzerindePUBLICATION/SUBSCRIPTIONtabanlı 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 = falseile 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
VACUUMsı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ı;
VACUUMdavranış 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
VACUUMveyaREINDEXgibi bakım işleri gerekebilir; bu nedenle sistem hemen tam kullanılabilir olmayabilir pg_dumpvepg_restoreyaklaşı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
PUBLICATIONveSUBSCRIPTIONö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_leveldeğerinilogicalyapmak olabilir - Zaten read replica, DB failover veya veri ambarı senkronizasyonu için replication slot kullanılıyorsa,
max_replication_slotsgibi 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_dumpallile--schema-only,--no-role-passwordsseç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 diyecreate_slot = true: replication slot'u Postgres yönetsin diyecopy_data = true: varsayılan olarak tablo içeriği kopyalansın diyedisable_on_error = true: beklenmeyen hatalarda subscription dursun ve sorun giderildikten sonra devam edilebilsin diye
FOR ALL TABLESile tüm tabloları tek seferde publication'a eklemek büyük DB'lerde performans sorunu yaratabileceği için Knock, tablolarıALTER PUBLICATION ... ADD TABLEile 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
VACUUMiş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 = falseile ayrı publication/subscription kurulabilir - Knock, bunu normal çoğaltmadan ayırmak için adlarda
_nocopysoneki 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
_snapshotgibi 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 NOTHINGile 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
VACUUMyapı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
WHEREkoş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 edilebiliri: başlatmad: tablo içeriğini kopyalamaf: kopya tamamlandı, son senkronizasyon bekleniyors: ilk senkronizasyon tamamlanıyorr: normal çoğaltma çalışıyor
daşaması eski Postgres transaction ID'lerini korumak zorunda olduğu içinVACUUM'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_atsü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
- Eski DB'deki tüm sequence'ler için
- 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
REINDEXve temelVACUUMbakı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_benchile 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
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.
Ö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...
Tablo bazlı ilerleme çok daha zahmetliydi, ama daha güvenilir görünüyordu.
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.
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.
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...
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.
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
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
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
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
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
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
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.
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
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_seqbulundurupgenerate_uuidv7()fonksiyonundaclock_timestamp(),NEXTVAL,RANDOM()kullanarak UUID v7 biçiminde bir değer döndürmek.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.
“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ı.
Gerçek cevap 60 saniyenin altında olsa bile, 15’e giden yolda bu kesintiyi birkaç kez yaşamış olurduk.