Skip to content

Per-entry cache TTL is silently ignored by the in-memory cache adapter #10630

Description

@AdrianCurtin

New Issue Checklist

Issue Description

LRUCache#put passes the per-entry TTL to lru-cache as a positional number, but lru-cache v11 takes it as a property of an options object. The number is silently ignored and the entry falls back to the cache-wide cacheTTL, so on the default in-memory adapter a per-entry TTL cannot be set at all.

// src/Adapters/Cache/LRUCache.js
put(key, value, ttl = this.ttl) {
  this.cache.set(key, value, ttl);   // v11 signature is set(key, value, { ttl })
}

LRUCache#set in v11 destructures its third argument, so a number contributes no properties and ttl resolves to the instance default set in the constructor.

Two related defects in the same path:

  1. The LRUCache constructor never assigns this.ttl, so the ttl = this.ttl default in put is always undefined.
  2. SubCache accepts a ttl in its constructor and stores it, but put() ignores this.ttl and forwards only the caller's argument. CacheController constructs all three sub-caches (role, user, graphQL) without a TTL anyway, so the parameter is unused.

The one caller in Parse Server that requests a per-entry TTL is ParseGraphQLController, which caches the GraphQL config for 60 seconds:

// src/Controllers/ParseGraphQLController.js
return this.cacheController.graphQL.put(this.configCacheKey, graphQLConfig, 60000);

On the in-memory adapter that entry actually lives for cacheTTL (5000ms by default), so the config is re-read from the database roughly 12 times more often than intended. RedisCacheAdapter#put honors the TTL correctly via PX, so the same call behaves differently depending on the configured cache adapter.

Steps to reproduce

Against lru-cache 11.2.7 as vendored:

const { LRUCache } = require('lru-cache');
const cache = new LRUCache({ max: 10, ttl: 5000 });   // cacheTTL default

cache.set('a', 1, 60000);          // what LRUCache#put does today
cache.set('b', 1, { ttl: 60000 }); // v11 signature

console.log(cache.getRemainingTTL('a')); // 5000
console.log(cache.getRemainingTTL('b')); // 60000

Equivalently, through Parse Server: start a server with the default cache adapter, call config.cacheController.graphQL.put('k', 'v', 60000), wait longer than cacheTTL but less than 60 seconds, and get('k') returns null.

Actual Outcome

The per-entry TTL is discarded. Entries expire after the cache-wide cacheTTL.

Expected Outcome

The per-entry TTL is applied. A put with an explicit TTL keeps the entry for that duration, matching RedisCacheAdapter and the documented intent of the third parameter.

Environment

Server

  • Parse Server version: 9.10.1-alpha.6
  • Operating system: macOS 15.5
  • Local or remote host: local

Database

  • System (MongoDB or Postgres): MongoDB
  • Database version: 8.0
  • Local or remote host: local

Client

  • SDK (iOS, Android, JavaScript, PHP, Unity, etc): not applicable, server-internal cache
  • SDK version: not applicable

Logs

No error is produced. The TTL is dropped silently.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions