- Nanit, bebek uyku durumu analizi için kullanılan video işleme hattında AWS S3 kullanıyordu; ancak saniyede binlerce yükleme nedeniyle PutObject istek maliyeti toplam maliyetin büyük kısmını oluşturuyordu
- Ayrıca S3 Lifecycle kurallarındaki en az 1 gün saklama sınırı yüzünden, gerçekte 2 saniye içinde işlenen videolar için bile 24 saatlik depolama ücreti ödemek zorunda kalıyordu
- Bunu çözmek için Rust tabanlı bellek içi depolama sistemi N3 geliştirildi ve S3 yalnızca bir taşma tamponu olarak kullanıldı
- N3, SQS FIFO üzerinden mevcut işleme hattıyla tamamen uyumlu çalışırken katı sıralama garantisini ve güvenilirliği korudu
- Sonuç olarak yıllık yaklaşık 500 bin dolar maliyet tasarrufu sağlanırken, aynı zamanda basit ve kararlı bir yapı elde edildi
Arka plan
Video işleme hattına genel bakış
- Nanit kameraları video parçalarını kaydediyor, Camera Service üzerinden S3 presigned URL istiyor ve ardından doğrudan S3’e yüklüyor
- AWS Lambda, nesne anahtarını SQS FIFO kuyruğuna yayınlıyor (
baby_uid ile shard edilerek); video işleme pod’ları ise SQS’den tüketip S3’ten indiriyor ve ardından uyku durumu çıkarımı yapıyor
- Bu yapının avantajları
- S3’e iniş + SQS kuyruklama, kamera yüklemeleri ile video işlemeyi birbirinden ayırarak bakım veya geçici kesintiler sırasında bile video kaybını önlüyor
- S3 sayesinde erişilebilirlik ve dayanıklılığı doğrudan yönetmek gerekmiyor
- SQS FIFO + grup ID ile bebek bazında sıra korunuyor, işleme düğümleri de büyük ölçüde stateless kalabiliyor
- S3 Lifecycle kuralları çöp toplamayı üstleniyor, bu yüzden işlenmiş videoları takip etmeye gerek kalmıyor
Neden değişiklik gerekiyordu
- PutObject maliyeti baskındı: videolar yalnızca birkaç saniye iniş alanında duran kısa ömürlü nesnelerdi; ancak saniyede binlerce yükleme ölçeğinde nesne başına istek maliyeti en büyük maliyet sürücüsü haline gelmişti
- Chunk sıklığını artırıp (daha fazla küçük parça göndermek) gecikmeyi azaltmak istenirse, her ek parça yeni bir PutObject isteği anlamına geldiğinden maliyet doğrusal biçimde artıyordu
- Depolama ikinci kez ücretlendiriliyordu: işleme yaklaşık 2 saniyede bitse bile Lifecycle silme kuralları yaklaşık 24 saatlik depolama maliyeti doğuruyordu
- Bu nedenle güvenilirliği ve katı sıralama garantisini korurken, normal yolda nesne başına maliyetten kaçınan ve “bekleme için ödeme yapılan” depolamayı en aza indiren bir tasarım gerekiyordu
Plan
-
Tasarım ilkeleri
- Mimari üzerinden sadelik: akıllı implementasyonlar yerine tasarım seviyesinde karmaşıklığı ortadan kaldırmak
- Doğruluk: hattın geri kalanı için şeffaf ve tam bir ikame olmak
- Normal yola göre optimizasyon: yaygın duruma göre tasarlamak ve uç durumlarda S3’ü güvenlik ağı olarak kullanmak; işleme algoritması ara sıra oluşan boşluklara dayanıklı olduğundan, karmaşık garantiler kurmaktan ziyade sadelik tercih edildi
-
Tasarımı yönlendiren etkenler
- Kısa ömürlü nesneler: segmentler iniş alanında yalnızca birkaç saniye kalıyor
- Sıralama: bebek bazında katı sıralama (yeniler eskilerden önce işlenmiyor)
- Verim: saniyede binlerce yükleme, segment başına 2-6 MB
- İstemci kısıtları: kameraların sınırlı yeniden deneme sayısı var, yeniden gönderime güvenilemiyor
- Operasyon: bakım veya ölçek büyütme sırasında milyonlarca öğelik backlog tolere edilmeli
- Firmware değişikliği yok: mevcut kameralarla çalışmalı
- Kayıp toleransı: çok küçük boşluklar kabul edilebilir, algoritma bunları maskeleyebiliyor
- Maliyet: normal yolda nesne başına S3 maliyetinden kaçınmak, “bekleme için ödeme yapılan” depolamayı en aza indirmek
Tasarıma genel bakış (N3 normal yol + S3 taşması)
-
Mimari
- N3, işleme tarafının boşaltması için gereken süre boyunca (yaklaşık 2 saniye) videoyu bellekte tutan özel bir iniş alanı; yalnızca N3 yükü kaldıramadığında S3 kullanılıyor
- İki bileşen
- N3-Proxy (stateless, çift arayüzlü)
- Dışarıya açık (internete bağlı): presigned URL üzerinden kamera yüklemelerini kabul ediyor
- İç tarafta (özel): Camera Service’e presigned URL veriyor
- N3-Storage (stateful, yalnızca iç erişim): yüklenen segmentleri RAM’de saklıyor ve pod adreslenebilir indirme URL’siyle SQS’ye kuyruğa alıyor
- Video işleme pod’ları SQS FIFO’dan tüketiyor ve URL’nin işaret ettiği depodan (N3 veya S3) indiriyor
-
Normal akış (Happy Path)
- Kamera, Camera Service’ten yükleme URL’si istiyor
- Camera Service, N3-Proxy’nin iç API’sinden presigned URL istiyor
- Kamera, videoyu N3-Proxy’nin dış endpoint’ine yüklüyor
- N3-Proxy bunu N3-Storage’a iletiyor
- N3-Storage videoyu bellekte tutuyor ve kendisini işaret eden indirme URL’siyle SQS’ye kuyruğa alıyor
- İşleme pod’u N3-Storage’dan indirip işliyor
-
İki katmanlı fallback
- Katman 1: proxy düzeyinde fallback (istek bazında)
- Bellek baskısı, işleme backlog’u, pod arızası gibi nedenlerle N3-Storage yükleme kabul edemezse, N3-Proxy kamera adına S3’e yükleme yapıyor
- Kamera ise arıza tespit edilmeden önce zaten N3 URL’sini almış oluyor
- Katman 2: küme düzeyinde yeniden yönlendirme (tüm trafik)
- N3-Proxy veya N3-Storage sağlıksızsa, Camera Service N3 URL vermeyi durduruyor ve doğrudan S3 presigned URL döndürüyor
- N3 toparlanana kadar tüm trafik S3’e akıyor
-
Neden iki bileşene ayrıldı
- Arıza yarıçapı: depolama çökse bile proxy trafiği S3’e yönlendirebiliyor; proxy çökerse yalnızca o düğümün trafiği etkileniyor, tüm depolama kümesi değil
- Kaynak profili: proxy CPU/ağ yoğun (TLS sonlandırma), depolama bellek yoğun (videoyu tutma); dolayısıyla farklı instance tipleri ve ölçekleme ihtiyaçları var
- Güvenlik: depolama katmanı asla internete doğrudan temas etmiyor
- Dağıtım güvenliği: proxy (stateless) güncellenirken depolama tarafındaki (aktif veri tutan) yapı etkilenmiyor
Tasarımın doğrulanması
-
Doğrulanması gerekenler
- Kapasite ve boyutlandırma: istemci ağları genelindeki gerçek yükleme süreleri, gereken hesaplama gücü ve yükleme tampon boyutu
- Depolama modeli: her şeyin RAM’de tutulup tutulamayacağı ya da disk gerekip gerekmediği
- Dayanıklılık: yük dengelemenin ve arıza yapan düğümlerin düşük maliyetle nasıl ele alınacağı
- Operasyon politikası: GC ihtiyacı, retry beklentileri, GET sırasında silmenin yeterli olup olmadığı
- Bilinmeyen bilinmezler: fikir gerçek dünyayla buluştuğunda hangi uç durumların ortaya çıkacağı
-
Yaklaşım 1: sentetik stres testi
- Farklı eşzamanlılık seviyeleri, yavaş istemciler, sürekli yük ve işleme kesintileriyle sistemi sınırına kadar zorlayan bir yük üretici kuruldu
- Amaç: limit noktalarını bulmak, beklenmeyen darboğazları tespit etmek ve kapasite planlaması için deterministik bir başlangıç çizgisi elde etmek
-
Yaklaşım 2: prodüksiyon PoC (mirror mode)
- Sentetik testler gerçek kamera davranışını kopyalayamıyordu: kararsız Wi-Fi, farklı firmware sürümleri, öngörülemeyen ağ koşulları
- Mirror mode:
n3-proxy önce S3’e yazıyor (prodüksiyon korunuyor), ardından PoC N3-Storage’a da yazıyor (canary SQS + video processor’a bağlı)
- Hedef kohortlar: firmware sürümüne veya Baby-UID listelerine göre
- Veri eşliği: PoC ile prodüksiyonun uyku durumu sonuçları karşılaştırılıyor, farklar inceleniyor
- Gözlemlenebilirlik: yol bazlı panolar (N3 vs S3), kuyruk derinliği, gecikme/RPS, hata bütçesi, egress analizi
- Feature flag’lerin (Unleash kullanılarak) önemi büyüktü: dağıtım yapmadan kohortların gerçek zamanlı değiştirilebilmesini sağladı; dar dilimler (eski firmware, zayıf Wi-Fi kullanan kameralar) test edilip sorun olduğunda anında geri alınabildi
-
Neler bulundu
- Darboğazlar: CPU’nun büyük kısmını TLS sonlandırma tüketiyordu; AWS burstable networking ise kredi tükendiğinde throttling’e yol açıyordu
- Yalnızca bellek kullanan depolama uygulanabilirdi: gerçek yükleme süresi dağılımı ve eşzamanlılık sayesinde çalışma setinin RAM’de güvenli payla tutulabildiği doğrulandı, diske gerek kalmadı
- TCP timestamp overhead’i: aktarılan toplam baytın yaklaşık %85’i ACK frame’lerinden oluşuyordu; TCP timestamp’leri devre dışı bırakmak (
sysctl -w net.ipv4.tcp_timestamps=0) ile ACK başına 12 bayt tasarruf edildi
- Risk: aynı sokette çok yüksek miktarda veri gönderilirse sıra numaralarının dolanması ve gecikmiş paketlerin yanlış birleştirilmesiyle bozulma oluşabilir
- Azaltım: (1) her yükleme için yeni soket, (2)
n3-proxy ↔ n3-storage soketlerini yaklaşık 1 GB veri aktarımından sonra yenilemek
- Bellek sızıntısı: ilk yayından sonra
n3-proxy belleği istikrarlı biçimde artıyordu
jemalloc profillemesi, artışın bağlantı başına hyper BytesMut tamponlarında olduğunu gösterdi
- Bazı istemci bağlantıları aktarım sırasında takılı kalıyor ve temizlenmediği için tamponlar elde tutuluyor, bu da belleğin sürekli artmasına yol açıyordu
- Düzeltme: soketleri kısa ömürlü yapmak ve süre sınırları uygulamak
- Keep-alive kapatıldı: her yükleme tamamlandıktan sonra bağlantı hemen kapatıldı
- Timeout’lar sıkılaştırıldı: header/soket timeout’larıyla takılı kalan yüklemeler sonlandırıldı ve tamponlar serbest bırakıldı
Depolama
-
Bellek içi depolama
- En basit yoldan başlandı: bellek içi depolama ile I/O ayarlarından kaçınıldı ve sezgisel veri yapıları kullanıldı
- Videolar
Arc<DashMap<Ulid, Bytes>> içinde saklandı; her video yüklemesi bytes_used değerini artırıyor, her indirme ise videoyu silip bu değeri azaltıyordu
- Kapasitenin yaklaşık %80’inin üzerine çıkıldığında OOM’dan kaçınmak için yüklemeler reddedilmeye başlanıyor ve
n3-proxy’ye yükleme URL’si imzalamayı durdurma sinyali veriliyordu
control handle ile yüklemeleri ve garbage collection’ı elle duraklatmak mümkün hale getirildi
-
Zarif yeniden başlatma
- Yalnızca bellek kullanan depolama nedeniyle yeniden başlatma sırasında ilerlemekte olan verilerin düşmemesi gerekiyordu
- Zarif yeniden başlatma süreci
- Pod’a
SIGTERM gönderiliyor (StatefulSet, rolling update’i aynı anda bir pod olacak şekilde yapıyor)
- Pod Not Ready durumuna geçiyor ve Service’ten çıkarılıyor (yeni yükleme gelmiyor)
- Daha önce yüklenmiş videoların indirilmesine hizmet etmeye devam ediyor
- İndirmeler durduğunda (yakın zamanda okuma yok → işleme boşaltıldı)
- Açık isteklerin tamamlanması bekleniyor
- Ardından yeniden başlatılıp sonraki pod’a geçiliyor
- Sağlıklı durumda pod’lar birkaç saniye içinde boşalıyor
-
GC
- İki temizleme mekanizması kullanıldı
- İndirme sırasında silme: video indirildikten hemen sonra siliniyor; PoC’de yeniden indirme sayısının sıfır olduğu görüldü, video processor zaten içeride retry yaptığı için veri tutmaya veya “işlendi” durumunu izlemeye gerek kalmadı
- Geride kalanlar için TTL GC: indirme sırasında silme, processor’ın atladığı segmentleri kapsayamıyor (indirilmezse silinmiyor)
- Bu yüzden hafif bir TTL GC eklendi: bellek içi DashMap periyodik olarak taranıyor ve yapılandırılabilir eşiğin (ör. birkaç saat) üzerindeki kayıtlar kaldırılıyor
- Bakım modu: planlı işleme kesintileri sırasında dahili kontrol yoluyla GC geçici olarak durdurulabiliyor, böylece tüketim durmuşken videolar silinmiyor
Sonuç
-
Temel kazanımlar
- S3’ü fallback tamponu, N3’ü ise ana iniş alanı olarak kullanarak sistemi basit ve güvenilir tutarken yıllık yaklaşık 500 bin dolar tasarruf elde edildi
- Ana içgörü: çoğu “build vs buy” kararı özelliklere odaklanır, ancak ölçek büyüdüğünde ekonomi hesabı değiştirir
- Kısa ömürlü nesneler için (normal çalışmada yaklaşık 2 saniye) replikasyon veya sofistike dayanıklılık gerekmiyor; basit bir bellek içi depo yeterli olabiliyor
- İşleme geciktiğinde veya bakım nesne ömrünü uzattığında ise S3’ün güvenilirlik garantilerine ihtiyaç duyuluyor
- İki dünyanın en iyi yanı: N3 normal yolu verimli şekilde yönetiyor, S3 ise nesnelerin daha uzun yaşaması gerektiğinde dayanıklılık sağlıyor
- N3’te sorun olursa (bellek baskısı, pod çökmesi, küme sorunu) yüklemeler sorunsuz biçimde S3’e failover ediyor
-
Başarı faktörleri
- Problemin önceden net tanımlanması: kısıtlar, varsayımlar ve sınırlar kapsamın gereksiz genişlemesini engelledi
- Mirror mode PoC ile erken doğrulama: darboğazlar (TLS, ağ throttling’i) bulundu ve varsayımlar büyük taahhütlerden önce test edildi
- aşırı mühendisliğin ve geri dönüp düzeltmenin önüne geçildi
-
Böyle bir şey ne zaman inşa edilmeli
- Ancak yeterli ölçekte anlamlı maliyet tasarrufu mümkünse ve basit çözümü mümkün kılan özel kısıtlar mevcutsa özel altyapı düşünülmeli
- Sistemi kurup sürdürmenin mühendislik maliyeti, ortadan kaldırılan altyapı maliyetinden düşük olmalı
- Nanit örneğinde, özel gereksinimler (geçici depolama, kayıp toleransı, S3 fallback’i) bakım maliyetini düşük tutacak kadar basit bir sistem kurmayı mümkün kıldı
- Bu iki koşul yoksa yönetilen servislerde kalmak daha doğru
- Tekrar yaparlar mıydı? Evet; sistem prodüksiyonda kararlı şekilde çalışıyor ve fallback tasarımı sayesinde güvenilirlikten ödün vermeden karmaşıklıktan kaçınılabiliyor
3 yorum
Sadece ec2 ya da eks pod’larının videoyu doğrudan yükleyip işlemesi olmaz mıydı diye merak ediyorum.
Proxy’ye kadar geliştirdilerse, pod yüküne göre eks otomatik ölçeklendirmesi de fazlasıyla mümkün görünüyor.
Video işleme tarafında genelde dosyanın tamamını belleğe almak gerekmez; her instance’ın yerel SSD’sinde geçici dosya oluşturup işleselerdi, s3 fallback’e de gerek kalmazdı gibi geliyor.
Sunucusuz mimari ile S3'ün kötü kullanıldığı bir örnek gibi görünüyor.
Ama çözüm de daha da tuhaf görünüyor.
Hacker News görüşleri
Gerçekten çok faydalı bir yazıydı. Böyle teknik yaklaşım sürecinin paylaşılmasını çok seviyorum
Aynı problemi bizzat yaşamıyor olsam bile, nasıl bir düşünce tarzıyla yaklaşıldığını görmek bile çok şey öğretiyor
Dürüst olmak gerekirse, buna en baştan serverless kullanılmasaydı çok daha temiz olurdu gibi geliyor
Saniyelik veriyi zorla AWS serverless paradigmasına sıkıştırmaya çalışırken gereksiz maliyet ve karmaşıklık ortaya çıkmış gibi
Yine de bellek tabanlı çözüme geçmeleri iyi bir tercih olmuş
TLS handshake'in CPU'yu çok kullandığını söylemişler ama asıl darboğazın bu olduğunu sanmıyorum
Yine de böyle bir iş akışına özel sistem tasarımı denemeleri ilginçti
Aslında başlığın dediği gibi “S3'ü kendileri implemente etmişler” değil, S3'ün önüne bellek önbelleği koymuşlar
Havalı ama tam anlamıyla şirket içi bir S3 alternatifi değil
Başlık her ne olursa olsun ilginç bir projeydi
HN tarzında söylemek gerekirse, Nanit şirketinin kendisi hakkında konuşmak istiyorum
Nanit, bulut tabanlı bebek monitörü kamerası işletiyor. Tüm video ve ses E2EE olmadan yükleniyor
Donanım pahalı ve abonelik olmadan neredeyse kullanılamıyor. Üstelik uyku takibi özelliğini açmak için 200 dolarlık bir stand da satın almanız gerekiyor
Böyle bir yapının sonuçta buluta bağımlı modeli güçlendirmesi üzücü
Ama yine de bu yazıdaki gibi S3 bağımlılığını azaltıp kendi depolamalarına geçmeleri iyi bir iş olmuş
Diğer ürünlerde uygulama kararsızdı. Local-first + E2EE bir çözüm güzel olurdu ama pratikte kullanılabilirlik daha önemliydi
Gerçek E2EE isteniyorsa analiz local'de yapılmalı ve yalnızca sonuçlar yüklenmeli
Bu yazı, sanki kendi sorununu kendi yaratıp sonra çözdüğünü kutlama havası veriyordu
En baştan local storage donanımı satsalardı daha basit ve daha ucuz olurdu
Bulut merkezli tasarım artık 2015 tarzı bir yaklaşım gibi geliyor
Yazı harikaydı ama S3 üzerinde 'delete on read' uygulandığında maliyet azaltma etkisinin ne olduğunu da merak ettim
Eğer S3 saniye bazlı faturalandırma yapıyorsa tasarruf oldukça büyük olmuş olabilir
Ayrıca bu çözüm aslında S3'ün 'reduced redundancy' seçeneğine de benziyor
500 bin dolar tasarruf ettiklerini söylüyorlar ama toplam maliyetin ne olduğunu bilmiyoruz
Bunun 500 bin doların içinden 500 bin 1 dolar mı olduğu, yoksa 55 milyon doların içinden 500 bin dolar mı olduğu anlamı değiştirir
En başta yanlış mimari seçilmiş, sonra da üstü önbellekle kapatılmış gibi hissettiriyor
Ortalama 2 saniyelik videoları S3'e yüklemek için yedekli depolama dışında bir sebep yok
Sunucuda doğrudan işleselerdi S3, SQS ve Lambda'nın hepsini ortadan kaldırabilirlerdi
Böyle basit bir problemi neden bu kadar karmaşık hale getirdiklerini anlamıyorum
Bu bana klasik “uygulama geliştirmeye odaklan, altyapıyı sadeleştir” dersini hatırlattı
Hatta önbelleği doğrudan video işleme sunucusunun içine koymak daha iyi olabilirdi
Başlık aslında “S3'ü yanlış kullandık” olsaydı daha doğru olurdu
Sonunda kendi bellek deposunu yapmışlar ama bunun yerine Redis gibi bir şey kullansalar olurmuş
Kendi yaptıkları sistem çökerse videolar kayboluyor mu?
En baştan Kinesis veya SQS'ye gönderseler çok daha iyi olurdu