Skip to content

Commit b140ab9

Browse files
author
Dmitry Polyakovsky
committed
valkey module rust SDK updates
Signed-off-by: dmitrypol <[email protected]> Signed-off-by: Dmitry Polyakovsky <[email protected]>
1 parent 392a849 commit b140ab9

File tree

2 files changed

+272
-0
lines changed

2 files changed

+272
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ _data/groups.json
1313
_data/resp2_replies.json
1414
_data/resp3_replies.json
1515
_data/modules.json
16+
.vscode/*
17+
.idea/*
18+
tmp/*
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
+++
2+
title= "Valkey Modules Rust SDK updates"
3+
date= 2025-05-06 01:01:01
4+
description= "Extending Valkey using Rust SDK."
5+
authors= ["dmitrypol"]
6+
categories= "modules"
7+
+++
8+
9+
In an earlier [article](/blog/modules-101/) we discussed the process of building Valkey Modules.
10+
Modules allow adding extra features (such as new commands and data types) to Valkey without making changes to the core code.
11+
We briefly touched on how to use the [Valkey Modules Rust SDK](https://github.com/valkey-io/valkeymodule-rs) to build a simple module.
12+
In this article we will expand on this topic and discuss some of the new features added to the SDK over the last year.
13+
14+
## What is the Valkey Modules Rust SDK?
15+
16+
The SDK is based on [Redis Modules Rust SDK](https://github.com/RedisLabsModules/redismodule-rs) and provides abstraction APIs on top of Valkey Modules own API.
17+
For those familiar with Rust development the SDK is a Rust crate that can be added to `Cargo.toml` file like any other Rust dependency.
18+
It requires the underlying Valkey version to have appropriate module APIs but allows writing Valkey modules in Rust, without needing to use raw pointers or unsafe code.
19+
The recently released [Bloom Filters module](/blog/introducing-bloom-filters/) is built using this crate and several of the developers who worked on the module contributed to the SDK.
20+
Let's deep dive into to new features.
21+
22+
## Client
23+
24+
`Context` struct now has several new functions to get information about the client connected to Valkey.
25+
It provides Rust wrappers around `Module_GetClient*` functions in the underlying Valkey Module API.
26+
Most new functions return `ValkeyResult` so the module developer can handle the error appropriately.
27+
The functions can be called for the current client or by specifying `client_id`.
28+
29+
```rust
30+
fn my_client_cmd(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
31+
let client_id = ctx.get_client_id();
32+
let username = ctx.get_client_username_by_id(client_id);
33+
Ok(ValkeyValue::from(username.to_string()))
34+
}
35+
valkey_module! {
36+
...
37+
commands: [
38+
["my_client_cmd", my_client_cmd, "", 0, 0, 0],
39+
]
40+
}
41+
```
42+
43+
## Auth callback
44+
45+
Valkey 7.2 introduced support for callbacks after authentication.
46+
Now it can be leveraged in modules.
47+
48+
```rust
49+
fn auth_callback(
50+
ctx: &Context,
51+
username: ValkeyString,
52+
_password: ValkeyString,
53+
) -> Result<c_int, ValkeyError> {
54+
if ctx.authenticate_client_with_acl_user(&username) == Status::Ok {
55+
// can be combined with ctx.get_client_ip to control access by IP address
56+
let _client_ip = ctx.get_client_ip()?;
57+
...
58+
return Ok(AUTH_HANDLED);
59+
}
60+
Ok(AUTH_NOT_HANDLED)
61+
}
62+
valkey_module! {
63+
...
64+
auth: [auth_callback],
65+
}
66+
```
67+
68+
## Preload validation
69+
70+
While the `valkey_module!` macro already provided an init callback to execute custom code during module load, init executed at the very end of the module load after new commands and data types are created.
71+
That can be useful but what if we wanted to stop module load before any of that happens?
72+
For example, we might need to restrict a module to be loaded only on specific version of Valkey.
73+
That's where the optional `preload` is useful.
74+
75+
```rust
76+
fn preload_fn(ctx: &Context, _args: &[ValkeyString]) -> Status {
77+
let _version = ctx.get_server_version().unwrap();
78+
// respond with Status::Ok or Status::Err to prevent loading
79+
Status::Ok
80+
}
81+
valkey_module! {
82+
...
83+
preload: preload_fn,
84+
commands: [],
85+
}
86+
```
87+
88+
## Filters
89+
90+
To execute custom code before specific Valkey commands we can use command filters. This can be leveraged to replace default commands with custom comands or modify arguments.
91+
Thanks to the abstractions provided by the SDK we simply need to create a Rust function and register it in the `valkey_module!` macro.
92+
Note of caution - since filters are executed before every command this code needs to be optimized.
93+
94+
```rust
95+
fn my_filter_fn(_ctx: *mut RedisModuleCommandFilterCtx) {
96+
let cf_ctx = CommandFilterCtx::new(ctx);
97+
// check the number of arguments
98+
if cf_ctx.args_count() != 3 {
99+
return;
100+
}
101+
// get which command is being executed
102+
let _cmd = cf_ctx.cmd_get_try_as_str().unwrap();
103+
// grab various args passed to the command
104+
let _all_args = cf_ctx.get_all_args_wo_cmd();
105+
// replace command
106+
cf_ctx.arg_replace(0, "custom_cmd");
107+
}
108+
valkey_module! {
109+
...
110+
filters: [
111+
[my_filter_fn, VALKEYMODULE_CMDFILTER_NOSELF],
112+
]
113+
}
114+
```
115+
116+
## New event handlers
117+
118+
The SDK now supports more server events. We can use this to execute our own code on client connect/disconnect, server shutdown or specific key events.
119+
120+
```rust
121+
#[client_changed_event_handler]
122+
fn client_changed_event_handler(_ctx: &Context, client_event: ClientChangeSubevent) {
123+
match client_event {
124+
ClientChangeSubevent::Connected => {}
125+
ClientChangeSubevent::Disconnected => {}
126+
}
127+
}
128+
#[shutdown_event_handler]
129+
fn shutdown_event_handler(_ctx: &Context, _event: u64) {
130+
...
131+
}
132+
#[key_event_handler]
133+
fn key_event_handler(ctx: &Context, key_event: KeyChangeSubevent) {
134+
match key_event {
135+
KeyChangeSubevent::Deleted => {}
136+
KeyChangeSubevent::Evicted => {}
137+
KeyChangeSubevent::Overwritten => {}
138+
KeyChangeSubevent::Expired => {}
139+
}
140+
}
141+
```
142+
143+
## Custom ACL categories support
144+
145+
Valkey 8 introduced support for custom ACL categories. To implement that we need to enable `required-features = ["min-valkey-compatibility-version-8-0"]` in `Cargo.toml` and register new categories in `valkey_module!` macro. Then we can restrict our custom commands to these ACL categories.
146+
147+
```rust
148+
valkey_module! {
149+
...
150+
acl_categories: [
151+
"acl_one",
152+
"acl_two"
153+
]
154+
commands: [
155+
["cmd1", cmd1, "write", 0, 0, 0, "acl_one"],
156+
["cmd2", cmd2, "", 0, 0, 0, "acl_one acl_two"],
157+
]
158+
```
159+
160+
## Validating / Rejecting CONFIG SET
161+
162+
The SDK now supports specifying optional callback functions to validate or reject custom configuations. This is available for `String`, `i64`, `bool` and `enum` configs. Here is an example of validation for i64 custom config.
163+
164+
```rust
165+
lazy_static! {
166+
static ref CONFIG_I64: ValkeyGILGuard<i64> = ValkeyGILGuard::default();
167+
}
168+
fn on_i64_config_set<G, T: ConfigurationValue<i64>>(
169+
config_ctx: &ConfigurationContext,
170+
_name: &str,
171+
val: &'static T,
172+
) -> Result<(), ValkeyError> {
173+
if val.get(config_ctx) == custom_logic_here {
174+
log_notice("log message here");
175+
Err(ValkeyError::Str("error message here "))
176+
} else {
177+
Ok(())
178+
}
179+
}
180+
valkey_module! {
181+
configurations: [
182+
i64: [
183+
["my_i64", &*CONFIG_I64, 10, 0, 1000, ConfigurationFlags::DEFAULT, Some(Box::new(on_configuration_changed)), Some(Box::new(on_i64_config_set::<ValkeyString, ValkeyGILGuard<i64>>))],
184+
],
185+
...
186+
]
187+
}
188+
```
189+
190+
## Defrag
191+
192+
There is a new `Defrag` struct abstracting away the raw C FFI calls to implement defrag for custom data types.
193+
194+
```rust
195+
static MY_VALKEY_TYPE: ValkeyType = ValkeyType::new(
196+
"mytype123",
197+
0,
198+
raw::RedisModuleTypeMethods {
199+
...
200+
defrag: Some(defrag),
201+
},
202+
);
203+
unsafe extern "C" fn defrag(
204+
defrag_ctx: *mut raw::RedisModuleDefragCtx,
205+
_from_key: *mut RedisModuleString,
206+
value: *mut *mut c_void,
207+
) -> i32 {
208+
let defrag = Defrag::new(defrag_ctx);
209+
...
210+
0
211+
}
212+
valkey_module! {
213+
...
214+
data_types: [
215+
MY_VALKEY_TYPE,
216+
],
217+
...
218+
}
219+
```
220+
221+
## Redis support
222+
223+
There is a new feature flag if your module needs to run on both Valkey and Redis.
224+
Specify `use-redismodule-api` so that module used RedisModule API Initialization for backwards compatibility.
225+
226+
```rust
227+
[features]
228+
use-redismodule-api = ["valkey-module/use-redismodule-api"]
229+
default = []
230+
231+
cargo build --release --features use-redismodule-api
232+
```
233+
234+
## Unit tests and memory allocation
235+
236+
This feature flag supports running unit tests outside of Valkey or Redis. As the result it cannot use Vakey Allocator and instead relies on the System Allocator instead. The core logis is present in `alloc.rs` but developers only need to specify this in the module `Cargo.toml`.
237+
238+
```rust
239+
[features]
240+
enable-system-alloc = ["valkey-module/system-alloc"]
241+
242+
cargo test --features enable-system-alloc
243+
```
244+
245+
## Ideas for future development
246+
247+
* Mock Context for unit testing - enable unit testing functions that require `&ctx`
248+
* Context in Filters - enable more operations to be done in filters.
249+
* Environment specific config support - during development or unit testing it is useful to have different config settings.
250+
* Crontab - `cron_event_handler` uses `serverCron` (10 times per second by default) but it can be extended to run custom code on pre-determined schedule and configured via custom commands.
251+
* And more. Please stay tuned.
252+
253+
## List of contributors
254+
255+
Below are the Github handles of the developers who contibuted to the SDK in the past year
256+
257+
* [KarthikSubbarao](https://github.com/KarthikSubbarao)
258+
* [dmitrypol](https://github.com/dmitrypol)
259+
* [sachinvmurthy](https://github.com/sachinvmurthy)
260+
* [zackcam](https://github.com/zackcam)
261+
* [YueTang-Vanessa](https://github.com/YueTang-Vanessa)
262+
* [hahnandrew](https://github.com/hahnandrew)
263+
* [Mkmkme](https://github.com/Mkmkme)
264+
265+
## Usefull links
266+
267+
* [Valkey repo](https://github.com/valkey-io/valkey)
268+
* [Valkey Modules Rust SDK](https://github.com/valkey-io/valkeymodule-rs)
269+
* [Rust in VS Code](https://code.visualstudio.com/docs/languages/rust)

0 commit comments

Comments
 (0)