# Keyspace notifications

> Receive Pub/Sub events when keys are written, deleted, expired or evicted, configured with notify-keyspace-events.

URL: https://docs.hotkv.com/develop/keyspace-notifications/


Keyspace notifications publish a [Pub/Sub](https://docs.hotkv.com/develop/pubsub/index.md) message whenever
a key changes: a write, a delete, an expiry, an eviction. Clients subscribe to
them like any other channel. Typical uses are invalidating a local cache,
reacting to session expiry, or auditing changes to a set of keys during
development.

Notifications are off by default, because publishing them costs CPU for every
write.

## Turning them on

Set `notify-keyspace-events` to a string of characters: one or both of `K`
and `E` to say where events go, plus the classes of events you want.

```
127.0.0.1:6379> CONFIG SET notify-keyspace-events KEA
OK
```

or in the configuration file:

```
notify-keyspace-events KEA
```

or in `hotkv.toml`:

```toml
[notifications]
keyspace_events = "KEA"
```

An empty string turns notifications off again. Without `K` or `E`, nothing is
published whatever classes you list.

| Character | Meaning |
| --- | --- |
| `K` | Publish on the keyspace channel, `__keyspace@<db>__:<key>`, with the event name as the message |
| `E` | Publish on the keyevent channel, `__keyevent@<db>__:<event>`, with the key name as the message |
| `g` | Generic events: `del`, `expire`, `rename_from`, `rename_to`, `persist`, `copy_to`, `move_from`, `move_to`, `restore` |
| `$` | String events |
| `l` | List events |
| `s` | Set events |
| `h` | Hash events |
| `z` | Sorted set events |
| `t` | Stream events |
| `x` | `expired`: a key's time to live ran out |
| `e` | `evicted`: a key was evicted under `maxmemory` |
| `m` | `keymiss`: a command looked up a key that does not exist |
| `n` | `new`: a key was created |
| `d` | Module key types (accepted; HotKV publishes no events in this class) |
| `A` | Shorthand for `g$lshztxed` (every class except `m` and `n`) |

Any other character makes `CONFIG SET` fail with an error ending in
`Invalid event class character. Use 'Ag$lshzxeKEtmdn'.`

## Listening

With `KEA` set, subscribe to the events you care about. A keyspace channel
follows one key; a keyevent channel follows one kind of event across all
keys. Use `PSUBSCRIBE` to cover many keys or events at once.

```
127.0.0.1:6379> PSUBSCRIBE __keyspace@0__:session:*
1) "psubscribe"
2) "__keyspace@0__:session:*"
3) (integer) 1
```

Another client runs `SET session:42 token EX 60`, then `DEL session:42`:

```
1) "pmessage"
2) "__keyspace@0__:session:*"
3) "__keyspace@0__:session:42"
4) "set"
1) "pmessage"
2) "__keyspace@0__:session:*"
3) "__keyspace@0__:session:42"
4) "expire"
1) "pmessage"
2) "__keyspace@0__:session:*"
3) "__keyspace@0__:session:42"
4) "del"
```

To hear about every expiry instead:

```
127.0.0.1:6379> SUBSCRIBE __keyevent@0__:expired
```

## Events by command

The event name is usually the command's name in lower case. The ones that
differ, or that a command emits in addition, are listed here.

| Class | Events |
| --- | --- |
| Generic (`g`) | `del` for `DEL`, `UNLINK`, and any command that removes a key's last element or field; `expire` when a TTL is set (`del` instead when the time has already passed); `rename_from` and `rename_to` for `RENAME`/`RENAMENX`; `persist`; `copy_to`; `move_from` and `move_to`; `restore` |
| String (`$`) | `set` (for `SET`, `SETEX`, `MSET`, `GETSET` and the like), `setrange`, `append`, `incrby` (for `INCR`, `DECR`, `INCRBY` and `DECRBY`), `incrbyfloat`, `setbit`, `pfadd` |
| List (`l`) | `lpush`, `rpush`, `lpop`, `rpop`, `linsert`, `lset`, `lrem`, `ltrim`, `sortstore`. `LMOVE` and `BLMOVE` emit a pop on the source and a push on the destination |
| Set (`s`) | `sadd`, `srem`, `spop`, `sinterstore`, `sunionstore`, `sdiffstore` |
| Hash (`h`) | `hset`, `hdel`, `hincrby`, `hincrbyfloat`, `hexpire`, `hpersist`, and `hexpired` when a field's TTL runs out |
| Sorted set (`z`) | `zadd` (also for `GEOADD`), `zincr`, `zrem`, `zremrangebyscore`, `zremrangebyrank`, `zremrangebylex`, `zpopmin`, `zpopmax`, `zinterstore`, `zunionstore`, `zdiffstore`, `zrangestore` |
| Stream (`t`) | `xadd`, `xdel`, `xtrim`, `xsetid`, `xgroup-create`, `xgroup-setid`, `xgroup-destroy`, `xgroup-createconsumer`, `xgroup-delconsumer` |
| Expired (`x`) | `expired` |
| Evicted (`e`) | `evicted` |
| Key miss (`m`) | `keymiss` |
| New key (`n`) | `new`, before the event of the command that created the key |

A command that changes nothing publishes nothing. For example, `SADD` of a
member that is already there, or `DEL` of a missing key, emits no event. The
events of a transaction or a script are published once it has finished, in
the order its commands caused them.

> **Note**
> The HotKV nanosecond commands [NEXPIRE](https://docs.hotkv.com/commands/nexpire/index.md) and
> [NEXPIREAT](https://docs.hotkv.com/commands/nexpireat/index.md) do not publish an `expire` event. The key's
> `expired` event, when it runs out, is published as usual.

## Delivery and timing

- Notifications are ordinary Pub/Sub messages: a client that is disconnected
  when an event happens never sees it. Do not use them as the only record of
  a change.
- `expired` is published when the key is actually removed, either when a
  command touches it after its TTL ran out or when the background expiry
  sweep finds it. It can come slightly after the TTL ended, and a key nobody
  touches is still removed by the sweep. See
  [Keys and expiration](https://docs.hotkv.com/develop/keys-and-expiration/index.md#when-expired-keys-disappear).
- In a cluster, each node publishes the events for the keys it serves to its
  own subscribers only. Subscribe on every primary to see the whole keyspace.

## Compatibility

The configuration string, channel names, and event names follow Redis 7. As
in Redis, `A` does not include `m` or `n`. HotKV adds no event classes of its
own, and its `NEXPIRE` and `NEXPIREAT` commands publish no `expire` event.