omeryanbas.com

Ömer Yanbaş

Genel Müdür, Ticofab Yazılım

Yöntem

Uzun işler küçük ve geri alınabilir adımlarla biter

Büyük bir işi, her biri tek başına çıkabilen ve geri alınabilen adımlara bölmek: feature flag, expand and contract migration ve önceden yazılmış rollback.

Bir yeniden yazım altı haftadır "neredeyse bitti" halinde. Branch'te yüz seksen değişmiş dosya var, review bir formalite çünkü kimse o kadarını kafasında tutamıyor ve merge etmek ya çalışan ya da bir perşembeyi mahveden tek bir olay. Ters giderse tek dönüş yolu her şeyi geri alıyor, iyi olan kısımları da. Sorun işin kendisi değildi. Adımın büyüklüğüydü.

Uzun branch'te gerçekte ne oluyor

Bir branch açık kaldıkça üç maliyet birlikte büyür ve nazikçe büyümezler.

Çakışma yüzeyi hem zamanla hem dosya sayısıyla büyür. Başkasının indirdiği her değişiklik, sizin branch'inizle ana hattın anlaşmazlığa düşmesi için bir şanstır ve o çakışmaları çözen merge, projedeki en az incelenmiş koddur: zaman baskısı altında, bir an önce bitirmek isteyen biri tarafından yazılır.

Review bozulur. Yirmi satırlık diff okunur. İki bin satırlık diff kaydırılır. Onay gerçektir, okuma değildir ve bunu işin içindeki herkes bilir.

Geri almak mümkün olmaktan çıkar. İçinde her şey olan tek bir merge ancak bütün olarak geri alınabilir; yani tek bir kötü kararı geri almanın bedeli, altı haftalık iyi kararı çöpe atmaktır. Bununla karşılaşan ekipler geri almak yerine baskı altında ileriye yama yapar; kötü bir perşembenin kötü bir aya dönüşme biçimi de budur.

Gözden kaçması kolay dördüncü bir maliyet var. Entegre edene kadar hiçbir şey öğrenmezsiniz, dolayısıyla iş sürerken tahmin iyileşmez. Ekip birinci haftada dört hafta der, beşinci haftada hâlâ iki hafta daha der, çünkü ortaya gerçek bir sisteme karşı yanlış çıkabilecek hiçbir şey konmamıştır.

İşin birimi

Bir adım, şu üçünün hepsi doğruysa çıkmaya değer:

  1. Aynı anda başka hiçbir şey çıkmadan, tek başına deploy edilebilir.
  2. Orada kalıcı olarak durursanız sistem hâlâ doğrudur. Bitmiş değil, doğru.
  3. Yazılı bir dönüş yolu vardır ve biri onu koşturmuştur.

Bu üç madde pratikte tek bir soruya iner: bu adım çıktıktan sonra ekip iki hafta tatile gitse sistem ayakta kalır mı? Cevap hayırsa elinizdeki henüz bir adım sayılmaz; yarısı bitmiş bir iştir ve onu çıkarmak cesaret yerine borç üretir.

Atlanan madde üçüncüsüdür ve farkı yaratan da odur. İnandığınız bir rollback, rollback değildir. Down migration'ı up migration ile aynı anda yazın ve değişiklik hiçbir yere gitmeden önce gerçek verinin bir kopyası üzerinde bir kez koşturun. Bu, yedeğin gerçekten geri yüklendiğini test etmekle aynı alışkanlıktır ve kimse denemediğinde aynı sebeple çöker: hiç çalıştırılmamış şey, ihtiyaç duyulan gün çalışmaz.

İşi bölmek

Feature flag, yeni yolun production'da kullanılmadan var olmasını sağlar. Eski yol olduğu gibi kalır, yeni yol kapalı çıkar ve onu açmak, deploy etmekten ayrı bir karardır:

// iki yol da çıkar; flag karar verir ve flag'in bir sahibi ve son kullanma tarihi vardır
export function price(cart, flags) {
  if (flags.enabled('pricing_v2')) return priceV2(cart);
  return priceV1(cart);
}

