DiffHSet is a Redis module providing a wrapper command around HSET
Redis command.
This wrapper publishes a rich notification on each successful invocation with the update data,
including the value before the update, the key being updated and the update itself.
DiffHSet requires CMake and a C++ compiler with C++20 support to be built.
The only external dependency is nlohmann/json.
The build sequence is standard for a CMake project, being the following:
mkdir cmake-build-release && cd cmake-build-release
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_LIBDIR=/var/lib/redis ..
cmake --build . -t install
Or, if you don't want the installation:
mkdir cmake-build-release && cd cmake-build-release
cmake -GNinja -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
This will build a self-contained shared library file libdhset.so
,
which can be loaded into Redis through the config:
loadmodule /var/lib/redis/libdhset.so
Or dynamically, via command:
MODULE LOAD /var/lib/redis/libdhset.so
By default, this module will publish notifications to __modevent@{db}__:dhset
channel.
Messages are serialized with JSON by default and have the following structure:
{
"k": "hash key",
"o": "'old' state as an object",
"u": "operation arguments as an object"
}
For example, if we have a key state
with the following content:
Field | Value |
---|---|
id |
101 |
content |
Hello, World! |
receiver |
broadcast |
And we perform this operation:
DHSET state content "Hello, Redis!" receiver Redis
The following notification will be published on channel __modevent@0__:dhset
:
{
"k": "state",
"o": {
"content": {
"bytes": [
72,
101,
108,
108,
111,
44,
32,
87,
111,
114,
108,
100,
33
],
"subtype": null
},
"id": {
"bytes": [
49,
48,
49
],
"subtype": null
},
"receiver": {
"bytes": [
98,
114,
111,
97,
100,
99,
97,
115,
116
],
"subtype": null
}
},
"u": {
"content": {
"bytes": [
72,
101,
108,
108,
111,
44,
32,
82,
101,
100,
105,
115,
33
],
"subtype": null
},
"receiver": {
"bytes": [
82,
101,
100,
105,
115
],
"subtype": null
}
}
}
Notice how the values are encoded. This is a special 'binary' type that used to serialize binary data safely. However, it is pretty bloated in JSON and will not be natively converted back to byte array, so this should be used only for debugging purposes. Consider using one of supported binary formats to use in production.
After processing the notification, execution will be redirected to a 'delegate' command, and its reply will be redirected back to caller.
This module provides only one startup option - the command that will be replaced with DHSET
command.
E.g. if the following configuration is used to load DHSet:
loadmodule /var/lib/redis/libdhset.so hmset
It will intercept every Redis call and replace command name with DHSET
if
original command name matches hmset
(case-insensitive).
If the option is not provided, command hooking is disabled.
NOTE: Command hooking blindly replaces any command that will be given as an argument, without any checks. Be aware to not accidentally replace e.g. plain
SET
command with it, it will just break it.
NOTE: When using command hooking, ensure that the correct delegate command is configured. Hooking
HMSET
and usingHSET
delegate will basically replaceHMSET
withHSET
, which may break some clients since these commands have different reply formats (and may as well behave differently on older Redis versions).
These options can be configured in redis.conf
or by using CONFIG SET
command.
Default value: hset
This option allows to specify which delegate command will be used by DHSET
.
DHSET
is a wrapper command, it does not modify the database by itself (apart from sending notifications).
Instead, it passes the input to an actual command, being either HSET
or HMSET
.
This setting can be very useful in combination with command hooking,
as HSET
and HMSET
have different replies and may as well behave slightly differently.
Default value: json
This option sets the serializer for notification payload.
There are 3 serializers available: json
, cbor
and msgpack
.
Default value: channel
This option is a flagset with two possible flags:
- In
channel
mode it will publish notifications to__modevent@{db}__:dhset
regardless of the key; - In
key
mode it will use a dedicated channel for each key like this:__modevent@{db}__:dhset:{key}
Flags can be combined by specifying a whitespace-separated list:
CONFIG SET dhset.notification-mode "key channel"
Default value: ``
A regular expression to select keys that should publish notifications.
This can be useful if command hooking is enabled
to only emit notifications when needed.
E.g. let the pattern be model.*
. Executing the following statements:
DHSET model:user id 12 name wunder
DHSET session:12 expires 2023-12-07T12:33Z
Will only give one notification for model:user
, but not for session:12
.
If pattern is set to an empty string, filtering is disabled.
NOTICE: The pattern is matched against the entire key, not just a part of it that might match, i.e. key
model:user
wouldn't match against^model
or:user$
patterns.
Default value: no
You might want to enable this option if you're planning to do a lot
of DHSET
's with key matching enabled (dhset.key-pattern
is set),
especially in cases where potential number of distinct keys is not very big.
Instead of matching the same key against the same pattern every DHSET
with that key,
it stores the matching result in a cache. This cache is instance-local in a sense that
it does not rely on Redis for caching. In fact, this is just a hashset of keys in the module's memory space.
Enabling caching has no effect if key matching is disabled.
Default value: 1000
Since the key cache is just an in-memory object we obviously need to limit its size. And there it is, a config option just for that.
Default value: flush
Cache size limit enforcement strategy. If the cache have reached the size limit when a new key arrives, one of the following actions might be performed:
flush
- the cache gets emptied completelyhalve
- half of cache entries get removedone-out
- exactly one cache entry gets removed
Due to the nature of hash tables, the order in which cache entries are removed is not guaranteed.