skip to content

Redis stores each value's recency in a 24-bit field inside the object header. What exactly is stored there, at what resolution, and what happens when that counter wraps around?

level: middleimportance: should knowfreq 26%

answer

  1. 24 spare bits in the redisObject header
  2. cached server lruclock, one-second resolution
  3. ~194 days before the 24-bit clock wraps
  4. LFU reuses the same bits: 16-bit time + 8-bit counter
  5. OBJECT IDLETIME reads without touching

basics

~20 s

Under an LRU policy the 24-bit field holds a coarse clock value in seconds, taken from a cached server clock refreshed about once per second. Idle time is now minus that value. Twenty-four seconds-resolution bits wrap after roughly 194 days, and the idle-time calculation compensates for the wrap.

solid answer

~50 s

Every Redis value carries a `redisObject` header with a 24-bit `lru` field. Under LRU policies it stores the value of `server.lruclock`, a global clock updated by the server cron at a one-second resolution, so per-key recency is precise only to the second - which is plenty when the question is only "which of these is idlest". Reading the global cached clock rather than calling the system clock on every key access matters: lookups are on the hottest path in the server, and a syscall per access would be a real cost. Twenty-four bits at one second wrap after about 194 days. The idle-time estimator handles that: if a key's stored clock is greater than the current clock, it assumes one wrap and adds the range before subtracting. A key untouched for longer than the wrap period can therefore report a wrong, smaller idle time - harmless in a cache, and a good detail to know. `OBJECT IDLETIME` exposes the value in seconds without refreshing it.

code

text · 8 lines
text
redis-cli CONFIG SET maxmemory-policy allkeys-lru
redis-cli SET user:42 '{"n":"ada"}'
redis-cli OBJECT IDLETIME user:42   # 0 - touched this second

# under an LFU policy the same bits mean something else
redis-cli CONFIG SET maxmemory-policy allkeys-lfu
redis-cli OBJECT IDLETIME user:42   # error: requires an LRU policy
redis-cli OBJECT FREQ user:42       # logarithmic access counter

go deeper

for a junior

Know that recency is a small per-key timestamp in seconds and that OBJECT IDLETIME shows how long a key has gone untouched.

for a middle

Explain the 24-bit field, the cached one-second server clock, the roughly 194-day wrap, and the fact that the same bits are reinterpreted under LFU.

for a senior

Add the operational implications: reads refresh recency so scans can poison LRU, and use no-touch inspection commands when surveying memory.

for a principal

Present it as a deliberate precision-versus-cost decision - free bits plus a cached clock buys an ordering good enough for eviction, and name what that approximation gives up.

## Where the field lives Every value stored in Redis is wrapped in a `redisObject` struct holding its type, encoding, a reference count and a 24-bit field named `lru`. Twenty-four bits was not an arbitrary choice: it is what was left over in the header's bit packing, so the recency metadata is genuinely free - no extra allocation per key, which is the entire reason Redis could afford per-key recency at all. The field is overloaded. Under the LRU policies it holds a timestamp; under the LFU policies (Redis 4.0 and later) the same 24 bits are split into a 16-bit last-decay time in minutes and an 8-bit frequency counter. Which interpretation applies depends on `maxmemory-policy`, which is why `OBJECT IDLETIME` is rejected while an LFU policy is active and `OBJECT FREQ` is rejected while it is not - the bits simply do not mean what the other command would report. ## The clock and its resolution Redis maintains a global `lruclock` refreshed by the server cron at a resolution of one second. When a command looks a key up, Redis copies that cached value into the object's `lru` field instead of asking the operating system for the time. That avoids a clock call on the hottest path in the server; at very high throughput even a cheap time source, called once per key access, is measurable overhead. The cost of that shortcut is granularity. Two keys touched within the same second are indistinguishable in recency, and the clock stored may be up to a second stale. For eviction this does not matter: the decision is a comparison between candidates that are usually idle for many seconds or minutes, not a race between two keys read microseconds apart. ## Wraparound A 24-bit counter ticking once per second holds about 16.7 million seconds, roughly 194 days, before it rolls over to zero. Idle time is computed as current clock minus stored clock; after a rollover the stored value can exceed the current one. Redis detects that case and adds the full range back before subtracting, so a single wrap is handled correctly. What cannot be recovered is a key idle for longer than one whole wrap period: its arithmetic aliases onto a smaller idle time, so an ancient key may look younger than it is. In a cache under memory pressure that key would have been evicted long before, so the situation is largely theoretical - but it is exactly the kind of bounded-counter detail an interviewer uses to check whether you actually understand the representation. ## Practical notes - The field is refreshed on *access*, so any read touches recency. A background job that scans keys with `GET` will make cold keys look hot and degrade eviction quality; use `OBJECT` inspection or `SCAN` with the appropriate no-touch semantics rather than reading values when you only want to survey. - `OBJECT IDLETIME <key>` returns the estimated idle time in seconds and is implemented with a no-touch lookup, so inspecting a key does not change its eviction odds. - `DEBUG OBJECT` and `MEMORY USAGE` are the other inspection routes; `OBJECT ENCODING` reports encoding from the same header. - Because the clock resolution is one second, an idle time of 0 simply means "touched within the current second", not "touched just now". The design principle is worth stating out loud: Redis spends 24 spare bits and a cached clock to get an ordering that is good enough for eviction, rather than exact recency that would cost memory per key and time per access.

  • Why does Redis read a cached clock instead of calling the system clock on every key access?
    Because the LRU field is written on every lookup, which is the busiest code path in the server. A time call per access would add measurable per-command cost for accuracy nobody needs, since eviction only compares keys that differ by seconds or more. The server cron refreshes the cached value roughly once a second, which is the resolution of the whole mechanism.
  • Does running OBJECT IDLETIME on a key reset its idle time?
    No. The OBJECT subcommands perform a no-touch lookup precisely so that inspection does not distort eviction decisions. Ordinary data commands such as GET or HGETALL do refresh the field, which is why surveying a keyspace by reading values is a bad idea when you care about LRU quality.

saying these in an interview costs you the question

  • Thinking Redis stores a full 64-bit or millisecond-precision timestamp per key
  • Assuming the LRU field means the same thing under an LFU policy
  • Believing the clock never wraps or that wraparound corrupts the dataset
  • Claiming OBJECT IDLETIME counts as an access and resets recency

context