node-cache

Node.jsキャッシュインメモリキャッシュTTL軽量JavaScript

GitHub概要

node-cache/node-cache

a node internal (in-memory) caching module

スター2,358
ウォッチ19
フォーク143
作成日:2011年10月18日
言語:CoffeeScript
ライセンス:MIT License

トピックス

cachecachingfastinternalmemorynodejs

スター履歴

node-cache/node-cache Star History
データ取得日時: 2025/10/22 09:55

ライブラリ

node-cache

概要

node-cacheはNode.js向け軽量インメモリキャッシュライブラリです。TTL設定、統計機能、イベント通知、キーベース・バリューベース操作をサポートし、外部依存なしでアプリケーション内高速データアクセスを実現する純JavaScript実装です。2025年Node.jsアプリケーションにおける軽量キャッシュの定番として安定した地位を維持し、マイクロサービス、Serverless環境で外部Redis依存を避けたい場合の第一選択として、Edge Computing環境での組み込みキャッシュとしても注目されています。

詳細

node-cache 5.1系列は2025年現在も活発に開発されており、Node.js 18+での最適化とTypeScript完全サポートを提供。内蔵TTL機能により自動的なデータ期限管理を実現し、統計API、期限切れイベント、カスタムチェック関数により高度なキャッシュ制御が可能。外部Redis接続不要でアプリケーション起動時間を短縮し、ネットワーク遅延を完全に排除。単一プロセス内での高速キャッシュ操作により、マイクロサービスアーキテクチャでの効率的なデータアクセスを実現。

主な特徴

  • TTLベース期限管理: 自動的なデータ期限切れと削除
  • イベント駆動: set/get/del/expiredイベントの監視
  • 統計とメトリクス: ヒット率、ミス率、キー数の追跡
  • カスタムチェック: 期限切れ時のカスタム関数実行
  • 純JavaScript実装: 外部依存なしの軽量動作
  • TypeScript対応: 完全な型定義サポート

メリット・デメリット

メリット

  • 外部依存なしで即座に利用開始可能(Redis不要)
  • 軽量でメモリ効率的な実装(オーバーヘッド最小)
  • Node.jsイベントループと統合した非ブロッキング動作
  • Serverless環境でのコールドスタート時間短縮
  • マイクロサービス間の外部キャッシュ依存削減
  • シンプルなAPIによる学習コストの低さ

デメリット

  • シングルプロセス内キャッシュ(分散環境で制約)
  • プロセス再起動時のデータ消失(永続化なし)
  • 複雑なデータ構造サポートの制限
  • 大容量データでのメモリ使用量増大
  • 高可用性機能の欠如(レプリケーション不可)
  • クラスター環境での一貫性保証なし

参考ページ

書き方の例

インストールと基本セットアップ

# NPM でのインストール
npm install node-cache

# Yarn でのインストール
yarn add node-cache

# TypeScript型定義は標準で含まれています

基本的なキャッシュ操作

const NodeCache = require('node-cache');

// キャッシュインスタンス作成
const cache = new NodeCache({
    stdTTL: 600,        // デフォルトTTL: 10分
    checkperiod: 120,   // 期限切れチェック: 2分間隔
    useClones: false    // オブジェクトクローンの無効化(パフォーマンス向上)
});

// 基本的なset/get操作
cache.set('user:1', { name: 'John', age: 30 });
const user = cache.get('user:1');
console.log(user); // { name: 'John', age: 30 }

// TTL付きでのデータ設定
cache.set('session:abc123', 'user_data', 3600); // 1時間TTL

// 複数キーの一括設定
const success = cache.mset([
    { key: 'user:2', val: { name: 'Jane', age: 25 }, ttl: 1800 },
    { key: 'user:3', val: { name: 'Bob', age: 35 } },
    { key: 'config:app', val: { theme: 'dark', lang: 'ja' } }
]);
console.log('Multiple set success:', success);

// 複数キーの一括取得
const users = cache.mget(['user:1', 'user:2', 'user:3']);
console.log('Multiple users:', users);
// { 'user:1': {...}, 'user:2': {...}, 'user:3': {...} }

// キー存在確認
const hasUser = cache.has('user:1');
console.log('User exists:', hasUser); // true

// キーのTTL確認
const ttl = cache.getTtl('session:abc123');
console.log('Session TTL:', new Date(ttl));

イベント監視と統計

const NodeCache = require('node-cache');

