I recently watched a podcast episode where Khawaja Shams, CEO of Momento, and Kyle Davis, Developer Advocate at Valkey, talked about everything BUT performance of Valkey 9.0.
“That’s the first time anybody’s asked me to talk about Valkey and not talk about performance,” Kyle laughed when Khawaja asked him to skip the speed benchmarks. What followed was one of those conversations where you realize a seemingly simple feature opens up architectural patterns you didn’t even know were possible.
I didn’t quite understand it at first. Kyle mentioned [numbered databases in Valkey 9.0](https://…
I recently watched a podcast episode where Khawaja Shams, CEO of Momento, and Kyle Davis, Developer Advocate at Valkey, talked about everything BUT performance of Valkey 9.0.
“That’s the first time anybody’s asked me to talk about Valkey and not talk about performance,” Kyle laughed when Khawaja asked him to skip the speed benchmarks. What followed was one of those conversations where you realize a seemingly simple feature opens up architectural patterns you didn’t even know were possible.
I didn’t quite understand it at first. Kyle mentioned numbered databases in Valkey 9.0. Then he mentioned clustering and how numbered databases get a boost when used together. The terminology was throwing me off until I heard it explained a different way.
Kyle referred to numbered databases as “namespaces” (which makes way more sense), and explained that they let you logically separate your keys. The same key name can exist in database 0 and database 42 with completely different data, and they’ll never collide.
“This is a feature that goes way back to Redis one,” Kyle explained. “They had this concept of numbered databases… but it allows you to take the keys that you have and separate them up logically into, by default, 16 databases.”
Up until v9, this only worked in standalone mode. The moment you needed cluster mode (which basically every production workload does), you were stuck with just database 0. For over a decade. An update has been due for a long time.
When Valkey calculates which slot a key belongs to, it only looks at the key name. The database number doesn’t matter. So if you have a key called user:100 in database 0, and another key called user:100 in database 5000, they both live in the same slot.
On the surface, this might not seem like a big deal. But digging into what that means – you can move data between databases almost for free. Moving keys between databases is literally a pointer change, not a data migration. You can move a sorted set with 3 million elements between databases with no network transfer. Just… instant.
Problems this solves
I immediately started thinking about real problems this solves. And I found a few that made me really excited.
No more key prefixing
The obvious use case we jump to when we hear “multiple databases” is multi-tenancy. In a multi-tenant SaaS app, you probably build your keys like this (if you’re anything like me):
cache.set("tenant_12345:user:100", user_data)
cache.set("tenant_12345:session:abc", session_data)
cache.set("tenant_12345:cart:xyz", cart_data)
Every key you get, set, and delete has the tenant id prefixed on it. This is a (generally) safe way to make sure you logically separate customer data. If you have 100 million keys with a 15-byte prefix, that’s 1.5GB of RAM just storing the same string over and over. It’s an incredibly wasteful pattern!
But now, we can save tenant data in a tenant-specific database for a set of keys. It not only makes the code cleaner, it also frees up tons of memory to be used on… more keys!
cache.select(12345) // Tenant 12345's namespace
cache.set("user:100", user_data)
cache.set("session:abc", session_data)
cache.set("cart:xyz", cart_data)
In other words, we’ve effectively expanded our potential working set size without any hardware changes. Same isolation. Zero prefix overhead. And remember, Valkey 8.0 made headlines for saving 8 bytes per key. We’re talking about eliminating 10-20 bytes here.
Black Friday-scale price switches
Kyle described a use case that made me immediately think of every e-commerce system I’ve ever seen:
“Let’s say you have something that you want to instantly switch over, atomically switch over from one set of data to another. So you’ve said, here it has a million elements and then at 12:01 AM you want to have a different set of million elements.”
Sounds exactly like holiday pricing to me. Businesses have their entire catalog of products and prices being served out of their cache cluster. The midnight after Thanksgiving, they need to flip to sale prices. All of them. Instantly.
Typically, you’d have a batch job that updates prices in bulk or update your data with feature flags, or some other process equally prone to error that results in customers seeing random mixes of old and new prices.
But with multiple databases, you can build your catalog in a different database and instantly move it over with a single command
cache.select(2) // holiday pricing database
cache.move("product:prices", 1) // moved to production database
No data copying. No network overhead. Really, just a configuration change and suddenly your entire pricing model flips. This is the kind of thing that sounds impossible until someone shows you it’s not.
Parallel test environments
This one hit home for me. If you’re running tests in CI/CD, you’ve probably tried to run multiple parallel test jobs, all needing isolated data – but all stepping on each other’s toes.
Of course, you could get around this by not running your tests in parallel and resetting your test data after every job, but nobody has time for that anymore. Multiple databases makes this one a trivial problem.
test_db = int(os.getenv("CI_JOB_ID")) % 10000
cache = Valkey(host='valkey-cluster')
cache.select(test_db)
cache.set("user:test@example.com", user_json)
assert cache.get("user:test@example.com") == user_json
cache.flushdb()
And it scales, too! Kind of. The podcast talked about running a cluster with 10 million databases just to see what would happen. Turns out, it worked fine! Valkey does lazy allocation on databases, so they only consume resources when you actually put data in it. So you can assign unique database numbers to thousands of concurrent test runs and it costs you nothing until you write data.
This is the kind of engineering that makes you want to high-five someone through the internet 🙌
More is (probably) coming
In the podcast, Kyle hinted at – but didn’t commit – to some cool feature enhancements for numbered databases that make it feel like a first-class citizen in Valkey.
Named databases – Instead of SELECT 42, imagine SELECT "production" or SELECT "tenant_acme_corp". This is a huge boost for developer experience for obvious reasons, but also makes it easier in multi-tenant scenarios so you don’t have to track KV mappings of your tenant ids to numbers.
Per-database memory limits – Prevent your test database from eating all your RAM. Sandbox test environments so they don’t take up all the resources. It also fights noisy neighbors in multi-tenant production scenarios.
Per-database rate limiting - True multi-tenant resource isolation at the namespace level.
Numbered databases in cluster mode give us something new – predictable, lightweight isolation built right into the cache layer. That’s a big deal for enterprise workloads. Multi-tenant apps get simpler. A/B testing and CI environments get safer. Rollouts and migrations get less risky.
Kyle summed it up perfectly:
Somebody can roll in and just take something, start saving memory with it, or have new use cases entirely by going SELECT 4 instead of not worrying about a database at all.
It’s a subtle-yet-powerful new feature that might end up shaping how we architect distributed caching in the future.
Happy coding!