- Published on
Redis in 2026 — Beyond Caching to the Multi-Model Database
- Authors

- Name
- Sanjeev Sharma
- @webcoderspeed1
Introduction
Redis started as a cache. Developers use it to store sessions, cache database queries, and avoid repeated work.
By 2026, Redis is much more. The Redis Stack adds vector search, time series, JSON documents, streams, and full-text indexing. It''s becoming a multi-model database—suitable for session storage, leaderboards, real-time metrics, and semantic search.
This shift matters. If your only use of Redis is caching, you''re missing capabilities. If you think "Redis is just a cache," it''s time to update your mental model.
- Redis 8 Performance Improvements
- Redis as a Vector Database
- Redis Time Series (RedisTimeSeries Module)
- Redis JSON (RedisJSON Module)
- Redis Search (Full-Text Indexing)
- Building a Session Store
- Leaderboard with Sorted Sets
- Rate Limiting with Sliding Window
- Pub/Sub vs Streams for Real-Time
- Redis Sentinel vs Redis Cluster
- Upstash Redis for Serverless
- When to Use Redis
- Checklist
- Conclusion
Redis 8 Performance Improvements
Redis 8 (released in 2026) is faster:
- Function calls: New scripting model (Redis Functions) is 5x faster than Lua.
- Pub/Sub: Sharded pub/sub reduces latency to single-digit milliseconds.
- Cluster: Improved cluster gossip protocol, faster failover.
- Memory: Optimized hash tables reduce memory overhead by 20%.
For applications at scale, these improvements compound. A 5x faster Lua script might mean the difference between 20 workers and 4.
Redis as a Vector Database
Redis 8 includes native vector indexing. Store vectors, search by similarity—no separate vector database.
import { createClient } from 'redis';
const client = createClient();
await client.connect();
// Create vector index
await client.ft.create('documents:idx', {
'embedding': {
type: 'VECTOR',
algorithm: 'HNSW',
attributes: {
dimensions: 1536,
distance_metric: 'COSINE',
},
},
'text': { type: 'TEXT' },
});
// Insert document with vector
await client.hSet('documents:1', {
embedding: Buffer.from(embeddingArray), // 1536-dim embedding
text: 'Machine learning is...',
});
// Search by vector similarity
const results = await client.ft.search('documents:idx', '*=>[KNN 10 @embedding $vec]', {
PARAMS: {
vec: Buffer.from(queryEmbedding),
},
});
Fast, simple, no separate database. For most applications, Redis vector search replaces Pinecone or Weaviate.
Latency: single-digit milliseconds for <10M vectors. At 10M+, specialized vector DBs win, but Redis is fine for most startups.
Redis Time Series (RedisTimeSeries Module)
Store metrics: CPU usage, request latency, revenue per hour. RedisTimeSeries compresses data and queries are fast.
import { createClient } from 'redis';
const client = createClient();
await client.connect();
// Create time series with 1-hour aggregation
await client.ts.create('metrics:cpu', {
labels: { host: 'server-1' },
});
// Append data points
await client.ts.add('metrics:cpu', '*', 75.2); // Current timestamp, 75.2% CPU
await client.ts.add('metrics:cpu', '*', 72.5);
await client.ts.add('metrics:cpu', '*', 78.1);
// Query range
const range = await client.ts.range('metrics:cpu', '-1h', '+', {
AGGREGATION_TYPE: 'avg',
AGGREGATION_TIMEBUCKET: 300000, // 5-minute buckets
});
console.log(range); // [[timestamp, value], ...]
Data is compressed. A year of hourly metrics = ~3MB. Compare to PostgreSQL: same data = 50MB+.
Redis JSON (RedisJSON Module)
Store JSON documents, query them with JSONPath.
import { createClient } from 'redis';
const client = createClient();
await client.connect();
// Store a user document
await client.json.set('user:123', '$', {
id: 123,
email: 'user@example.com',
name: 'John',
profile: {
bio: 'Software engineer',
followers: 1500,
},
});
// Query a specific field
const email = await client.json.get('user:123', { path: '$.email' });
console.log(email); // 'user@example.com'
// Update nested field
await client.json.set('user:123', '$.profile.followers', 1501);
// Query complex path
const bios = await client.json.get('user:*', { path: '$.profile.bio' });
Useful for storing profiles, settings, or any semi-structured data. Queries happen in Redis (fast), not in your application.
Redis Search (Full-Text Indexing)
Search text fields: product descriptions, blog posts, documentation.
import { createClient } from 'redis';
const client = createClient();
await client.connect();
// Create search index on documents
await client.ft.create('posts:idx', {
title: { type: 'TEXT', weight: 2.0 },
content: { type: 'TEXT' },
author: { type: 'TEXT' },
created_at: { type: 'NUMERIC', sortable: true },
});
// Index posts
await client.hSet('posts:1', {
title: 'Getting started with Redis',
content: 'Redis is a data structure server...',
author: 'Alice',
created_at: '1710777600',
});
// Full-text search
const results = await client.ft.search('posts:idx', 'redis data structure', {
LIMIT: { offset: 0, count: 10 },
SORT_BY: { BY: 'created_at', DIRECTION: 'DESC' },
});
console.log(results.documents);
Redis Search handles: tokenization, stemming, phrase queries, fuzzy matching. It''s faster than Elasticsearch for most use cases and requires no separate infrastructure.
Building a Session Store
Redis is a standard choice for sessions. Store encrypted session data, set TTL for auto-expiry.
import { createClient } from 'redis';
const client = createClient({
socket: {
host: 'localhost',
port: 6379,
},
});
await client.connect();
// Store session
await client.setEx(
`session:${sessionId}`,
3600, // 1 hour TTL
JSON.stringify({
userId: 123,
email: 'user@example.com',
cart: [{ id: 1, qty: 2 }],
})
);
// Retrieve session
const session = await client.get(`session:${sessionId}`);
if (session) {
console.log(JSON.parse(session));
}
// Update session
await client.expire(`session:${sessionId}`, 3600); // Extend expiry
TTL is automatic. After 1 hour, the key expires and is deleted. No cleanup jobs needed.
Leaderboard with Sorted Sets
Ranked data: game scores, user rankings, top products by revenue.
import { createClient } from 'redis';
const client = createClient();
await client.connect();
// Add scores
await client.zAdd('leaderboard', [
{ score: 1500, member: 'alice' },
{ score: 1200, member: 'bob' },
{ score: 1800, member: 'charlie' },
]);
// Get top 10
const topPlayers = await client.zRangeByScore('leaderboard', '-inf', '+inf', {
REV: true,
LIMIT: { offset: 0, count: 10 },
});
console.log(topPlayers); // ['charlie', 'alice', 'bob']
// Get rank
const rank = await client.zRevRank('leaderboard', 'alice');
console.log(`Alice is rank ${rank + 1}`);
// Increment score
await client.zIncrBy('leaderboard', 100, 'alice');
Sorted sets are fast: O(log N) for insertion, O(log N) for queries. Leaderboards with millions of players are trivial.
Rate Limiting with Sliding Window
Prevent abuse: limit requests per user, per IP, per API key.
import { createClient } from 'redis';
const client = createClient();
await client.connect();
async function rateLimit(userId: string, limit: number, windowSeconds: number) {
const key = `rate_limit:${userId}`;
const now = Math.floor(Date.now() / 1000);
// Use a ZSET to store timestamps
// Remove old entries outside the window
await client.zRemRangeByScore(key, 0, now - windowSeconds);
// Count remaining entries
const count = await client.zCard(key);
if (count >= limit) {
return { allowed: false, retryAfter: windowSeconds };
}
// Add current request
await client.zAdd(key, { score: now, member: `${now}-${Math.random()}` });
// Set expiry on key
await client.expire(key, windowSeconds);
return { allowed: true };
}
// Usage
const result = await rateLimit('user:123', 100, 60); // 100 requests per minute
if (!result.allowed) {
console.log(`Rate limited. Retry after ${result.retryAfter}s`);
}
Sliding window is more accurate than fixed windows. Users get a consistent rate, not bursty resets.
Pub/Sub vs Streams for Real-Time
Pub/Sub is simple but unreliable—if a subscriber misses a message, it''s gone.
Streams are persistent—every message is stored, allowing subscribers to replay.
Pub/Sub: Good for notifications that don''t need history (user typing, presence updates).
Streams: Good for events that require history (chat messages, transactions, orders).
// Pub/Sub: Fire and forget
await client.publish('notifications', JSON.stringify({ userId: 123, message: 'New order' }));
// Subscribe
await client.subscribe('notifications', (message) => {
console.log('Notification:', JSON.parse(message));
});
// Streams: Persistent events
const id = await client.xAdd('events', '*', {
event: 'order:created',
orderId: '456',
amount: '99.99',
});
// Consumer groups: track offset per consumer
await client.xGroupCreate('events', 'order-processors', '$', { MKSTREAM: true });
const messages = await client.xReadGroup(
{ key: 'events', group: 'order-processors', consumer: 'processor-1' },
{ count: 10 }
);
for (const msg of messages) {
console.log(msg);
// Process...
await client.xAck('events', 'order-processors', msg.id);
}
Streams are more complex but enable reliable event processing. Use for critical workflows.
Redis Sentinel vs Redis Cluster
Sentinel: Replication with automatic failover. Master-replica setup. Suitable for single-region deployments.
Cluster: Sharded across multiple nodes. Handles multi-region. More complex but scales linearly.
For most startups, Sentinel is sufficient. Cluster is for >100GB datasets or very high throughput.
Configure Sentinel:
# sentinel.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
Configure Cluster:
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
Cluster requires managing 6+ nodes. Sentinel requires 3. Simpler is often better.
Upstash Redis for Serverless
Upstash provides Redis-compatible serverless infrastructure. No setup, automatic scaling, HTTP API.
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});
// Works in Cloudflare Workers, Vercel Functions, etc.
export default async function handler(req: Request) {
const count = await redis.incr('requests');
return new Response(`Requests: ${count}`);
}
No persistent connections, no cold starts. Perfect for edge functions.
Pricing: based on commands, not hours. Pay-per-use.
When to Use Redis
Use Redis for:
- Caching (still its primary use case)
- Sessions (proven, fast)
- Leaderboards (sorted sets are perfect)
- Real-time metrics (time series)
- Pub/Sub notifications
- Rate limiting
- Job queues (with Bull)
- Vector search (<10M vectors)
Don''t use Redis for:
- Primary data store (use PostgreSQL)
- ACID transactions (Redis transactions are weaker)
- Complex joins (use SQL)
- Persistent audit logs (data can be evicted)
Redis is a cache and helper database, not a primary datastore. Design accordingly.
Checklist
- Set up Redis locally or use Upstash
- Create a session store with TTL
- Build a leaderboard with sorted sets
- Implement rate limiting
- Test pub/sub notifications
- Create a stream for events
- Set up consumer groups
- Benchmark caching vs no-cache
- Monitor Redis memory usage
- Plan backup and replication strategy
Conclusion
Redis evolved from a cache into a multi-model database. The Stack (vectors, time series, JSON, search) makes it viable for use cases once reserved for specialized databases.
For most applications, Redis + PostgreSQL is a powerful combo. PostgreSQL for durable data and complex queries, Redis for speed and specialized data models.
The simplicity is the strength. Redis is easy to reason about and operate. Embrace it beyond caching, but don''t let it replace your primary database.
Use the right tool for each job. Redis is invaluable for speed.