Contents

Redis

Core Redis Archtecture

Process Model (Single-threaded Core)

Redis using single-threaded event loop for command processing. It’s extremely fast because:

  • No locks -> no thread contention.
  • Uses I/O multiplexing (epoll/kqueue) to handle thousands of client connections.
  • Heavy wrok (AOF rewrite, RDB save) offloaded to background processses.

📌 Key insight: Redis trades CPU parallelism for predictable latency and simplicity.

Data Structures (In-Memory Storage)

Redis isn’t just key-value store: it provides specialized data structures. Each is optimized for memory and speed.

TypeExample CommandUse Case
StringINCR counterCounters, cache
ListLPUSH queue job1Task queues
HashHSET user:1 name AliceUser profiles
SetSADD tags redis nosqlUnique tags
Sorted SetZADD leaderboard 200 BobLeaderboards
StreamXADD mystream * msg hiEvent sourcing
NEW: JSONJSON.SET user:1 $.city '"Paris"'Document store
NEW: Vector SetVSET myvec 1 [0.1,0.9]Semantic search
NEW: ProbabilisticBF.ADD seen user42Deduplication, top-K
NEW: Time SeriesTS.ADD sensor:1 167000 temp 22.5IoT metrics, monitoring

NEW: supported in Redis 8.0

Memory Model

  • SDS (Simple Dynamic Strings): safe strings, O(1) length.
  • Encodings:
    • Small hash -> ziplist; grows -> hashtable.
    • Set of ints -> intset; grows -> hashtable.
  • Redis 8 Optimizations:
    • Vector data can use BFLOAT16 /FLOAT16.
    • JSON memory defragmentation.

Persistence

Redis can persist data for recovery:

  • RDB Snapshots: fast restart, some data loss.
  • AOF: append-only, durable, bigger file.
  • Hybrid: default in Redis 8

JSON & Has Enhancements

  • Partial Updates: update only one field inside JSON.
1
JSON.SET user:1 $.address.city '"Paris"'
  • Field-Level TTL: expire one field in a hash.
1
HPEXPIREFIELD profile:123 session_token 60000
  • Better Memory Efficiency: JSON defragmentation reduces latency spikes.

Time Series Enhancements

At its core, a time series is just timestamp + value pairs. RedisTimeSeries builds efficient structures on top of Redis primitives.

How Data is Stored

  • Each series is implemented as an append-only chunk list:
    • Chunk = fixed-size block of consecutive samples (timestamps + values).
    • New samples go into the active chunk until full → new chunk created.
    • Chunks are kept sorted by time for efficient range queries.
  • Chunks are compressed in memory to reduce footprint (delta-of-delta for timestamps, Gorilla-like encoding for values).

Insertion

  • TS.ADD key timestamp value -> appends to the latest chunk.
  • If timestamp is out-of-order but still within chunk bounds, it’s inserted in place.
  • Late data allowed, depending on config.

Query Engine

Support efficient reads:

  • Range queries -> TS.RANGE key from to.
  • Aggregations (avg, min, max, sum, count, stddev, percentile).
  • Downsampling via compaction rules.
1
2
TS.RANGE temp:room1 - +
  AGGREGATION avg 60000

(avg per 60s window).

Scaling with Large Datasets

RedisTimeSeries is designed to support millions of samples per second and billions stored.

How it scales:

  1. Chunk-based storage -> O(1) appends, memory-efficient.
  2. 2Retention policies -> old data auto-expired, keeps RAM manageable.
  3. Compaction rules -> store raw + aggregates, so queries don’t scan billions.
  4. Multi-key partitioning -> year can shard by metric, location, or device.
  5. Redis Cluster -> distribute series across many masters/replicas for horizontal scale.
Core Query Language Concepts

Redis TimeSeries provides a powerful query language that operates on two fundamental levels:

  • Individual time series operations (TS.GET, TS.RANGE)
  • Multi-series operations with label filtering (TS.MGET, TS.MRANGE, TS.MREVRANGE)
Label-Based Indexing System

Every time series can have labels (key-value pairs) that create searchable indexes:

1
2
3
4
5
6
7
8
9
# Create time series with comprehensive labels for querying
TS.CREATE BTCUSDT:price:binance:tick 
    LABELS symbol BTCUSDT 
           type price 
           exchange binance 
           interval tick 
           market spot
           base BTC
           quote USDT

Setting Up Cryptocurrency Data Structure

Complete Multi-Exchange Trading System
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# ===== RAW TICK DATA SERIES =====
# Binance tick data
TS.CREATE BTCUSDT:price:binance:tick 
    RETENTION 86400000 
    LABELS symbol BTCUSDT type price exchange binance interval tick market spot

TS.CREATE BTCUSDT:volume:binance:tick 
    RETENTION 86400000
    LABELS symbol BTCUSDT type volume exchange binance interval tick market spot

# Kraken tick data
TS.CREATE BTCUSDT:price:kraken:tick 
    RETENTION 86400000
    LABELS symbol BTCUSDT type price exchange kraken interval tick market spot

TS.CREATE BTCUSDT:volume:kraken:tick 
    RETENTION 86400000
    LABELS symbol BTCUSDT type volume exchange kraken interval tick market spot

# Coinbase tick data
TS.CREATE BTCUSDT:price:coinbase:tick 
    RETENTION 86400000
    LABELS symbol BTCUSDT type price exchange coinbase interval tick market spot

# ===== 1-MINUTE CANDLESTICKS =====
TS.CREATE BTCUSDT:open:binance:1m 
    RETENTION 604800000
    LABELS symbol BTCUSDT type open exchange binance interval 1m market spot

