20 Feb 2022

Namespace Redis In Perl

Redis doesn't do namespacing. This is my quick hack attempt to work around that in Perl, then a link to how to do it properly.

Background

I’ve got a project on the go that deals with processing subscription data out of Microsoft and plugging it in to SQL. It’s a relatively small web server component to receive a HTTP POST request, which then fires off several rounds of queued jobs to augment the data. Redis is the backend store for all of this before it hits SQL. Redis is fast, simple to understand, has an easy CLI which doesn’t require any extra user setup etc to view data. It is ideal for this project. There is however one minor issue. Redis doesn’t have a way to namespace the data.

For other projects which have used Redis this has never been a real issue, but for this project I want to run several completely isolated environments all from one machine and I’m using the same test account to trigger inbound data, which would definitely cause some clashes.

The code I have so far allow new environments/instances to be made by coping a config and starting the web server and worker daemon listening on a new pair of ports, if I can only solve the Redis bit.

Quick Hack: Perl’s AUTOLOAD

It is left up to the Redis client to decide how/if namespacing should be implemented. The typical approach is to add a prefix to every key, think dev:foo and prod:foo instead of just foo. As the Perl Redis lib doesn’t have a namespace option, I knocked one up. It’s super simple, pretty dubious, but totally fine for my purposes. It relies on Perl trying to automatically load undefined subs, which is basically everything in this case. I can then intercept the arguments, add my string prefix to the Redis key (usually the first argument), then call the real sub and return the result.

Use of Moo is not at all necessary, but I’d already used extensively elsewhere in the project so it was force of habit.

package Dubious::Redis;

# Redis doesn't do namespacing, we are going to do our own version by using a
# key prefix with environment name.  This module is a super loose wrapper around
# the perl module 'Redis'. It tweaks the first argument to be '$env$key' instead
# of just '$key'.

use namespace::clean;
use Moo;
use Redis;

has _redis => (is => 'ro', default => sub { return Redis->new() });
has redis_namespace => (is => 'ro', required => 1);

sub AUTOLOAD {
    my $self = shift;
    my $key = shift;

    our $AUTOLOAD;              # keep 'use strict' happy
    my $program = $AUTOLOAD;

    my $pkg = __PACKAGE__ . '::';
    my ($new_program) = ($program =~ m/^$pkg(.*)$/);

    # Filter out specials, e.g. new, or DESTROY
    if ($key && $new_program =~ m/new|[a-z]/) {
        my ($new_program) = ($program =~ m/^$pkg(.*)$/);
        my $new_key = $self->redis_namespace . $key;
        return $self->_redis->$new_program($new_key, @_);
    }
}

1;

I should make it clear that I have not super thought this through and currently I am only using hget,hset and hdel, which do all work with the above hack. The module is safely namespaced under Dubious so I’ll think twice before using it any important (read production) capacity.

Proper Module = Redis::Namespace

After writing the above code (and using it for months!) I’ve realised there is already a module on CPAN to add a namespace to Redis! I have no idea how I missed this before, I must have checked the core Redis module and nothing else.

Scanning the source, this module is built properly and considers the target method required arguments. It is therefore considerably more robust compared to my hack. I would strongly recommend using it unless you like living dangerously!

Dev SysAdmin
Back to posts