const cache = new NodeCache({ 
    stdTTL: 300,
    checkperiod: 60 
});

// イベントリスナーの設定
cache.on('set', (key, value) => {
    console.log(`Cache SET: ${key} = ${JSON.stringify(value)}`);
});

cache.on('get', (key, value) => {
    console.log(`Cache GET: ${key} (hit: ${value !== undefined})`);
});

cache.on('del', (key, value) => {
    console.log(`Cache DELETE: ${key}`);
});

cache.on('expired', (key, value) => {
    console.log(`Cache EXPIRED: ${key}`);
    // 期限切れ時のクリーンアップ処理
    cleanupExpiredData(key, value);
});

cache.on('flush', () => {
    console.log('Cache FLUSH: All data cleared');
});

// 統計情報の取得
function displayCacheStats() {
    const stats = cache.getStats();
    
    console.log('=== Cache Statistics ===');
    console.log(`Keys: ${stats.keys}`);
    console.log(`Hits: ${stats.hits}`);
    console.log(`Misses: ${stats.misses}`);
    console.log(`Hit Rate: ${(stats.hits / (stats.hits + stats.misses) * 100).toFixed(2)}%`);
    console.log(`Cache Size: ${cache.keys().length}`);
}

// 定期的な統計レポート
setInterval(displayCacheStats, 30000); // 30秒ごと

function cleanupExpiredData(key, value) {
    // カスタムクリーンアップロジック
    if (key.startsWith('temp:')) {
        console.log(`Temporary data ${key} expired, performing cleanup`);
    }
}

TTLとタイムアウト管理

const NodeCache = require('node-cache');

class TTLCacheManager {
    constructor() {
        this.cache = new NodeCache({
            stdTTL: 0,          // デフォルトTTLなし
            checkperiod: 60,    // 1分間隔でチェック
            deleteOnExpire: true
        });
        
        this.setupEventHandlers();
    }
    
    setupEventHandlers() {
        this.cache.on('expired', (key, value) => {
            console.log(`Expired: ${key}`);
            this.handleExpiredData(key, value);
        });
    }
    
    // 短期キャッシュ(5分)
    setShortTerm(key, value) {
        return this.cache.set(key, value, 300);
    }
    
    // 中期キャッシュ(1時間)
    setMediumTerm(key, value) {
        return this.cache.set(key, value, 3600);
    }
    
    // 長期キャッシュ(24時間)
    setLongTerm(key, value) {
        return this.cache.set(key, value, 86400);
    }
    
    // 動的TTL設定
    setWithCalculatedTTL(key, value, factor = 1) {
        const dataSize = JSON.stringify(value).length;
        const baseTTL = 600; // 10分
        const ttl = Math.min(baseTTL * factor, 3600); // 最大1時間
        
        return this.cache.set(key, value, ttl);
    }
    
    // TTL更新
    refreshTTL(key, newTtl) {
        const value = this.cache.get(key);
        if (value !== undefined) {
            return this.cache.set(key, value, newTtl);
        }
        return false;
    }
    
    // 期限切れまでの時間取得
    getTimeToExpire(key) {
        const ttlTimestamp = this.cache.getTtl(key);
        if (ttlTimestamp === undefined) return null;
        
        const now = Date.now();
        return Math.max(0, ttlTimestamp - now);
    }
    
    handleExpiredData(key, value) {
        // カテゴリ別の期限切れ処理
        if (key.startsWith('user:')) {
            this.handleUserDataExpiration(key, value);
        } else if (key.startsWith('session:')) {
            this.handleSessionExpiration(key, value);
        }
    }
    
    handleUserDataExpiration(key, value) {
        console.log(`User data expired: ${key}`);
        // ユーザーデータの期限切れ処理
    }
    
    handleSessionExpiration(key, value) {
        console.log(`Session expired: ${key}`);
        // セッションの期限切れ処理
    }
}

// 使用例
const cacheManager = new TTLCacheManager();

// 異なるTTLでのデータ設定
cacheManager.setShortTerm('temp:calc:123', { result: 42 });
cacheManager.setMediumTerm('user:profile:456', { name: 'Alice', preferences: {} });
cacheManager.setLongTerm('config:global', { version: '1.0.0', features: [] });

// 動的TTL設定
const largeData = { data: new Array(1000).fill('item') };
cacheManager.setWithCalculatedTTL('heavy:data:789', largeData, 0.5);

Express.js での統合