// flags.json
// { "pricing_v2": { "owner": "billing", "added": "2026-07-06", "remove_by": "2026-08-17" } }

Son kullanma tarihiyle ilgili o satır, flag'in kendisinden daha önemli. Silinme tarihi olmayan bir flag kalıcı hale gelir ve kalıcı flag'ler çarpar: üç tanesi kodda sekiz olası yol üretir, test'leriniz ikisini kapsar.

Flag'i açmak da tek hamle olmak zorunda değil. Önce kendi hesaplarınızda açın, sonra trafiğin küçük bir diliminde, sonra tamamında. Her basamakta geri dönüş tek bir ayar değişikliğidir ve deploy bile gerektirmez, bu yüzden yeni yolu kapatma kararı geç değil erken verilir. İzlemeniz gereken şey de yalnızca hata oranı değil, eski ve yeni yolun aynı girdiye aynı sonucu verip vermediğidir: iki yolu bir süre paralel koşturup çıktıları karşılaştırmak, en ucuz doğrulama biçimidir.

Şema değişiklikleri de aynı muameleyi expand and contract ile görür. Riskli tek bir migration, altı güvenli deploya dönüşür:

/* adım 1, expand: eklemeli ve geri alınabilir, henüz kimse okumuyor */
alter table invoice add column currency text;

/* adım 2 kod tarafı: iki kolona da yaz, okumaya eskiden devam et */

/* adım 3, kaldığı yerden devam eden batch'lerle backfill, sıfır satır diyene kadar koştur */
with batch as (
  select id from invoice where currency is null order by id limit 5000
)
update invoice i set currency = 'EUR'
from batch b where i.id = b.id;

/* adım 4 kod tarafı: yeni kolonu oku, yazmaya iki tarafa da devam et */
/* adım 5 kod tarafı: eski kolona yazmayı bırak */

/* adım 6, contract: ancak eski kolona hiç dokunulmayan tam bir sürümden sonra */
alter table invoice alter column currency set not null;
alter table invoice drop column legacy_currency;

Bir ile beş arasındaki adımların hepsi, önceki sürüm yeniden deploy edilerek geri alınır. Altıncı adım alınamaz; sonda yalnız durmasının ve aynı öğleden sonra değil bir sürüm sonra yapılmasının sebebi budur.

Üçüncü teknik, eski yolu bir sürüm boyunca ayakta tutmaktır. Eski endpoint, eski queue adı, eski URL, eski mesaj biçimi: hepsi yenisi devralırken yanıt vermeye devam eder. Sebebi nezaketten çok aritmetik. Deploy anlık değildir, cache'ler eski yanıtları tutar, kuyruklar eski mesajları tutar ve bazı client'lar sayfayı yenilememiş olur. Eski bir kalıcı yönlendirmeyi taşıyan tarayıcı, aynı sorunun uç halidir; onu şurada anlatmıştım: yeniden tasarımdan sonra önbellekteki 301.

Adımın gerçekten geri alınabilir olduğunu nasıl doğrularsınız

Üzerine düşünmeyin, koşturun. Alıştırma on dakika sürer ve bir planı garantiye çeviren tek şeydir:

# adımı deploy et, geri al, sonra tekrar çık
./deploy.sh release-41 && ./smoke.sh
./deploy.sh release-40 && ./smoke.sh
./deploy.sh release-41 && ./smoke.sh

Ortadaki satır patlıyorsa adım geri alınabilir değildir ve bunu bir olay anında değil sakin bir öğleden sonra öğrenmişsinizdir. Aynısı merge'ün geri alınması için de geçerli: git revert -m 1 <merge> derlenmeli ve test'leri geçmeli; geçmiyorsa merge fazla büyüktü.

Hiçbir şey yeniden başlamadan önce build çıkış kodunu kontrol edin, çünkü hatalı bir build'i deploy eden bir rollback, hiç rollback olmamasından kötüdür. O mekanizmayı bir kez doğru kurmaya değer ve anlatıldığı yer şurası: yeniden başlatmadan önce build çıkış kodunu kontrol edin.

