#10630·parse-server

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

Author: AdrianCurtinCreated Aug 13, 2026Updated Aug 13, 2026

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.

javascript
// 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:

javascript
// 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:

javascript
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.

Source: parse-community/parse-server