TS.CREATE BTCUSDT:high:binance:1m 
    RETENTION 604800000
    LABELS symbol BTCUSDT type high exchange binance interval 1m market spot

TS.CREATE BTCUSDT:low:binance:1m 
    RETENTION 604800000
    LABELS symbol BTCUSDT type low exchange binance interval 1m market spot

TS.CREATE BTCUSDT:close:binance:1m 
    RETENTION 604800000
    LABELS symbol BTCUSDT type close exchange binance interval 1m market spot

TS.CREATE BTCUSDT:volume:binance:1m 
    RETENTION 604800000
    LABELS symbol BTCUSDT type volume exchange binance interval 1m market spot

# ===== 5-MINUTE CANDLESTICKS =====
TS.CREATE BTCUSDT:open:binance:5m 
    LABELS symbol BTCUSDT type open exchange binance interval 5m market spot

TS.CREATE BTCUSDT:high:binance:5m 
    LABELS symbol BTCUSDT type high exchange binance interval 5m market spot

TS.CREATE BTCUSDT:low:binance:5m 
    LABELS symbol BTCUSDT type low exchange binance interval 5m market spot

TS.CREATE BTCUSDT:close:binance:5m 
    LABELS symbol BTCUSDT type close exchange binance interval 5m market spot

TS.CREATE BTCUSDT:volume:binance:5m 
    LABELS symbol BTCUSDT type volume exchange binance interval 5m market spot

# ===== TECHNICAL INDICATORS =====
TS.CREATE BTCUSDT:sma20:binance:1h 
    LABELS symbol BTCUSDT type sma20 exchange binance interval 1h market spot indicator technical

TS.CREATE BTCUSDT:rsi14:binance:1h 
    LABELS symbol BTCUSDT type rsi14 exchange binance interval 1h market spot indicator technical
Automatic Candlestick Generation Rules
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Automatic 1-minute candlesticks from tick data
TS.CREATERULE BTCUSDT:price:binance:tick BTCUSDT:open:binance:1m 
    AGGREGATION first 60000

TS.CREATERULE BTCUSDT:price:binance:tick BTCUSDT:high:binance:1m 
    AGGREGATION max 60000

TS.CREATERULE BTCUSDT:price:binance:tick BTCUSDT:low:binance:1m 
    AGGREGATION min 60000

TS.CREATERULE BTCUSDT:price:binance:tick BTCUSDT:close:binance:1m 
    AGGREGATION last 60000

TS.CREATERULE BTCUSDT:volume:binance:tick BTCUSDT:volume:binance:1m 
    AGGREGATION sum 60000

# Automatic 5-minute candlesticks from 1-minute data
TS.CREATERULE BTCUSDT:open:binance:1m BTCUSDT:open:binance:5m 
    AGGREGATION first 300000

TS.CREATERULE BTCUSDT:high:binance:1m BTCUSDT:high:binance:5m 
    AGGREGATION max 300000

TS.CREATERULE BTCUSDT:low:binance:1m BTCUSDT:low:binance:5m 
    AGGREGATION min 300000

TS.CREATERULE BTCUSDT:close:binance:1m BTCUSDT:close:binance:5m 
    AGGREGATION last 300000

TS.CREATERULE BTCUSDT:volume:binance:1m BTCUSDT:volume:binance:5m 
    AGGREGATION sum 300000
Advanced Query Patterns
  1. Label-Based Discovery Queries
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Find all Bitcoin series
TS.QUERYINDEX symbol=BTCUSDT
# Returns: All series with BTCUSDT symbol

# Find all 1-minute candlestick data
TS.QUERYINDEX interval=1m
# Returns: All 1-minute interval series

# Find all Binance price data
TS.QUERYINDEX exchange=binance type=price
# Returns: All price series from Binance

# Complex filter: All OHLC data for Bitcoin on Binance at 1-minute intervals
TS.QUERYINDEX symbol=BTCUSDT exchange=binance interval=1m type=(open,high,low,close)
# Returns: 4 series (open, high, low, close)

# Find series WITHOUT specific labels (e.g., non-technical indicators)
TS.QUERYINDEX indicator=
# Returns: All series without 'indicator' label

# Wildcard patterns (all USD trading pairs)
TS.QUERYINDEX symbol=*USDT
# Returns: BTCUSDT, ETHUSDT, etc.

# Exclusion filters (all except volume)
TS.QUERYINDEX symbol=BTCUSDT type!=(volume)

# Multiple exchange filter
TS.QUERYINDEX symbol=BTCUSDT exchange=(binance,kraken,coinbase) type=price
  1. Time-Based Range Queries
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Get latest Bitcoin price from Binance
TS.GET BTCUSDT:price:binance:tick

# Get 1-minute candlesticks for last hour
TS.RANGE BTCUSDT:open:binance:1m - + 
    FILTER_BY_TS * -3600000
    
# Get candlesticks with specific timestamps
TS.RANGE BTCUSDT:close:binance:1m - + 
    FILTER_BY_TS 1700000000000 1700000060000 1700000120000

# Filter by value range (find prices between 44000 and 46000)
TS.RANGE BTCUSDT:price:binance:tick - + 
    FILTER_BY_VALUE 44000 46000

# Reverse order (newest first)
TS.REVRANGE BTCUSDT:close:binance:1m - + COUNT 100
  1. Multi-Series Queries with MRANGE
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Get latest prices from all exchanges
TS.MGET FILTER symbol=BTCUSDT type=price exchange=(binance,kraken,coinbase)

