lru-cache

repository·main·Indexed 26 days ago

https://github.com/isaacs/node-lru-cache

A high-performance Least-Recently-Used (LRU) cache implementation for JavaScript (v11.5.2). It allows developers to limit memory usage by automatically evicting the least recently accessed items based on capacity (max), total storage size (maxSize), or time-to-live (ttl) constraints.

Tokens
5.1K
Snippets
7
Records
37
Agent score
89%

What's inside lru-cache

  1. Store undefined values in LRUCache

    main

    The cache does not store undefined because it is used internally to signal missing keys. Calling cache.set(key, undefined) is equivalent to cache.delete(key).

    To track undefined values, use a unique Symbol as a placeholder:

    import { LRUCache } from 'lru-cache'
    const undefinedValue = Symbol('undefined')
    const cache = new LRUCache({ max: 100 })
    
    const mySet = (key, value) =>
      cache.set(key, value === undefined ? undefinedValue : value)
    
    const myGet = (key) => {
      const v = cache.get(key)
      return v === undefinedValue ? undefined : v
    }
  2. Ensure storage bounds safety

    main

    To prevent unbounded memory growth, follow these rules:

    1. Using max: Allocates storage for a specific number of items. This is the most performant method.
    2. Using maxSize: Sets a limit on total storage consumed. You must provide a sizeCalculation function in the constructor or a size option in cache.set(). Sizes must be positive integers.
    3. Using ttl: If neither max nor maxSize are set, ttl tracking must be enabled. Note that items are only purged when requested unless ttlAutopurge is enabled. If ttlAutopurge, max, and maxSize are all unset, the cache may grow unbounded.
  3. Optimize performance for different key types

    main

    The performance of lru-cache depends on your key types. This library is optimized for repeated get operations and minimizing eviction time.

    • Best performance: Use small integer values as keys (if you don't need the advanced features of this library, consider lru-fast).
    • Good performance: Use short non-numeric strings (less than 256 characters).
    • Recommended for complex keys: Use this library if your keys are long strings, strings that look like floats, objects, or a mix of types.

    Note: Avoid using dispose functions, size tracking, TTL behavior, or observability features unless absolutely necessary, as these add minimal but non-zero performance overhead.

  4. Observe LRUCache with diagnostics_channel

    main

    The library uses node:diagnostics_channel for metrics and tracing.

    • Metrics: Listen to lru-cache:metrics to receive LRUCache.Status objects for synchronous operations.
    • Tracing: Subscribe to tracingChannel('lru-cache') to track the lifecycle of asynchronous operations like cache.fetch() and cache.forceFetch().

    Note: Using these features imposes a modest performance penalty. Support for node:diagnostics_channel is currently limited to Node, Bun, Deno, and compatible edge platforms.

  5. Mock time for TTL testing

    main

    When testing TTL (Time-To-Live) functionality, the library captures a reference to the global performance or Date objects at import time. To ensure your test mocks (like jest.useFakeTimers()) are respected, you must dynamically import the package within your tests instead of using a top-level static import.

    Alternatively, you can provide a custom perf option during instantiation with a now method to control time manually.

    // ❌ Not recommended
    import { LRUCache } from 'lru-cache'
    // mocking timers, e.g. jest.useFakeTimers()
    
    // ✅ Recommended for TTL tests
    // mocking timers, e.g. jest.useFakeTimers()
    const { LRUCache } = await import('lru-cache')
  6. Configure LRUCache options

    main

    When instantiating LRUCache, you can provide an options object to control behavior.

    Required (at least one):

    • max: Maximum number of items to keep.
    • maxSize: Maximum storage size (requires sizeCalculation).
    • ttl: Time-to-live in milliseconds.

    Optional:

    • maxSize: Used for tracking overall storage size.
    • sizeCalculation: Function (value, key) => number used to determine the size of an item for maxSize tracking.
    • dispose: Callback (value, key, reason) => void called when an item is evicted.
    • onInsert: Callback (value, key, reason) => void called when an item is inserted.
    • ttl: Default time-to-live in ms.
    • allowStale: Boolean; if true, returns stale items before removing them.
    • updateAgeOnGet: Boolean; if true, updates the item's age when get() is called.
    • updateAgeOnHas: Boolean; if true, updates the item's age when has() is called.
    • fetchMethod: Async function for cache.fetch() to implement stale-while-revalidate behavior.
  7. Initialize LRUCache with different limit types

    main

    The LRUCache constructor accepts an Options object that must define at least one type of limit to prevent unbounded memory consumption. You can limit the cache by:

    • max: The maximum number of items (count) allowed in the cache.
    • maxSize: The maximum total size of all items in the cache. Requires sizeCalculation or providing a size during set().
    • ttl: A Time-To-Live in milliseconds. Items expire after this duration.

    Important: If you use ttl without max, maxSize, or ttlAutopurge, the cache may grow unbounded and trigger a warning.

  8. Basic usage of LRUCache

    main

    Import LRUCache using ESM or CommonJS. To prevent unbounded storage, you must specify at least one of max, ttl, or maxSize. For optimal performance, it is recommended to specify max so memory allocation can be done up-front.

    import { LRUCache } from 'lru-cache'
    // or:
    // const { LRUCache } = require('lru-cache')
    
    const options = {
      max: 500,
      ttl: 1000 * 60 * 5,
      dispose: (value, key, reason) => {
        // cleanup logic
      },
      onInsert: (value, key, reason) => {
        // logging logic
      }
    }
    
    const cache = new LRUCache(options)
    
    cache.set('key', 'value')
    cache.get('key') // "value"
    cache.clear() // empty the cache
  9. Use the `perf` option for manual time control

    main

    You can pass a perf object to the LRUCache constructor to provide a custom now method. This is useful for manual time-mocking in tests without a framework.

    import { LRUCache } from 'lru-cache'
    
    let myClockTime = 0
    
    const cache = new LRUCache<string>({
      max: 10,
      ttl: 1000,
      perf: {
        now: () => myClockTime,
      },
    })
    
    // run tests, updating myClockTime as needed
  10. Configure size tracking with `sizeCalculation`

    main

    To use maxSize or maxEntrySize, you must provide a sizeCalculation function. This function takes the value and key and returns a Size (number).

    sizeCalculation: (value, key) => value.length

    If sizeCalculation is not provided but maxSize or maxEntrySize are set, you must provide an explicit size in every set() call.