cache-manager

JavaScriptTypeScriptNode.jsライブラリキャッシュマルチストア統一インターフェース

GitHub概要

jaredwray/cacheable

a robust, scalable, and maintained set of caching packages

スター1,891
ウォッチ10
フォーク200
作成日:2013年4月6日
言語:TypeScript
ライセンス:MIT License

トピックス

cachecachingcaching-libraryhttpkey-valuekeyvnodejsredisrfc-72wrapper

スター履歴

jaredwray/cacheable Star History
データ取得日時: 2025/10/22 09:55

ライブラリ

cache-manager

概要

cache-managerは、Node.js用のキャッシュモジュールで、複数のキャッシュストアを統一インターフェースで管理できるマルチストアキャッシュマネージャーです。TypeScript対応で、階層化されたキャッシュアーキテクチャを簡単に構築できます。

詳細

cache-manager(キャッシュマネージャー)は、関数を簡単にキャッシュでラップし、階層キャッシュとプラグインアーキテクチャによる一貫したインターフェースを提供するNode.jsキャッシュライブラリです。TypeScriptで作成され、ESModulesと互換性があり、バージョン7.0.0が最新リリースです。バックグラウンドでの期限切れキャッシュキー更新メカニズムを提供し、Redis、MongoDB、データベース、ファイルシステムなど多様なストレージでコンテンツをキャッシュできます。マルチキャッシュ機能により、レベル1(メモリ)とレベル2(Redis)といったキャッシュ階層ラッパーを作成し、複数ストア使用時には最初に見つかった(最速の)値を返すか、すべてのストアの完了を待たずに動作する非ブロッキングモードの設定が可能です。NestJSや他のフレームワークでの採用実績があり、npmレジストリで1404以上のプロジェクトで使用されています。

メリット・デメリット

メリット

  • 統一インターフェース: 複数のキャッシュストアを一貫したAPIで操作
  • TypeScript完全対応: 型安全性と優れた開発体験
  • マルチストア対応: 階層化されたキャッシュアーキテクチャの構築
  • 豊富なドライバー: Redis、MongoDB、ファイルシステム等幅広いサポート
  • フレームワーク統合: NestJS等での実績とサポート
  • 非ブロッキングモード: 高速レスポンス時間の実現
  • プラグインアーキテクチャ: 拡張性と柔軟性の確保

デメリット

  • 複雑性: 単純なキャッシュ需要には機能が過剰
  • パフォーマンスオーバーヘッド: 抽象化レイヤーによる若干の性能低下
  • 設定の複雑さ: マルチストア設定時の構成管理が複雑
  • 依存関係: 使用するドライバーに応じた追加パッケージが必要
  • 学習コスト: 高度な機能活用には概念理解が必要

主要リンク

書き方の例

基本的なインメモリキャッシュ

import { createCache } from 'cache-manager'

// インメモリストア作成
const memoryCache = createCache({
  store: 'memory',
  max: 100,    // 最大アイテム数
  ttl: 60000   // TTL (ミリ秒)
})

// 値を設定
await memoryCache.set('key', 'value', 30000) // 30秒TTL

// 値を取得
const value = await memoryCache.get('key')
console.log(value) // 'value'

// 値を削除
await memoryCache.del('key')

// キャッシュクリア
await memoryCache.reset()

マルチストア設定(階層キャッシュ)

import { createCache } from 'cache-manager'
import { createKeyv } from 'cacheable'
import { createKeyv as createKeyvRedis } from '@keyv/redis'

// 複数ストアの設定
const memoryStore = createKeyv() // L1: メモリ
const redisStore = createKeyvRedis('redis://user:pass@localhost:6379') // L2: Redis

const cache = createCache({
  stores: [memoryStore, redisStore]
})

// 階層キャッシュの使用
await cache.set('user:123', { name: 'John', age: 30 })

// 最初に見つかった値を返す(通常はメモリから)
const user = await cache.get('user:123')
console.log(user)

wrap関数による自動キャッシュ

import { createCache } from 'cache-manager'

const cache = createCache({
  store: 'memory',
  ttl: 60000
})

