Bagaimana Membangun “S3” Sendiri dan Menghemat $500.000 per Tahun
(engineering.nanit.com)- Nanit menggunakan AWS S3 dalam pipeline pemrosesan video untuk analisis tidur bayi, tetapi dengan ribuan upload per detik, biaya permintaan PutObject menjadi porsi terbesar dari total biaya
- Selain itu, karena batas retensi minimum 1 hari pada aturan S3 Lifecycle, untuk video yang sebenarnya diproses dalam 2 detik, mereka tetap harus membayar biaya penyimpanan 24 jam
- Untuk mengatasinya, mereka membangun N3, sistem penyimpanan in-memory berbasis Rust, dan menggunakan S3 hanya sebagai buffer overflow
- N3 sepenuhnya kompatibel dengan pipeline pemrosesan yang ada melalui SQS FIFO, sambil tetap mempertahankan jaminan urutan yang ketat dan keandalan
- Hasilnya, mereka menghemat sekitar $500.000 per tahun sekaligus mendapatkan arsitektur yang sederhana dan stabil
Latar belakang
Gambaran pipeline pemrosesan video
- Kamera Nanit merekam potongan video, meminta S3 presigned URL dari Camera Service, lalu mengunggah langsung ke S3
- AWS Lambda memublikasikan object key ke antrian SQS FIFO (dishard berdasarkan baby_uid), lalu pod pemrosesan video mengonsumsinya dari SQS, mengunduh dari S3, dan menjalankan inferensi status tidur
- Keuntungan dari pengaturan ini
- Landing di S3 + antrean SQS memisahkan upload kamera dan pemrosesan video, sehingga mencegah kehilangan video bahkan selama pemeliharaan atau downtime sementara
- Tidak perlu mengelola availability dan durability sendiri berkat S3
- SQS FIFO + group ID menjaga urutan per bayi, dan node pemrosesan bisa tetap sebagian besar stateless
- Aturan S3 Lifecycle menangani garbage collection sehingga tidak perlu melacak video yang sudah diproses
Mengapa perlu diubah
- Biaya PutObject mendominasi: video adalah objek berumur sangat pendek yang hanya singgah selama beberapa detik sebelum diproses, tetapi pada skala ribuan upload per detik, biaya permintaan per objek menjadi pendorong biaya terbesar
- Jika frekuensi chunking ditingkatkan (mengirim lebih banyak chunk kecil) demi menurunkan latensi, maka setiap chunk tambahan berarti satu permintaan PutObject lagi, sehingga biaya naik secara linear
- Penyimpanan menjadi pajak kedua: walaupun pemrosesan selesai dalam sekitar 2 detik, aturan penghapusan Lifecycle tetap mengenakan biaya penyimpanan sekitar 24 jam
- Mereka membutuhkan desain yang bisa menghindari biaya per objek di jalur normal sambil tetap menjaga keandalan dan jaminan urutan yang ketat, serta meminimalkan penyimpanan yang “membayar biaya saat menunggu”
Rencana
-
Prinsip desain
- Kesederhanaan melalui arsitektur: menghilangkan kompleksitas di tingkat desain, bukan lewat implementasi yang terlalu cerdas
- Ketepatan: menjadi pengganti penuh yang transparan bagi bagian pipeline lainnya
- Dioptimalkan untuk jalur normal: dirancang untuk kasus umum, dan menggunakan S3 sebagai jaring pengaman untuk edge case; karena algoritme pemrosesan tahan terhadap jeda sesekali, kesederhanaan diprioritaskan daripada membangun jaminan yang kompleks
-
Pendorong desain
- Objek berumur pendek: segmen hanya berada di area landing selama beberapa detik
- Urutan: sequencing ketat per bayi (tidak memproses data yang lebih baru lebih dulu)
- Throughput: ribuan upload per detik, 2-6 MB per segmen
- Keterbatasan klien: kamera memiliki jumlah retry terbatas, jadi tidak bisa mengandalkan retransmisi
- Operasional: harus bisa menoleransi backlog jutaan item selama pemeliharaan/scale-up
- Tanpa perubahan firmware: harus bekerja dengan kamera yang sudah ada
- Toleransi kehilangan: celah yang sangat kecil dapat diterima dan ditutupi oleh algoritme
- Biaya: menghindari biaya S3 per objek di jalur normal dan meminimalkan penyimpanan “membayar biaya saat menunggu”
Gambaran desain (jalur normal N3 + overflow S3)
-
Arsitektur
- N3 adalah area landing kustom yang menyimpan video di memori hanya selama waktu yang dibutuhkan untuk menguras pemrosesan (sekitar 2 detik), dan S3 hanya dipakai ketika N3 tidak mampu menangani beban
- Dua komponen
- N3-Proxy (stateless, antarmuka ganda)
- eksternal (terhubung ke internet): menerima upload kamera melalui presigned URL
- internal (privat): menerbitkan presigned URL ke Camera Service
- N3-Storage (stateful, khusus internal): menyimpan segmen yang diunggah di RAM dan mengantrekan ke SQS dengan download URL yang dapat dialamatkan ke pod
- N3-Proxy (stateless, antarmuka ganda)
- Pod pemrosesan video mengonsumsi dari SQS FIFO dan mengunduh dari storage yang ditunjuk URL tersebut (N3 atau S3)
-
Alur normal (Happy Path)
- Kamera meminta upload URL dari Camera Service
- Camera Service meminta presigned URL dari API internal N3-Proxy
- Kamera mengunggah video ke endpoint eksternal N3-Proxy
- N3-Proxy meneruskannya ke N3-Storage
- N3-Storage menyimpan video di memori dan mengantrekan ke SQS dengan download URL yang menunjuk ke dirinya sendiri
- Pod pemrosesan mengunduh dari N3-Storage lalu memprosesnya
-
Fallback dua tingkat
- Tier 1: fallback tingkat proxy (per permintaan)
- Jika N3-Storage tidak bisa menerima upload karena tekanan memori, backlog pemrosesan, kegagalan pod, dan sebagainya, N3-Proxy akan mengunggah ke S3 atas nama kamera
- Kamera sudah telanjur menerima URL N3 sebelum kegagalan terdeteksi
- Tier 2: rerouting tingkat cluster (seluruh trafik)
- Jika N3-Proxy atau N3-Storage tidak sehat, Camera Service akan berhenti menerbitkan URL N3 dan langsung mengembalikan S3 presigned URL
- Seluruh trafik akan mengalir ke S3 sampai N3 pulih
- Tier 1: fallback tingkat proxy (per permintaan)
-
Mengapa dipisah menjadi dua komponen
- Blast radius: jika storage crash, proxy masih bisa merutekan ke S3; jika proxy crash, hanya trafik node itu yang terdampak, seluruh cluster storage tetap aman
- Profil resource: proxy intensif CPU/jaringan (terminasi TLS), storage intensif memori (menahan video), sehingga membutuhkan tipe instance dan skala yang berbeda
- Keamanan: storage tidak pernah bersentuhan dengan internet
- Keamanan rollout: saat memperbarui proxy (stateless), storage (yang memegang data aktif) tidak disentuh
Validasi desain
-
Hal yang perlu divalidasi
- Kapasitas dan sizing: durasi upload nyata di berbagai jaringan klien, compute yang dibutuhkan, dan ukuran buffer upload
- Model storage: apakah semuanya bisa disimpan di RAM atau perlu disk
- Resiliensi: bagaimana melakukan load balancing dengan murah dan menangani node gagal
- Kebijakan operasional: kebutuhan GC, ekspektasi retry, dan apakah hapus saat GET sudah cukup
- Unknown unknowns: edge case apa yang akan muncul saat ide ini bertemu dunia nyata
-
Pendekatan 1: stress test sintetis
- Membangun load generator yang mendorong sistem hingga batasnya dengan berbagai concurrency, klien lambat, beban berkelanjutan, dan downtime pemrosesan
- Tujuan: menemukan titik batas, mengidentifikasi bottleneck tak terduga, dan mendapatkan baseline deterministik untuk perencanaan kapasitas
-
Pendekatan 2: PoC produksi (mode mirror)
- Uji sintetis tidak bisa mereplikasi perilaku kamera nyata: Wi‑Fi tidak stabil, berbagai versi firmware, dan kondisi jaringan yang tak terduga
- Mode mirror: n3-proxy lebih dulu menulis ke S3 (retensi produksi), lalu juga menulis ke N3-Storage PoC (terhubung ke SQS canary + pemroses video)
- Cohort target: berdasarkan versi firmware / daftar Baby-UID
- Data parity: membandingkan status tidur antara PoC dan produksi, lalu menyelidiki perbedaannya
- Observability: dashboard per jalur (N3 vs S3), kedalaman antrean, latensi/RPS, error budget, analisis egress
- Feature flag (menggunakan Unleash) sangat penting: memungkinkan peralihan cohort secara real time tanpa deploy, menguji irisan sempit (firmware lama, kamera dengan Wi‑Fi lemah), lalu langsung memulihkan jika ada masalah
-
Temuan
- Bottleneck: terminasi TLS menghabiskan sebagian besar CPU, dan AWS burstable networking mengalami throttling setelah kredit habis
- Storage khusus memori layak dijalankan: distribusi waktu upload dan concurrency nyata menunjukkan working set bisa disimpan aman di RAM dengan margin, jadi disk tidak diperlukan
- Overhead TCP timestamp: sekitar 85% dari total byte yang ditransmisikan adalah frame ACK; menonaktifkan TCP timestamp (
sysctl -w net.ipv4.tcp_timestamps=0) menghemat 12 byte per ACK- Risiko: saat mengirim banyak byte pada soket yang sama, nomor urut bisa wrap sehingga paket tertunda bisa salah digabung dan menyebabkan korupsi
- Mitigasi: (1) soket baru untuk setiap upload, (2) mendaur ulang soket n3-proxy ↔ n3-storage setelah sekitar 1 GB transfer
- Kebocoran memori: setelah rilis awal, memori n3-proxy terus meningkat
- Profiling
jemallocmenunjukkan pertumbuhan pada bufferhyperBytesMut per koneksi - Beberapa koneksi klien berhenti di tengah transfer dan tidak dibersihkan, membuat buffer tertinggal dan memori terus naik
- Perbaikan: membuat soket berumur pendek dan menerapkan batas waktu
- Menonaktifkan keep-alive: koneksi langsung ditutup setelah setiap upload selesai
- Memperketat timeout: menetapkan timeout header/soket untuk menghentikan upload yang macet dan membebaskan buffer
- Profiling
Storage
-
Storage in-memory
- Memulai dari jalur paling sederhana: storage in-memory untuk menghindari tuning I/O dan memakai struktur data yang intuitif
- Menyimpan video dengan
Arc<DashMap<Ulid, Bytes>>; setiap upload video menaikkanbytes_used, dan setiap download menghapus video lalu menurunkannya - Mulai menolak upload saat kapasitas mencapai sekitar 80% atau lebih untuk menghindari OOM, dan memberi sinyal ke n3-proxy agar berhenti menandatangani upload URL
- Tersedia handle
controluntuk menjeda upload dan garbage collection secara manual
-
Restart yang graceful
- Karena storage hanya ada di memori, perlu mencegah data aktif hilang saat restart
- Proses restart graceful
- Pod menerima
SIGTERM(StatefulSet rolling satu per satu) - Pod menjadi Not Ready dan keluar dari Service (tidak ada upload baru)
- Tetap melayani download untuk video yang sudah telanjur diunggah
- Saat download berhenti (tidak ada pembacaan baru belakangan ini → pemrosesan sudah terkuras)
- Menunggu semua permintaan terbuka selesai
- Setelah restart, lanjut ke pod berikutnya
- Pod menerima
- Dalam kondisi normal, pod akan terkuras dalam beberapa detik
-
GC
- Menggunakan dua mekanisme pembersihan
- Hapus saat download: video dihapus tepat setelah diunduh; PoC menunjukkan nol re-download, dan karena pemroses video melakukan retry secara internal, tidak perlu menyimpan data atau melacak status “sudah diproses”
- TTL GC untuk yang tertinggal: hapus saat download tidak mencakup segmen yang dilewati pemroses (tidak diunduh → tidak dihapus)
- Ditambahkan TTL GC ringan: memindai DashMap in-memory secara berkala dan menghapus entri yang lebih tua dari ambang yang dapat dikonfigurasi (misalnya beberapa jam)
- Mode pemeliharaan: selama downtime pemrosesan terencana, GC bisa dijeda melalui kontrol internal agar video tidak terhapus saat konsumsi berhenti
- Menggunakan dua mekanisme pembersihan
Kesimpulan
-
Hasil utama
- Dengan menggunakan S3 sebagai buffer fallback dan N3 sebagai area landing utama, mereka berhasil menghemat sekitar $500.000 per tahun sambil menjaga sistem tetap sederhana dan andal
- Insight utama: kebanyakan keputusan “build vs buy” berfokus pada fitur, tetapi pada skala besar, faktor ekonomi mengubah perhitungannya
- Untuk objek berumur pendek (sekitar 2 detik dalam operasi normal), replikasi atau durability canggih tidak diperlukan; storage in-memory sederhana sudah cukup
- Saat pemrosesan tertunda atau pemeliharaan memperpanjang umur objek, barulah diperlukan jaminan keandalan S3
- Keunggulan kedua sisi: N3 menangani jalur normal secara efisien, S3 menyediakan durability saat objek perlu hidup lebih lama
- Jika ada masalah pada N3 (tekanan memori, pod crash, masalah cluster), upload akan fail over ke S3 dengan mulus
-
Faktor keberhasilan
- Mendefinisikan masalah dengan jelas sejak awal: kendala, asumsi, dan batasan mencegah scope creep
- Validasi dini lewat PoC mode mirror: menemukan bottleneck (TLS, throttling jaringan) dan memverifikasi asumsi sebelum commit
- Mencegah over-engineering dan backtracking
-
Kapan layak membangun hal seperti ini
- Pertimbangkan infrastruktur kustom saat ada skala yang cukup untuk menghasilkan penghematan biaya yang berarti, dan ketika kendala spesifik memungkinkan solusi sederhana
- Upaya engineering untuk membangun dan merawat sistem harus lebih kecil daripada biaya infrastruktur yang dihilangkan
- Dalam kasus Nanit, kebutuhan spesifiknya (penyimpanan sementara, toleransi kehilangan, fallback S3) memungkinkan mereka membangun sesuatu yang cukup sederhana sehingga biaya pemeliharaannya tetap rendah
- Jika kedua faktor itu tidak ada, tetaplah menggunakan managed service
- Apakah akan dilakukan lagi? Ya, sistem ini berjalan stabil di produksi, dan desain fallback memungkinkan mereka menghindari kompleksitas tanpa mengorbankan keandalan
3 komentar
Saya penasaran kenapa tidak cukup EC2 atau pod EKS saja yang langsung menerima unggahan video lalu memprosesnya.
Kalau sampai membuat proxy, rasanya autoscaling EKS berdasarkan beban pod juga mestinya cukup memungkinkan.
Pemrosesan video biasanya tidak perlu memuat seluruh file ke memori, jadi kalau tiap instance memprosesnya dengan membuat file sementara di SSD lokal, rasanya fallback ke S3 juga mungkin tidak diperlukan.
Sepertinya ini contoh penggunaan serverless dan S3 yang keliru.
Namun, solusinya juga terasa lebih aneh.
Komentar Hacker News
Tulisan yang benar-benar sangat bermanfaat. Sangat menyenangkan melihat orang membagikan proses pendekatan teknis seperti ini
Meski saya tidak mengalami masalah yang sama secara langsung, hanya dengan melihat cara berpikir mereka dalam mendekatinya saja sudah banyak yang bisa dipelajari
Jujur saja, ini rasanya akan jauh lebih rapi kalau tidak memakai serverless sejak awal
Kesan saya, mereka memaksakan data berdurasi beberapa detik ke dalam paradigma AWS serverless sehingga menimbulkan biaya dan kompleksitas yang tidak perlu
Meski begitu, memindahkannya ke solusi berbasis memori adalah pilihan yang bagus
Mereka memang bilang TLS handshake cukup banyak memakai CPU, tapi rasanya itu bukan bottleneck utamanya
Tetap saja, menarik melihat mereka mencoba desain sistem yang disesuaikan dengan workflow seperti ini
Sebenarnya, alih-alih “mengimplementasikan S3 sendiri” seperti judulnya, ini lebih tepat disebut arsitektur dengan cache memori di depan S3
Keren sih, tapi bukan pengganti S3 mandiri yang sepenuhnya
Apa pun judulnya, ini tetap proyek yang menarik
Kalau bicara ala HN, saya justru ingin membahas perusahaan Nanit itu sendiri
Nanit mengoperasikan kamera monitor bayi berbasis cloud. Semua video dan audio diunggah tanpa E2EE
Hardwarenya mahal, dan tanpa langganan hampir tidak bisa dipakai. Bahkan harus membeli stand seharga $200 untuk membuka fitur pelacakan tidur
Sangat disayangkan karena struktur seperti ini pada akhirnya memperkuat model yang bergantung pada cloud
Meski begitu, seperti tulisan ini, mengurangi ketergantungan pada S3 dan pindah ke storage sendiri tetap langkah yang bagus
Produk lain aplikasinya tidak stabil. Akan bagus kalau ada solusi local-first + E2EE, tetapi secara realistis kemudahan penggunaan lebih penting
Kalau benar-benar ingin E2EE, analisisnya harus dilakukan secara lokal dan hanya hasilnya yang diunggah
Tulisan ini terasa seperti membuat masalah sendiri lalu merayakan karena berhasil menyelesaikannya
Andai sejak awal mereka menjual hardware dengan penyimpanan lokal, semuanya akan lebih sederhana dan murah
Desain yang berpusat pada cloud sekarang terasa seperti pendekatan ala 2015
Tulisannya bagus, tapi saya juga penasaran dengan efek penghematan biaya jika ‘delete on read’ diterapkan di S3
Kalau S3 ditagih per detik, mungkin penghematannya juga lumayan besar
Selain itu, solusi ini pada dasarnya mirip dengan opsi ‘reduced redundancy’ di S3
Katanya menghemat $500 ribu, tapi kita tidak tahu total biayanya berapa
Artinya beda kalau itu $500.001 dari $500 ribu, atau $500 ribu dari $55 juta
Rasanya seperti memilih arsitektur yang salah sejak awal lalu menutupinya dengan cache
Tidak ada alasan mengunggah video rata-rata 2 detik ke S3 selain untuk penyimpanan duplikat
Kalau diproses langsung di server, S3, SQS, dan Lambda semuanya bisa dihilangkan
Saya tidak paham kenapa masalah sesederhana ini dibuat jadi serumit itu
Ini terasa seperti pelajaran klasik: “fokus pada pengembangan aplikasi dan sederhanakan infrastruktur”
Bahkan akan lebih baik jika cache itu langsung dimasukkan ke dalam server pemrosesan video
Judul yang lebih akurat mungkin adalah “Kami memakai S3 dengan cara yang salah”
Pada akhirnya mereka membangun memory store sendiri, padahal mestinya cukup pakai Redis atau yang semacamnya
Kalau sistem buatan mereka down, apa videonya langsung hilang?
Kalau dari awal dikirim ke Kinesis atau SQS, hasilnya pasti akan jauh lebih baik