Redis Command Creation
Introduction
Redis is a powerful, open-source, in-memory data structure store that can be used as a database, cache, message broker, and more. While Redis comes with a rich set of built-in commands, there are scenarios where you might need to extend its functionality with custom commands to better suit your specific application requirements.
In this guide, we'll explore how to create custom Redis commands through Redis modules. This powerful feature allows you to extend Redis with new functionality written in C, giving you the ability to implement operations that aren't available in the standard command set.
Understanding Redis Modules
Redis modules are dynamic libraries that can be loaded into Redis at startup or runtime, adding new commands and data types. This modular architecture enables developers to extend Redis's functionality without modifying the Redis core.
Prerequisites
Before diving into creating Redis commands, you'll need:
- Basic knowledge of C programming language
- Redis server installed (version 4.0 or later)
- Development tools (gcc, make, etc.)
- Understanding of Redis data structures and commands
Creating Your First Redis Module
Let's start by creating a simple "HELLO" command that returns a greeting message.
Step 1: Set up the project structure
Create a directory for your module:
mkdir redis-hello-module
cd redis-hello-module
Step 2: Create the module source file
Create a file named hello.c
with the following content:
#include "redismodule.h"
#include <string.h>
/* HELLO [name] - Return a greeting to the user */
int HelloCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc < 2) {
return RedisModule_WrongArity(ctx);
}
RedisModuleString *name = argv[1];
size_t len;
const char *nameStr = RedisModule_StringPtrLen(name, &len);
RedisModule_ReplyWithSimpleString(ctx, nameStr ?
RedisModule_StringPtrLen(name, &len) :
"world");
return REDISMODULE_OK;
}
/* Module initialization */
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
/* Register the module with Redis */
if (RedisModule_Init(ctx, "hello", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
/* Register the HELLO command */
if (RedisModule_CreateCommand(ctx, "HELLO",
HelloCommand,
"readonly",
1, 1, 1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
Step 3: Get the Redis module header file
Download the Redis module header file:
wget https://raw.githubusercontent.com/redis/redis/unstable/src/redismodule.h
Step 4: Compile the module
Create a Makefile:
CC=gcc
CFLAGS=-fPIC -Wall -std=c99 -O2
MODULE=hello.so
SOURCE=hello.c
DEPS=redismodule.h
all: $(MODULE)
$(MODULE): $(SOURCE) $(DEPS)
$(CC) $(CFLAGS) -shared -o $@ $<
clean:
rm -f $(MODULE)
Then compile the module:
make
Step 5: Load the module into Redis
Start Redis with the module:
redis-server --loadmodule ./hello.so
Or load it at runtime:
redis-cli MODULE LOAD /path/to/hello.so
Step 6: Test your new command
Connect to Redis and try your new command:
127.0.0.1:6379> HELLO Alice
"Alice"
Understanding the Code
Let's break down the key components of our module:
-
Module Header: We include
redismodule.h
which provides the interface to Redis's module API. -
Command Function:
HelloCommand
is the implementation of our command. It:- Checks for the correct number of arguments
- Extracts the name argument
- Returns a greeting using Redis's reply API
-
Module Initialization:
RedisModule_OnLoad
is called when the module is loaded. It:- Initializes the module with Redis
- Registers our command with Redis
-
Command Registration:
RedisModule_CreateCommand
registers our command with Redis, specifying:- Command name ("HELLO")
- Implementation function (HelloCommand)
- Command flags ("readonly")
- Allowed first key, last key, and step count for key extraction (1, 1, 1)
Creating More Complex Commands
Let's create a more practical command that performs a useful operation not available in Redis by default.
Example: MOVINGAVG Command
This command calculates the moving average of values in a list.
/* MOVINGAVG key window - Calculate the moving average of values in a list */
int MovingAvgCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 3) {
return RedisModule_WrongArity(ctx);
}
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
long long window;
/* Parse the window size */
if (RedisModule_StringToLongLong(argv[2], &window) != REDISMODULE_OK || window <= 0) {
RedisModule_ReplyWithError(ctx, "ERR invalid window size");
RedisModule_CloseKey(key);
return REDISMODULE_ERR;
}
/* Check if key exists and is a list */
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
RedisModule_ReplyWithError(ctx, "ERR key does not exist");
RedisModule_CloseKey(key);
return REDISMODULE_ERR;
} else if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST) {
RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
RedisModule_CloseKey(key);
return REDISMODULE_ERR;
}
/* Get list length */
size_t len = RedisModule_ValueLength(key);
if (len == 0) {
RedisModule_ReplyWithError(ctx, "ERR list is empty");
RedisModule_CloseKey(key);
return REDISMODULE_ERR;
}
/* Cap window size to list length */
if (window > len) window = len;
/* Calculate moving average */
double sum = 0;
for (size_t i = 0; i < window; i++) {
RedisModuleString *elem;
RedisModule_ListGet(key, len - 1 - i, &elem);
long long val;
if (RedisModule_StringToLongLong(elem, &val) != REDISMODULE_OK) {
RedisModule_ReplyWithError(ctx, "ERR list contains non-numeric values");
RedisModule_CloseKey(key);
return REDISMODULE_ERR;
}
sum += val;
}
double avg = sum / window;
RedisModule_CloseKey(key);
RedisModule_ReplyWithDouble(ctx, avg);
return REDISMODULE_OK;
}
Redis Command Guidelines
When creating Redis commands, follow these best practices:
-
Command Naming:
- Use uppercase letters (by convention)
- Choose clear, descriptive names
- Use a prefix for related commands (e.g.,
HELLO.WORLD
,HELLO.NAME
)
-
Command Arguments:
- Validate all arguments carefully
- Provide clear error messages for invalid inputs
- Support optional arguments when appropriate
-
Error Handling:
- Check return values of Redis Module API functions
- Close keys and free resources in error cases
- Use descriptive error messages
-
Performance Considerations:
- Be mindful of time complexity
- Avoid blocking operations in commands
- Consider using threaded commands for CPU-intensive operations
Real-World Applications
Custom Redis commands are useful in various scenarios:
Example 1: Enhanced Analytics
/* TIMESERIES.INCREMENTBY key timestamp value - Add a value to a time series */
int TimeSeriesIncrementBy(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// Implementation details omitted for brevity
// This would create or update a sorted set with scores representing timestamps
// and increment the value at the given timestamp
}
Example 2: Domain-Specific Validations
/* VALIDATE.EMAIL email - Check if an email is valid */
int ValidateEmailCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// Implementation details omitted for brevity
// This would check if the provided string is a valid email address
// and return 1 if valid, 0 otherwise
}
Example 3: Custom Data Types
/* GRAPH.ADDEDGE graph source target [properties] - Add an edge to a graph */
int GraphAddEdgeCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// Implementation details omitted for brevity
// This would add an edge to a custom graph data type
}
Deployment and Distribution
Once you've created your Redis module, you can:
-
Package it for distribution:
- Create a README file explaining your module
- Add a LICENSE file
- Publish your module on GitHub or other platforms
-
Configure it for production:
- Add configuration options to your module
- Create initialization scripts
- Document the command's behavior and performance characteristics
-
Load it automatically:
- Configure Redis to load your module at startup
- Add the module to a Redis Stack or Redis Enterprise setup
Summary
Creating custom Redis commands through modules is a powerful way to extend Redis's functionality to meet your specific application needs. By writing C code and following Redis's module API, you can add new commands that work seamlessly with the rest of Redis.
In this guide, we've learned:
- How Redis modules work and how they extend Redis
- How to create and register custom Redis commands
- Best practices for command implementation
- Real-world applications of custom Redis commands
Custom Redis commands allow you to leverage the performance and reliability of Redis while adding domain-specific functionality that might not be available in the core Redis command set.
Additional Resources
Exercises
- Create a Redis module with a command that reverses a string.
- Implement a command that calculates the median of a list of numbers.
- Create a command that performs set operations with custom logic not available in Redis's built-in commands.
- Build a command that validates data according to a specific schema.
- Implement a command that performs a complex calculation on multiple data structures.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)