Ana sayfa → Teknik
Yeniden Yazmak: Temiz Sayfa Değil, Göç Planı
Pazartesi 10:25, destekten mesaj: “Müşteri 4,20 TL komisyon ödemiş, geçen hafta aynı emirde 3,15 ödüyormuş.” Yeni komisyon motoru sabah 07:00’de devreye girmişti. 11:10’da eskisine geri döndük. Beş aylık rewrite, canlıda dört saat dayandı.
- Eski kod, spesifikasyonun kendisidir. Dokuz yılın istisnaları hiçbir dokümanda yok; sadece kodda var.
- Tek seferde geçiş, testi müşteriye yaptırmaktır. Bizde 570 emirde yanlış komisyon demekti.
- Önce gölge. İki sistem aynı isteği hesaplar, cevabı eski verir, fark kaydedilir. Altı haftada 14 fark kategorisi bulduk.
- Her fark sınıflandırılır. Yeni sistemin hatası mı, eskisinin hatası mı, bilerek yapılan değişiklik mi? Üçü ayrı liste.
- Geçiş segment segment. Önce kendi personel hesaplarımız, sonra %10, sonra hepsi.
- Rewrite, eski sistem kapandığı gün biter. Kapatma kriteri işe başlarken yazılır, sonra değil.
Sahadan: dört saat dayanan rewrite
Komisyon motoru, her gerçekleşen emir için müşteriden alınacak komisyonu hesaplayan parça. Dokuz yaşındaydı, veritabanında saklı yordamlar (stored procedure) içinde 4.200 satırdı. Her tarife değişikliği üç gün sürüyordu, çünkü kimse o koda dokunmak istemiyordu. Ocak ayında yeniden yazmaya karar verdik. Karar bence hâlâ doğru.
Yanlış olan plandı. Plan şuydu: yeni motoru temiz bir sayfaya yaz, tarife dokümanından test senaryoları çıkar, testler geçince bir sabah anahtarı çevir. Beş ay sonra, 16 Haziran Pazartesi sabahı çevirdik. Testlerin hepsi yeşildi.
Piyasa 10:00’da açıldı. 11:10’a kadar 18.400 emir gerçekleşti ve bunların 570’inde komisyon eskisinden farklıydı (%3,1). Toplam tutar küçüktü: 1.940 TL. Ama 570 müşteri vardı ve her birine ayrı iade, ayrı açıklama gerekti. Operasyon ekibi bu işi iki günde bitirdi.
Farkların üç kaynağı vardı ve üçü de tarife dokümanında yoktu. 2019 öncesi açılan hesaplar eski bir tarifeden gidiyordu. Kısmi gerçekleşen emirlerde eski motor her parçayı ayrı yuvarlıyordu. Bir grup kurumsal müşterinin sözleşmesinde günlük üst sınır vardı. Bunları kimse saklamamıştı. Sadece hiçbiri bir yere yazılmamıştı, kod hariç.
Hatam şuydu: tarife dokümanını spesifikasyon sandım. Oysa spesifikasyon, müşterilerin dokuz yıldır ödediği rakamlardı ve o rakamları üreten tek şey eski koddu. Temiz sayfa, o bilginin hepsini bilerek çöpe atmak demekti.
Rewrite neden cazip, neden tehlikeli
Yeniden yazmak cazip, çünkü eski sistemin maliyeti her gün görünüyor: yavaş değişiklik, korkulan deploy’lar, kodu bilen tek bir kişi. Yeni sistemin maliyeti ise henüz görünmüyor. Kâğıtta hep temiz.
Tehlikeli olan kısmı şu: rewrite süresince iki sistemin sahibisin. Eski sistem çalışmaya devam ediyor, tarife değişiklikleri gelmeye devam ediyor ve hepsini iki yere yazman gerekiyor. Yap mı al mı yazısında anlattığım sahiplik sorusu burada ikiye katlanıyor: geçiş dönemi uzadıkça iki sistemi birden taşıyan bir ekip oluyorsun.
Bu yüzden asıl soru “yeniden yazalım mı” değil. Asıl soru: eskisinden yenisine hangi adımlarla geçeceğiz ve her adımda nasıl geri döneceğiz? Bu sorunun cevabı yoksa, rewrite planın yok demektir. Sadece yeni bir sistem yazma planın var.
Göç planı: beş evre
İkinci denemede anahtarı çevirmedik. Yeni motoru eskisinin yanına koyduk ve trafiği evre evre taşıdık. Her evrenin bir geri dönüş yolu ve bir sonraki evreye geçiş şartı vardı:
| Evre | Cevabı kim veriyor | Süre | Sonrakine geçiş şartı |
|---|---|---|---|
| 1. Gölge | Eski; yeni sadece hesaplar | 7 Tem – 15 Ağu (6 hafta) | Açıklanmamış fark oranı %0,01’in altında, 5 iş günü üst üste |
| 2. Personel hesapları | Yeni, 38 hesap için | 18 – 22 Ağu | Personelden şikâyet yok, fark yok |
| 3. %10 | Yeni, hesap numarasına göre %10 | 25 – 29 Ağu | Destek kaydında komisyon şikâyeti artmadı |
| 4. %100 + ters gölge | Yeni; eski sadece hesaplar | 1 Eyl – 1 Eki (30 gün) | 30 gün boyunca açıklanmamış fark sıfır |
| 5. Kapatma | Yeni | 10 Ekim | Aşağıdaki kapatma kriteri |
Buna genelde strangler yaklaşımı deniyor: yeni sistem eskisini bir anda değil, parça parça sarıp yerini alıyor. Bizim durumumuzda parçalar kod modülleri değil, müşteri segmentleriydi. Motor bölünebilir değildi, ama müşteriler bölünebilirdi.
En sevdiğim evre ikincisi. İlk gerçek müşteri biziz: şirket personelinin kendi yatırım hesapları. Yanlış bir komisyon kesilirse şikâyet koridordan geliyor, 570 kişiden değil.
Geri dönüş yolunu da her evrede bir kez denedik. Hangi hesabın hangi motordan cevap alacağı tek bir yapılandırma tablosunda duruyordu; geri dönmek, o tablodaki bir satırı değiştirmekti. Haziran’da ilk mesajdan geri dönüşe 45 dakika geçmişti, çünkü eski motoru yeniden devreye almak bir deploy gerektiriyordu. İkinci denemede her evreye geçişten önce geri dönüşü test ortamında değil, canlıda, personel hesaplarıyla çalıştırdık: 40 saniye. Denenmemiş bir geri dönüş yolu, kâğıt üzerinde bir geri dönüş yoludur. Kriz anında çalışıp çalışmadığını öğrenmek için yanlış bir an.
Gölge: iki sistem, tek cevap
Gölge evresinin mantığı basit: her emir hem eski hem yeni motora gidiyor, müşteriye cevabı eski veriyor, iki sonuç karşılaştırılıyor. Basit ama üç ayrıntısı var:
def komisyon_hesapla(emir):
eski = eski_motor.hesapla(emir) # cevabi bu verir
try:
# yeni motor SADECE hesaplar: yazma yok, kuyruk yok, log tablosu yok
yeni = yeni_motor.hesapla(emir, yan_etkisiz=True)
karsilastir(emir, eski, yeni)
except Exception as hata:
fark_kaydet(emir, tur="YENI_HATA", detay=hata) # musteriyi etkilemez
return eski
def karsilastir(emir, eski, yeni):
# normalize et: zaman damgasi, alan sirasi, kurus alti basamak
e, y = normalize(eski), normalize(yeni)
if e.tutar != y.tutar:
fark_kaydet(emir, tur="TUTAR", eski=e.tutar, yeni=y.tutar,
ipucu=[emir.hesap_tipi, emir.kismi_mi, emir.tarife_kodu])
Yan etkisiz. Yeni motor gölgedeyken hiçbir yere yazmamalı. Bunu zor yoldan öğrendik: ilk gün yeni motorun denetim kaydı, eskisinin kullandığı tabloya da yazdı ve o günün komisyon raporu iki katı çıktı. Bir gün sürdü fark etmemiz.
Normalize et. İlk gün fark oranı %40 çıktı. Çoğu gürültüydü: zaman damgasının milisaniyesi, alanların sırası, kuruşun altındaki basamaklar. Normalize etmeden karşılaştırırsan gerçek farkları gürültünün içinde kaybedersin.
İpucu kaydet. Sadece “fark var” demek yetmiyor. Fark kaydına hesap tipi, kısmi gerçekleşme ve tarife kodu gibi alanları ekledik. Farkları bu alanlara göre grupladığımızda kategoriler kendiliğinden ortaya çıktı.
Her fark bir karar
Altı haftalık gölgede 2,1 milyon emri karşılaştırdık ve 14 fark kategorisi bulduk. Fark oranı ilk hafta %3,1’di (Haziran’daki oranla aynı, çünkü kod aynıydı), altıncı haftada %0,004’e indi. Asıl iş, her kategoriyi üç kutudan birine koymaktı:
| Kutu | Sayı | Örnek | Ne yaptık |
|---|---|---|---|
| Yeni motorun hatası | 11 | 2019 öncesi hesapların eski tarifesi, kurumsal günlük üst sınır | Yeni motoru eskisine uydurduk |
| Eski motorun hatası | 2 | Kısmi gerçekleşmede her parçayı ayrı yuvarlamak | Operasyon ve hukukla karar, müşteriye duyuru, sonra düzeltme |
| Bilerek yapılan değişiklik | 1 | Vergi tutarının ayrı satırda yuvarlanması | Belgeledik, beklenen fark listesine aldık |
Ortadaki satır en öğretici olanı. Eski motorun iki hatası vardı ve ilk refleksimiz “yenisinde düzgün yapalım” oldu. Yapmadık. Müşteri yıllardır o rakamı görüyordu; geçişle aynı gün sessizce değiştirmek, destek hattına “komisyonum neden değişti?” sorusunu davet etmekti. Önce yeni motor eskisini birebir taklit etti, hatalarıyla birlikte. Hataları geçiş bittikten sonra, duyurusuyla, ayrı bir değişiklik olarak düzelttik.
Kural şu: göç ve düzeltme aynı anda yapılmaz. Aynı anda yapılırsa, bir fark çıktığında hangisinden geldiğini bilemezsin.
Nasıl bozuluyor
- Her evrenin geri dönüş yolu, tek komutla
- Her evreye geçiş şartı, sayıyla
- Yan etkisiz gölge: yazma yok, mesaj yok
- Farkların sınıflandırıldığı tek liste ve sahibi
- Eski sisteme gelen değişikliklerin yenisine de yazılma kuralı
- Kapatma kriteri ve tarihi, baştan
- Dokümanı spesifikasyon sanmak
- “Testler yeşil”i “davranış aynı” sanmak
- Geçişle birlikte eski hataları sessizce düzeltmek
- Gölgede farkları sınıflandırmadan saymak
- Eski sistemi “yedek olarak” süresiz açık tutmak
Son madde en sinsi olanı. Geçiş biter, yeni sistem çalışır, eski sistem “ne olur ne olmaz” diye açık kalır. Bir yıl sonra ona bağlı iki rapor, bir gece işi ve kimsenin bilmediği bir entegrasyon bulursun. İki sistemin bakımını yapmaya devam edersin, bu sefer farkında olmadan.
Kapatma kriteri: işe başlarken yazılır
Eski motoru kapatma tarihini gölge evresi başlamadan yazdık, geçiş belgesinin ilk sayfasına. Tarih kaysa da kriter kaymadı:
- Ters gölgede 30 gün boyunca açıklanmamış fark sıfır.
- Eski motora gelen çağrı sayısı 14 gündür sıfır (çağrıları sayan bir sayaç ekledik).
- Eski motorun beslediği üç rapor yeni kaynaktan üretiliyor ve bir ay yan yana karşılaştırıldı.
- Eski kodun son hâli etiketlendi, tablo verisinin anlık görüntüsü alındı.
- Kapatmanın sahibi tek isim, tarihi 10 Ekim.
Çağrı sayacı beklenmedik bir şey buldu: muhasebe ekibinin ay sonu mutabakat betiği eski motoru doğrudan çağırıyordu. Hiçbir mimari çizimde yoktu. Sayaç olmasa bunu kapatmadan sonraki ilk ay sonunda, 31 Ekim’de öğrenecektik.
Ne izlemeli?
| Ne | Neden |
|---|---|
| Açıklanmamış fark oranı / gün | Bir sonraki evreye geçişin tek dürüst ölçüsü |
| Kategorisi atanmamış fark sayısı | Sayılan ama anlaşılmayan fark, ertelenmiş bir olaydır |
| Yeni motorun gölgedeki hata / zaman aşımı oranı | Müşteriyi etkilemiyor diye görmezden gelinir; %100’e geçince etkiler |
| Eski sisteme gelen çağrı sayısı ve kaynağı | Kimsenin bilmediği bağımlılığı kapatmadan önce bulur |
| İki yere yazılan değişiklik sayısı | Geçiş uzadıkça çift bakım maliyeti; evreler yavaşlıyorsa burada görünür |
Bende işe yaramayanlar
- Dokümandan test senaryosu çıkarmak. 300 senaryo yazdık, hepsi geçti. Senaryolar bizim bildiğimiz kuralları test ediyordu; bilmediklerimizi bulan tek şey gerçek trafikti.
- Gölgeyi örneklemle çalıştırmak. Maliyet olmasın diye ilk hafta emirlerin %5’ini karşılaştırdık. Kurumsal üst sınır gibi nadir kurallar o %5’e hiç düşmedi. %100’e çıkınca iki kategori daha çıktı.
- Eski sistemi gölge döneminde dondurmak. “Altı hafta tarife değişikliği yok” dedik; üçüncü haftada yeni bir kampanya tarifesi geldi. Dondurma kuralı yerine şunu koyduk: eski sisteme giren her değişiklik aynı PR’da yenisine de girer.
Kontrol listesi
- Spesifikasyon olarak dokümanı mı kullanıyorum, eski sistemin gerçek çıktısını mı?
- Göç planında kaç evre var ve her evrenin geri dönüşü tek adımda mı?
- Yeni sistem gölgedeyken gerçekten hiçbir yere yazmıyor mu?
- Farkları normalize ediyor muyum, yoksa gürültüyü mü sayıyorum?
- Her fark kategorisi üç kutudan birinde mi, sahibiyle birlikte?
- Eski sistemin hatalarını geçişle aynı anda mı düzeltmeye çalışıyorum?
- İlk gerçek kullanıcı kim? Şikâyeti koridordan duyabileceğim biri mi?
- Eski sistemi kapatma kriteri ve sahibi şimdiden yazılı mı?
- Eski sisteme gelen çağrıları sayan bir şey var mı?
Sonuç
16 Haziran’daki plan beş ay sürecekti. Göç planıyla toplam süre dokuz aya çıktı: eski motor 10 Ekim’de kapandı. Karşılığında ikinci denemede yanlış komisyon kesilen müşteri sayısı sıfır.
Temiz sayfa fikri, eski sistemin yalnızca bir yük olduğunu varsayıyor. Oysa eski sistem aynı zamanda bir hafıza: kimsenin yazmadığı her istisnayı, müşterinin alıştığı her rakamı tutuyor. Yeniden yazmak, o hafızayı çöpe atmak değil, satır satır yeni sisteme taşımak demek.
Test şu: yarın sabah yeni sisteme geçsen ve bir şey ters gitse, beş dakikada eskisine dönebilir misin? Dönemiyorsan elinde bir göç planı yok. Sadece bir umut var.