# Keys and expiration

> Name keys, find and inspect them, use logical databases, and give keys a time to live down to the nanosecond.

URL: https://docs.hotkv.com/develop/keys-and-expiration/


Every value in HotKV lives under a key. This page covers the commands that
work on keys whatever type of value they hold: finding them, renaming and
deleting them, choosing a database, and making them expire.

## Naming keys

A key is a binary-safe string: any sequence of bytes, from an empty string up
to `proto-max-bulk-len` (512 MB by default). In practice, short readable keys
with a consistent structure are easiest to work with. A common convention is
to join the parts with colons, from the most general to the most specific:

```
user:42
user:42:sessions
order:2026-10:1187
```

Keep keys short when you have many millions of them, since every key takes
memory, but prefer clarity over saving a few bytes. In a cluster, the part of
a key between the first `{` and the following `}` decides its hash slot, so
`user:{42}:profile` and `user:{42}:cart` always land on the same node. See
[Clustering](https://docs.hotkv.com/clustering/index.md#slots-and-redirects).

## Finding keys

[SCAN](https://docs.hotkv.com/commands/scan/index.md) walks the keyspace a page at a time with a cursor.
Start with cursor `0` and call it again with the cursor each reply gives you
until it returns `0`. `MATCH` filters by a glob pattern, `COUNT` hints how
much work to do per call, and `TYPE` keeps only keys of one type.

```
127.0.0.1:6379> SCAN 0 MATCH user:* COUNT 100
1) "0"
2) 1) "user:42"
   2) "user:7"
127.0.0.1:6379> SCAN 0 TYPE hash
1) "0"
2) 1) "profile:42"
```

A full iteration returns every key that existed for the whole scan exactly
once. Keys added or removed during the scan may or may not appear. Code
written for Redis, where a key can come back more than once, works the same.

[KEYS](https://docs.hotkv.com/commands/keys/index.md) returns every matching key in one reply. It is fine on
a small development database, but on a large one it builds a very large reply
and holds up other work while it runs, so use `SCAN` in production code.
[RANDOMKEY](https://docs.hotkv.com/commands/randomkey/index.md) returns one key at random and
[DBSIZE](https://docs.hotkv.com/commands/dbsize/index.md) counts the keys in the current database.

## Inspecting and changing keys

| Command | What it does |
| --- | --- |
| [EXISTS](https://docs.hotkv.com/commands/exists/index.md) | How many of the given keys exist |
| [TYPE](https://docs.hotkv.com/commands/type/index.md) | The value's type: `string`, `list`, `set`, `zset`, `hash`, `stream`, or `none` |
| [RENAME](https://docs.hotkv.com/commands/rename/index.md) | Give a key a new name, replacing any key already there |
| [RENAMENX](https://docs.hotkv.com/commands/renamenx/index.md) | Rename only if the new name is free |
| [COPY](https://docs.hotkv.com/commands/copy/index.md) | Copy a value to another key, optionally in another database |
| [TOUCH](https://docs.hotkv.com/commands/touch/index.md) | Count a key as accessed (for idle time and LRU/LFU eviction) |
| [DEL](https://docs.hotkv.com/commands/del/index.md) | Delete keys and return how many existed |
| [UNLINK](https://docs.hotkv.com/commands/unlink/index.md) | Same reply as `DEL` |

`TYPE` reports Bloom filters, Count-Min sketches and Top-K structures with the
same names Redis Stack uses (`MBbloom--`, `CMSk-TYPE`, `TopK-TYPE`), so tools
that check those names keep working.

```
127.0.0.1:6379> SET greeting "hello"
OK
127.0.0.1:6379> TYPE greeting
string
127.0.0.1:6379> RENAME greeting welcome
OK
127.0.0.1:6379> RENAME missing other
(error) ERR no such key
127.0.0.1:6379> EXISTS welcome greeting
(integer) 1
127.0.0.1:6379> DEL welcome
(integer) 1
```

> **Note**
> `UNLINK` exists so that code written for Redis runs unchanged. In HotKV it
> behaves the same as `DEL` from a client's point of view: both remove the keys
> before they reply and return the number removed.

### OBJECT

[OBJECT](https://docs.hotkv.com/commands/object/index.md) reports how a value is stored and how it has been
used:

- [OBJECT ENCODING](https://docs.hotkv.com/commands/object-encoding/index.md) returns the encoding name Redis
  would report (`int`, `embstr`, `raw`, `listpack`, `quicklist`, `intset`,
  `hashtable`, `skiplist`, `stream`, ...), so tools that look at encodings
  keep working.
- [OBJECT IDLETIME](https://docs.hotkv.com/commands/object-idletime/index.md) is the number of seconds since
  the key was last accessed. It is available unless `maxmemory-policy` is an
  LFU policy.
- [OBJECT FREQ](https://docs.hotkv.com/commands/object-freq/index.md) is the key's access frequency counter.
  It is available only with an LFU `maxmemory-policy`.
- [OBJECT REFCOUNT](https://docs.hotkv.com/commands/object-refcount/index.md) always returns `1`.

Each subcommand replies nil for a key that does not exist.

```
127.0.0.1:6379> SET counter 42
OK
127.0.0.1:6379> OBJECT ENCODING counter
"int"
127.0.0.1:6379> OBJECT FREQ counter
(error) ERR An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.
```

## Databases

A standalone HotKV server has 16 logical databases by default, numbered from
0 (set the count with `databases`). Each connection starts in database 0;
[SELECT](https://docs.hotkv.com/commands/select/index.md) switches it. Keys in different databases are
independent, so `user:42` in database 0 and `user:42` in database 1 are two
different keys.

```
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> SET user:42 "in db 1"
OK
127.0.0.1:6379[1]> SELECT 0
OK
127.0.0.1:6379> GET user:42
(nil)
```

[MOVE](https://docs.hotkv.com/commands/move/index.md) moves a key to another database,
[SWAPDB](https://docs.hotkv.com/commands/swapdb/index.md) swaps two databases at once, and
[FLUSHDB](https://docs.hotkv.com/commands/flushdb/index.md) empties the current one.

> **Caution**
> A cluster node has one database by default, as in Redis Cluster: `SELECT` with
> any other index replies `ERR SELECT is not allowed in cluster mode`, and
> `MOVE` and `COPY ... DB` are refused. The setting `cluster-databases` (1 to
> 128, set when the node starts) gives a cluster node more databases; `SWAPDB`
> is refused on a cluster node either way. See
> [Databases in a cluster](https://docs.hotkv.com/clustering/keys-and-commands/index.md#databases). Use key
> prefixes instead of databases if you may move to a cluster later.

## Expiration

Any key can have a time to live (TTL). When it runs out, the key is deleted
as if you had called `DEL`. Expiry is set on the key as a whole; for hashes,
you can also expire individual fields (see the `HEXPIRE` family on the
[Hashes](https://docs.hotkv.com/data-types/hashes/index.md) page).

### Setting a TTL

| Command | Expire after or at |
| --- | --- |
| [EXPIRE](https://docs.hotkv.com/commands/expire/index.md) | A number of seconds from now |
| [PEXPIRE](https://docs.hotkv.com/commands/pexpire/index.md) | A number of milliseconds from now |
| [EXPIREAT](https://docs.hotkv.com/commands/expireat/index.md) | A Unix time in seconds |
| [PEXPIREAT](https://docs.hotkv.com/commands/pexpireat/index.md) | A Unix time in milliseconds |
| [NEXPIRE](https://docs.hotkv.com/commands/nexpire/index.md) | A number of nanoseconds from now (HotKV only) |
| [NEXPIREAT](https://docs.hotkv.com/commands/nexpireat/index.md) | A Unix time in nanoseconds (HotKV only) |

You can also set a TTL while writing: [SET](https://docs.hotkv.com/commands/set/index.md) takes `EX`, `PX`,
`EXAT` and `PXAT`, and [GETEX](https://docs.hotkv.com/commands/getex/index.md) changes the TTL of a key as
it reads it.

`EXPIRE`, `PEXPIRE`, `EXPIREAT` and `PEXPIREAT` accept one condition:

| Option | Set the TTL only if |
| --- | --- |
| `NX` | The key has no TTL yet |
| `XX` | The key already has a TTL |
| `GT` | The new expiry is later than the current one (a key without a TTL counts as never expiring) |
| `LT` | The new expiry is earlier than the current one |

They reply `1` when the TTL was set and `0` when the key does not exist or the
condition was not met. A time that has already passed deletes the key at once.

```
127.0.0.1:6379> SET session:42 "token"
OK
127.0.0.1:6379> EXPIRE session:42 3600
(integer) 1
127.0.0.1:6379> EXPIRE session:42 60 GT
(integer) 0
127.0.0.1:6379> TTL session:42
(integer) 3600
```

### Reading and removing a TTL

| Command | Returns |
| --- | --- |
| [TTL](https://docs.hotkv.com/commands/ttl/index.md) | Seconds left |
| [PTTL](https://docs.hotkv.com/commands/pttl/index.md) | Milliseconds left |
| [NTTL](https://docs.hotkv.com/commands/nttl/index.md) | Nanoseconds left (HotKV only) |
| [EXPIRETIME](https://docs.hotkv.com/commands/expiretime/index.md) | The expiry as a Unix time in seconds |
| [PEXPIRETIME](https://docs.hotkv.com/commands/pexpiretime/index.md) | The expiry as a Unix time in milliseconds |
| [PERSIST](https://docs.hotkv.com/commands/persist/index.md) | Removes the TTL; `1` if there was one |

All of the reading commands return `-2` when the key does not exist and `-1`
when it exists without a TTL. Commands that replace a value, such as `SET`
without `KEEPTTL`, clear its TTL. Commands that change a value in place, such
as `INCR`, `LPUSH` or `HSET`, keep it. `RENAME` carries the TTL over to the
new name.

### Nanosecond precision

`NEXPIRE`, `NEXPIREAT` and `NTTL` are HotKV commands that work in nanoseconds,
for short-lived keys where milliseconds are too coarse, such as
sub-millisecond leases or test fixtures. `NEXPIRE` takes no `NX`, `XX`, `GT`
or `LT` option.

```
127.0.0.1:6379> SET lease:job:9 "worker-3"
OK
127.0.0.1:6379> NEXPIRE lease:job:9 1500000000
(integer) 1
127.0.0.1:6379> NTTL lease:job:9
(integer) 1499986211
127.0.0.1:6379> PTTL lease:job:9
(integer) 1499
```

The exact `NTTL` value depends on how much time passed between the two
commands. Other clients and the regular `TTL`/`PTTL` commands see the same
expiry, rounded to their unit.

### When expired keys disappear

From a client's point of view a key is gone as soon as its TTL runs out: every
command treats it as missing, and `TTL` returns `-2`. Behind the scenes the
key is removed either when a command next touches it or by a background
sweep that runs a number of times per second (`hz`, 10 by default;
`active-expire-effort` from 1 to 10 makes the sweep work harder). Memory used
by keys that expire but are never read again is reclaimed by that sweep, so
it may lag the TTL slightly. If you listen for `expired`
[keyspace notifications](https://docs.hotkv.com/develop/keyspace-notifications/index.md), they are sent
when the key is actually removed, which can be a little after the TTL ran out.

## Compatibility

Key commands, their options, replies and errors follow Redis 7. HotKV adds
`NEXPIRE`, `NEXPIREAT` and `NTTL`. `OBJECT REFCOUNT` always returns `1`, and
`UNLINK` behaves like `DEL`.