lru-cache
GitHub概要
isaacs/node-lru-cache
A fast cache that automatically deletes the least recently used items
スター5,714
ウォッチ51
フォーク364
作成日:2010年5月21日
言語:TypeScript
ライセンス:ISC License
トピックス
cachecachinglrulru-cachelrucache
スター履歴
データ取得日時: 2025/10/22 09:55
ライブラリ
lru-cache
概要
lru-cache は Node.js向け高性能LRU(Least Recently Used)キャッシュライブラリです。最近使用された順序に基づく自動的な容量管理により、メモリ効率的なキャッシュ運用を実現。 TypeScript で書き直されたバージョン7以降では大幅な性能向上を実現し、2025年現在JavaScript/Node.jsエコシステムで最もパフォーマンスの優れたLRUキャッシュ実装として広く利用されています。get操作に最適化された設計により、繰り返しアクセスが多いアプリケーションで特に優秀な性能を発揮します。
詳細
lru-cache v10系列は2025年現在も活発に開発されており、Node.js 16.14.0以降での最適化と完全なTypeScriptサポートを提供。バージョン7で内部アルゴリズムとデータ構造を根本的に書き直し、大幅なパフォーマンス向上を実現。Named exportのみ採用し、CJS/MJSの両ビルドとminified版を提供。内部プロパティは実際のprivateクラスプロパティに移行し、より厳密なAPI設計を採用。キーと値のnull/undefined制限により予期しないエラーを排除。
主な特徴
- LRUエビクション: 最も最近使用されていないアイテムの自動削除
- 高速get操作: get操作に特化した最適化により高速アクセス
- TypeScript完全対応: TypeScriptで書き直され完全な型安全性を提供
- メモリ効率: 自動的なサイズ管理による予測可能なメモリ使用量
- 軽量設計: 最小限の依存関係と高効率な内部実装
- TTL機能: オプションのTTL(Time To Live)サポート
メリット・デメリット
メリット
- JavaScript/Node.jsで最高クラスのLRUキャッシュ性能
- get操作の繰り返しと削除時間最小化に最適化
- 自動的なメモリ管理によるOOMエラー防止
- TypeScript完全サポートによる開発生産性向上
- 長期間の実績による高い安定性と信頼性
- minified版提供によるバンドルサイズ最適化
デメリット
- set操作が他の選択肢よりもやや重い(get最適化の代償)
- Node.js 16.14.0以降が必要(レガシー環境制約)
- null/undefined値の制限(一部ユースケースで制約)
- シングルプロセス内キャッシュ(分散キャッシュ不可)
- LRU以外のエビクション戦略なし
- 永続化機能なし(プロセス再起動でデータ消失)
参考ページ
書き方の例
インストールと基本セットアップ
# NPM でのインストール
npm install lru-cache
# Yarn でのインストール
yarn add lru-cache
# TypeScript型定義は標準で含まれています(v7以降)
基本的なLRUキャッシュ操作
import { LRUCache } from 'lru-cache'
// LRUキャッシュの作成
const cache = new LRUCache({
max: 500, // 最大アイテム数
maxSize: 5000, // 最大サイズ
sizeCalculation: (value, key) => {
// カスタムサイズ計算
return JSON.stringify(value).length + key.length
},
ttl: 1000 * 60 * 5, // TTL: 5分(オプション)
allowStale: false, // 期限切れアイテムの取得を禁止
updateAgeOnGet: false, // get時の年齢更新を無効化
updateAgeOnHas: false // has時の年齢更新を無効化
})
// 基本的な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', 'session_data', { ttl: 3600000 }) // 1時間
// サイズベースの設定
cache.set('large:data', {
content: new Array(1000).fill('data'),
metadata: { created: Date.now() }
})
// 存在確認と削除
if (cache.has('user:1')) {
console.log('User exists in cache')
}
cache.delete('user:1')
console.log('Cache size:', cache.size) // 現在のアイテム数
console.log('Cache calculatedSize:', cache.calculatedSize) // 総サイズ
高度なキャッシュ設定と監視
import { LRUCache } from 'lru-cache'
class AdvancedLRUCache {
constructor() {
this.cache = new LRUCache({
max: 1000,
maxSize: 50000,
sizeCalculation: this.calculateSize.bind(this),
ttl: 1000 * 60 * 30, // 30分のデフォルトTTL
allowStale: true, // 期限切れアイテムも返す
updateAgeOnGet: true, // アクセス時にage更新
fetchMethod: this.fetchMethod.bind(this),
dispose: this.disposeMethod.bind(this),
noDisposeOnSet: false,
noUpdateTTL: false
})
this.stats = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0,
evictions: 0
}
}
calculateSize(value, key) {
if (typeof value === 'string') {
return value.length + key.length
}
return JSON.stringify(value).length + key.length
}
// fetch method: キャッシュミス時の自動データ取得
async fetchMethod(key, staleValue, { options, signal }) {
console.log(`Fetching data for key: ${key}`)
if (key.startsWith('user:')) {
return await this.fetchUserData(key)
} else if (key.startsWith('api:')) {
return await this.fetchApiData(key)
}
return null
}
async fetchUserData(key) {
const userId = key.split(':')[1]
// 模擬的なデータベースアクセス
await new Promise(resolve => setTimeout(resolve, 100))
return {
id: userId,
name: `User ${userId}`,
lastAccess: new Date(),
fetchedAt: Date.now()
}
}
async fetchApiData(key) {
// 模擬的なAPI呼び出し
await new Promise(resolve => setTimeout(resolve, 200))
return {
key,
data: `API data for ${key}`,
timestamp: Date.now()
}
}
// dispose method: アイテム削除時のクリーンアップ
disposeMethod(value, key, reason) {
console.log(`Disposed: ${key} (reason: ${reason})`)
this.stats.evictions++
// カスタムクリーンアップロジック
if (key.startsWith('temp:')) {
console.log(`Cleaning up temporary data: ${key}`)
}
}
// 統計情報付きget
async get(key) {
const value = await this.cache.get(key)
if (value !== undefined) {
this.stats.hits++
} else {
this.stats.misses++
}
return value
}
// 統計情報付きset
set(key, value, options = {}) {
this.stats.sets++
return this.cache.set(key, value, options)
}
// 統計情報付きdelete
delete(key) {
const deleted = this.cache.delete(key)
if (deleted) {
this.stats.deletes++
}
return deleted
}
// 統計レポート
getStats() {
const total = this.stats.hits + this.stats.misses
return {
...this.stats,
hitRate: total > 0 ? (this.stats.hits / total * 100).toFixed(2) + '%' : '0%',
size: this.cache.size,
calculatedSize: this.cache.calculatedSize,
remainingTTL: this.cache.getRemainingTTL('user:1')
}
}
// キャッシュクリーンアップ
prune() {
const beforeSize = this.cache.size
this.cache.purgeStale()
const afterSize = this.cache.size
console.log(`Pruned ${beforeSize - afterSize} stale entries`)
}
}
// 使用例
const advancedCache = new AdvancedLRUCache()
// 自動fetch付きでのデータ取得
advancedCache.get('user:123').then(user => {
console.log('User data:', user)
})
// 定期的な統計レポート
setInterval(() => {
console.log('Cache Stats:', advancedCache.getStats())
}, 30000)
Express.js でのキャッシュミドルウェア
import express from 'express'
import { LRUCache } from 'lru-cache'
const app = express()
// レスポンスキャッシュの設定
const responseCache = new LRUCache({
max: 500,
ttl: 1000 * 60 * 10, // 10分
sizeCalculation: (value) => JSON.stringify(value).length,
maxSize: 1024 * 1024, // 1MB
allowStale: true
})
// キャッシュミドルウェアの実装
function cacheMiddleware(ttl = 600000) {
return (req, res, next) => {
const key = req.originalUrl || req.url
const cached = responseCache.get(key)
if (cached) {
console.log(`Cache HIT: ${key}`)
return res.json(cached)
}
console.log(`Cache MISS: ${key}`)
// レスポンスのキャプチャ
const originalJson = res.json
res.json = function(data) {
// レスポンスをキャッシュに保存
responseCache.set(key, data, { ttl })
console.log(`Cache SET: ${key}`)
originalJson.call(this, data)
}
next()
}
}
// 条件付きキャッシュミドルウェア
function conditionalCache(condition, ttl = 600000) {
return (req, res, next) => {
if (!condition(req)) {
return next()
}
return cacheMiddleware(ttl)(req, res, next)
}
}
// APIエンドポイント
app.get('/api/users', cacheMiddleware(300000), 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/profile',
conditionalCache(req => req.headers.authorization, 180000),
async (req, res) => {
const profile = {
id: 1,
name: 'Current User',
preferences: {},
lastLogin: new Date()
}
res.json(profile)
}
)
// キャッシュ統計エンドポイント
app.get('/cache/stats', (req, res) => {
res.json({
size: responseCache.size,
calculatedSize: responseCache.calculatedSize,
maxSize: responseCache.maxSize,
ttl: responseCache.ttl
})
})
// キャッシュクリアエンドポイント
app.delete('/cache', (req, res) => {
const beforeSize = responseCache.size
responseCache.clear()
res.json({
message: 'Cache cleared',
clearedItems: beforeSize
})
})
app.listen(3000, () => {
console.log('Server running on port 3000')
})
TTL管理とメモリ最適化
import { LRUCache } from 'lru-cache'
class TTLOptimizedCache {
constructor() {
// 短期キャッシュ(1分)
this.shortTermCache = new LRUCache({
max: 1000,
ttl: 60000,
updateAgeOnGet: true,
allowStale: false
})
// 中期キャッシュ(15分)
this.mediumTermCache = new LRUCache({
max: 500,
ttl: 900000,
updateAgeOnGet: true,
allowStale: true
})
// 長期キャッシュ(1時間)
this.longTermCache = new LRUCache({
max: 100,
ttl: 3600000,
updateAgeOnGet: false,
allowStale: true
})
}
set(key, value, duration = 'medium') {
switch (duration) {
case 'short':
return this.shortTermCache.set(key, value)
case 'medium':
return this.mediumTermCache.set(key, value)
case 'long':
return this.longTermCache.set(key, value)
default:
throw new Error('Invalid duration. Use: short, medium, long')
}
}
get(key) {
// 短期キャッシュから探す
let value = this.shortTermCache.get(key)
if (value !== undefined) {
return { value, source: 'short' }
}
// 中期キャッシュから探す
value = this.mediumTermCache.get(key)
if (value !== undefined) {
// 短期キャッシュに昇格
this.shortTermCache.set(key, value)
return { value, source: 'medium' }
}
// 長期キャッシュから探す
value = this.longTermCache.get(key)
if (value !== undefined) {
// 中期キャッシュに昇格
this.mediumTermCache.set(key, value)
return { value, source: 'long' }
}
return { value: undefined, source: 'none' }
}
delete(key) {
return (
this.shortTermCache.delete(key) ||
this.mediumTermCache.delete(key) ||
this.longTermCache.delete(key)
)
}
clear() {
this.shortTermCache.clear()
this.mediumTermCache.clear()
this.longTermCache.clear()
}
getMemoryUsage() {
return {
short: {
size: this.shortTermCache.size,
calculatedSize: this.shortTermCache.calculatedSize
},
medium: {
size: this.mediumTermCache.size,
calculatedSize: this.mediumTermCache.calculatedSize
},
long: {
size: this.longTermCache.size,
calculatedSize: this.longTermCache.calculatedSize
}
}
}
}
// 使用例
const tieredCache = new TTLOptimizedCache()
// 異なる期間でのデータ設定
tieredCache.set('session:temp', { data: 'temporary' }, 'short')
tieredCache.set('user:profile', { name: 'John' }, 'medium')
tieredCache.set('config:app', { version: '1.0' }, 'long')
// データ取得と昇格の確認
const result = tieredCache.get('user:profile')
console.log(result) // { value: {...}, source: 'medium' }
TypeScript での型安全な使用
import { LRUCache } from 'lru-cache'
interface User {
id: number
name: string
email: string
lastLogin?: Date
}
interface CacheConfig {
maxUsers: number
userTTL: number
sessionTTL: number
}
class TypedLRUCache {
private userCache: LRUCache<string, User>
private sessionCache: LRUCache<string, string>
private configCache: LRUCache<string, any>
constructor(config: CacheConfig) {
this.userCache = new LRUCache<string, User>({
max: config.maxUsers,
ttl: config.userTTL,
sizeCalculation: (user: User, key: string) => {
return JSON.stringify(user).length + key.length
}
})
this.sessionCache = new LRUCache<string, string>({
max: 10000,
ttl: config.sessionTTL,
allowStale: false
})
this.configCache = new LRUCache<string, any>({
max: 100,
ttl: 1000 * 60 * 60 * 24, // 24時間
updateAgeOnGet: false
})
}
// ユーザーキャッシュ
setUser(userId: number, user: User): boolean {
return this.userCache.set(`user:${userId}`, user)
}
getUser(userId: number): User | undefined {
return this.userCache.get(`user:${userId}`)
}
// セッションキャッシュ
setSession(sessionId: string, data: string): boolean {
return this.sessionCache.set(`session:${sessionId}`, data)
}
getSession(sessionId: string): string | undefined {
return this.sessionCache.get(`session:${sessionId}`)
}
// 型安全な設定キャッシュ
setConfig<T>(key: string, config: T): boolean {
return this.configCache.set(`config:${key}`, config)
}
getConfig<T>(key: string): T | undefined {
return this.configCache.get<T>(`config:${key}`)
}
// 統計情報の型安全な取得
getStats() {
return {
users: {
size: this.userCache.size,
calculatedSize: this.userCache.calculatedSize
},
sessions: {
size: this.sessionCache.size,
calculatedSize: this.sessionCache.calculatedSize
},
config: {
size: this.configCache.size,
calculatedSize: this.configCache.calculatedSize
}
}
}
}
// 使用例
const typedCache = new TypedLRUCache({
maxUsers: 1000,
userTTL: 1800000, // 30分
sessionTTL: 3600000 // 1時間
})
const user: User = {
id: 1,
name: 'John Doe',
email: '[email protected]',
lastLogin: new Date()
}
typedCache.setUser(1, user)
const cachedUser = typedCache.getUser(1) // 型は User | undefined
// 型安全な設定管理
interface AppConfig {
theme: string
language: string
features: string[]
}
const appConfig: AppConfig = {
theme: 'dark',
language: 'ja',
features: ['cache', 'auth', 'logging']
}
typedCache.setConfig<AppConfig>('app', appConfig)
const config = typedCache.getConfig<AppConfig>('app') // 型は AppConfig | undefined