Müşteri destek kaydı, fizik kurallarını ihlal eden bir rapor içeriyordu: araç, sefer raporuna göre varış noktasına 14:03:11'de girmiş, ama seferi 14:03:13'te başlatmıştı. Yani araç, yola çıkmadan iki saniye önce varmıştı. Raporu üreten kodu üç kez okuduk; hata yoktu. Veriye baktık; veri gerçekten öyleydi. Sefer başlangıç olayı bir sunucuda, bitiş olayı başka bir sunucuda işlenmişti ve iki sunucunun duvar saatleri arasında 3.2 saniye fark vardı. Kod doğruydu, veri doğruydu, zaman yanlıştı.
Saat Nasıl Sessizce Kayar
İlk soru şuydu: NTP varken bu nasıl olur? Cevap, "NTP kurulu olmak" ile "NTP çalışıyor olmak" arasındaki uçurumda gizliydi. Sorunlu sunucu bir sanal makineydi ve birkaç hafta önce hypervisor bakımı sırasında canlı taşınmıştı. Taşıma sonrası ntpd, ani saat sıçramasını "bu kaynak güvenilmez" diye yorumlayıp senkronizasyonu bırakmış, kimseye de haber vermemişti. O günden beri makine kendi kristal osilatörünün insafına kalmıştı; günde yaklaşık yarım saniye kayıyordu. Alarmımız yoktu, çünkü saat senkronizasyonunu izlenecek bir şey olarak hiç düşünmemiştik. Disk doluluğunu, bellek kullanımını, servis ayaktalığını izliyorduk; zamanın kendisinin bozulabileceği aklımıza gelmemişti.
Hasar Listesi Sandığımızdan Uzundu
Sefer raporu buzdağının görünen ucuydu. Kazıdıkça o 3.2 saniyenin başka nelere dokunduğunu bulduk. Önbellek katmanında TTL hesapları şaşıyor, bir sunucunun taze diye yazdığını diğeri doğmadan ölmüş sayıyordu. Log'ları zaman damgasına göre birleştiren hata ayıklama aracımız, olayları yanlış sıraya diziyor ve bizi iki kez sahte neden-sonuç ilişkilerine inandırıyordu; birinde yarım gün, sonucun sebepten önce loglandığı bir hayaleti kovaladık. En sinsisi de rate limiter'dı: pencere hesabı iki sunucuda farklı çalıştığı için bazı kullanıcılar limitlerine hiç ulaşamıyor, bazıları erken çarpıyordu. Tek bir bozuk saat, aralarında hiçbir kod bağı olmayan dört ayrı sistemde dört farklı hastalık üretmişti.
Vakanın hemen ertesinde bütün filoyu denetledik: her makinede referans NTP kaynağına olan ofseti ölçen küçük bir script koşturduk. Sonuç dağılımı ders gibiydi: makinelerin çoğu 10 milisaniyenin altındaydı, üçü 100-500 milisaniye bandındaydı ve kahramanımız 3.2 saniyedeydi. Yani sorun tekil bir kaza değildi; filo, kimse bakmadığı için sessizce dağılıyordu. O üç ara vakanın ikisi de aynı sebebe çıktı: sanal makine taşıması sonrası küsen ntpd. Aynı hastalığın farklı şiddette üç vakasını aynı günde bulmak, "bu bir daha olmaz" avuntusunu daha doğmadan öldürdü ve izleme metriğinin pazarlığını bitirdi.
Onarım: chrony, Alarm ve Bir Karar
Teknik onarım hızlıydı. Bütün makinelerde ntpd yerine chrony'ye geçtik; sanal makine taşımalarından sonra toparlanması belirgin şekilde daha akıllı. Saat ofsetini merkezi izlemeye metrik olarak ekledik: her makine, referans kaynağına uzaklığını sürekli raporluyor ve 100 milisaniyeyi aşan ofset uyarı, 500 milisaniyeyi aşan ofset kritik alarm üretiyor. İlk hafta bu alarm iki makine daha yakaladı; meğer sürü hâlinde kayıyormuşuz da haberimiz yokmuş.
Ama asıl kalıcı onarım bir mimari karardı: olayların sırasını duvar saatine emanet etmekten vazgeçtik. Aynı cihazdan gelen olaylar için cihazın paket sıra numarası tek gerçek; sefer başlangıcı ve bitişi artık iki ayrı sunucunun saatinden değil, aynı monotonik sayaçtan sıralanıyor. Sıra numarası olmayan yerlerde de kural şu: bir olaya kim zaman damgası basacaksa hep aynı katman bassın. İki farklı makinenin saatini aynı cetvelde kıyaslamak, iki farklı terazinin gramlarını toplamak kadar anlamsız.
Onarımın gözden kaçan bir ayağı da süre ölçümüydü. Kod tabanında "bitiş zamanı eksi başlangıç zamanı" hesabı yapan ve iki değeri de duvar saatinden alan onlarca yer bulduk. Duvar saati NTP tarafından geriye çekilebildiği için bu farklar negatif çıkabiliyor; nitekim loglarda eksi 1.4 saniyelik bir "işlem süresi" bulduk ve bir metrik kütüphanemizin negatif değerde sessizce kayıt atladığını fark ettik. Süre ölçen her nokta monotonik saate taşındı — o saat hiç geri gitmez, yalnızca ilerler ve zaten tam bu iş için vardır. Kural artık kod incelemesi listesinde: duvar saati "ne zaman oldu" sorusuna, monotonik saat "ne kadar sürdü" sorusuna bakar; ikisini karıştıran kod geri döner.
Cihaz Saatleri: Ayrı Bir Dert Dünyası
Sunucularımızı hizaya soktuk ama sahada binlerce araç takip cihazı var ve onların saatleri bambaşka bir âlem. GPS modülü uydudan mükemmel zaman alır; ama uydu görüşü yokken bazı modeller dahili saate düşer ve o saatler dakikalarca kayabiliyor. Bir cihaz modelinin, tünelden çıkınca saati sıçratıp aynı konum paketini hem "geçmişte" hem "şimdi" damgasıyla gönderdiğini gördük. Bu yüzden şemamızda her olayın iki zamanı var: cihazın beyan ettiği zaman ve bizim kapıda bastığımız alım zamanı. İkisinin farkı da başlı başına bir sinyal; fark tutarlı biçimde büyükse o cihazın saati arızalı demektir ve bunu artık otomatik raporluyoruz.
Bir de bu olaydan sonra saat davranışını test edilebilir hâle getirdik. Zamanı doğrudan sistemden okuyan kod test edilemez; bu yüzden bütün servislere enjekte edilebilir bir saat arayüzü girdi. Entegrasyon testlerinde artık saatleri bilerek kaydırıyoruz: "rapor servisi, olay üreticisinden 5 saniye ilerideyse ne olur" senaryosu bir test dosyası olarak duruyor ve her pipeline koşusunda çalışıyor. İlk yazdığımızda üç test daha kırıldı; yani üç gizli saat varsayımı daha bulduk. En öğreticisi oturum süre aşımıydı: oturumun "son aktivite" damgası bir sunucuda, kontrolü başka sunucuda yapılınca, saat farkı kadar erken düşen oturumlar olabiliyormuş ve müşterilerden gelen "durup dururken attı" şikâyetlerinin izahı da buymuş. Yıllardır ara ara gelen, hiç açıklayamadığımız bir şikâyetin cevabının 3.2 saniyelik bir ofsette saklanması, bu mesleğin insana tevazu öğreten anlarından.
Skew'in bir kurbanı da kimlik doğrulama katmanıydı ve bunu neredeyse tesadüfen bulduk. Servisler arası çağrılarda kısa ömürlü imzalı token kullanıyoruz ve token'ların "şu andan önce geçersiz" alanı var. Saati ileri kaymış sunucunun ürettiği token, saati doğru komşuya "henüz geçerli değil" diye reddettiriyordu. Loglarda aylardır seyrek görülen, yeniden denemede kaybolduğu için kimsenin peşine düşmediği o gizemli yetkilendirme hataları, meğer saat farkının parmak iziymiş. Token doğrulamasına 30 saniyelik tolerans ekledik ama asıl çözümün o tolerans değil, ofset alarmı olduğunu artık biliyoruz; tolerans semptomu örter, alarm hastalığı yakalar.
Bu vakadan aklımda kalan cümle şu: dağıtık bir sistemde duvar saati bir ölçüm değil, bir rivayettir. Rivayetler işe yarar — loglara, grafiklere, insan okumasına yeter — ama sıralama, süre hesabı ve doğruluk iddiası gibi yük taşıyan yerlerde ya tek bir saat kullanın ya saate hiç güvenmeyen sıra numaraları. Ve mutlaka, mutlaka saat ofsetini izleyin; bozulduğunu size kendiliğinden söyleyen tek şey, fizik kurallarını ihlal eden bir müşteri raporu olmasın. Sisteminizde açıklayamadığınız sıralama tuhaflıkları görüyorsanız bir mesaj atın; ilk bakacağım yer muhtemelen kodunuz değil, saatleriniz olur.