Nelere dikkat etmeli

  • Amacından uzun yaşayan flag'ler. Silinmeyi, açılışla aynı planın içine tarihiyle birlikte koyun ve tarih geçtiğinde build patlasın. Kimsenin budamadığı bir flag listesi, dostane bir arayüzü olan teknik borçtur.
  • Geri alınabilir kod, geri alınamaz veri. Deploy geriye gidebilir. Silinmiş bir kolon ve silinmiş bir satır gidemez. Yıkıcı olan her şey, kimsenin okumadığını kanıtladığınız bir sürümün ardından, son adıma aittir.
  • Doğru çıkan ama kullanıcının önünde ürünü yarım bırakan adımlar. Yarım özellik bir flag'in arkasına aittir, yarı yarıya çalışan bir ekranın arkasına değil.
  • Hiç kaldırılmayan eski yol. Kaldırma planlanmadıysa hiçbir şeyi sadeleştirmemişsiniz, ikinci bir yapma biçimi ekleyip ikisini de tutmuşsunuzdur.
  • Tabloyu kilitleyen backfill'ler. Batch'e bölün, kaldığı yerden devam edebilir yapın ve yoğun saatler dışında koşturun. Tek transaction'da bitmesi gereken bir backfill, kılık değiştirmiş uzun bir branch'tir.

Ne söz verebilirsiniz

Bu yaklaşım kodu değiştirdiğinden çok, tarih konuşmasını değiştirir. Altı hafta sonrası için taahhüt kılığına girmiş tek bir tahmin yerine bir sıra önerebilirsiniz: bu adım gelecek hafta, şu adım ondan sonraki hafta ve herhangi birinin ardından çalışan bir sistemle durabilirsiniz. Kapsam değişikliği bütün plana değil tek bir adıma mal olur, çünkü henüz hiçbir şey bir varsayımın üstüne inşa edilmemiştir.

Dürüst kısmı şu: toplam süre nadiren kısalır. Altı deploy, tek deploydan daha uzun sürer ve rollback yazmak kullanıcının göreceği hiçbir şey üretmez. Satın aldığınız şey, projenin hiçbir zaman tıkanmaması, hiçbir zaman incelenemez hale gelmemesi ve hiçbir zaman kötü bir merge yüzünden bir haftalık toparlanmaya bir adım uzakta olmamasıdır. Uzun bir işte bunun değeri hızdan fazladır; savunduğunuz tarih ile tuttuğunuz tarih arasındaki fark da budur.

Sorular ve cevaplar

Bir adımı geri alınabilir yapan nedir?
Üç şey: tek başına deploy edilebilmesi, ondan sonra durulduğunda sistemin doğru kalması ve hayal edilmiş değil gerçekten koşturulmuş bir dönüş yolunun olması. Kod, önceki artifact yeniden deploy edilerek geri alınır. Veri alınamaz; column silmek ve satır silmek bu yüzden planın en sonuna aittir, ortasına asla.
Uzun ömürlü branch neden riskli?
Çakışma ihtimali her gün ve her dosyayla büyür, diff büyüdükçe review özensizleşir ve işin tamamı kısmen geri alınamayan tek bir olay olarak iner. Ayrıca sona kadar hiçbir şey öğrenmezsiniz, yani tahmininiz iş sürerken iyileşmez; oysa iyileşmesine tam da o sırada ihtiyacınız vardır.
Veritabanı kolonunu kesintisiz nasıl değiştiririm?
Expand and contract ile. Yeni kolonu nullable olarak ekleyin, iki kolona birden yazın, kaldığı yerden devam edebilen batch'lerle backfill yapın, okumayı yeni kolona alın, eskiye yazmayı bırakın ve ancak ondan sonra eskiyi silin. Bunların her biri ayrı bir deploy'dur ve her biri önceki sürümü yeniden deploy ederek geri alınır.
Feature flag kendi karmaşasını yaratmıyor mu?
Sonsuza kadar yaşarsa yaratır, çünkü her flag koddaki yol sayısını ikiye katlar ve kombinasyonları kimse test etmez. Flag'i, oluşturulurken sahibi ve silinme tarihi kaydedilmiş geçici bir iskele olarak görün ve onu açan sürümün bir sonrakinde kaldırın.