Redis 8.0 & Valkey 8: In-Memory Database Evolution in 2026
on Redis, Valkey, Database, Cache, Performance, Backend
Redis 8.0 & Valkey 8: In-Memory Database Evolution in 2026
The in-memory database landscape was upended in March 2024 when Redis Ltd. changed its license from BSD to dual SSPL/RSALv2. The response was swift: Amazon, Google, Oracle, and others forked Redis to create Valkey under the Linux Foundation. Now, nearly two years later, both projects have matured significantly. Redis 8.0 ships with vector search and JSON improvements, while Valkey 8 brings performance gains and true OSS governance. Here’s everything you need to know.
Photo by Lars Kienle on Unsplash
Redis 8.0: What’s New
Redis 8.0 (released late 2025) represents a major feature release focused on AI/ML use cases and operational improvements.
1. Native Vector Search (Redis Query Engine)
Redis 8 integrates vector similarity search directly into the core, no longer requiring the separate RediSearch module:
# Create a vector index
FT.CREATE product_idx ON HASH PREFIX 1 product:
SCHEMA
name TEXT WEIGHT 5.0
description TEXT
category TAG SORTABLE
price NUMERIC SORTABLE
embedding VECTOR HNSW 6
TYPE FLOAT32
DIM 1536
DISTANCE_METRIC COSINE
# Add a product with embedding
HSET product:1001
name "Wireless Headphones"
category "electronics"
price 149.99
embedding <binary_vector_data>
# Hybrid search: filter by category + vector similarity
FT.SEARCH product_idx
"@category:{electronics} @price:[50 300]=>[KNN 10 @embedding $vec AS score]"
SORTBY score
PARAMS 2 vec <query_embedding>
DIALECT 2
import redis
import numpy as np
from redis.commands.search.field import VectorField, TextField, NumericField, TagField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis.commands.search.query import Query
client = redis.Redis(host='localhost', port=6379, decode_responses=False)
# Create index
schema = (
TextField("name", weight=5.0),
TextField("description"),
TagField("category", sortable=True),
NumericField("price", sortable=True),
VectorField(
"embedding",
"HNSW",
{
"TYPE": "FLOAT32",
"DIM": 1536,
"DISTANCE_METRIC": "COSINE",
"INITIAL_CAP": 1000,
"M": 16,
"EF_CONSTRUCTION": 200,
}
)
)
client.ft("product_idx").create_index(
schema,
definition=IndexDefinition(prefix=["product:"], index_type=IndexType.HASH)
)
# Semantic search function
def semantic_search(query_embedding: np.ndarray, top_k: int = 10, category: str = None):
vec_bytes = query_embedding.astype(np.float32).tobytes()
# Build filter
filter_expr = f"@category:}" if category else "*"
query = (
Query(f"{filter_expr}=>[KNN {top_k} @embedding $vec AS score]")
.sort_by("score")
.return_fields("name", "category", "price", "score")
.dialect(2)
)
results = client.ft("product_idx").search(
query,
query_params={"vec": vec_bytes}
)
return [
{
"id": doc.id,
"name": doc.name,
"category": doc.category,
"price": float(doc.price),
"similarity": 1 - float(doc.score)
}
for doc in results.docs
]
2. Redis Triggers and Functions (GA in 8.0)
// Register a JavaScript function in Redis
redis.registerFunction('updateProductScore', (client, eventData) => {
const { key, event } = eventData;
if (event === 'hset' && key.startsWith('product:')) {
const views = client.call('HGET', key, 'views');
const sales = client.call('HGET', key, 'sales');
// Compute popularity score
const score = (parseInt(views) * 0.3) + (parseInt(sales) * 0.7);
client.call('HSET', key, 'popularity_score', score.toString());
client.call('ZADD', 'products:by_popularity', score, key);
}
});
// Register as a keyspace notification trigger
redis.registerKeySpaceTrigger(
'productScoreUpdater',
'product:',
updateProductScore
);
3. Enhanced JSON Support (RedisJSON 2.8)
import redis
from redis.commands.json.path import Path
client = redis.Redis(host='localhost', port=6379)
# Store complex nested document
user_doc = {
"id": "user:12345",
"profile": {
"name": "Alice Chen",
"email": "alice@example.com",
"preferences": {
"theme": "dark",
"language": "en",
"notifications": {
"email": True,
"push": False,
"sms": True
}
}
},
"cart": [],
"orders": [],
"metadata": {
"created_at": "2026-01-15T09:00:00Z",
"last_login": "2026-03-28T08:30:00Z"
}
}
client.json().set("user:12345", Path.root_path(), user_doc)
# Atomic increment/operations on nested paths
client.json().numincrby("user:12345", "$.cart_count", 1)
# Update specific nested field
client.json().set("user:12345", "$.profile.preferences.theme", "light")
# Get only needed fields (avoid over-fetching)
profile = client.json().get("user:12345", "$.profile.name", "$.metadata.last_login")
# Array operations
client.json().arrappend(
"user:12345",
"$.orders",
{"order_id": "ord:9999", "total": 149.99, "status": "pending"}
)
# Redis 8.0: JSON Path indexing
client.ft("users_idx").create_index(
[
TextField("$.profile.name", as_name="name"),
TagField("$.profile.preferences.language", as_name="lang"),
NumericField("$.metadata.last_login", as_name="last_login")
],
definition=IndexDefinition(prefix=["user:"], index_type=IndexType.JSON)
)
Valkey 8: The Open Source Alternative
Valkey is a fully open-source (BSD 3-Clause) drop-in Redis replacement. Valkey 8.0 (released mid-2025) focuses on performance and reliability.
Performance Improvements in Valkey 8
Benchmark: Valkey 8 vs Redis 7.4 (same hardware, AWS m7g.2xlarge)
Operation | Redis 7.4 | Valkey 8.0 | Delta
----------------|-------------|-------------|-------
GET (100B) | 1,250,000/s | 1,380,000/s | +10.4%
SET (100B) | 980,000/s | 1,150,000/s | +17.3%
HGETALL (10flds)| 620,000/s | 790,000/s | +27.4%
ZADD | 540,000/s | 610,000/s | +13.0%
LPUSH/LPOP | 890,000/s | 1,020,000/s | +14.6%
Valkey’s Threading Model
Valkey 8 introduces enhanced multi-threading for I/O operations:
# valkey.conf
# Enable I/O threading (Valkey 8 default: 4 threads)
io-threads 8
io-threads-do-reads yes
# Background thread optimizations
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
# Valkey 8: Dual-channel replication (2x faster)
repl-diskless-sync yes
repl-diskless-sync-delay 5
Valkey RESP3 Protocol Enhancements
# Valkey fully supports RESP3 for richer data types
import valkey # pip install valkey (drop-in redis replacement)
client = valkey.Valkey(
host='localhost',
port=6379,
protocol=3 # RESP3
)
# RESP3 returns typed responses natively
# Hash → dict (not list of pairs)
user_data = client.hgetall("user:12345")
# Returns: {'name': 'Alice', 'age': '28'} instead of ['name', 'Alice', 'age', '28']
# Sets → Python sets
tags = client.smembers("product:tags")
# Returns: {'electronics', 'wireless', 'audio'}
# Push notifications (RESP3 pub/sub)
pubsub = client.pubsub()
pubsub.subscribe("notifications")
Key Data Structures for Modern Applications
Sorted Sets for Leaderboards
def update_leaderboard(user_id: str, score: float, leaderboard: str = "global"):
"""Thread-safe leaderboard update with Redis"""
pipe = client.pipeline()
# Add/update score
pipe.zadd(f"leaderboard:{leaderboard}", {user_id: score})
# Trim to top 1000
pipe.zremrangebyrank(f"leaderboard:{leaderboard}", 0, -1001)
pipe.execute()
def get_leaderboard_with_ranks(leaderboard: str, page: int = 1, per_page: int = 20):
"""Get paginated leaderboard with ranks"""
start = (page - 1) * per_page
end = start + per_page - 1
# Get scores with ranks
entries = client.zrevrange(
f"leaderboard:{leaderboard}",
start, end,
withscores=True
)
return [
{
"rank": start + i + 1,
"user_id": user_id.decode(),
"score": score
}
for i, (user_id, score) in enumerate(entries)
]
def get_user_rank(user_id: str, leaderboard: str = "global"):
"""Get a specific user's rank (0-indexed from top)"""
rank = client.zrevrank(f"leaderboard:{leaderboard}", user_id)
score = client.zscore(f"leaderboard:{leaderboard}", user_id)
return {"rank": rank + 1 if rank is not None else None, "score": score}
Streams for Event Sourcing
# Redis Streams: append-only log with consumer groups
import time
def publish_event(stream: str, event_type: str, data: dict):
"""Publish an event to a Redis Stream"""
return client.xadd(
stream,
{
"type": event_type,
"timestamp": str(int(time.time() * 1000)),
**{k: str(v) for k, v in data.items()}
},
maxlen=100_000, # Keep last 100k events
approximate=True # Allow slight overflow for performance
)
def create_consumer_group(stream: str, group: str):
"""Create a consumer group (idempotent)"""
try:
client.xgroup_create(stream, group, id="$", mkstream=True)
except redis.exceptions.ResponseError as e:
if "BUSYGROUP" not in str(e):
raise
def process_events(stream: str, group: str, consumer: str, batch_size: int = 100):
"""Process events with at-least-once delivery guarantee"""
# First, check for unacknowledged messages (recovery)
pending = client.xreadgroup(
groupname=group,
consumername=consumer,
streams={stream: "0"}, # "0" = read pending messages
count=batch_size
)
if not pending:
# No pending, read new messages
pending = client.xreadgroup(
groupname=group,
consumername=consumer,
streams={stream: ">"}, # ">" = undelivered messages
count=batch_size,
block=1000 # Wait up to 1s for new messages
)
if not pending:
return []
processed_ids = []
for stream_name, messages in pending:
for msg_id, fields in messages:
try:
# Process the event
handle_event(fields)
processed_ids.append(msg_id)
except Exception as e:
# Log but don't ack - will be retried
print(f"Failed to process {msg_id}: {e}")
# Acknowledge processed messages
if processed_ids:
client.xack(stream, group, *processed_ids)
return processed_ids
Bloom Filters for Deduplication
# Redis 8 / Valkey: Built-in probabilistic data structures
def setup_bloom_filter(name: str, expected_items: int, error_rate: float = 0.01):
"""Create a Bloom filter for efficient deduplication"""
# BF.RESERVE name error_rate initial_capacity
try:
client.bf().reserve(name, error_rate, expected_items)
except redis.exceptions.ResponseError:
pass # Already exists
def track_seen_event(event_id: str) -> bool:
"""Returns True if this is a NEW event, False if already seen"""
# BF.ADD returns 1 if newly added, 0 if already existed
return bool(client.bf().add("seen_events", event_id))
def bulk_dedup(event_ids: list) -> list:
"""Efficiently check/add multiple events at once"""
results = client.bf().madd("seen_events", *event_ids)
return [event_id for event_id, is_new in zip(event_ids, results) if is_new]
Caching Patterns for 2026
Cache-Aside with Stampede Prevention
import asyncio
import aioredis
from typing import Optional, Callable, TypeVar
T = TypeVar('T')
class RedisCache:
def __init__(self, redis_url: str):
self.redis = aioredis.from_url(redis_url)
self._locks: dict = {}
async def get_or_set(
self,
key: str,
fetcher: Callable,
ttl: int = 3600,
early_refresh_ttl: int = 60 # Refresh 60s before expiry
) -> T:
"""Cache-aside with probabilistic early expiration to prevent stampede"""
# Try to get cached value
cached = await self.redis.get(key)
if cached:
remaining_ttl = await self.redis.ttl(key)
# Probabilistic early refresh (XFetch algorithm)
# Refresh with increasing probability as TTL decreases
import math, random
refresh_probability = math.exp(-remaining_ttl / early_refresh_ttl)
if random.random() > refresh_probability:
return cached # Use cached value
# Acquire a distributed lock to prevent stampede
lock_key = f"lock:{key}"
lock = await self.redis.set(lock_key, "1", nx=True, ex=10)
if not lock:
# Another process is fetching - wait and retry
await asyncio.sleep(0.1)
return await self.redis.get(key) or await fetcher()
try:
# Fetch fresh value
value = await fetcher()
# Store with TTL
await self.redis.setex(key, ttl, value)
return value
finally:
await self.redis.delete(lock_key)
# Usage
cache = RedisCache("redis://localhost:6379")
async def get_user_profile(user_id: str):
return await cache.get_or_set(
f"user:profile:{user_id}",
lambda: fetch_from_database(user_id),
ttl=3600
)
Redis vs Valkey: 2026 Decision Guide
| Factor | Redis 8.0 | Valkey 8.0 |
|---|---|---|
| License | SSPL/RSALv2 (proprietary) | BSD 3-Clause (true OSS) |
| Cloud Managed | Varies by provider | AWS ElastiCache, GCP Memorystore |
| Performance | Baseline | +10-27% on key operations |
| Vector Search | Native (RQE) | Via modules (Valkey-Search) |
| JSON | Native (8.0) | Via Valkey-JSON module |
| Cluster Mode | Stable | Stable |
| Support | Redis Ltd. commercial | Linux Foundation + community |
| Cost | Higher (commercial) | Lower (OSS) |
| API Compatibility | Reference | 99%+ compatible |
Who Should Use What
Use Redis 8.0 if:
- You rely heavily on Redis Cloud / HCP Redis
- Vector search without modules is critical
- You need Redis enterprise features (ACLs+, active-active geo-replication)
- Your workload is already on Redis Cloud with SLAs
Use Valkey 8.0 if:
- You’re self-hosting or on AWS/GCP managed services
- License compliance matters (FSI, government)
- Maximizing performance per dollar
- Starting a new project without Redis investment
Conclusion
The Redis/Valkey split has benefited the ecosystem. Redis 8 shipped genuinely innovative features (native vector search, Triggers & Functions), while Valkey 8 delivered meaningful performance improvements under a true open-source license.
For new applications in 2026: Valkey is the pragmatic choice for most workloads. It’s faster, truly open-source, and supported by major cloud providers. For AI-native applications with heavy vector search requirements or if you’re already invested in Redis Cloud, Redis 8 makes a compelling case.
The good news: code written for one is almost entirely portable to the other. Redis commands haven’t changed; only governance and license differ. Start with Valkey, upgrade your client library, and benefit from the competitive innovation between two well-maintained projects.
Tags: #Redis #Valkey #InMemoryDB #Cache #Database #Backend2026
이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)