# Get all OHLCV data for specific time range
TS.MRANGE 1700000000000 1700003600000 
    WITHLABELS 
    FILTER symbol=BTCUSDT interval=1m exchange=binance

# Compare volumes across exchanges
TS.MRANGE - + 
    SELECTED_LABELS exchange 
    FILTER symbol=BTCUSDT type=volume interval=1m

# Get candlesticks with aggregation
TS.MRANGE - + 
    AGGREGATION avg 300000 
    FILTER symbol=BTCUSDT type=close interval=1m
  1. Multi-Series Operations

Cross-Exchange Arbitrage Detection

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Get real-time prices from all exchanges
TS.MGET FILTER symbol=BTCUSDT type=price interval=tick

# Response structure:
1) 1) "BTCUSDT:price:binance:tick"
   2) (empty array)
   3) 1) (integer) 1700000000000
      2) "45000.50"
2) 1) "BTCUSDT:price:kraken:tick"
   2) (empty array)
   3) 1) (integer) 1700000000000
      2) "45020.75"
3) 1) "BTCUSDT:price:coinbase:tick"
   2) (empty array)
   3) 1) (integer) 1700000000000
      2) "44995.25"

Volume-Weighted Average Price (VWAP) Across Exchanges

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Group by timestamp and calculate average price weighted by volume
TS.MRANGE - + 
    FILTER symbol=BTCUSDT type=price interval=1m 
    GROUPBY symbol 
    REDUCE avg

# Get aggregated volume across all exchanges
TS.MRANGE - + 
    FILTER symbol=BTCUSDT type=volume interval=1m 
    GROUPBY symbol 
    REDUCE sum
  1. Aggregation and Candlestick Generation

Dynamic Candlestick Aggregation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Generate 15-minute candlesticks on-the-fly from 1-minute data
TS.RANGE BTCUSDT:close:binance:1m - + 
    AGGREGATION last 900000

# Generate hourly high values from 5-minute data
TS.RANGE BTCUSDT:high:binance:5m - + 
    AGGREGATION max 3600000

# Time-weighted average for irregular tick data
TS.RANGE BTCUSDT:price:binance:tick - + 
    AGGREGATION twa 60000

Bucket Alignment Strategies

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Default alignment (from epoch)
TS.RANGE BTCUSDT:close:binance:1m - + 
    AGGREGATION avg 300000

# Align to start of query range
TS.RANGE BTCUSDT:close:binance:1m 1700000000000 1700003600000 
    AGGREGATION avg 300000 
    ALIGN start

# Align to end of range (useful for recent data)
TS.RANGE BTCUSDT:close:binance:1m - + 
    AGGREGATION avg 300000 
    ALIGN end

# Custom alignment to specific timestamp
TS.RANGE BTCUSDT:close:binance:1m - + 
    AGGREGATION avg 300000 
    ALIGN 1700000000000
  1. Redis Workbench Query Examples
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# ===== DISCOVERY QUERIES =====
# Find all available symbols
TS.QUERYINDEX symbol=*

# Find all series for a specific trading pair
TS.QUERYINDEX symbol=BTCUSDT

# Find all 1-minute candlestick series across all symbols
TS.QUERYINDEX interval=1m type=(open,high,low,close,volume)

# Find all technical indicators
TS.QUERYINDEX indicator=technical

# ===== REAL-TIME MONITORING =====
# Get latest prices from all exchanges for Bitcoin
TS.MGET FILTER symbol=BTCUSDT type=price interval=tick

# Monitor multiple symbols simultaneously
TS.MGET FILTER symbol=(BTCUSDT,ETHUSDT,BNBUSDT) type=price exchange=binance

# ===== HISTORICAL ANALYSIS =====
# Get hourly candlesticks for the last 24 hours
TS.MRANGE - + 
    COUNT 24 
    FILTER symbol=BTCUSDT interval=1h exchange=binance

# Compare volumes across exchanges for the last hour
TS.MRANGE - + 
    FILTER symbol=BTCUSDT type=volume interval=1m 
    GROUPBY exchange 
    REDUCE sum

# ===== AGGREGATION QUERIES =====
# Generate 4-hour candlesticks from 1-hour data
TS.RANGE BTCUSDT:close:binance:1h - + 
    AGGREGATION last 14400000

# Calculate VWAP (Volume-Weighted Average Price)
TS.RANGE BTCUSDT:price:binance:tick - + 
    AGGREGATION twa 3600000

# ===== PERFORMANCE ANALYSIS =====
# Find highest price in last 24 hours
TS.RANGE BTCUSDT:high:binance:1m - + 
    AGGREGATION max 86400000

# Find lowest price in last 24 hours
TS.RANGE BTCUSDT:low:binance:1m - + 
    AGGREGATION min 86400000

# Calculate price volatility (range)
TS.RANGE BTCUSDT:price:binance:tick - + 
    AGGREGATION range 3600000

Best Practices and Optimization

  1. Label Design Strategy Use hierarchical labels for efficient filtering Include both broad (symbol) and specific (exchange, interval) labels Add metadata labels (market type, base/quote currency)
  2. Query Performance Optimization Use specific filters to reduce result sets Create composite labels for frequently used combinations Leverage SELECTED_LABELS to reduce response size
  3. Memory Management Set appropriate retention policies per series Use compaction rules for automatic aggregation Monitor memory usage with TS.INFO
  4. Production Deployment Use connection pooling for high-throughput applications Implement circuit breakers for exchange API failures Cache frequently accessed aggregations

Redis Cluster Architecture Overview

Sharding with Hash Slots: Redis Cluster automatically shards data across multiple nodes using hash slots. There are 16,384 hash slots, and each key is assigned a slot by taking the CRC16 hash of the key and modding by 16,384. Each master node in the cluster is responsible for a subset of these slots (e.g. in a 3-node cluster, Node A might handle slots 0–5500, Node B 5501–11000, Node C 11001–16383). This design makes scaling transparent – slots (and the keys in them) can be moved between nodes to rebalance or add/remove nodes without downtime. redis.io

Masters and Replicas (Failover): Redis Cluster uses a master-replica model for high availability. You need at least 3 master nodes for a functional cluster (to allow majority consensus) serverfault.com. Each master can have one or more replicas (slaves) as copies. If a master fails, the cluster will automatically promote one of its replicas to master to maintain availability medium.com. (However, if an entire master group – the master and all its replicas – goes down, the hash slots served by that group become unavailable. The cluster cannot continue if a majority of masters are offline.) Redis Cluster’s built-in failover is self-healing – it detects a down master via gossip and elections over the cluster bus and triggers failover without external orchestration.

Cluster Bus and Gossip: In addition to the standard Redis client port (6379 by default), each node opens a cluster bus port at (client port + 10000) (e.g. 16379) for internal node-to-node communication. Nodes use the cluster bus to gossip cluster state, detect failures, coordinate slot migrations, and authorize failovers. This communication is binary and efficient, and clients do not use the bus port (only the regular Redis ports). Important: Both the client port and the bus port must be accessible between all nodes (no firewalls blocking them), or the cluster will not function properly.

Hash Slot Allocation & Multi-Key Operations: A key benefit of clustering is that it can continue operations if some nodes fail (as long as the slots are covered by remaining masters). However, because data is sharded, multi-key operations in Redis (transactions, scripts, MSET/MGET, etc.) are limited to keys that reside on the same shard. Redis Cluster ensures the highest throughput when a multi-key command or transaction involves keys in the same hash slot, since it can be executed by a single node without cross-node communication redis.io . If you send a command involving keys from different slots, the cluster will return a CROSSSLOT error and refuse the operation (certain read commands like MGET have special handling, but in general cross-slot writes/transactions are not allowed) dragonflydb.io. To handle use cases that need multiple related keys, Redis Cluster offers hash tags: you can wrap a portion of the key name in {…} braces to force that part to be hashed, ensuring keys with the same tag go to the same slot. For example, keys named user:{42}:profile and user:{42}:settings share the tag {42}, so they will hash to the same slot – allowing a multi-key operation like MGET on those two keys to succeed in cluster mode. (Use this feature judiciously – putting too many keys under one tag can unbalance the cluster.)

Consistency Model: Redis Cluster favors availability and partition tolerance. It uses asynchronous replication to replicas, so a failover may lead to some recent writes being lost if the master failed before propagating to replicas. It provides eventual consistency (not strong consistency) in failover scenarios. In practice, this means Redis Cluster can continue operating through node failures, but may lose the last few writes during a failover event. Clients should be designed to handle the possibility of retries or lost updates on failover.

Diagram – Redis Cluster Topology: Below is a visual representation of a Redis OSS Cluster with 3 masters and 3 replicas. Each master handles a portion of the hash slot range, and each replica continuously replicates its master. If a master fails, its replica will be promoted to master automatically.

(Each arrow shows a master -> replica relationship. All masters also communicate with each other over the cluster bus to manage the cluster state.)

Setting Up a Redis Cluster Lab with Docker Compose

It’s easy to experiment with Redis Cluster on a single machine using Docker. The recommended minimal cluster for learning is 6 Redis containers (3 masters + 3 replicas), which provides sharding and one replica per master for failove serverfault.com. However, you can also start with 3 masters (no replicas) if you just want to see sharding in action (note that without replicas, a node failure will take the cluster down since no backup node can take over its slots).

