Upstash Redis vs Redis Cloud vs Valkey
TL;DR
Upstash for serverless. Redis Cloud for always-on production workloads. Valkey for self-hosted. Upstash's HTTP API makes it the only Redis that works in edge environments (Cloudflare Workers, Vercel Edge) and its per-request pricing means $0 when you're not using it. Redis Cloud is the managed version of standard Redis — better performance at high throughput but $70/month minimum. Valkey (the Redis open-source fork created after Redis re-licensed in 2024) is now the go-to for self-hosted, with growing cloud support.
Key Takeaways
- Upstash: HTTP API, works in Cloudflare Workers/edge, $0.20/100K commands, free 10K/day
- Redis Cloud: lowest latency (<1ms), persistent connections, $70/month minimum for production
- Valkey: 100% Redis-compatible open-source fork, self-hosted via Docker
- Edge requirement: only Upstash supports Cloudflare Workers (HTTP, no persistent connections)
- Rate limiting: Upstash
@upstash/ratelimitis the standard for Next.js/edge apps - Session storage: all three work; Redis Cloud/Valkey have lower latency for session-heavy apps
Upstash: Redis for Serverless
Best for: Cloudflare Workers, Vercel Edge, Next.js, any serverless function that can't hold persistent connections.
// npm install @upstash/redis
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// All standard Redis operations:
await redis.set('key', 'value', { ex: 3600 }); // With TTL
const value = await redis.get<string>('key');
await redis.del('key');
// Hash:
await redis.hset('user:123', { name: 'Alice', plan: 'pro' });
const user = await redis.hgetall<{ name: string; plan: string }>('user:123');
// Lists:
await redis.lpush('queue', 'job1', 'job2');
const job = await redis.rpop('queue');
// Sets:
await redis.sadd('active-users', 'user:123', 'user:456');
const count = await redis.scard('active-users');
Rate Limiting with @upstash/ratelimit
This is Upstash's killer feature for Next.js and edge apps:
// npm install @upstash/ratelimit @upstash/redis
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { NextRequest, NextResponse } from 'next/server';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
analytics: true, // Track rate limit hits in Upstash console
});
// Next.js middleware:
export async function middleware(request: NextRequest) {
const ip = request.ip ?? '127.0.0.1';
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Rate limit exceeded. Try again later.' },
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString(),
},
}
);
}
return NextResponse.next();
}
export const config = {
matcher: '/api/:path*',
};
// Different limits per plan:
const rateLimiters = {
free: new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10/minute
}),
pro: new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(100, '1 m'), // 100/minute
}),
};
export async function POST(request: NextRequest) {
const session = await getSession();
const limiter = session?.user?.plan === 'pro' ? rateLimiters.pro : rateLimiters.free;
const { success } = await limiter.limit(session?.user?.id ?? request.ip ?? '127.0.0.1');
if (!success) return new Response('Rate limited', { status: 429 });
// ... your API logic
}
Caching with Upstash
// Simple cache wrapper:
async function cachedQuery<T>(
key: string,
ttlSeconds: number,
query: () => Promise<T>
): Promise<T> {
const cached = await redis.get<T>(key);
if (cached !== null) return cached;
const result = await query();
await redis.set(key, result, { ex: ttlSeconds });
return result;
}
// Usage:
const user = await cachedQuery(
`user:${userId}`,
300, // 5 minutes
() => db.users.findUnique({ where: { id: userId } })
);
Upstash Pricing
Free: 10,000 commands/day
Pay-as-you-go: $0.20/100K commands
For reference:
1M API requests, each doing 1 Redis read: $2/month
10M API requests, each doing 2 Redis ops: $40/month
Standard plans (always-on, lower latency):
$10/month: 100M commands/month
$25/month: 500M commands/month
Redis Cloud: Production-Grade
Best for: high-throughput apps needing <1ms latency, persistent connections, complex data structures.
// Standard Redis client (ioredis):
import Redis from 'ioredis';
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT ?? '6379'),
password: process.env.REDIS_PASSWORD,
tls: {}, // Redis Cloud requires TLS
retryStrategy: (times) => Math.min(times * 50, 2000),
maxRetriesPerRequest: 3,
});
// Connection pooling for serverless (use lazyConnect):
const redis = new Redis({
host: process.env.REDIS_HOST,
password: process.env.REDIS_PASSWORD,
tls: {},
lazyConnect: true,
});
await redis.connect();
// Pipeline for batching multiple operations (reduces round trips):
const pipeline = redis.pipeline();
pipeline.get('key1');
pipeline.get('key2');
pipeline.set('key3', 'value3', 'EX', 300);
const results = await pipeline.exec();
// [null, 'value1'], [null, 'value2'], [null, 'OK']
// Pub/Sub for real-time features:
const subscriber = new Redis({ host: process.env.REDIS_HOST, password: process.env.REDIS_PASSWORD });
const publisher = new Redis({ host: process.env.REDIS_HOST, password: process.env.REDIS_PASSWORD });
await subscriber.subscribe('notifications');
subscriber.on('message', (channel, message) => {
const notification = JSON.parse(message);
// Handle notification...
});
// Publish from another process:
await publisher.publish('notifications', JSON.stringify({
userId: '123',
type: 'message',
content: 'New message from Alice',
}));
Redis Cloud Pricing
Free: 30MB (dev/testing only)
Essentials: $7/month (250MB, no persistence)
Production:
$43/month: 1GB RAM, persistence, replication
$107/month: 3GB RAM
Redis Cloud requires minimum $43/month for production features.
Compare to Upstash: $10/month for equivalent workloads at moderate QPS.
Redis Cloud wins at >1M commands/day where per-request pricing adds up.
Valkey: Self-Hosted Redis (Open Source)
Valkey is the Redis fork created by ex-Redis engineers after Redis changed its license in 2024. It's now the default "open source Redis" maintained by the Linux Foundation.
# docker-compose.yml — run Valkey locally or on a VPS:
services:
valkey:
image: valkey/valkey:8
ports:
- "6379:6379"
command: valkey-server --save 60 1 --loglevel warning --requirepass ${REDIS_PASSWORD}
volumes:
- valkey_data:/data
restart: unless-stopped
volumes:
valkey_data:
# Valkey CLI:
valkey-cli -a $REDIS_PASSWORD ping
# PONG
# Or use redis-cli (100% compatible):
redis-cli -h localhost -a $REDIS_PASSWORD
Valkey is 100% drop-in compatible with Redis — any Redis client works:
// Works with any Redis client (ioredis, redis npm package):
import Redis from 'ioredis';
const valkey = new Redis({
host: 'your-vps-ip',
port: 6379,
password: process.env.REDIS_PASSWORD,
});
// Exact same API as Redis
await valkey.set('test', 'hello');
const val = await valkey.get('test');
Valkey monthly cost: just your VPS ($5-20/month on DigitalOcean/Hetzner).
Common Patterns: Session Storage
// Session storage with any Redis:
// (Upstash example, same for Redis Cloud/Valkey)
const SESSION_TTL = 7 * 24 * 60 * 60; // 7 days in seconds
async function createSession(userId: string): Promise<string> {
const sessionId = crypto.randomUUID();
await redis.set(
`session:${sessionId}`,
JSON.stringify({ userId, createdAt: Date.now() }),
{ ex: SESSION_TTL }
);
return sessionId;
}
async function getSession(sessionId: string) {
const data = await redis.get<{ userId: string; createdAt: number }>(`session:${sessionId}`);
return data;
}
async function deleteSession(sessionId: string) {
await redis.del(`session:${sessionId}`);
}
// Extend session on activity:
async function extendSession(sessionId: string) {
await redis.expire(`session:${sessionId}`, SESSION_TTL);
}
Which One to Use
Use UPSTASH if:
→ Serverless / edge functions (required for Cloudflare Workers)
→ Irregular traffic (scale to zero cost)
→ Rate limiting in Next.js middleware
→ Simple caching with per-request billing
→ Cost: $0 at low volume, predictable at scale
Use REDIS CLOUD if:
→ Long-running servers (not serverless)
→ >10M commands/day (per-request pricing gets expensive)
→ Need <1ms latency with persistent connections
→ Complex pub/sub or Redis Streams
→ Enterprise support needed
Use VALKEY (self-hosted) if:
→ Full control required
→ Data sovereignty / compliance
→ Cost optimization at scale (VPS cost only)
→ Already running own infrastructure
→ Development and staging environments
Find and compare caching and Redis-compatible APIs at APIScout.