layercache – Node.js के लिए मल्टी-लेयर कैश लाइब्रेरी
(github.com/flyingsquirrel0419)layercache क्या है?
यह Node.js के लिए एक मल्टी-लेयर कैश लाइब्रेरी है, जो Memory → Redis → Disk को एक ही API में जोड़ती है।
कैश हिट होने पर यह सबसे तेज़ लेयर से वैल्यू निकालती है और ऊपर की लेयर्स को अपने-आप भर देती है। मिस होने पर, भले ही 100 concurrent requests आएँ, fetcher सिर्फ़ एक बार ही चलेगा।
इसे क्यों बनाया गया?
Node.js services चलाते समय caching layers जोड़ने का तरीका आम तौर पर एक जैसा होता है। शुरुआत in-memory cache से होती है, फिर instances बढ़ने पर Redis जोड़ा जाता है, फिर stampede समस्या सामने आती है, instances के बीच cache inconsistency होने लगती है... इनमें से हर समस्या को अलग-अलग हल किया जा सकता है, लेकिन इन्हें production स्तर पर एक साथ सही तरह से जोड़ना उम्मीद से ज़्यादा मेहनत वाला काम था।
इसीलिए इसे इस सोच के साथ बनाया गया कि यह काम एक बार ठीक से कर लिया जाए।
मुख्य फीचर्स क्या हैं?
कोर व्यवहार
- Layered reads + automatic backfill (L1 miss → L2 lookup → L1 fill)
- Stampede prevention: 100 concurrent requests → fetcher 1 बार execute
- Distributed single-flight: Redis distributed lock से instances के बीच duplicate execution हटता है
- Redis pub/sub आधारित L1 invalidation bus (instances के बीच memory cache sync)
Invalidation / freshness
- Tag-based invalidation, wildcard/prefix invalidation
- Stale-while-revalidate, Stale-if-error
- Sliding TTL, Adaptive TTL, Refresh-ahead
Resilience
- Graceful degradation: Redis failure होने पर layer skip करके अपने-आप recovery
- Circuit breaker
- Strict / best-effort write policy
Observability
- Prometheus exporter, OpenTelemetry hooks
- हर layer के latency measurements, event hooks
- Admin CLI (
npx layercache stats|keys|invalidate)
Framework integration
Express, Fastify, Hono, tRPC, GraphQL, Next.js
बेंचमार्क नंबर क्या हैं?
यह single-core VM + वास्तविक Docker Redis के आधार पर है।
| परिदृश्य | औसत latency |
| L1 memory warm hit | 0.005 ms |
| L2 Redis warm hit (1 KiB) | 0.193 ms |
| बिना कैश (DB simulation) | 5.030 ms |
- HTTP throughput:
/layered16,211 req/s बनाम/nocache158 req/s - Stampede: 75 concurrent requests → origin fetch 5 बार (बिना कैश 375 बार)
- Distributed single-flight: 60 concurrent requests → origin fetch 1 बार
पूरी benchmarking methodology और raw results को docs/benchmarking.md में संकलित किया गया है।
मौजूदा लाइब्रेरीज़ से यह कैसे अलग है?
node-cache-manager, keyv, cacheable — ये सभी अच्छे विकल्प हैं। फ़र्क को संक्षेप में देखें तो:
- Stampede prevention / Distributed single-flight: इन तीनों लाइब्रेरीज़ में यह डिफ़ॉल्ट रूप से नहीं मिलता। layercache को इन्हीं दो चीज़ों को केंद्र में रखकर डिज़ाइन किया गया है।
- Cross-instance L1 invalidation: Redis pub/sub के ज़रिए instances के बीच memory cache अपने-आप sync होता है। इससे multi-instance environment में memory cache को भरोसे के साथ इस्तेमाल किया जा सकता है।
- Auto backfill: निचली layer पर hit होने पर ऊपरी layers अपने-आप भर जाती हैं।
- Graceful degradation + Circuit breaker: Redis बंद हो जाए, तब भी service चलती रहती है।
इंस्टॉलेशन और लिंक
npm install layercache
- GitHub: https://github.com/flyingsquirrel0419/layercache
- npm: https://www.npmjs.com/package/layercache
अगर design decisions, खासकर single-flight coordination के तरीके या graceful degradation के व्यवहार को लेकर आपके कोई सवाल हों, तो बेझिझक पूछें।
4 टिप्पणियां
अच्छी लाइब्रेरी लग रही है!
क्या Redis को डिज़ाइन में शामिल करने की कोई खास वजह है? क्या यह ऐसी स्थिति मानकर चल रहा है जहाँ कई read-only instances एक साथ उठते हैं? अगर ऐसा है, तो क्या (local) Disk को Redis से आगे वाली layer में नहीं रखा जाना चाहिए?
Redis को शामिल करने का मतलब यह मानना है कि सर्वर कई हैं। हर सर्वर की मेमोरी में अलग-अलग वैल्यू हो सकती हैं, इसलिए Redis "shared truth" की भूमिका निभाता है.
Disk को Redis के बाद रखने की वजह यह है कि, यह मानकर कि Redis उसी local network में है, वह ज़्यादा तेज़ है। बेंचमार्क के अनुसार Disk ~2ms है, जबकि Redis ~0.02ms है। लेकिन अगर Redis दूर हो या नेटवर्क खराब हो, तो local Disk ज़्यादा तेज़ हो सकता है, और ऐसे में क्रम बदलना सही है। लाइब्रेरी भी क्रम को force नहीं करती और इसकी संरचना ऐसी है कि यूज़र खुद तय करता है।
Disk, वह कहीं भी हो, speed competition के लिए कम और Memory तथा Redis दोनों के पूरी तरह बंद हो जाने पर बची रहने वाली आख़िरी safety net के रूप में ज़्यादा महत्वपूर्ण है.
आपके डिज़ाइन इरादे के लिए धन्यवाद। तो आपका मतलब है कि सभी remote calls को local disk write के रूप में सेव किया जाता है, और जब remote call विफल हो जाए तो disk read किया जाता है, सही? यह भी सोचकर देखना अच्छा होगा कि cache layer में Disk वाकई ज़रूरी है या नहीं।
DiskLayer उस तरह का pattern नहीं है, बल्कि यह एक सामान्य cache layer की तरह काम करता है — यह read भी करता है और write भी, और ऊपर की layer में miss होने पर क्रम से access करने वाली संरचना है। मेरी वजह से भ्रम हुआ।
आपने जो "remote call के result को disk में सेव करके, failure होने पर उसे पढ़ना" वाला pattern कहा, वह दरअसल stale-if-error option के ज़्यादा करीब है, लेकिन वह memory में रखा जाता है, इसलिए process restart होने पर गायब हो जाता है।
और DiskLayer सच में ज़रूरी है या नहीं, इस पर आपकी बात भी हाँ। वास्तव में ज़्यादातर multi-instance environments में Memory → Redis ही काफ़ी होता है, और जैसे ही Disk एक layer के रूप में आती है, serialization cost और file management की complexity साथ आती है।