This compose keeps each node’s config simple but correct for clustering in containers: we set cluster-enabled yes, cluster-announce-ip, and explicit announce ports so other nodes and your host can reach them. (The bus port must be reachable, or cluster gossip/FAILOVER won’t work.) Stack Overflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
for port in $(seq 1 6); do
  mkdir -p ./redis/node-${port}/conf
  touch ./redis/node-${port}/conf/redis.conf
  cat << EOF > ./redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.29.0.2${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
---
services:
  redis-1:
    image: redis:8.0.3-alpine
    container_name: redis-1
    command: ["redis-server","/etc/redis/redis.conf"]
    ports:
      - "7001:6379"     # client
      - "17001:16379"   # cluster bus
    volumes:
      - ./redis/node-1/data:/data
      - ./redis/node-1/conf/redis.conf:/etc/redis/redis.conf
    networks:
      redisnet:
        ipv4_address: 172.29.0.21

  redis-2:
    image: redis:8.0.3-alpine
    container_name: redis-2
    command: ["redis-server", "/etc/redis/redis.conf"]
    ports: ["7002:6379", "17002:16379"]
    volumes:
      - ./redis/node-2/data:/data
      - ./redis/node-2/conf/redis.conf:/etc/redis/redis.conf
    networks:
      redisnet:
        ipv4_address: 172.29.0.22

  redis-3:
    image: redis:8.0.3-alpine
    container_name: redis-3
    command: ["redis-server", "/etc/redis/redis.conf"]
    ports: ["7003:6379", "17003:16379"]
    volumes:
      - ./redis/node-3/data:/data
      - ./redis/node-3/conf/redis.conf:/etc/redis/redis.conf
    networks:
      redisnet:
        ipv4_address: 172.29.0.23

  redis-4:
    image: redis:8.0.3-alpine
    container_name: redis-4
    command: ["redis-server", "/etc/redis/redis.conf"]
    ports: ["7004:6379", "17004:16379"]
    volumes:
      - ./redis/node-4/data:/data
      - ./redis/node-4/conf/redis.conf:/etc/redis/redis.conf
    networks:
      redisnet:
        ipv4_address: 172.29.0.24

  redis-5:
    image: redis:8.0.3-alpine
    container_name: redis-5
    command: ["redis-server", "/etc/redis/redis.conf"]
    ports: ["7005:6379", "17005:16379"]
    volumes:
      - ./redis/node-5/data:/data
      - ./redis/node-5/conf/redis.conf:/etc/redis/redis.conf
    networks:
      redisnet:
        ipv4_address: 172.29.0.25

  redis-6:
    image: redis:8.0.3-alpine
    container_name: redis-6
    command: ["redis-server", "/etc/redis/redis.conf"]
    ports: ["7006:6379", "17006:16379"]
    volumes:
      - ./redis/node-6/data:/data
      - ./redis/node-6/conf/redis.conf:/etc/redis/redis.conf
    networks:
      redisnet:
        ipv4_address: 172.29.0.26

networks:
  redisnet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.29.0.0/16
          gateway: 172.29.0.1

Create a single shared config file ./conf/redis-node.conf (each container’s env overrides what needs to be unique):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
port 6379
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

# Make cluster discovery & host access work from Docker:
cluster-announce-ip ${ANNOUNCE_IP}
cluster-announce-port ${ANNOUNCE_PORT}
cluster-announce-bus-port ${ANNOUNCE_BUS_PORT}

# Map the default bus port (port+10000) explicitly to the announced value:
cluster-port ${ANNOUNCE_BUS_PORT}

Notes:

  • Redis Cluster needs both the client port and bus port open; we explicitly expose and announce both.
  • You can also use a prebuilt “all-in-one” cluster image (ports 7000–7005) for quick demos, but Compose above mirrors production networking better. Docker Hub

Create the cluster

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# If created successful, enter to the redis-1 to create cluster
redis-cli --cluster create 172.29.0.21:6379 172.29.0.22:6379 172.29.0.23:6379 172.29.0.24:6379 172.29.0.25:6379 172.29.0.26:6379 --cluster-replicas 1
# Check again, enter to the redis-cli with cluster
root@b59ad0535541:/data$ redis-cli -c
# check the relationship of all redis nodes
127.0.0.1:6379> cluster nodes
2f60bc6a24f56a2b80892cb099ba2129e12b4d15 172.29.0.23:6379@16379 master - 0 1758618143000 3 connected 10923-16383
7b234dc61dc3b00b427a2c2d67e96b9dfe634f12 172.29.0.22:6379@16379 master - 0 1758618144588 2 connected 5461-10922
6b4cb91474d41e946dd632c4d6547535fff9a6f2 172.29.0.24:6379@16379 slave 2f60bc6a24f56a2b80892cb099ba2129e12b4d15 0 1758618144000 3 connected
6b46bf0ef0eddb3b97e39a96cb1ec8ee2f6ef3ba 172.29.0.21:6379@16379 myself,master - 0 0 1 connected 0-5460
668712d9ddcd8f82a4fc0c29ee3834e1c35271c9 172.29.0.25:6379@16379 slave 6b46bf0ef0eddb3b97e39a96cb1ec8ee2f6ef3ba 0 1758618143553 1 connected
6ad3f07b35aab31ddab8df18a6057ae0aff670ee 172.29.0.26:6379@16379 slave 7b234dc61dc3b00b427a2c2d67e96b9dfe634f12 0 1758618143553 2 connected

--cluster-replicas 1 gives you one replica per master automatically when forming the cluster. Use --cluster-yes to skip the interactive confirmation (useful for scripting/CI). medium

Auto-failover test scenarios

Redis Cluster’s gossip + voting promotes a replica when it detects a primary failure (subject to cluster-node-timeout). Ensure the bus port is open or failover won’t coordinate. Redis

Scenario A — Kill one primary

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Identify a primary:
docker exec -it redis-1 redis-cli cluster nodes | grep master
# Stop its container (say it’s redis-2):
docker stop redis-2
# Watch failover:
watch -n 1 'docker exec -it redis-1 redis-cli cluster nodes | sort'

# MacOS: while :; do clear; docker exec -it redis-1 redis-cli cluster nodes | sort; sleep 2; done
# You’ll see its replica promoted to master (flag switches to master and it takes over the failed node’s slots).
# Bring it back and it will rejoin as a replica (or you can re-balance as needed).

Scenario B — Network-isolate a primary (simulate partition)

1
2
3
4
# Example with docker network disconnect (simple);
# for finer control use `tc`/iptables on the host.
docker network disconnect $(docker network ls --filter name=redisnet -q) redis-3
# Observe cluster nodes again; after timeout, a replica should promote.

Two practical sharding strategies for OHLCV

What Cluster can/can’t do

  • A cluster has 16,384 hash slots; each master owns a subset. A key’s slot = CRC16(key) % 16384. Keys land on different masters based on their slots.
  • Multi-key commands only work when all keys hash to the same slot (use {…} hash tags to force colocation). If keys are on different slots, you’ll hit CROSSSLOT. CodeProject
  • RedisTimeSeries stores one time series per key. You scale by creating many series keys; you do not split one series across masters. Querying multiple series is done with TS.MRANGE/MGET + labels.
  • Keys (colocate each symbol’s 5 fields; different symbols spread naturally):
1
2
3
4
5
ts:ohlcv:{BTCUSDT:1d}:open
ts:ohlcv:{BTCUSDT:1d}:high
ts:ohlcv:{BTCUSDT:1d}:low
ts:ohlcv:{BTCUSDT:1d}:close
ts:ohlcv:{BTCUSDT:1d}:volume

The {BTCUSDT:1d} hash tag keeps a symbol’s fields on one shard (enables multi-key ops like TS.MADD); other symbols (ETHUSDT, SOLUSDT, …) use different tags and naturally spread across masters. This balances the cluster when you have many symbols.

  • Create with labels for flexible queries:
1
2
3
4
5
TS.CREATE ts:ohlcv:{BTCUSDT:1d}:open   LABELS symbol BTCUSDT interval 1d field open  source binance
TS.CREATE ts:ohlcv:{BTCUSDT:1d}:high   LABELS symbol BTCUSDT interval 1d field high  source binance
TS.CREATE ts:ohlcv:{BTCUSDT:1d}:low    LABELS symbol BTCUSDT interval 1d field low   source binance
TS.CREATE ts:ohlcv:{BTCUSDT:1d}:close  LABELS symbol BTCUSDT interval 1d field close source binance
TS.CREATE ts:ohlcv:{BTCUSDT:1d}:volume LABELS symbol BTCUSDT interval 1d field volume source binance

Then you can query across many series using labels:

1
TS.MRANGE - + FILTER symbol=BTCUSDT interval=1d

(MRANGE queries many series by labels.)

  • Ingest (atomic multi-key on one slot):
1
2
3
4
5
TS.MADD ts:ohlcv:{BTCUSDT:1d}:open 1696118400000 1.0886 \
        ts:ohlcv:{BTCUSDT:1d}:high 1696118400000 1.0899 \
        ts:ohlcv:{BTCUSDT:1d}:low  1696118400000 1.0088 \
        ts:ohlcv:{BTCUSDT:1d}:close 1696118400000 1.0304 \
        ts:ohlcv:{BTCUSDT:1d}:volume 1696118400000 9.8555e7

(Works because all five keys share the same hash slot via the tag.)

When to use: You have many symbols—distribution emerges “for free” because each symbol’s tag hashes to a different slot. This is the simplest, most robust design.

Redis Cluster Scaling

Redis Cluster allows horizontal scaling by sharding data across multiple nodes. In Redis OSS (open source), scaling is a manual operation – you must explicitly add nodes and redistribute slots, since auto-resharding is not built-in for OSS severalnines.com (unlike Redis Enterprise which supports automatic rebalancing triggers). The basic scale-out workflow is:

  1. Add new Redis nodes (pods) as cluster members – these can be masters or replicas.
  2. Assign hash slots to new masters (resharding) – move a portion of existing slots (and their keys) to the new master node(s), i.e. rebalance.
  3. Adjust replicas if needed – attach new replica pods to masters for HA.
  4. Validate cluster state – ensure all 16384 slots are covered and cluster is balanced.

Skipping the rebalancing step can leave new nodes empty and others overloaded, so always plan to migrate slots when scaling out.

Adding New Master and Replica Nodes in Kubernetes

There are two common scenarios: manual scaling (using kubectl and redis-cli) and operator/Helm-driven scaling. In either case, use a Kubernetes StatefulSet for Redis pods so that each node has a stable network identity and persistent storage (to survive restarts without losing cluster config) medium.com

Manual Scaling with redis-cli (CLI Example)

If managing the cluster manually, you can scale out by increasing the StatefulSet replica count and then using redis-cli --cluster commands:

  1. Scale up the Redis StatefulSet to create new pods. For example, if you have 6 pods (3 masters + 3 replicas) and want to add one master with one replica (total 8 pods):
1
kubectl scale statefulset redis-cluster --replicas=8

This will start two new pods (e.g., redis-cluster-6 and redis-cluster-7) 2. Add a new master node to the cluster using the CLUSTER ADDNODE command (via redis-cli). We target an existing cluster node to introduce the new node:

1
2
3
4
# Use kubectl exec to run redis-cli in one of the existing master pods (e.g., index 0)
kubectl exec -it redis-cluster-0 -- \
  redis-cli --cluster add-node \
    <new-node-ip>:6379 <existing-master-ip>:6379

Replace <new-node-ip> with the IP of the first new pod (e.g. redis-cluster-6 pod IP), and <existing-master-ip> with an IP of a current cluster master (e.g. redis-cluster-0 pod IP). This command sends a cluster MEET to join the new node as a master (initially with no slots). 3. Add a new replica node and attach it to a master. You can either specify the master ID or let Redis auto-select the master with the fewest replicas. For example, to join redis-cluster-7 as a replica:

1
2
3
kubectl exec -it redis-cluster-0 -- \
  redis-cli --cluster add-node --cluster-slave \
    <new-replica-ip>:6379 <existing-master-ip>:6379

Using --cluster-slave without specifying --cluster-master-id will attach the new node as a replica to the master that has the least replicas (likely the newly added master). You could also provide a specific master’s node ID via --cluster-master-id <ID> if needed. 4. Rebalance the cluster slots to utilize the new master. Until this step, the new master has 0 hash slots (no data). Use the Redis cluster rebalance command:

1
2
3
kubectl exec -it redis-cluster-0 -- \
  redis-cli --cluster rebalance --cluster-use-empty-masters \
    <any-master-ip>:6379

The --cluster-use-empty-masters flag ensures that empty nodes get their fair share of slots. This will move hash slots from existing masters to the new master, migrating keys online. You will see output indicating slot transfers and key migrations. For example, a new master will start receiving roughly 1/(new master count) of the slots from the others. During this resharding process, the cluster remains operational for clients. humansecurity.com

Redis Cluster Slot Rebalancing Strategies

When new masters join, you must redistribute slots, otherwise they remain unused. There are two approaches:

  • Manual Resharding: Using redis-cli --cluster reshard allows fine-grained control. You specify how many slots to move and from which source node(s) to which target. For example, to move 4096 slots to a new node ID NODE_NEW taken evenly from all existing masters:
1
2
3
4
5
redis-cli --cluster reshard <any-node>:6379 \
  --cluster-to <NODE_NEW> \
  --cluster-slots 4096 \
  --cluster-from all \
  --cluster-yes

This would interactively (or with --cluster-yes for non-interactive) migrate slots. You could choose specific source node IDs if you want to offload certain masters more than others.

  • Automatic (Equal Distribution) Rebalance: Using redis-cli --cluster rebalance. This command will evenly balance slots across all masters. By default it tries to minimize movement; with --cluster-use-empty-masters it specifically gives empty new nodes a proportionate share humansecurity
1
redis-cli --cluster rebalance --cluster-use-empty-masters <any-node>:6379

will compute the ideal slots per master (roughly total_slots / N_masters) and migrate slots accordingly. This is the simplest way to incorporate new nodes.

Which to use? For most cases, --cluster rebalance is sufficient and easier. Use manual reshard only if you need custom control (e.g., migrating specific slots or doing stepwise moves). Keep in mind Redis OSS doesn’t auto-rebalance on its own – you or your tooling must trigger it. (Some operators will effectively call these commands under the hood as part of scaling.)

Rebalancing Best Practices:

  • Size your migration: moving massive amounts of data will incur load. If possible, add one master at a time and rebalance, rather than adding many at once. This limits the scope of each reshard operation.
  • Off-peak operations: Perform slot rebalancing during low traffic periods. Key migration is CPU and network intensive and can temporarily increase latency as keys are copied between nodes. ibm.github.io
  • Minimize moved data: If you can, purge or archive cold data before scaling out. “Reducing data ensures smaller amounts have to be moved, which reduces the time required for the scale operation.” For instance, if using retention on time-series, consider lowering retention (dropping very old entries) before adding nodes. learn.microsoft.com
  • Verify after rebalancing: Use redis-cli --cluster check to confirm all slots are covered and roughly evenly distributed. Also check that no node is handling a disproportionate number of keys (use CLUSTER NODES or INFO keyspace on each). severalnines.com

Redis Sentinel: High Availability and Monitoring

Core Purpose and Architecture

Redis Sentinel is a high availability solution designed specifically for non-clustered Redis deployments. It provides monitoring, notification, automatic failover, and configuration management for master-replica Redis setups. redis.io

Redis Sentinel architecture with monitoring and failover

Key Features and Mechanisms

Redis Sentinel operates as a distributed system where multiple Sentinel processes cooperate to monitor Redis instances. The minimum recommended setup includes 3 Sentinel instances to avoid false positives and ensure robust failure detection. understanding-sentinels manage-sentinels

Quorum-Based Failover

Sentinel uses a quorum-based approach for failover decisions. The quorum represents the minimum number of Sentinels that must agree a master is unreachable before triggering failover. For example, with 3 Sentinels and a quorum of 2, at least 2 Sentinels must confirm master failure before initiating failover. gitlab hevodata

Automatic Discovery and Configuration

Sentinels automatically discover other Sentinels and replica nodes through Redis’s Pub/Sub capabilities, specifically using the __sentinel__:hello channel. This eliminates the need to manually configure all Sentinel addresses in each instance.

High Availability Focus

Redis Sentinel provides very high availability without human intervention. Even if all replica nodes fail, the system can continue operating with just the master node, though this reduces redundancy. system-design-redis-sentinel-vs-cluster-pros-and-cons

Setting up Redis Sentinel via Docker compose

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
---

services:
  redis-master:
    image: redis:8.0
    command: [
      "redis-server",
      "/usr/local/etc/redis/redis.conf"
    ]
    volumes:
      - ./master/redis.conf:/usr/local/etc/redis/redis.conf
      - ./master/data:/data
    #ports:
    #  - "6379:6379"
    networks:
      redisnet:
        ipv4_address: 172.28.0.20

  redis-replica1:
    image: redis:8.0
    #ports:
    #  - "6380:6379"
    depends_on:
      - redis-master
    command: [
      "redis-server",
      "/usr/local/etc/redis/redis.conf",
      "--replicaof", "redis-master", "6379"
    ]
    volumes:
      - ./replica1/redis.conf:/usr/local/etc/redis/redis.conf
      - ./replica1/data:/data
    networks:
      redisnet:
        ipv4_address: 172.28.0.21

  redis-replica2:
    image: redis:8.0
    depends_on:
      - redis-master
    #ports:
    #  - "6381:6379"
    command: [
      "redis-server",
      "/usr/local/etc/redis/redis.conf",
      "--replicaof", "redis-master", "6379"
    ]
    volumes:
      - ./replica2/redis.conf:/usr/local/etc/redis/redis.conf
      - ./replica2/data:/data
    networks:
      redisnet:
        ipv4_address: 172.28.0.22

  sentinel1:
    image: redis:8.0
    #container_name: sentinel1
    depends_on:
      - redis-master
      - redis-replica1
      - redis-replica2
    command: [
      "redis-sentinel",
      "/usr/local/etc/sentinel/sentinel.conf"
    ]
    volumes:
      - ./sentinel1/sentinel.conf:/usr/local/etc/sentinel/sentinel.conf
    ports:
      - "26379:26379"
    networks:
      - redisnet

  sentinel2:
    image: redis:8.0
    #container_name: sentinel2
    depends_on:
      - redis-master
      - redis-replica1
      - redis-replica2
    command: [
      "redis-sentinel",
      "/usr/local/etc/sentinel/sentinel.conf"
    ]
    volumes:
      - ./sentinel2/sentinel.conf:/usr/local/etc/sentinel/sentinel.conf
    ports:
      - "26380:26379"
    networks:
      - redisnet

  sentinel3:
    image: redis:8.0
    #container_name: sentinel3
    depends_on:
      - redis-master
      - redis-replica1
      - redis-replica2
    command: [
      "redis-sentinel",
      "/usr/local/etc/sentinel/sentinel.conf"
    ]
    volumes:
      - ./sentinel3/sentinel.conf:/usr/local/etc/sentinel/sentinel.conf
    ports:
      - "26381:26379"
    networks:
      - redisnet
  haproxy:
    image: haproxy:2.9
    volumes:
      - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    depends_on:
      - redis-master
      - redis-replica1
      - redis-replica2
    ports:
      - "6379:6379"
      - "6380:6380"
      - "8404:8404"   # HAProxy stats (optional)
    networks:
      - redisnet
networks:
  redisnet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# sentinel.conf

bind 0.0.0.0
protected-mode no

port 26379

# Monitor the master: name mymaster, host redis-master, port 6379, with quorum 2
sentinel monitor mymaster 172.28.0.20  6379 2

# If master is down after this many ms
sentinel down-after-milliseconds mymaster 5000

# Timeout for failover
sentinel failover-timeout mymaster 10000

# Number of replicas to sync with new master during failover
sentinel parallel-syncs mymaster 1

# If using auth
# sentinel auth-pass mymaster YourStrongPassword

# Notification script, client reconfig script etc (optional)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# redis.conf

bind 0.0.0.0
protected-mode no

port 6379
dir /data

# Persistence
appendonly yes
# RDB snapshotting as needed, e.g.
save 900 1
save 300 10
save 60 10000

# Require authentication (optional but recommended)
# For example:
# requirepass YourStrongPassword
# masterauth YourStrongPassword

# Enable all new data types etc: (nothing special needed for streams etc, built-in)

# TLS if you want (optional)
# tls-port 6379
# tls-cert-file /path/to/cert.pem
# tls-key-file /path/to/key.pem
# tls-ca-cert-file /path/to/ca.pem
# tls-auth-clients yes/no

Architectural Differences and Trade-offs

Scalability Comparison

Redis Cluster excels in horizontal scalability, allowing applications to scale beyond single-server limitations. It can handle datasets larger than available RAM on a single machine by distributing data across multiple nodes.

Redis Sentinel focuses on vertical scaling and cannot address read-write separation at scale. All write operations must go through the single master node, limiting write scalability.

Complexity and Client Requirements

Redis Cluster requires cluster-aware clients that can handle redirection (MOVED and ASK errors) and understand the distributed nature of the system. Clients need to cache the mapping between keys and nodes for optimal performance.

Redis Sentinel works with standard Redis clients that support Sentinel discovery. Popular client libraries include redis-py (Python), Jedis/Lettuce (Java), go-redis (Go), and NRedisStack (.NET).

Data Distribution Models

Redis Cluster implements true data sharding, where each node contains only a subset of the total dataset. This enables massive scaling but introduces complexity in multi-key operations.

Redis Sentinel uses full data replication, where each replica contains the complete dataset. This simplifies operations but limits scalability due to memory requirements on each node.

Consistency Guarantees

Redis Cluster provides eventual consistency due to asynchronous replication. Write acknowledgments may occur before data reaches all replicas, potentially leading to data loss during failures.

Redis Sentinel can provide stronger consistency for single-master operations, especially when combined with synchronous replication using the WAIT command.

Use Case Recommendations

Choose Redis Cluster When:

  • Large datasets that exceed single-server memory capacity
  • High write throughput requirements across multiple nodes
  • Horizontal scaling is essential for future growth
  • Application can handle eventual consistency trade-offs
  • Resources available for cluster-aware client development

Choose Redis Sentinel When:

  • High availability is the primary concern over scaling
  • Simpler deployment and management requirements
  • Strong consistency needs for single-master operations
  • Existing Redis applications that can easily integrate Sentinel support
  • Smaller datasets that fit comfortably on single nodes

Performance and Operational Considerations

Redis Cluster Performance Redis Cluster can achieve 200,000+ operations per second with multiple nodes, though performance may decrease slightly during resharding operations (~5% throughput impact). The distributed nature allows for linear performance scaling as nodes are added. redis-cluster-vs-redis-sentinel

Redis Sentinel Performance Redis Sentinel typically provides sub-10 second failover times, ensuring minimal downtime during master failures. However, all operations are limited by the single master’s capacity.

Network and Configuration Requirements

Redis Cluster requires two TCP ports per node: the standard Redis port (6379) and a cluster bus port (typically +10000). All nodes must be able to communicate on both ports.

Redis Sentinel uses standard Redis ports with additional Sentinel instances running on separate ports (typically 26379). The configuration is generally simpler with fewer network requirements.