Skip to main content

Upstash Redis vs Redis Cloud vs Valkey

·APIScout Team
upstashredisvalkeycachingrate-limitingserverless2026

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/ratelimit is 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.

Comments