Disclaimer: As a non-native English speaker, I used AI to help structure and polish this article.
A few dayss ago, I shared Implementing a Zero-Allocation S3-FIFO Cache in Node.js—an exploratory v0.1 release demonstrating how the S3-FIFO (Simple and Scalable Scan-Resistant FIFO) caching algorithm can be implemented using pre-allocated TypedArrays to eliminate Garbage Collection (GC) pressure in Node.js.
Today, after extensive stress testing, architectural hardening, and 100% unit test coverage, I'm excited to announce the release of s3fifo v1.0.0! 🚀
Quick Recap: Why S3-FIFO + Zero Allocation?
Traditional LRU (Least Recently Used) caches suffer from two major production drawbacks:
- Cache Pollution by One-Hit Wonders: Scans or sequential queries flood the cache with items accessed only once, evicting your high-frequency working set.
-
Garbage Collection Overhead: Naive object-based cache implementations allocate nodes dynamically on every
set(), triggering frequent GC pauses under high-throughput workloads. S3-FIFO solves cache pollution by organizing cache entries into three lightweight queues: - Small (S): Filters out one-hit wonders quickly (typically ~10% of total capacity).
- Main (M): Holds multi-access, high-frequency items (~90% of capacity).
-
Ghost (G): Remembers evicted keys from Small to instantly promote them to Main if re-requested.
s3fifoachieves zero dynamic object allocation during hotget/setcycles by backing these queues with contiguous TypedArrays (Uint32Array,Float64Array) and reusable index pools. --- ## What's New in v1.0.0? (Production Readiness) While v0.1 focused on core algorithm speed, v1.0.0 delivers all the developer ergonomics, lifecycle tools, and safety guarantees required for mission-critical Node.js microservices. ### 1. 💾 Cold-Start Persistence (dump&load) Prevent Database Thundering Herd / Cache Stampede during container restarts or deployments.s3fifo1.0 supports serializing active resident entries alongside their original creation timestamps and remaining TTL:
import fs from "node:fs";
import { S3Fifo } from "s3fifo";
const cache = new S3Fifo<string>({ max: 10000 });
// 1. Export active cache items (optionally filter out temporary keys)
const dumpData = cache.dump((key, value) => !key.startsWith("temp:"));
fs.writeFileSync("cache-snapshot.json", JSON.stringify(dumpData));
// 2. On server startup / pre-warming: restore cache state instantly
const snapshot = JSON.parse(fs.readFileSync("cache-snapshot.json", "utf-8"));
cache.load(snapshot);
Enter fullscreen mode Exit fullscreen mode
2. ♻️ Safe Resource Lifecycle (dispose Callback)
When cache items are evicted, overwritten, or cleared, you often need to release external resources (e.g., closing file descriptors, destroying DB handles, or tracking eviction metrics).
const cache = new S3Fifo<Buffer>({
max: 500,
dispose: (key, buffer, reason) => {
console.log(`Key ${key} removed due to: ${reason}`); // 'evict' | 'set' | 'delete' | 'clear'
// Safely free native memory or resources
},
});
Enter fullscreen mode Exit fullscreen mode
🛡️ Re-Entrancy Protection:
disposecallbacks are safely deferred until the cache operation completes, preventing internal state corruption if a callback invokescache.set()orcache.delete()recursively.3. 🔍 Side-Effect-Free Inspection (
peek)Need to check a cached value for logging, health checks, or monitoring without bumping frequency counters or altering eviction status?
peek()allows pure, side-effect-free reading:
// Does not modify S3-FIFO frequency bit fields or TTL timestamps
const val = cache.peek("user:1001");
Enter fullscreen mode Exit fullscreen mode
4. 🔄 Standard ES6 Iterators & Map API
s3fifo 1.0 integrates seamlessly with JavaScript's native iteration protocols:
// Standard JS Map style iterators
for (const [key, value] of cache) {
console.log(key, value);
}
const keys = Array.from(cache.keys());
const values = Array.from(cache.values());
const entries = Array.from(cache.entries());
cache.forEach((val, key) => {
/* ... */
});
Enter fullscreen mode Exit fullscreen mode
5. 🧹 Leak-Free Lifecycle Teardown (close)
In serverless, hot-reloading (HMR), or test environments, background timers can prevent process termination. The close() method clears active background TTL tickers, releases memory buffers, and prevents memory leaks:
// Clean teardown on shutdown
cache.close();
console.log(cache.isClosed); // true
Enter fullscreen mode Exit fullscreen mode
📊 Benchmark: Hit Rate & Throughput vs lru-cache
Tested on a Zipfian distribution (skew 0.99, working set of 100,000 keys) comparing s3fifo v1.0 to Node's popular lru-cache:
Average Hit Rate (%)
| Cache Size (% of Pool) | lru-cache | s3fifo |
|---|---|---|
| 1% | 48.90% | 58.30% |
| 5% | 65.00% | 71.10% |
| 10% | 72.30% | 76.40% |
| 25% | 82.10% | 82.70% |
| 50% | 89.00% | 86.30% |
Average Throughput (ops/sec)
| Cache Size (% of Pool) | lru-cache | s3fifo |
|---|---|---|
| 1% | 10.8M | 15.5M |
| 5% | 10.8M | 14.4M |
| 10% | 10.2M | 14.3M |
| 25% | 10.3M | 13.5M |
| 50% | 10.3M | 14.7M |
Key Takeaways:
- Up to +9.4% higher hit rate when cache capacity is small relative to dataset size (1%–10% range)—ideal for front-end database caches or high-concurrency microservices.
- ~40% higher throughput (~14.5M–15.5M ops/sec vs ~10.5M ops/sec) due to zero object allocations during get/set operations.
Get Started
Install via npm:
npm install s3fifo
Enter fullscreen mode Exit fullscreen mode
Basic usage:
import { S3Fifo } from "s3fifo";
const cache = new S3Fifo<string>({
max: 1000,
ttl: 60000, // 60s global TTL
});
cache.set("session:abc", "user_data");
console.log(cache.get("session:abc")); // 'user_data'
console.log(cache.size); // 1
Enter fullscreen mode Exit fullscreen mode
Links & Feedback
- 📦 NPM: s3fifo
- 🐙 GitHub Repository: BJS-kr/s3fifo
If you're looking for an in-memory cache alternative in Node.js with scan-resistance and minimal GC footprint, give
s3fifoa try! Bug reports, feedback, and GitHub stars are greatly appreciated! 🙏
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.