Local LRU

JavaScriptNode.jsキャッシュライブラリLRUインメモリ

キャッシュライブラリ

Local LRU

概要

Local LRUは、Node.jsアプリケーション向けのローカルLRU(Least Recently Used)キャッシュライブラリです。

詳細

Local LRUは、Node.js環境でのインメモリキャッシュソリューションとして設計されたLRU(Least Recently Used)キャッシュライブラリです。LRUアルゴリズムは、キャッシュが満杯になった際に最も長い間使用されていないアイテムを自動的に削除し、新しいデータのための領域を確保する効率的なキャッシュ戦略です。このライブラリは高速なget操作と最小限の排除時間に最適化されており、コストの高い操作を可能な限り少なくキャッシュすることを想定して設計されています。TypeScriptフルサポートにより型安全性を提供し、TTL(Time To Live)機能によるアイテムの自動期限切れ、サイズベースの制限、カスタム削除ハンドラーなどの豊富な機能を備えています。メモリ効率的な実装により、Webアプリケーション、API サーバー、マイクロサービスなど様々なNode.js環境での使用に適しています。ES6モジュールとCommonJSの両方をサポートし、ブラウザ環境でも利用可能な柔軟性を持っています。

メリット・デメリット

メリット

  • 高性能: get操作に最適化された高速アクセス
  • 自動メモリ管理: LRUアルゴリズムによる効率的なメモリ使用
  • TypeScriptサポート: 完全な型定義による開発体験向上
  • TTL機能: アイテムの自動期限切れ対応
  • 柔軟な設定: サイズ、TTL、カスタム削除ハンドラーが設定可能
  • 軽量: 最小限の依存関係とコンパクトなサイズ
  • マルチプラットフォーム: Node.js、ブラウザ両環境対応

デメリット

  • メモリ制限: インメモリのため大量データには不適切
  • 永続化なし: プロセス再起動時にデータが失われる
  • 単一プロセス: 複数プロセス間でのキャッシュ共有不可
  • set操作コスト: get操作に比べてset操作が若干重い
  • 機能のオーバーヘッド: TTLやsize tracking使用時の性能影響

主要リンク

書き方の例

基本的なLRUキャッシュ

import { LRUCache } from 'lru-cache'

// 基本的なキャッシュの作成
const cache = new LRUCache({
  max: 500,  // 最大500アイテム
  ttl: 1000 * 60 * 5  // 5分間のTTL
})

// 値の設定
cache.set('user:123', { name: 'Alice', age: 30 })
cache.set('user:456', { name: 'Bob', age: 25 })

// 値の取得
const user = cache.get('user:123')
console.log(user) // { name: 'Alice', age: 30 }

// 存在確認
if (cache.has('user:123')) {
  console.log('ユーザーキャッシュが存在します')
}

// キャッシュの削除
cache.delete('user:456')

// 全消去
cache.clear()

TypeScriptでの型安全な使用

interface User {
  id: number
  name: string
  email: string
  lastLogin: Date
}

interface CacheStats {
  hits: number
  misses: number
  sets: number
}

// 型安全なキャッシュ
const userCache = new LRUCache<string, User>({
  max: 1000,
  ttl: 1000 * 60 * 30, // 30分
  updateAgeOnGet: true, // アクセス時に年齢を更新
  updateAgeOnHas: false
})

// ユーザーデータのキャッシュ
function cacheUser(user: User): void {
  userCache.set(`user:${user.id}`, user)
}

// ユーザーデータの取得
function getCachedUser(userId: number): User | undefined {
  return userCache.get(`user:${userId}`)
}

// 使用例
const user: User = {
  id: 123,
  name: 'Alice Smith',
  email: '[email protected]',
  lastLogin: new Date()
}

cacheUser(user)
const cachedUser = getCachedUser(123)

サイズ制限とカスタム削除

