Skip to content
Muhammet Şafak
tr
Updated 2 min read

Namespace Isolation for a Shared Redis

The pattern I use to avoid key collisions and accidental deletes when a single Redis instance is shared across several projects

TL;DR

On a shared Redis, isolation comes from the key scheme rather than from logical DBs. A three-layer env, app and bounded-context prefix makes searching precise and accidental deletion harder, and baking that prefix into the connection protects the system even when a developer forgets it. Logical DBs cannot do the same job: they are per-connection state, and Redis Cluster only has database zero.

Namespace Isolation for a Shared Redis — cover image

Running one Redis per VPS is a perfectly reasonable call for small-to-medium projects. The trouble starts when several apps begin sharing the same instance: one project’s users key clobbers another’s users key, and the moment someone runs FLUSHALL everyone goes down.

The fix isn’t standing up a new Redis; it’s namespace discipline.

A three-layer key scheme

In production I use this pattern:

<env>:<app>:<bounded-context>:<key>

For example:

prod:billing:invoice:42
prod:auth:session:1c2f...
staging:notify:queue:retry

Having all three layers buys you two things:

  1. Clarity when searching. With redis-cli --scan --pattern 'prod:billing:*' I can see the keys of just one project.
  2. Friction against accidental deletes. I think twice when I type redis-cli --scan --pattern 'staging:*' | xargs redis-cli DEL instead of FLUSHDB.

Don’t rely on logical DBs

Redis has logical DBs numbered 0–15, but in a word: don’t use them. Three reasons:

  • The SELECT command is scoped only to that connection — chaos in a connection pool.
  • Some libraries don’t support a DB switch inside MULTI/EXEC blocks.
  • Redis Cluster doesn’t support logical DBs at all; the day you move to a cluster, every one of your assumptions collapses.

Learning and applying a namespace prefix is the only approach.

Connection-level isolation

If you’re on Laravel, define a separate Redis connection per app in config/database.php and bake the namespace into the connection with prefix:

'redis' => [
    'billing' => [
        'host' => env('REDIS_HOST'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', 6379),
        'database' => 0,
        'prefix' => 'prod:billing:',
    ],
    'auth' => [
        // ...
        'prefix' => 'prod:auth:',
    ],
],

Now a Redis::connection('billing')->set('invoice:42', ...) call automatically writes prod:billing:invoice:42. Even if a developer forgets to write the namespace by hand, the system protects itself.

Disable the dangerous commands

In redis.conf it’s a good idea to rename or disable these commands in production:

rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command KEYS ""

KEYS matters most — it blocks the event loop for the whole scan, and every other client waits behind it. Redis’s own docs put a 1M-key database at about 40 ms on an entry-level laptop, so on a 100k-key instance you’re talking milliseconds — but the cost grows with the keyspace, and on multi-million-key instances the stall gets long enough to hurt. Using SCAN instead should be mandatory.

When do you drop this pattern?

Move to separate Redis instances if any one of three signals shows up:

  1. One project’s traffic measurably affects the latency of the others.
  2. One project wants persistent storage (RDB/AOF) while the others are cache-only.
  3. One project needs a different Redis version or feature.

Before any of those, hold to this simple rule: writing a prefix is cheaper than starting a new process.

Frequently asked

3 questions

  • Why not separate the applications with Redis logical DBs?

    Three reasons. The selected database is a property of the connection, which turns chaotic in a connection pool; some libraries do not support switching databases inside MULTI/EXEC blocks; and Redis Cluster supports only database zero, so every assumption you built collapses the day you move to a cluster.

  • Is KEYS really dangerous on a small instance?

    It blocks the event loop for the whole scan and every other client waits behind it. Redis's own documentation puts a one-million-key database at about 40 milliseconds on an entry-level laptop, so a 100k-key instance costs milliseconds. But the cost grows with the keyspace, and on multi-million-key instances the stall gets long enough to hurt. SCAN is O(1) per call; use it.

  • When do you give up the shared instance?

    When one project's traffic measurably affects the latency of the others, when one project needs persistent storage while the rest are cache-only, or when one project needs a different Redis version or feature. Before any of those, writing a prefix is cheaper than starting a new process.

Sources

Related

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind