Introducing RedisShake

An open-source Redis migration tool by Alibaba, released under the MIT license. It can also be used for backup and restore scenarios. It supports migrating data across versions and across topologies — for example, migrating data from a standalone Redis instance into a Redis Cluster. However, since Redis is generally used as a cache, real-world data migration scenarios are not that common.

GitHub: https://github.com/tair-opensource/RedisShake.git

image

Usage Example

The usage is very simple: configure the shake.toml file and run redis-shake shake.toml.

postgres@slpc:~/redisshake$ ./redis-shake shake.toml
2026-03-05 11:02:50 INF load config from file: shake.toml
2026-03-05 11:02:50 INF log_level: [info], log_file: [/home/postgres/redisshake/data/shake.log]
2026-03-05 11:02:50 INF changed work dir. dir=[/home/postgres/redisshake/data]
2026-03-05 11:02:50 INF GOMAXPROCS defaults to the value of runtime.NumCPU [8]
2026-03-05 11:02:50 INF not set pprof port
2026-03-05 11:02:50 INF create SyncClusterReader
2026-03-05 11:02:50 INF * address (should be the address of one node in the Redis cluster): 192.168.232.128:6379
2026-03-05 11:02:50 INF * username: admin
2026-03-05 11:02:50 INF * password: **************
2026-03-05 11:02:50 INF * tls: false
2026-03-05 11:02:50 INF address=192.168.232.128:6379, reply=ca62daac7e5028e18f65def8a3ffdfb519c7be63 192.168.232.138:6379@16379 master - 0 1772679768763 3 connected 10923-16383
831e4c891d2025233320461e4b6aa3ea3e9c3c97 192.168.232.128:6379@16379 myself,master - 0 1772679768000 1 connected 0-5460
e0856d63d6a1170c0755f74782ba828be23e8fc2 192.168.232.137:6379@16379 master - 0 1772679769768 2 connected 5461-10922
2026-03-05 11:02:50 INF NewFileWriter absolute path=[/home/postgres/redisshake/cmd.dump],type=[cmd]
2026-03-05 11:02:50 INF start syncing...
2026-03-05 11:02:50 INF [reader_192.168.232.138_6379] source db is not doing bgsave! continue.
2026-03-05 11:02:50 INF [reader_192.168.232.128_6379] source db is not doing bgsave! continue.
2026-03-05 11:02:50 INF [reader_192.168.232.137_6379] source db is not doing bgsave! continue.
2026-03-05 11:03:00 INF read_count=[0], read_ops=[0.00], write_count=[0], write_ops=[0.00], src-2, waiting bgsave
2026-03-05 11:03:05 INF read_count=[0], read_ops=[0.00], write_count=[0], write_ops=[0.00], src-0, waiting bgsave
2026-03-05 11:03:10 INF read_count=[0], read_ops=[0.00], write_count=[0], write_ops=[0.00], src-1, waiting bgsave
2026-03-05 11:03:15 INF read_count=[0], read_ops=[0.00], write_count=[0], write_ops=[0.00], src-2, receiving rdb, size=[654 MiB/2.0 GiB]
2026-03-05 11:03:20 INF read_count=[0], read_ops=[0.00], write_count=[0], write_ops=[0.00], src-0, receiving rdb, size=[1000 MiB/2.0 GiB]
2026-03-05 11:03:25 INF read_count=[0], read_ops=[0.00], write_count=[0], write_ops=[0.00], src-1, receiving rdb, size=[1.6 GiB/2.0 GiB]
2026-03-05 11:03:30 INF read_count=[206046], read_ops=[41209.00], write_count=[206046], write_ops=[41209.00], src-2, receiving rdb, size=[1.8 GiB/2.0 GiB]
2026-03-05 11:03:35 INF read_count=[831945], read_ops=[125182.55], write_count=[831942], write_ops=[125182.15], src-0, syncing rdb, size=[158 MiB/2.0 GiB]
2026-03-05 11:03:40 INF read_count=[1664476], read_ops=[166505.37], write_count=[1664473], write_ops=[166505.37], src-1, syncing rdb, size=[759 MiB/2.0 GiB]
2026-03-05 11:03:45 INF read_count=[2577500], read_ops=[182606.22], write_count=[2577497], write_ops=[182606.22], src-2, syncing rdb, size=[662 MiB/2.0 GiB]
2026-03-05 11:03:50 INF read_count=[3303362], read_ops=[145171.40], write_count=[3303359], write_ops=[145171.40], src-0, syncing rdb, size=[974 MiB/2.0 GiB]
2026-03-05 11:03:55 INF read_count=[3955934], read_ops=[130507.06], write_count=[3955931], write_ops=[130507.06], src-1, syncing rdb, size=[1.5 GiB/2.0 GiB]
2026-03-05 11:04:00 INF read_count=[4609175], read_ops=[130650.84], write_count=[4609172], write_ops=[130650.84], src-2, syncing rdb, size=[1.3 GiB/2.0 GiB]
2026-03-05 11:04:05 INF read_count=[5269922], read_ops=[132155.14], write_count=[5269919], write_ops=[132155.14], src-0, syncing rdb, size=[1.6 GiB/2.0 GiB]
2026-03-05 11:04:10 INF read_count=[5946592], read_ops=[135333.63], write_count=[5946590], write_ops=[135333.83], src-1, syncing rdb, size=[2.0 GiB/2.0 GiB]
2026-03-05 11:04:11 INF all done

image

Source Readers

Read data from the source via the sync method, i.e. through the PSYNC protocol — similar to attaching as a replica to obtain the source node’s data (rdb + aof).

[sync_reader]
cluster = true            # Set to true if the source is a Redis cluster
address = "192.168.232.128:6379" # For clusters, specify the address of any cluster node; use the master or replica address in master-replica mode
username = "admin"              # Keep empty if ACL is not in use
password = "admin_password"     # Keep empty if no authentication is required
tls = false                     # Set to true to enable TLS if needed
sync_rdb = true                 # Set to false if RDB synchronization is not required
sync_aof = false                # Set to false if AOF synchronization is not required
prefer_replica = false          # Set to true to sync from a replica node
try_diskless = false            # Set to true for diskless sync if the source has repl-diskless-sync=yes

image

Read data from the source via the SCAN method — continuously fetching keys via the SCAN command, effectively scanning all keys on the source node.

[scan_reader]
cluster = false            # set to true if source is a redis cluster
address = "127.0.0.1:6379" # when cluster is true, set address to one of the cluster nodes
username = ""              # keep empty if not using ACL
password = ""              # keep empty if no authentication is required
tls = false
dbs = []                   # set the dbs you want to scan, e.g. [1,5,7]; leave empty to scan all
scan = true                # set to false if you don't want to scan keys
ksn = false                # set to true to enable Redis keyspace notifications (KSN) subscription
count = 1                  # number of keys to scan per iteration
## RedisShake uses the TYPE command to get each key's type.
## If the type is in this skip list, the key will be skipped.
skip_unknown_type = []  # e.g. ["imset", "mbloom", "string", "hash", "list", "set", "zset"]

image

Read a data snapshot via the rdb method.

[rdb_reader]
filepath = "/tmp/dump.rdb"

Read data from the source via the aof method.

[aof_reader]
aoffilepath="/tmp/appendonly.aof.manifest" # or a single aof file "/tmp/appendonly.aof"
aoftimestamp="0"

image

Destination Writers

Where does the data collected from the source get written? redis-shake supports writing to Redis or to files. Note that writing to files or Redis does not use the RDB format — instead it replays Redis commands.

Redis Writer

redis_writer writes data into a Redis-like database. During data migration it writes into the target database; during restore it writes data back into Redis.

Configuration
[redis_writer]
cluster = false
address = "127.0.0.1:6379" # when cluster is true, address is one of the cluster nodes
username = ""              # keep empty if not using ACL
password = ""              # keep empty if no authentication is required
tls = false
  • cluster: whether the target is a cluster.
  • address: the connection address. When the target is a cluster, address can be any one of the cluster nodes.
  • Authentication:
    • When using the ACL account system, configure username and password.
    • When using the legacy account system, configure only password.
    • When there is no authentication, do not configure username and password.
  • tls: whether to enable TLS/SSL. No certificate is required because RedisShake does not verify the server certificate.

Notes:

  1. When the target is a cluster, ensure the commands sent from the source satisfy the constraint that keys hash to the same slot.
  2. The target version should ideally be greater than or equal to the source version; otherwise unsupported commands may appear. If you really must downgrade, set target_redis_proto_max_bulk_len to 0 to avoid using the restore command for data recovery.

file_writer

You can use file_writer to write to a file. Supported formats are CMD/JSON/AOF, commonly used to extract, migrate, or correct data via a file medium.

The cmd format is a command format that converts the source data into commands written to the file. Regardless of what commands were executed in between, recovery at the end can always be done by inserting keys with commands like set.

Configuration
[file_writer]
filepath = "/tmp/cmd.txt"
type = "cmd" # cmd,aof,json (default cmd)
  • The absolute path filepath is required.
Use Cases
  • Two systems sharing data: one system writes the file to disk/s3/oss, and the other reads from it.
  • Cross-system partial migration of data with a given prefix: migrate data with the “XXX:” prefix out of system A, and system B imports it with redis-cli --pipe XXX.aof.
  • Correcting data via command files: export data from one system in cmd format, correct it, then import with redis-cli < cmd.txt.
  • Data analysis via json format: export to a json file and import into mongodb/bi for analysis.
Example Output
cmd_writer output:
SELECT 0
set key1 1
set key2 2
set key3 3
sadd key4 1 2 3 4
lpush key5 1 2 3 4 5
zadd key6 1 2 3 4 5 6
json_writer output:
{"DbId":0,"Argv":["SELECT","0"],"CmdName":"SELECT","Group":"CONNECTION","Keys":null,"KeyIndexes":null,"Slots":[],"SerializedSize":23}
{"DbId":0,"Argv":["set","key1","1"],"CmdName":"SET","Group":"STRING","Keys":["key1"],"KeyIndexes":[2],"Slots":[9189],"SerializedSize":30}
{"DbId":0,"Argv":["set","key2","2"],"CmdName":"SET","Group":"STRING","Keys":["key2"],"KeyIndexes":[2],"Slots":[4998],"SerializedSize":30}
{"DbId":0,"Argv":["set","key3","3"],"CmdName":"SET","Group":"STRING","Keys":["key3"],"KeyIndexes":[2],"Slots":[935],"SerializedSize":30}
{"DbId":0,"Argv":["sadd","key4","1","2","3","4"],"CmdName":"SADD","Group":"SET","Keys":["key4"],"KeyIndexes":[2],"Slots":[13120],"SerializedSize":52}
{"DbId":0,"Argv":["lpush","key5","1","2","3","4","5"],"CmdName":"LPUSH","Group":"LIST","Keys":["key5"],"KeyIndexes":[2],"Slots":[9057],"SerializedSize":60}
{"DbId":0,"Argv":["zadd","key6","1","2","3","4","5","6"],"CmdName":"ZADD","Group":"SORTED_SET","Keys":["key6"],"KeyIndexes":[2],"Slots":[4866],"SerializedSize":66}
aof_writer output:
*2
$6
SELECT
$1
0
*3
$3
set
$4
key1
$1
1