All engineering notes
ProductOctober 14, 20265 min read

Every key expires. PERSIST is not a feature.

A cache without expiry is a database with worse tooling. Cacheify enforces a TTL on every key, defaults to 24 hours and rejects attempts to remove it.

Most Redis deployments treat expiry as optional. Cacheify treats it as a required property of every write. That is a product decision before it is a technical one.

The rule

Every key carries an expiry. A write that does not specify one receives the platform default of 24 hours. The command plane accepts EX and PX options, the string endpoint accepts expiresInMs, and the key detail response includes ttlMs so a client can show the remaining lifetime without a second call.

There is no state in which a key has no expiry. PERSIST is rejected with 422, and there is no dashboard action that removes a TTL.

Explicit expiry via the string endpoint
await fetch(`${base}/api/data/${dbId}/strings/otp:user@mail.com`, {
  method: 'PUT',
  headers: { ...auth, 'Content-Type': 'application/json' },
  body: JSON.stringify({ value: otpHash, expiresInMs: 300000 }),
});

Why enforce it

A free, multi-tenant cache has a capacity problem: the most expensive key is the one nobody remembers writing. Optional expiry turns every forgotten session token, feature flag or temporary counter into permanent storage. The platform would either need aggressive manual cleanup or quietly degrade for everyone else.

Mandatory expiry makes the storage bound predictable. Keys per database and value bytes are capped by quotas, and time is capped by TTL. An operator can reason about worst-case usage without auditing every tenant.

How to work with it

Long-lived data needs an explicit expiry on each write, or a re-arm through the expire endpoint. Short-lived data should choose a TTL that matches its purpose rather than inheriting 24 hours by accident.

Expiry is evaluated lazily: the first read after the deadline removes the key and returns 404. There is no client-side cleanup job to schedule.

  • Prefer the shortest TTL that still serves the use case.
  • Re-arm with POST /keys/{key}/expire when the lifetime should extend.
  • Handle a 404 as a normal cache miss, not an exception that breaks the request path.

What this is not

This is not a retention policy for durable records. If a value must survive indefinitely, a cache is the wrong store. Export it to a database with its own backup and retention story rather than fighting the cache expiration model.

Try the contract yourself.

Five databases are free. Create one, mint a scoped key and send a command.