Migrating from Redis
Swytch’s Redis mode is 100% command-compatible with Redis. Same wire protocol (RESPv2 and RESPv3), same commands, same ACL format, same client libraries. Migrating doesn’t mean rewriting your application; it means moving traffic safely. This page is the playbook for that: shadow testing, dual-write, cutover, and rollback, with Redis available as a fallback at every step until the day you decommission it.
Migrations are disruptive enough that “it’s newer” isn’t a reason. The reasons teams move a Redis workload to Swytch usually come down to three things:
- Operations. There is no Sentinel to configure, no hash slots to rebalance, no failover runbooks to write. Every node is a peer, and cluster membership is a DNS name. Swytch vs Redis covers the architecture.
- Cost. Swytch runs as a sidecar on the application servers you already operate, using RAM they already have. For most deployments, the dedicated cache tier disappears from the bill entirely. Economics of Swytch has the numbers.
- Multi-region. Every node accepts writes, and adding a region is adding a node, so active-active is the default topology rather than an enterprise add-on. Swytch vs Redis (Enterprise) compares against the commercial product.
There is a correctness upgrade too: MULTI/EXEC in Swytch is a serializable transaction, so the WATCH-and-retry
loops Redis requires for atomicity become optional.
If none of that applies to your workload, staying on Redis is a reasonable choice, and the comparison pages linked above are explicit about where Redis remains the better fit. If it does apply, the rest of this page is the migration.
| Phase | Duration | Risk level | Rollback |
|---|---|---|---|
| Preparation | Days-weeks | None | N/A |
| Shadow testing | Days | Low | Instant |
| Dual-write | Hours-days | Low | Instant |
| Cutover | Minutes | Medium | Minutes |
| Validation | Hours-days | Low | Minutes |
| Cleanup | Days | None | N/A |
Review your application’s Redis usage against supported commands. A few Redis features map to architectural differences rather than missing commands:
| Feature | Swytch | Notes |
|---|---|---|
| Client-side cluster mode | Not needed | Swytch uses its own leaderless clustering |
| Primary-replica replication | Not used | Swytch replicates synchronously to subscribers; cluster membership via --join |
Multiple databases (SELECT) | Single database | SELECT 0 is accepted for compatibility; run a separate instance per database |
| Persistence (RDB/AOF) | Different model | Data lives in RAM across subscribed nodes; Swytch Cloud adds durable storage |
| Modules (RediSearch, RedisJSON) | Not supported | No module system yet |
| Lua cjson/cmsgpack | Not supported | Use application-side serialization |
| ACLs | Supported | Use --aclfile (Redis-compatible ACL format) |
| TLS | Supported | Native TLS and mTLS via --tls-* flags |
The first two rows aren’t missing features; they’re places where Redis’s single-primary model and Swytch’s leaderless model differ at the architecture level. Your application’s Redis client connects to Swytch the same way it connects to Redis; what happens behind the wire protocol is what’s different.
One behavior difference deserves a specific code audit: commands inside MULTI return their actual values, not
QUEUED placeholders. Code that parses for QUEUED needs to adjust. Code that ever wanted intermediate values inside
an atomic block gets them for free, which removes most reasons to reach for Lua.
Deploy Swytch as a sidecar next to your application, using a different port from your existing Redis:
# Sidecar deployment on the same host as the app
swytch redis --port 6380 --maxmemory 4gb --metrics-port 9090
The application will connect to both Redis and Swytch over localhost during the shadow and dual-write phases. This is also the deployment shape you keep after the migration: the app talks to its local node, and the network hop moves to replication between nodes instead of sitting between your app and its cache.
Modify your application to write to both Redis and Swytch:
import redis
class DualWriteClient:
def __init__(self, primary_host, shadow_host):
self.primary = redis.Redis(host=primary_host, port=6379)
self.shadow = redis.Redis(host=shadow_host, port=6380)
self.shadow_enabled = True
def set(self, key, value, **kwargs):
# Always write to primary
result = self.primary.set(key, value, **kwargs)
# Shadow write (fire-and-forget, don't block on errors)
if self.shadow_enabled:
try:
self.shadow.set(key, value, **kwargs)
except Exception as e:
# Log but don't fail the request
logger.warning(f"Shadow write failed: {e}")
return result
def get(self, key):
# Read from primary only during shadow phase
return self.primary.get(key)
After the cache warms up, add shadow reads to compare results:
def get_with_validation(self, key):
primary_result = self.primary.get(key)
if self.shadow_enabled:
try:
shadow_result = self.shadow.get(key)
if primary_result != shadow_result:
logger.error(f"Mismatch for {key}: primary={primary_result}, shadow={shadow_result}")
metrics.increment("shadow_mismatch")
else:
metrics.increment("shadow_match")
except Exception as e:
logger.warning(f"Shadow read failed: {e}")
return primary_result
Run this for at least one full cache TTL cycle to validate consistency.
Once validation passes:
- Update connection strings to point to Swytch.
- Keep Redis running as fallback.
- Monitor closely for the first hour.
# Feature flag cutover
if feature_flags.get("use_swytch"):
cache = redis.Redis(host="swytch-host", port=6379)
else:
cache = redis.Redis(host="redis-host", port=6379)
If issues arise:
- Immediate. Flip a feature flag back to Redis.
- Data sync. If Swytch had writes Redis doesn’t have, replay from application logs or accept data loss for cache.
- Post-mortem. Analyze what went wrong before retrying.
After running successfully for 1–2 weeks:
- Remove dual-write code.
- Decommission the old Redis instances, along with the Sentinel processes or Cluster tooling that managed them.
- Update monitoring and runbooks. The failover runbooks come out entirely; there is no failover to run.
RIOT can replicate data between Redis instances. Since Swytch doesn’t respond to Redis replication commands, use RIOT’s scan mode:
riot-redis -u redis://old-redis:6379 replicate -u redis://swytch:6380 --mode scan
For cache workloads, the simplest path is to let the application repopulate Swytch naturally:
- Deploy Swytch with an empty database.
- Point the application at Swytch.
- Cache misses hit the source of truth and populate Swytch.
- After one TTL cycle, the cache is fully warm.
This is often the safest approach for cache workloads, since there’s no data-transfer step that could fail mid-migration.
Before declaring migration complete:
- All application endpoints tested
- Hit rate matches or exceeds the previous cache
- Latency p50/p99 within acceptable range
- No error rate increase
- Memory usage stable
- Metrics and alerts configured
- Runbooks updated
- On-call team briefed
- Rollback tested and documented
| Issue | Cause | Solution |
|---|---|---|
| Higher miss rate after cutover | Cache not warmed | Pre-warm, or accept temporary misses |
| Latency spike | Cold cache | Increase --maxmemory or wait for warm |
| Connection errors | Client timeout too aggressive | Increase client timeout |
| Memory growing unexpectedly | Different overhead than Redis | Adjust --maxmemory based on actual |
| Command not supported | Using an unsupported Redis feature | Check compatibility, modify app |