- Nanit ने शिशु की नींद की स्थिति का विश्लेषण करने वाली video processing pipeline में AWS S3 का उपयोग किया, लेकिन प्रति सेकंड हजारों uploads के कारण PutObject request cost कुल लागत का अधिकांश हिस्सा बन गई
- साथ ही, S3 Lifecycle rule की कम-से-कम 1 दिन retention सीमा के कारण, वास्तव में 2 सेकंड के भीतर process हो जाने वाले वीडियो के लिए भी 24 घंटे का storage शुल्क देना पड़ता था
- इसका समाधान करने के लिए Rust-आधारित in-memory storage system N3 बनाया गया, और S3 को केवल overflow buffer के रूप में इस्तेमाल किया गया
- N3, SQS FIFO के ज़रिए मौजूदा processing pipeline के साथ पूरी तरह compatible है, और सख्त ordering guarantee तथा reliability बनाए रखता है
- परिणामस्वरूप, सालाना लगभग 5 लाख डॉलर की लागत बचत हुई और साथ ही एक सरल लेकिन स्थिर architecture मिला
पृष्ठभूमि
video processing pipeline का अवलोकन
- Nanit के कैमरे video chunks रिकॉर्ड करते हैं, Camera Service से S3 presigned URL मांगते हैं, और सीधे S3 पर upload करते हैं
- AWS Lambda object key को SQS FIFO queue में publish करता है (
baby_uid के आधार पर sharding), और video processing pods SQS से consume करके S3 से download करते हैं और फिर sleep-state inference चलाते हैं
- इस setup के फायदे
- S3 landing + SQS queuing, camera upload और video processing को अलग रखते हैं, जिससे maintenance या अस्थायी downtime के दौरान भी वीडियो loss नहीं होता
- S3 के कारण availability और durability को सीधे manage करने की ज़रूरत नहीं होती
- SQS FIFO + group ID से हर बच्चे के लिए ordering बनी रहती है, और processing nodes अधिकतर stateless रह सकते हैं
- S3 Lifecycle rule garbage collection संभालता है, इसलिए processed video को track करने की ज़रूरत नहीं होती
बदलाव की ज़रूरत क्यों पड़ी
- PutObject cost प्रमुख थी: वीडियो सिर्फ कुछ सेकंड के लिए landing area में रहने वाले short-lived objects थे, लेकिन प्रति सेकंड हजारों uploads के scale पर per-object request cost सबसे बड़ा cost driver बन गई
- chunking frequency बढ़ाने से (यानी अधिक छोटे chunks भेजने से) latency घटती थी, लेकिन हर अतिरिक्त chunk एक और PutObject request बनता था, इसलिए लागत रैखिक रूप से बढ़ती थी
- storage पर दूसरी बार शुल्क: processing लगभग 2 सेकंड में पूरी हो जाती थी, फिर भी Lifecycle deletion rule के कारण लगभग 24 घंटे की storage cost लगती थी
- इसलिए ऐसा design चाहिए था जो reliability और सख्त ordering guarantee बनाए रखते हुए normal path में per-object cost से बचे और “waiting cost” वाली storage को न्यूनतम करे
योजना
-
design principles
- architecture के माध्यम से simplicity: चालाक implementation नहीं, बल्कि design स्तर पर complexity हटाना
- correctness: pipeline के बाकी हिस्सों के लिए पूरी तरह transparent replacement
- normal path के लिए optimization: सामान्य case के लिए design, और edge cases में S3 को safety net की तरह उपयोग; processing algorithm कभी-कभार होने वाले gaps को सहन कर सकता है, इसलिए जटिल guarantees बनाने से अधिक simplicity को प्राथमिकता
-
design drivers
- short-lived objects: segments landing area में सिर्फ कुछ सेकंड रहते हैं
- ordering: हर बच्चे के लिए सख्त sequencing, यानी नया segment पहले process नहीं होना चाहिए
- throughput: प्रति सेकंड हजारों uploads, हर segment 2-6 MB
- client constraints: कैमरों में retry count सीमित है, इसलिए retransmission मानकर नहीं चला जा सकता
- operations: maintenance/scale-up के दौरान लाखों items का backlog स्वीकार्य होना चाहिए
- firmware changes नहीं: मौजूदा कैमरों के साथ काम करना चाहिए
- loss tolerance: बहुत छोटे gaps स्वीकार्य हैं, algorithm उन्हें mask कर देता है
- cost: normal path में per-object S3 cost से बचना, और “waiting cost” storage को न्यूनतम रखना
design overview (N3 normal path + S3 overflow)
-
architecture
- N3 एक custom landing area है जो वीडियो को सिर्फ उतनी देर memory में रखता है जितनी processing drain होने में लगती है (लगभग 2 सेकंड), और S3 का उपयोग तभी होता है जब N3 load संभाल नहीं पाता
- दो components
- N3-Proxy (stateless, dual interface)
- बाहरी (internet-connected): presigned URL के माध्यम से कैमरा uploads स्वीकार करता है
- आंतरिक (private): Camera Service को presigned URL जारी करता है
- N3-Storage (stateful, internal-only): uploaded segments को RAM में रखता है और pod-addressable download URL के साथ SQS में queue करता है
- video processing pods SQS FIFO से consume करते हैं और URL जिस storage की ओर इशारा करता है (N3 या S3), वहीं से download करते हैं
-
normal flow (Happy Path)
- कैमरा Camera Service से upload URL मांगता है
- Camera Service, N3-Proxy के internal API से presigned URL मांगता है
- कैमरा N3-Proxy के external endpoint पर वीडियो upload करता है
- N3-Proxy उसे N3-Storage तक forward करता है
- N3-Storage वीडियो को memory में रखता है और अपने download URL के साथ उसे SQS में queue करता है
- processing pod, N3-Storage से download करके process करता है
-
two-tier fallback
- Tier 1: proxy-level fallback (per-request)
- अगर N3-Storage memory pressure, processing backlog, pod failure आदि के कारण upload स्वीकार नहीं कर पाता, तो N3-Proxy कैमरे की ओर से S3 में upload कर देता है
- कैमरे को failure detect होने से पहले ही N3 URL मिल चुका होता है
- Tier 2: cluster-level rerouting (all traffic)
- अगर N3-Proxy या N3-Storage अस्वस्थ हो, तो Camera Service N3 URL जारी करना बंद कर देता है और सीधे S3 presigned URL लौटाता है
- N3 के recover होने तक सारा traffic S3 की ओर जाता है
-
दो components में विभाजन क्यों
- blast radius: storage crash होने पर भी proxy traffic को S3 की ओर route कर सकता है; proxy crash होने पर केवल उस node का traffic प्रभावित होता है, पूरा storage cluster नहीं
- resource profile: proxy CPU/network intensive है (TLS termination), storage memory intensive है (वीडियो hold करना), इसलिए instance type और scaling requirements अलग हैं
- security: storage कभी भी सीधे internet को touch नहीं करता
- rollout safety: proxy (stateless) update करते समय storage (जिसमें live data है) को छूना नहीं पड़ता
design validation
-
क्या validate करना था
- capacity और sizing: अलग-अलग client networks पर वास्तविक upload duration, आवश्यक compute और upload buffer size
- storage model: क्या सब कुछ RAM में रखा जा सकता है या disk चाहिए
- resilience: सस्ते में load balancing करना और failed nodes संभालना कैसे
- operational policies: GC की ज़रूरतें, retry expectations, और क्या GET पर delete पर्याप्त है
- unknown unknowns: जब idea वास्तविक दुनिया से टकराएगा तो कौन से edge cases सामने आएँगे
-
approach 1: synthetic stress test
- अलग-अलग concurrency, slow clients, sustained load और processing downtime के साथ system को सीमा तक धकेलने वाला load generator बनाया गया
- लक्ष्य: breaking points ढूँढना, अप्रत्याशित bottlenecks पहचानना, और capacity planning के लिए deterministic baseline पाना
-
approach 2: production PoC (mirror mode)
- synthetic tests से वास्तविक camera behavior की नकल संभव नहीं थी: unstable Wi‑Fi, अलग-अलग firmware versions, unpredictable network conditions
- mirror mode: n3-proxy पहले S3 में write करता था (production retention), फिर PoC N3-Storage में भी write करता था (जो canary SQS + video processor से जुड़ा था)
- target cohorts: firmware version / Baby-UID lists के आधार पर
- data parity: PoC और production के sleep-state की तुलना, और अंतर की जांच
- observability: path-wise dashboards (N3 vs S3), queue depth, latency/RPS, error budget, egress analysis
- feature flags (Unleash का उपयोग) बेहद महत्वपूर्ण थे: deployment के बिना real time में cohort switch करना संभव था, पहले छोटे slices (पुराना firmware, कमजोर Wi‑Fi वाले cameras) पर test और समस्या होने पर तुरंत rollback
-
क्या पता चला
- bottlenecks: TLS termination सबसे अधिक CPU खा रही थी, और AWS burstable networking credits खत्म होने के बाद throttling हो रही थी
- memory-only storage व्यावहारिक है: वास्तविक upload-time distribution और concurrency के आधार पर यह पुष्टि हुई कि working set को पर्याप्त safety margin के साथ RAM में रखा जा सकता है, disk की ज़रूरत नहीं
- TCP timestamps overhead: भेजे गए कुल bytes का लगभग 85% ACK frames थे; TCP timestamps disable करने (
sysctl -w net.ipv4.tcp_timestamps=0) से हर ACK पर 12 bytes की बचत हुई
- जोखिम: एक ही socket पर बहुत अधिक bytes भेजने से sequence number wrap हो सकते हैं, और delayed packets के गलत merge होने से corruption हो सकता है
- mitigation: (1) हर upload के लिए नया socket, (2) n3-proxy ↔ n3-storage sockets को लगभग 1 GB transfer के बाद recycle करना
- memory leak: शुरुआती release के बाद n3-proxy की memory लगातार बढ़ रही थी
jemalloc profiling से पता चला कि per-connection hyper BytesMut buffers में वृद्धि हो रही थी
- कुछ client connections transfer के बीच रुक जाती थीं और cleanup नहीं होता था, इसलिए buffers बचे रहते थे और memory लगातार बढ़ती रही
- fix: sockets को short-lived बनाना और time limits लगाना
- Keep-alive disable: हर upload पूरा होते ही connection बंद
- timeouts कड़े किए: header/socket timeout set करके अटके हुए uploads बंद किए गए और buffers मुक्त किए गए
storage
-
in-memory storage
- सबसे सरल रास्ते से शुरुआत: in-memory storage ताकि I/O tuning से बचा जा सके और सहज data structures इस्तेमाल हों
Arc<DashMap<Ulid, Bytes>> में वीडियो store किए गए; हर video upload पर bytes_used बढ़ता था, और हर download पर वीडियो delete होकर यह घटता था
- capacity के लगभग 80% से ऊपर uploads reject होने लगते थे ताकि OOM से बचा जा सके, और n3-proxy को upload URL sign करना बंद करने का signal भेजा जाता था
control handle के जरिए uploads और garbage collection को manually pause किया जा सकता था
-
graceful restart
- memory-only storage होने के कारण restart के दौरान in-flight data drop होने से बचाना ज़रूरी था
- graceful restart process
- pod को
SIGTERM मिलता है (StatefulSet एक बार में एक pod rolling करता है)
- pod Not Ready हो जाता है और Service से बाहर हो जाता है (कोई नया upload नहीं)
- पहले से uploaded videos के downloads serve होते रहते हैं
- जब downloads रुक जाते हैं (हाल में कोई read नहीं → processing drain)
- खुले requests के पूरा होने का इंतज़ार
- restart के बाद अगले pod पर बढ़ना
- सामान्य स्थिति में pod कुछ ही सेकंड में drain हो जाता था
-
GC
- दो cleanup mechanisms इस्तेमाल किए गए
- download पर delete: download होते ही वीडियो delete; PoC में re-download शून्य पाया गया, और video processor अंदर ही retry करता है, इसलिए data retention या “processed” state track करने की ज़रूरत नहीं
- पीछे छूटे items के लिए TTL GC: download पर delete, उन segments को cover नहीं करता जिन्हें processor skip कर देता है (download नहीं हुआ → delete नहीं हुआ)
- इसलिए हल्का TTL GC जोड़ा गया: समय-समय पर in-memory DashMap scan करना और configurable threshold (जैसे कुछ घंटे) से पुराने items हटाना
- maintenance mode: नियोजित processing downtime के दौरान internal controls से GC pause किया जा सकता था, ताकि consumption बंद रहने पर भी वीडियो delete न हों
निष्कर्ष
-
मुख्य उपलब्धियाँ
- S3 को fallback buffer और N3 को primary landing area बनाकर, सालाना लगभग 5 लाख डॉलर की लागत बचत हासिल की गई, जबकि system सरल और reliable बना रहा
- मुख्य insight: अधिकतर “build vs buy” फैसले features पर केंद्रित होते हैं, लेकिन scale पर economics पूरी गणना बदल देती है
- short-lived objects (सामान्य स्थिति में लगभग 2 सेकंड) के लिए replication या sophisticated durability की ज़रूरत नहीं थी; सरल in-memory store काफी था
- जब processing delay हो या maintenance के कारण object lifetime बढ़े, तब S3 की reliability guarantees की ज़रूरत पड़ती है
- दोनों दुनियाओं का लाभ: N3 normal path को कुशलता से संभालता है, S3 तब durability देता है जब objects को अधिक देर तक जीवित रहना हो
- अगर N3 में समस्या हो (memory pressure, pod crash, cluster issue), तो uploads बिना रुकावट S3 पर fail over हो जाते हैं
-
सफलता के कारण
- समस्या को पहले से स्पष्ट रूप से परिभाषित किया गया: constraints, assumptions, boundaries ने scope creep रोका
- mirror mode PoC से शुरुआती validation: bottlenecks (TLS, network throttling) सामने आए और commit करने से पहले assumptions validate हो गईं
- over-engineering और backtracking से बचाव
-
कब ऐसा कुछ बनाना चाहिए
- जब पर्याप्त scale हो जिससे सार्थक cost savings संभव हों, और ऐसी विशिष्ट constraints मौजूद हों जो एक सरल solution को संभव बनाती हों, तब custom infrastructure पर विचार किया जा सकता है
- system को बनाना और maintain करना जितना engineering effort ले, वह बचने वाली infrastructure cost से कम होना चाहिए
- Nanit के मामले में, विशिष्ट requirements (temporary storage, loss tolerance, S3 fallback) ने इतना सरल system बनाना संभव किया कि maintenance cost कम रहे
- अगर ये दोनों कारक मौजूद न हों, तो managed services पर टिके रहना बेहतर है
- क्या वे इसे फिर करेंगे? हाँ, system production में स्थिर रूप से चल रहा है, और fallback design की वजह से reliability खोए बिना complexity से बचना संभव हुआ
3 टिप्पणियां
मुझे जिज्ञासा है कि क्या सिर्फ़ EC2 या EKS pod को सीधे वीडियो upload लेने और उसे process करने देना पर्याप्त नहीं होता।
अगर proxy तक बनाना पड़ा, तो लगता है कि pod लोड के अनुसार EKS autoscaling भी काफ़ी संभव था।
वीडियो processing में आमतौर पर पूरी फ़ाइल को memory में पूरा लोड करने की ज़रूरत नहीं होती, इसलिए अगर हर instance के local SSD पर temporary file बनाकर process किया जाता, तो शायद S3 fallback की भी ज़रूरत नहीं पड़ती।
यह serverless और S3 का गलत इस्तेमाल किए जाने का उदाहरण लगता है
लेकिन इसका समाधान तो उससे भी ज़्यादा अजीब लगता है
Hacker News टिप्पणियाँ
यह सच में बहुत उपयोगी लेख था। ऐसे तकनीकी approach की process साझा करना बहुत अच्छा लगता है
भले ही मुझे खुद वही समस्या न झेलनी पड़े, लेकिन उन्होंने किस सोच के साथ approach किया, सिर्फ यह देखना भी काफी सीख देता है
ईमानदारी से कहूँ तो, अगर शुरू से serverless का इस्तेमाल न किया होता तो यह कहीं ज़्यादा साफ-सुथरा होता
कुछ सेकंड के डेटा को ज़बरदस्ती AWS serverless paradigm में फिट करने की कोशिश में अनावश्यक लागत और complexity पैदा हुई लगती है
फिर भी memory-based solution पर जाना एक अच्छा फैसला था
उन्होंने कहा कि TLS handshake CPU ज़्यादा खाता है, लेकिन वह मुख्य bottleneck नहीं लगता
फिर भी ऐसा workflow-केंद्रित system design आज़माना दिलचस्प था
असल में, शीर्षक की तरह यह “S3 को खुद implement किया” नहीं था, बल्कि S3 के आगे memory cache रखने वाली architecture थी
यह शानदार है, लेकिन पूरी तरह अपना S3 replacement नहीं है
शीर्षक जो भी हो, यह एक दिलचस्प project था
HN style में कहूँ तो, मैं Nanit कंपनी के बारे में ही बात करना चाहूँगा
Nanit cloud-based baby monitor camera चलाती है। सारा video और audio E2EE के बिना upload होता है
hardware महंगा है, और subscription के बिना लगभग बेकार है। ऊपर से sleep tracking feature खोलने के लिए $200 का stand भी खरीदना पड़ता है
अफसोस यह है कि ऐसी संरचना आखिरकार cloud-dependent model को और मजबूत करती है
फिर भी इस लेख की तरह S3 पर निर्भरता घटाकर अपनी storage पर जाना अच्छी बात है
दूसरे products के apps अस्थिर थे। local-first + E2EE solution अच्छा होता, लेकिन व्यवहारिक रूप से usability ज़्यादा महत्वपूर्ण थी
अगर सच में E2EE चाहिए, तो analysis local में करना होगा और सिर्फ result upload करना होगा
यह लेख कुछ ऐसा लगा जैसे पहले खुद समस्या बनाई और फिर उसे हल करके खुद की तारीफ़ की
इसकी जगह शुरू से local storage hardware बेचा होता तो यह ज़्यादा simple और सस्ता होता
cloud-केंद्रित design अब 2015 वाली approach जैसा लगता है
लेख शानदार था, लेकिन S3 में ‘delete on read’ लागू करने पर cost saving का असर कितना होता, यह भी जानना चाहता था
अगर S3 में प्रति सेकंड billing होती, तो बचत काफ़ी बड़ी हो सकती थी
और यह solution असल में S3 के ‘reduced redundancy’ option जैसा भी लगता है
उन्होंने $500k बचाने की बात की, लेकिन कुल लागत कितनी थी यह पता नहीं
फर्क पड़ता है कि यह $500,001 में से $500k था, या $55 million में से $500k
शुरू से गलत architecture चुनी गई, और बाद में उस पर cache का लेप कर दिया गया, ऐसा लगता है
औसतन 2 सेकंड के वीडियो S3 पर चढ़ाने की कोई वजह नहीं, सिवाय duplicate storage के
अगर सर्वर पर सीधे process किया होता, तो S3, SQS, Lambda तीनों हट सकते थे
इतनी simple समस्या को इतना जटिल क्यों बनाया, समझ नहीं आता
यह मानो वही पुराना सबक है: “app development पर ध्यान दो और infrastructure को simple रखो”
बेहतर होता कि cache को सीधे video processing server के अंदर ही डाल देते
शीर्षक शायद “S3 का गलत इस्तेमाल किया गया” अधिक सही होता
अंत में उन्होंने अपना memory store बना लिया, जबकि Redis जैसा कुछ इस्तेमाल कर सकते थे
अगर उनका खुद का बनाया system down हो जाए, तो क्या video गायब हो जाएंगे?
शुरू से Kinesis या SQS में भेजा होता तो कहीं बेहतर होता