EXPIRE(3valkey)

๐Ÿ“ฆ
version 9.0.6 ยท 2 versions
, in fedora 44

๐Ÿ’– EXPIRE ๐Ÿ’–

okay bestie, welcome to the EXPIRE page of the Valkey Command Manual (that's manual section 3valkey, for the record). it's giving "make this key self-destruct in seconds" and honestly? iconic behavior. let's get into it ๐ŸŽ€


โœจ NAME โœจ

expire โ€” sets the expiration time of a key in seconds. that's it. that's the whole job. slay ๐Ÿ’…


๐ŸŒธ SYNOPSIS ๐ŸŒธ

EXPIRE key seconds [NX | XX | GT | LT]

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

so basically, you're setting a timeout on key. once that timeout has expired, the key gets automatically deleted. a key that has a timeout attached to it is what Valkey calls volatile โ€” cute word, terrifying concept ๐Ÿ’€

here's the tea on when that timeout gets cleared: only commands that delete or overwrite the contents of the key will clear it. that includes valkey-del(7) DEL, valkey-set(7) SET, valkey-getset(7) GETSET, and literally all the *STORE commands. so anything that conceptually alters the value sitting at that key without replacing it with a whole new one? timeout stays untouched, bestie. for example, incrementing a key's value with valkey-incr(7) INCR, pushing a new value into a list with valkey-lpush(7) LPUSH, or altering a hash field's value with valkey-hset(7) HSET โ€” all of those leave the timeout exactly as it was. no cap. โœจ

you can also clear the timeout on purpose and turn the key back into a persistent key using the valkey-persist(7) PERSIST command. the more you know ๐Ÿซถ

if a key gets renamed with valkey-rename(7) RENAME, the associated time to live gets transferred to the new key name. it follows the key, like a loyal little shadow ๐Ÿฆ‹

and if a key gets overwritten by RENAME โ€” like, say you've got an existing key Key_A and someone calls RENAME Key_B Key_A โ€” then it does not matter whether the original Key_A had a timeout or not. the new Key_A inherits all the characteristics of Key_B. new key, who dis ๐Ÿ’…

okay important one: calling EXPIRE / valkey-pexpire(7) PEXPIRE with a non-positive timeout, or valkey-expireat(7) EXPIREAT / valkey-pexpireat(7) PEXPIREAT with a time in the past, results in the key being deleted rather than expired. accordingly, the emitted [key event][ntf] will be del, not expired. so likeโ€ฆ it's not expiring, it's evicting. know the difference ๐Ÿ˜ญ

๐Ÿ’… OPTIONS ๐Ÿ’…

the EXPIRE command supports a whole little set of options, and they're all baddies:

  • NX โ€” set expiry only when the key has no expiry
  • XX โ€” set expiry only when the key has an existing expiry
  • GT โ€” set expiry only when the new expiry is greater than the current one
  • LT โ€” set expiry only when the new expiry is less than the current one

for the purposes of GT and LT, a non-volatile key is treated as having an infinite TTL. and heads up: GT, LT and NX are mutually exclusive. pick one, bestie, you can't have all three ๐Ÿ™ˆ

๐Ÿ”„ Refreshing expires

you can absolutely call EXPIRE with a key that already has an existing expire set. in that case, the time to live of the key just gets updated to the new value. there are so many useful applications for this โ€” one example is documented down in the Navigation session pattern section below ๐Ÿ‘‘

EXPIRE would return 0 and not alter the timeout for a key with a timeout set.


๐ŸŒˆ REPLY ๐ŸŒˆ

one of the following, bestie:

  • valkey-protocol(7) Integer reply: 0 if the timeout was not set โ€” for example, the key doesn't exist, or the operation was skipped because of the provided arguments.
  • valkey-protocol(7) Integer reply: 1 if the timeout was set. slay โœจ

โšก COMPLEXITY โšก

O(1) โ€” constant time, baby. instant. effortless. main character energy ๐Ÿ‘‘


๐Ÿ” ACL CATEGORIES ๐Ÿ”

@fast @keyspace @write


๐Ÿ“œ HISTORY ๐Ÿ“œ

  • Available since: 1.0.0
  • Changed in 7.0.0: Added options: NX, XX, GT and LT. glow-up fr fr ๐Ÿ’–

๐Ÿ“ EXAMPLES ๐Ÿ“

127.0.0.1:6379> SET mykey "Hello"
OK
127.0.0.1:6379> EXPIRE mykey 10
(integer) 1
127.0.0.1:6379> TTL mykey
(integer) 10
127.0.0.1:6379> SET mykey "Hello World"
OK
127.0.0.1:6379> TTL mykey
(integer) -1
127.0.0.1:6379> EXPIRE mykey 10 XX
(integer) 0
127.0.0.1:6379> TTL mykey
(integer) -1
127.0.0.1:6379> EXPIRE mykey 10 NX
(integer) 1
127.0.0.1:6379> TTL mykey
(integer) 10

see how XX said "no โค๏ธ" and NX said "yes ๐Ÿ’…"? vibe check passed.

๐Ÿงญ Pattern: Navigation session

imagine you've got a web service and you're obsessed (valid) with knowing the latest N pages your users recently visited โ€” such that each adjacent page view was not performed more than 60 seconds after the previous one. conceptually you can think of this set of page views as a Navigation session of your user, and it might contain genuinely interesting info about what kind of products they're looking for right now, so you can recommend related products. targeted marketing but make it โœจ thoughtful โœจ

you can easily model this pattern in Valkey with the following strategy: every time the user does a page view, you call these commands:

MULTI
RPUSH pagewviews.user:<userid> http://.....
EXPIRE pagewviews.user:<userid> 60
EXEC

if the user will be idle for more than 60 seconds, the key gets deleted, and only subsequent page views that have less than 60 seconds of difference will be recorded. bye bestie, session over ๐Ÿ˜ญ

this pattern is easily modified to use counters using INCR instead of lists using valkey-rpush(7) RPUSH. same energy, different data structure ๐ŸŽ€


๐Ÿ‘‘ APPENDIX: VALKEY EXPIRES ๐Ÿ‘‘

๐Ÿ”‘ Keys with an expire

normally, Valkey keys are created without an associated time to live. the key will simply live forever, unless it's removed by the user in an explicit way โ€” for instance, using the DEL command. immortal until proven otherwise ๐Ÿ’€

the EXPIRE family of commands is able to associate an expire to a given key, at the cost of some additional memory used by the key. when a key has an expire set, Valkey will make sure to remove the key when the specified amount of time has elapsed. punctual. professional. iconic.

the key's time to live can be updated or entirely removed using the EXPIRE and valkey-persist(7) PERSIST command (or other strictly related commands).

๐ŸŽฏ Expire accuracy

the expire error is from 0 to 1 milliseconds. like, that precise. obsessed ๐Ÿ˜Œ

๐Ÿ’พ Expires and persistence

keys' expiring information is stored in milliseconds. this means the time is flowing even when the Valkey instance is not active. it never rests, bestie โณ

for expires to work well, the computer time must be taken stable. if you move an RDB file from two computers with a big desync in their clocks, funny things may happen (like all the keys loaded to be expired at loading time). "funny" in the "not funny at all" way ๐Ÿ˜ญ

even running instances will always check the computer clock. so for instance, if you set a key with a time to live of 1000 seconds, and then set your computer time 2000 seconds in the future, the key will be expired immediately, instead of lasting for 1000 seconds. time travel is a crime in Valkey fr fr ๐Ÿ•ฐ๏ธ

๐Ÿงน How Valkey reclaims expired keys

Valkey reclaims expired keys in two ways: on access, and in the background in what is called the "active expire key" cycles. on access expiration is when a client tries to access a key with the expiration time which is found to be timed out. such a key is deleted on this access attempt.

relying solely on the on access expiration is not enough, because there are expired keys that may never be accessed again. to address this, Valkey uses the background expiration algorithm known as the "active expire key" effort. this algorithm is adaptive: it tries to use less CPU if there are few expired keys to reclaim. otherwise, it gets more aggressive, trying to free more memory by reclaiming more keys in shorter runs but using more CPU. it reads the room, basically ๐ŸŒธ

here's how it works:

Valkey slowly scans the keyspace to identify and reclaim expired keys. this "slow cycle" is the main way to collect expired keys and operates with the server's hertz frequency (usually 10 hertz). during the slow cycle, Valkey tolerates having not more than 10% of the expired keys in the memory and tries to use a maximum of 25% CPU power. these default values are adjusted if the user changes the active expire key effort configuration.

if the number of expired keys remains high after the slow cycle, the active expire key effort transitions into the "fast cycle", trying to do less work but more often. the fast cycle runs no longer than 1000 microseconds and repeats at the same interval. during the fast cycle, the check of every database is interrupted once the number of already expired keys in the database is estimated to be lower than 10%. this is done to avoid doing too much work to gain too little memory. boundaries, bestie ๐Ÿ’…

you can modify the active expire key effort with the active-expire-effort parameter in the configuration file, up to the maximum value of 10. the default active-expire-effort value is 1, and it is described by the following base values:

  • ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP has a base value of 20. the number of keys for each DB loop.
  • ACTIVE_EXPIRE_CYCLE_FAST_DURATION has a base value of 1000. the maximum duration of the fast cycle in microseconds.
  • ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC has a base value of 25. the maximum % of CPU to use during the slow cycle.
  • ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE has a base value of 10. the maximum % of expired keys to tolerate in memory.

changing the active-expire-effort value results in a lower percentage of expired keys tolerated in memory. however, it will lead to longer cycles and increased CPU usage, which may introduce latency. so likeโ€ฆ more effort, more cost. math is math ๐Ÿงฎ

to calculate the new values, use the following formulas:

  • ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP + (ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP / 4 * (effort - 1))
  • ACTIVE_EXPIRE_CYCLE_FAST_DURATION + (ACTIVE_EXPIRE_CYCLE_FAST_DURATION / 4 * (effort - 1))
  • ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC + (2 * (effort - 1))
  • ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE - (effort - 1)

where ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP, ACTIVE_EXPIRE_CYCLE_FAST_DURATION, ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC, and ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE are the base values, and effort is the specified active-expire-effort. write that down ๐Ÿ“

in order to obtain a correct behavior without sacrificing consistency, when a key expires, a DEL operation is synthesized in both the AOF file and gains all the attached replicas nodes. this way the expiration process is centralized in the primary instance, and there is no chance of consistency errors. teamwork makes the dream work ๐Ÿซถ

however, while the replicas connected to a primary will not expire keys independently (but will wait for the DEL coming from the primary), they'll still take the full state of the expires existing in the dataset. so when a replica is elected to primary, it will be able to expire the keys independently, fully acting as a primary. promotion arc, honestly ๐Ÿ‘‘


๐Ÿ”Ž SEE ALSO ๐Ÿ”Ž

copy(3valkey), del(3valkey), dump(3valkey), exists(3valkey), expireat(3valkey), expiretime(3valkey), keys(3valkey), migrate(3valkey), move(3valkey), object(3valkey), object-encoding(3valkey), object-freq(3valkey), object-help(3valkey), object-idletime(3valkey), object-refcount(3valkey), persist(3valkey), pexpire(3valkey), pexpireat(3valkey), pexpiretime(3valkey), pttl(3valkey), randomkey(3valkey), rename(3valkey), renamenx(3valkey), restore(3valkey), scan(3valkey), sort(3valkey), sort_ro(3valkey), touch(3valkey), ttl(3valkey), type(3valkey), unlink(3valkey), wait(3valkey), waitaof(3valkey)


okay that's the whole page, bestie!! go forth and set some timeouts ๐Ÿ’–

this masterpiece is from Valkey 9.0.0, dated 2025-10-21, and it's the EXPIRE(3valkey) manual page. love that for us. periodt. ๐ŸŽ€โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from