DEV Community

niuniu
niuniu

Posted on

I Ran the Same KV Workload on Upstash, Redis Cloud, and Self-Hosted Valkey — One Clear Winner

My Discord webhook project (the one I deployed on Fly.io last week) started getting duplicate events — GitHub retries webhooks when your endpoint is slow, and my deduplication logic was just an in-memory Python set. Restart the container, lose the set, process everything twice.

I needed a KV store with TTL. Same workload, three approaches: Upstash (serverless Redis), Redis Cloud (managed free tier), and Valkey (the open-source Redis fork) self-hosted on the same Fly.io VM.

The Workload

# Deduplication: store event ID with 24h TTL, check before processing
import time, os

def is_duplicate(event_id: str) -> bool:
    key = f"gh:event:{event_id}"
    if kv.exists(key):
        return True
    kv.setex(key, 86400, "1")  # 24h TTL
    return False
Enter fullscreen mode Exit fullscreen mode

Traffic: ~300 webhook events/day, 99.8% reads on duplicates (GitHub retries aggressively), sub-10ms lookup target.

The Contenders

Upstash Redis Cloud Valkey (self-hosted)
Free tier 10K commands/day 30MB storage Unlimited (your RAM)
Latency (same region) 8-15ms 12-25ms 0.3ms (localhost)
Cold start penalty Pay-per-request Always-on Always-on
Setup time 2 min 5 min 20 min
Data persistence You configure it

The Results (7 Days, 2,147 Events)

Metric Upstash Redis Cloud Valkey
Daily command usage 8,412 / 10K N/A (storage-based) N/A
Storage used 1.2MB 1.2MB / 30MB 1.2MB / 256MB
p50 lookup latency 11ms 18ms 0.4ms
p99 lookup latency 47ms 89ms 1.2ms
Free tier headroom 16% left 96% left Infinite
Monthly cost at this usage $0 $0 $0

The Plot Twist

Upstash's 10K commands/day free tier sounds generous until you realize GitHub's retry logic burns 25 commands per event (each retry is a new command). My 300 events/day became 8,400 commands. Two busy days and I'd be rate-limited into the paid tier ($0.2 per 100K commands — still cheap, but not free).

Redis Cloud's 30MB is storage-based, not command-based. My 1.2MB of event IDs fits forever. But 18ms p50 latency from Fly.io (Singapore) to Redis Cloud (AWS us-east-1) was the slowest option.

Valkey on the same VM: 0.4ms p50. It's just Redis with a different license and governance. Setup was one docker run command:

docker run -d --name valkey -p 6379:6379 \
  --restart unless-stopped \
  valkey/valkey:7.2-alpine \
  valkey-server --maxmemory 64mb --maxmemory-policy allkeys-lru
Enter fullscreen mode Exit fullscreen mode

The Verdict

Use Case Winner Why
Bursty, low-volume Upstash Pay-per-request scales to zero
Small persistent cache Redis Cloud 30MB storage-based free tier, no command counting
Co-located with your app Valkey Sub-millisecond, no egress, no surprises

For my webhook dedup (same VM, tiny data, latency-sensitive), Valkey wins by a mile. If I were building a serverless function that sleeps between invocations, Upstash's pay-per-request model would be the only sane choice.

The Tooling

I sketched the benchmark harness and the Valkey Docker setup with MonkeyCode — the free AI coding assistant that runs entirely on your machine. It generated the retry-aware command counting logic that caught Upstash's free tier trap: https://ly.cyberserval.tech/iIETXiF


What's your free-tier KV strategy? I keep going back and forth on whether "self-hosted on the same box" is cheating or just good architecture. Convince me in the comments.

Top comments (0)