Redis 8.0 & Valkey 8: In-Memory Database Evolution in 2026



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.

Redis and Valkey in-memory databases 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

FactorRedis 8.0Valkey 8.0
LicenseSSPL/RSALv2 (proprietary)BSD 3-Clause (true OSS)
Cloud ManagedVaries by providerAWS ElastiCache, GCP Memorystore
PerformanceBaseline+10-27% on key operations
Vector SearchNative (RQE)Via modules (Valkey-Search)
JSONNative (8.0)Via Valkey-JSON module
Cluster ModeStableStable
SupportRedis Ltd. commercialLinux Foundation + community
CostHigher (commercial)Lower (OSS)
API CompatibilityReference99%+ 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

이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)