const express = require('express');
const NodeCache = require('node-cache');

const app = express();
const cache = new NodeCache({ stdTTL: 600, checkperiod: 120 });

// キャッシュミドルウェア
function cacheMiddleware(duration) {
    return (req, res, next) => {
        // キャッシュキーの生成
        const key = req.originalUrl || req.url;
        const cachedResponse = cache.get(key);
        
        if (cachedResponse) {
            console.log('Cache HIT:', key);
            return res.json(cachedResponse);
        }
        
        console.log('Cache MISS:', key);
        
        // レスポンスのキャプチャ
        const originalJson = res.json;
        res.json = function(data) {
            // データをキャッシュに保存
            cache.set(key, data, duration);
            console.log('Cache SET:', key);
            
            // 元のjsonメソッドを呼び出し
            originalJson.call(this, data);
        };
        
        next();
    };
}

// キャッシュ統計エンドポイント
app.get('/cache/stats', (req, res) => {
    const stats = cache.getStats();
    const keys = cache.keys();
    
    res.json({
        ...stats,
        keyCount: keys.length,
        keys: keys.slice(0, 10), // 最初の10キーのみ表示
        hitRate: stats.hits / (stats.hits + stats.misses)
    });
});

// キャッシュクリアエンドポイント
app.delete('/cache', (req, res) => {
    const deletedKeys = cache.keys().length;
    cache.flushAll();
    
    res.json({
        message: 'Cache cleared',
        deletedKeys
    });
});

// キャッシュ付きAPIエンドポイント
app.get('/api/users', cacheMiddleware(300), async (req, res) => {
    // データベースからユーザー取得(重い処理をシミュレーション)
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    const users = [
        { id: 1, name: 'John', email: '[email protected]' },
        { id: 2, name: 'Jane', email: '[email protected]' }
    ];
    
    res.json(users);
});

app.get('/api/user/:id', cacheMiddleware(600), async (req, res) => {
    const userId = req.params.id;
    
    // データベースアクセスのシミュレーション
    await new Promise(resolve => setTimeout(resolve, 500));
    
    const user = {
        id: parseInt(userId),
        name: `User ${userId}`,
        email: `user${userId}@example.com`,
        lastAccess: new Date()
    };
    
    res.json(user);
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
    
    // 定期的なキャッシュ監視
    setInterval(() => {
        const stats = cache.getStats();
        console.log(`Cache Stats - Keys: ${stats.keys}, Hits: ${stats.hits}, Misses: ${stats.misses}`);
    }, 60000);
});

パフォーマンス最適化

const NodeCache = require('node-cache');

class OptimizedCache {
    constructor(options = {}) {
        this.cache = new NodeCache({
            stdTTL: options.stdTTL || 600,
            checkperiod: options.checkperiod || 120,
            useClones: false,        // パフォーマンス向上のためクローン無効
            maxKeys: options.maxKeys || 1000  // メモリ使用量制限
        });
        
        this.metrics = {
            operations: 0,
            errors: 0,
            totalTime: 0
        };
        
        this.setupEventHandlers();
    }
    
    setupEventHandlers() {
        this.cache.on('set', () => this.metrics.operations++);
        this.cache.on('get', () => this.metrics.operations++);
        this.cache.on('del', () => this.metrics.operations++);
    }
    
    // 高速set操作
    fastSet(key, value, ttl) {
        const start = process.hrtime.bigint();
        
        try {
            const result = this.cache.set(key, value, ttl);
            this.updateMetrics(start);
            return result;
        } catch (error) {
            this.metrics.errors++;
            throw error;
        }
    }
    
    // 高速get操作
    fastGet(key) {
        const start = process.hrtime.bigint();
        
        try {
            const result = this.cache.get(key);
            this.updateMetrics(start);
            return result;
        } catch (error) {
            this.metrics.errors++;
            throw error;
        }
    }
    
    // バルク操作の最適化
    optimizedMget(keys) {
        const start = process.hrtime.bigint();
        
        try {
            const result = this.cache.mget(keys);
            this.updateMetrics(start);
            return result;
        } catch (error) {
            this.metrics.errors++;
            throw error;
        }
    }
    
    // メモリ使用量監視
    getMemoryUsage() {
        const keys = this.cache.keys();
        let totalSize = 0;
        
        keys.forEach(key => {
            const value = this.cache.get(key);
            if (value !== undefined) {
                totalSize += JSON.stringify(value).length;
            }
        });
        
        return {
            keyCount: keys.length,
            estimatedSize: totalSize,
            averageKeySize: totalSize / keys.length || 0
        };
    }
    