const cache = new LRUCache({
  max: 100,
  maxSize: 5000, // 最大サイズ(バイト)
  sizeCalculation: (value, key) => {
    // カスタムサイズ計算
    return JSON.stringify(value).length + key.length
  },
  dispose: (value, key, reason) => {
    // アイテム削除時のコールバック
    console.log(`削除: ${key}, 理由: ${reason}`)
    if (value.cleanup) {
      value.cleanup()
    }
  }
})

// サイズを考慮したキャッシュ
cache.set('large-data', {
  data: new Array(1000).fill('sample'),
  cleanup: () => console.log('リソースクリーンアップ')
})

非同期データの取得とキャッシュ

class APICache {
  constructor() {
    this.cache = new LRUCache({
      max: 200,
      ttl: 1000 * 60 * 10, // 10分
      allowStale: false,
      updateAgeOnGet: true,
      updateAgeOnHas: true
    })
  }

  async getUserData(userId) {
    const cacheKey = `user:${userId}`
    
    // キャッシュから取得試行
    let userData = this.cache.get(cacheKey)
    if (userData) {
      console.log('キャッシュヒット')
      return userData
    }

    // キャッシュミス時はAPIから取得
    console.log('キャッシュミス - APIから取得')
    try {
      const response = await fetch(`/api/users/${userId}`)
      userData = await response.json()
      
      // 結果をキャッシュに保存
      this.cache.set(cacheKey, userData)
      return userData
    } catch (error) {
      console.error('APIエラー:', error)
      throw error
    }
  }

  // キャッシュ統計の取得
  getStats() {
    return {
      size: this.cache.size,
      calculatedSize: this.cache.calculatedSize,
      remainingTTL: this.cache.getRemainingTTL('user:123')
    }
  }
}

// 使用例
const apiCache = new APICache()

async function example() {
  try {
    const user1 = await apiCache.getUserData(123) // API呼び出し
    const user2 = await apiCache.getUserData(123) // キャッシュから取得
    
    console.log('キャッシュ統計:', apiCache.getStats())
  } catch (error) {
    console.error('エラー:', error)
  }
}

Express.jsミドルウェアとしての使用

import express from 'express'
import { LRUCache } from 'lru-cache'

const app = express()

// レスポンスキャッシュミドルウェア
function createCacheMiddleware(options = {}) {
  const cache = new LRUCache({
    max: 500,
    ttl: 1000 * 60 * 5, // 5分
    ...options
  })

  return (req, res, next) => {
    const key = req.originalUrl || req.url
    const cachedResponse = cache.get(key)

    if (cachedResponse) {
      console.log(`キャッシュヒット: ${key}`)
      return res.json(cachedResponse)
    }

    // レスポンスをインターセプト
    const originalJson = res.json
    res.json = function(data) {
      // レスポンスをキャッシュに保存
      cache.set(key, data)
      console.log(`キャッシュ保存: ${key}`)
      return originalJson.call(this, data)
    }

    next()
  }
}

// ミドルウェアの適用
app.use('/api/users', createCacheMiddleware({ ttl: 1000 * 60 * 10 }))

app.get('/api/users/:id', (req, res) => {
  // 重い処理のシミュレーション
  setTimeout(() => {
    res.json({
      id: req.params.id,
      name: `User ${req.params.id}`,
      timestamp: new Date().toISOString()
    })
  }, 1000)
})

メモリ効率的な設定

// 本番環境での推奨設定
const productionCache = new LRUCache({
  max: 10000, // アプリケーションに応じて調整
  ttl: 1000 * 60 * 60, // 1時間
  allowStale: true, // 期限切れでも stale データを許可
  updateAgeOnGet: false, // パフォーマンス重視
  updateAgeOnHas: false,
  fetchMethod: async (key, staleValue, { options, signal }) => {
    // 自動フェッチ機能
    if (staleValue && !options.forceRefresh) {
      return staleValue
    }
    
    // 新しいデータを取得
    const freshData = await fetchDataFromAPI(key, { signal })
    return freshData
  }
})

// 自動フェッチ機能付きの取得
async function getDataWithAutoFetch(key) {
  try {
    return await productionCache.fetch(key)
  } catch (error) {
    console.error('データ取得エラー:', error)
    return null
  }
}