// データベースアクセス関数をキャッシュでラップ
async function getUserFromDb(userId) {
  console.log('データベースから取得中...')
  // 実際のDB処理をシミュレート
  await new Promise(resolve => setTimeout(resolve, 100))
  return { id: userId, name: `User${userId}`, email: `user${userId}@example.com` }
}

// wrap関数でキャッシュ機能付きに
async function getUser(userId) {
  return await cache.wrap(`user:${userId}`, () => getUserFromDb(userId))
}

// 初回はDBから取得、2回目以降はキャッシュから
const user1 = await getUser('123') // DB アクセス
const user2 = await getUser('123') // キャッシュから取得

Redis統合

import { createCache } from 'cache-manager'
import { createRedisStore } from 'cache-manager-redis-store'

const redisCache = createCache({
  store: createRedisStore({
    host: 'localhost',
    port: 6379,
    password: 'your-password',
    db: 0
  }),
  ttl: 600 // 10分
})

// Redisキャッシュの使用
await redisCache.set('session:abc123', {
  userId: '456',
  loginTime: new Date().toISOString()
}, 3600000) // 1時間

const session = await redisCache.get('session:abc123')

TypeScript型安全な使用

import { createCache, Cache } from 'cache-manager'

interface User {
  id: string
  name: string
  email: string
}

interface CacheConfig {
  userCache: Cache
  sessionCache: Cache
}

class UserService {
  private caches: CacheConfig

  constructor() {
    this.caches = {
      userCache: createCache({
        store: 'memory',
        max: 1000,
        ttl: 300000 // 5分
      }),
      sessionCache: createCache({
        store: 'memory',
        max: 10000,
        ttl: 1800000 // 30分
      })
    }
  }

  async getUser(userId: string): Promise<User | null> {
    const cacheKey = `user:${userId}`
    
    // キャッシュから取得試行
    let user = await this.caches.userCache.get<User>(cacheKey)
    
    if (!user) {
      // DBから取得
      user = await this.fetchUserFromDatabase(userId)
      if (user) {
        await this.caches.userCache.set(cacheKey, user)
      }
    }
    
    return user
  }

  private async fetchUserFromDatabase(userId: string): Promise<User | null> {
    // データベースアクセスの実装
    return {
      id: userId,
      name: `User ${userId}`,
      email: `user${userId}@example.com`
    }
  }
}

マルチストア非ブロッキングモード

import { createCache } from 'cache-manager'
import { createKeyv } from 'cacheable'
import { createKeyv as createKeyvRedis } from '@keyv/redis'

const memoryStore = createKeyv()
const redisStore = createKeyvRedis('redis://localhost:6379')

const cache = createCache({
  stores: [memoryStore, redisStore],
  nonBlocking: true // 非ブロッキングモード
})

// 非ブロッキングモードでは
// set/mset - すべてのストアの完了を待たない
// get/mget - 最初に見つかった値を即座に返す
// del/mdel - すべてのストアの完了を待たない

await cache.set('fast-key', 'fast-value') // すぐに完了
const value = await cache.get('fast-key')  // 最速ストアから取得

カスタムストアの実装

class CustomMemoryStore {
  constructor() {
    this.cache = new Map()
    this.timers = new Map()
  }

  async get(key) {
    return this.cache.get(key)
  }

  async set(key, value, ttl) {
    this.cache.set(key, value)
    
    if (ttl) {
      // 既存タイマーをクリア
      if (this.timers.has(key)) {
        clearTimeout(this.timers.get(key))
      }
      
      // 新しいタイマーを設定
      const timer = setTimeout(() => {
        this.cache.delete(key)
        this.timers.delete(key)
      }, ttl)
      
      this.timers.set(key, timer)
    }
  }

  async del(key) {
    if (this.timers.has(key)) {
      clearTimeout(this.timers.get(key))
      this.timers.delete(key)
    }
    this.cache.delete(key)
  }

  async reset() {
    this.timers.forEach(timer => clearTimeout(timer))
    this.cache.clear()
    this.timers.clear()
  }
}

// カスタムストアの使用
const customCache = createCache({
  store: new CustomMemoryStore()
})