Skip to main content

Redis as In-Memory Database

While commonly used as a cache, Redis can serve as a primary database for workloads where speed matters more than ACID guarantees and where the dataset fits in RAM. Understanding its persistence and durability characteristics is essential for senior engineers.


Redis as Primary DB vs Cache

DimensionRedis as CacheRedis as Primary DB
Data loss toleranceAcceptableNot acceptable
Dataset sizeCan exceed RAM (use eviction)Must fit in RAM
PersistenceOptionalRequired (RDB + AOF)
ConsistencyEventualDepends on config
Recovery timeFast (warm cache)Depends on AOF size
Use caseSpeed layer on top of real DBSessions, leaderboards, real-time data

Persistence Mechanisms

1. RDB (Redis Database Snapshot)

Creates a point-in-time binary snapshot of the entire dataset via fork().

# redis.conf
save 3600 1 # Snapshot if 1+ change in last 3600s
save 300 100 # Snapshot if 100+ changes in last 300s
save 60 10000 # Snapshot if 10000+ changes in last 60s

# Manual trigger
BGSAVE # Forks Redis process, writes snapshot in background
LASTSAVE # Unix timestamp of last successful save

# Disable all saving
save "" # Pure cache mode — no persistence

How RDB fork() works:

Main Redis Process (parent)
↓ fork()
Child Process (snapshot writer)

Writes binary .rdb file to disk

Main process continues serving requests.
Copy-on-Write (CoW): modified pages are copied when written.
Both processes see the same memory initially.

RDB trade-offs:

ProCon
Compact single fileData loss between snapshots
Fast DB load on restartfork() can cause latency spike (large heaps)
Good for backupsNot suitable for strict durability
Minimal IO during operation10GB heap → 100–200ms latency spike on fork

Monitoring RDB fork latency:

INFO latencystats
LATENCY HISTORY fork
# Or check: rdb_last_bgsave_time_sec

2. AOF (Append-Only File)

Logs every write command to disk. On restart, Redis replays the AOF to rebuild the dataset.

# redis.conf
appendonly yes
appendfsync everysec # Recommended — sync to disk every second
# appendfsync always # Sync after every write (slowest, most durable)
# appendfsync no # OS decides when to flush (fastest, least safe)

AOF fsync modes:

ModeDurabilityPerformanceRisk
alwaysMax (~0 data loss)Slow (disk write per command)Low throughput
everysec~1 second of data lossGoodRecommended
noOS-controlledFastestUp to kernel buffer loss on crash

AOF Rewrite (compaction):

# AOF grows indefinitely — rewrite compresses it
BGREWRITEAOF # Background AOF rewrite

# Auto-rewrite when AOF grows
auto-aof-rewrite-percentage 100 # Rewrite when AOF doubles in size
auto-aof-rewrite-min-size 64mb # Minimum size before rewrite

During rewrite, Redis forks and writes a new AOF from current in-memory state. Parent continues appending to the old AOF; differences are merged when the child completes.

AOF trade-offs:

ProCon
Near-real-time durabilityLarger file than RDB
Can reconstruct to exact stateSlower restart (replay all commands)
Human-readable formatAOF rewrite causes another fork() spike
everysec = at most 1s data lossOld commands not compacted until rewrite

Use both for the best of both worlds:

appendonly yes
appendfsync everysec
save 3600 1
save 300 100

# On restart: Redis prefers AOF (more complete); RDB used as backup
aof-use-rdb-preamble yes # Redis 4.0+: AOF starts with RDB snapshot header → faster rewrite

Recovery scenarios:

FailureRDB onlyAOF onlyBoth
Server crashLose since last snapshotLose up to 1sLose up to 1s
Corrupted AOFN/ANeed redis-check-aof repairRDB as fallback
Disk failureLose everythingLose everythingSame

Data Modeling for Redis as Primary DB

Use Case: User Sessions

# One hash per session (rich structure, partial field update)
HSET session:abc123 userId "1234" role "admin" expiresAt "1700000000"
EXPIRE session:abc123 3600

# Index by userId (for "show all sessions for user")
SADD user:1234:sessions "abc123"

Use Case: Real-Time Leaderboard

# Sorted Set — atomic, O(log N) updates
ZADD game:leaderboard 15000 "alice"
ZADD game:leaderboard 12500 "bob"
ZINCRBY game:leaderboard 500 "alice" # Atomic score increment
ZREVRANK game:leaderboard "alice" # Rank (0-indexed) → 0 (first place)
ZREVRANGEBYSCORE game:leaderboard +inf -inf WITHSCORES LIMIT 0 10 # Top 10

Use Case: Rate Limiting (Primary Store)

-- Lua script: atomic sliding window rate limit
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random())
redis.call('PEXPIRE', key, window * 1000)
return 1 -- allowed
end
return 0 -- blocked

Use Case: Distributed Lock (Redlock)

# Single-node lock (sufficient for most cases)
SET lock:resource "owner-uuid" NX EX 30
# NX: only set if not exists
# EX: auto-release after 30s (prevents deadlock if owner crashes)

# Release lock (Lua for atomicity — don't release if owner changed)
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0

Redlock (multi-node): For stronger guarantees, acquire lock on N/2+1 nodes:

# Redlock algorithm (5 Redis nodes)
nodes = [redis1, redis2, redis3, redis4, redis5]
acquired = sum(1 for r in nodes
if r.set("lock:key", token, nx=True, ex=30))
if acquired >= 3: # Majority
return True # Lock acquired

Clock drift caveat: Redlock assumes clocks don't drift significantly. Martin Kleppman's critique: use fencing tokens (monotonic counter) for true linearizability.


Durability Guarantees by Configuration

ConfigMax Data LossThroughputUse Case
No persistence100%HighestPure cache
RDB only (60s)Up to 60sHighCache with backup
AOF noOS bufferHighBalanced
AOF everysecUp to 1sMedium-HighRecommended primary
AOF always~0LowCritical data
RDB + AOF everysecUp to 1sMedium-HighProduction DB

Redis vs Traditional Databases

RedisPostgreSQL / MySQLMongoDB
StorageIn-memory (optional disk)Disk-firstDisk-first
Throughput100K–1M+ ops/sec1K–100K ops/sec10K–100K ops/sec
Query languageCommand-basedSQLQuery DSL
Joins❌ (model differently)❌ (embed/link)
TransactionsLimited (MULTI/EXEC)Full ACIDMulti-doc (limited)
Dataset sizeLimited by RAMUnlimitedUnlimited
Full-text searchLimitedpgvector, FTSAtlas Search
Best forSpeed-critical, structured dataComplex queries, ACIDFlexible documents

When to Use Redis as Primary DB

Good fit:

  • Sessions, tokens, temporary data with natural TTL
  • Leaderboards, rankings, counters
  • Rate limiting, quota tracking
  • Short-lived job/task queues
  • Real-time collaborative features (cursors, presence)
  • Configuration/feature flags

Bad fit:

  • Complex relational queries (joins, aggregations)
  • Large datasets that don't fit in RAM
  • Long-term audit logs (use Elasticsearch or cold storage)
  • Financial transactions requiring ACID guarantees
  • Full-text search (use Elasticsearch)