    // パフォーマンスメトリクス更新
    updateMetrics(startTime) {
        const endTime = process.hrtime.bigint();
        const duration = Number(endTime - startTime) / 1000000; // ナノ秒→ミリ秒
        this.metrics.totalTime += duration;
    }
    
    // パフォーマンスレポート
    getPerformanceReport() {
        const avgTime = this.metrics.operations > 0 
            ? this.metrics.totalTime / this.metrics.operations 
            : 0;
            
        return {
            operations: this.metrics.operations,
            errors: this.metrics.errors,
            averageTime: avgTime.toFixed(4) + 'ms',
            errorRate: (this.metrics.errors / this.metrics.operations * 100).toFixed(2) + '%',
            memory: this.getMemoryUsage()
        };
    }
    
    // キャッシュクリーンアップ
    cleanup() {
        const before = this.cache.keys().length;
        
        // 手動での期限切れチェック
        this.cache.keys().forEach(key => {
            this.cache.get(key); // 期限切れキーを自動削除
        });
        
        const after = this.cache.keys().length;
        console.log(`Cleanup completed: ${before - after} keys removed`);
    }
}

// ベンチマークテスト
function benchmarkCache() {
    const cache = new OptimizedCache({ maxKeys: 10000 });
    const iterations = 10000;
    
    console.log('Starting cache benchmark...');
    
    // 書き込みベンチマーク
    const writeStart = Date.now();
    for (let i = 0; i < iterations; i++) {
        cache.fastSet(`key:${i}`, { value: i, data: `data_${i}` }, 600);
    }
    const writeTime = Date.now() - writeStart;
    
    // 読み込みベンチマーク
    const readStart = Date.now();
    for (let i = 0; i < iterations; i++) {
        cache.fastGet(`key:${i % 1000}`); // 1000キーを繰り返し読み込み
    }
    const readTime = Date.now() - readStart;
    
    console.log('=== Benchmark Results ===');
    console.log(`Write: ${iterations} ops in ${writeTime}ms (${(iterations / writeTime * 1000).toFixed(0)} ops/sec)`);
    console.log(`Read: ${iterations} ops in ${readTime}ms (${(iterations / readTime * 1000).toFixed(0)} ops/sec)`);
    console.log('Performance Report:', cache.getPerformanceReport());
}

// benchmarkCache();

TypeScript での型安全な使用

import NodeCache from 'node-cache';

interface User {
    id: number;
    name: string;
    email: string;
    createdAt: Date;
}

interface CacheConfig {
    userTTL: number;
    sessionTTL: number;
    configTTL: number;
}

class TypedCacheService {
    private cache: NodeCache;
    private config: CacheConfig;
    
    constructor(config: CacheConfig) {
        this.config = config;
        this.cache = new NodeCache({
            stdTTL: 600,
            checkperiod: 120,
            useClones: false
        });
    }
    
    // ユーザーキャッシュ
    setUser(userId: number, user: User): boolean {
        return this.cache.set(`user:${userId}`, user, this.config.userTTL);
    }
    
    getUser(userId: number): User | undefined {
        return this.cache.get<User>(`user:${userId}`);
    }
    
    // セッションキャッシュ
    setSession(sessionId: string, data: any): boolean {
        return this.cache.set(`session:${sessionId}`, data, this.config.sessionTTL);
    }
    
    getSession<T>(sessionId: string): T | undefined {
        return this.cache.get<T>(`session:${sessionId}`);
    }
    
    // 設定キャッシュ
    setConfig<T>(key: string, config: T): boolean {
        return this.cache.set(`config:${key}`, config, this.config.configTTL);
    }
    
    getConfig<T>(key: string): T | undefined {
        return this.cache.get<T>(`config:${key}`);
    }
    
    // 統計の型安全な取得
    getStats(): NodeCache.Stats {
        return this.cache.getStats();
    }
}

// 使用例
const cacheService = new TypedCacheService({
    userTTL: 3600,
    sessionTTL: 1800,
    configTTL: 86400
});

const user: User = {
    id: 1,
    name: 'John Doe',
    email: '[email protected]',
    createdAt: new Date()
};

cacheService.setUser(1, user);
const cachedUser = cacheService.getUser(1); // 型は User | undefined