---
title: "Namespace Isolation for a Shared Redis"
description: "The pattern I use to avoid key collisions and accidental deletes when a single Redis instance is shared across several projects"
url: https://sade.dev/en/notes/shared-redis-namespace-isolation/
lang: en
author: "Muhammet Şafak"
published: 2026-05-12
updated: 2026-09-06
section: Note
tags: ["redis","production","architecture"]
summary: "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

> 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.

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`:

```php
'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

**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

- [SELECT: the selected database is a property of the connection, and Redis Cluster supports only database zero](https://redis.io/docs/latest/commands/select/) — Redis
- [KEYS: an entry-level laptop scans a one-million-key database in 40 milliseconds — use SCAN instead](https://redis.io/docs/latest/commands/keys/) — Redis
- [SCAN: a cursor-based iteration that is O(1) per call](https://redis.io/docs/latest/commands/scan/) — Redis
