Get the data out: migrate from Redis to KiviDB

There are four ways. All of them carry strings, lists, sets, sorted sets, hashes with per-field expiry, streams with their consumer groups and pending…

There are four ways. All of them carry strings, lists, sets, sorted sets, hashes with per-field expiry, streams with their consumer groups and pending entries, bitmaps, geo sets, RedisJSON documents and key TTLs, from Redis 6.2 to 8.10.

MethodDowntimeInto self-hostedInto KiviDB Cloud
ReplicateSecondsYesNo: a Cloud database cannot replicate from an outside server
RDB fileFrom the snapshot to the switchYesYes: import the file
Copy tool (RedisShake, RIOT)Seconds with sync or live modeYesYes
MIGRATEFor the keys you moveYesPossible, not recommended

1. Replicate, then switch

KiviDB connects to Redis as a replica: it takes a full copy, then follows every write until you promote it. Redis sees an ordinary replica, so this needs PSYNC on the primary, which self-hosted Redis always allows.

kividb --port 6380 --dir /var/lib/kividb \
  --replicaof redis.internal 6379 \
  --masterauth "$REDIS_PASSWORD"
# with an ACL user on Redis, add: --masteruser replicator

The user needs the replication commands on Redis (ACL SETUSER replicator on >… +psync +replconf +ping).

Watch it catch up:

redis-cli -p 6380 INFO replication | grep -E 'master_link_status|master_sync_in_progress|slave_repl_offset'
redis-cli -h redis.internal INFO replication | grep master_repl_offset

It has caught up when master_link_status:up, master_sync_in_progress:0, and KiviDB's slave_repl_offset equals Redis's master_repl_offset. The cutover steps are in the runbook.

For a large dataset with many writes, check client-output-buffer-limit replica on Redis is large enough for the writes made while the snapshot transfers. If it is not, Redis drops the link and the full sync starts again.

2. An RDB file

Take a snapshot. BGSAVE writes it in the background; SAVE blocks Redis until it is done. Taking it from a Redis replica keeps the work off the primary.

redis-cli -h redis.internal BGSAVE
redis-cli -h redis.internal INFO persistence | grep -E 'rdb_bgsave_in_progress|rdb_last_bgsave_status'
redis-cli -h redis.internal CONFIG GET dir         # where dump.rdb is
redis-cli -h redis.internal CONFIG GET dbfilename

When rdb_bgsave_in_progress:0 and the status is ok, copy the file. Writes made after the snapshot are not in it: stop writes first, or plan to replay them, or use a method that follows writes.

redis-cli --rdb dump.rdb also fetches a snapshot over the network, from any Redis that allows SYNC.

3. Copy with RedisShake

RedisShake (open source, MIT) reads from Redis and writes to KiviDB with ordinary commands, so it works into KiviDB Cloud too. Download the release for your platform, unpack it, and run it with a TOML file:

curl -LO https://github.com/tair-opensource/RedisShake/releases/download/v4.7.0/redis-shake-v4.7.0-linux-amd64.tar.gz
tar xzf redis-shake-v4.7.0-linux-amd64.tar.gz
./redis-shake shake.toml

It has three readers:

ReaderHow it readsFollows new writesNeeds on the source
sync_readerActs as a replica: a full snapshot, then the write streamYes, until you stop itPSYNC
scan_readerSCAN, then DUMP each keyWith ksn = true (keyspace notifications)DUMP; for ksn, CONFIG SET notify-keyspace-events
rdb_readerReads an RDB fileNoThe file

All three write with RESTORE, which keeps TTLs, field expiry and stream consumer groups.

Sync (follows writes until you switch):

[sync_reader]
cluster = false
address = "redis.internal:6379"
username = ""                 # an ACL user, if you use one
password = "redis-password"
tls = false

[redis_writer]
cluster = false
address = "kividb.internal:6380"
username = ""
password = "kividb-password"
tls = false

[advanced]
dir = "shake-data"
log_file = "shake.log"

Its log reports progress every few seconds. Once it reads syncing aof, diff=[0], KiviDB has the snapshot and every write since, and stays up to date until you stop the tool.

Scan (where PSYNC is not allowed): replace the reader with:

[scan_reader]
cluster = false
address = "redis.internal:6379"
password = "redis-password"
tls = false
dbs = []          # all databases; or e.g. [0, 2]
scan = true
ksn = false       # true: keep following writes through keyspace notifications
count = 1         # keys per SCAN call; 100 or more copies faster

With ksn = false it copies every key once and exits. With ksn = true it also subscribes to keyspace notifications and copies each key again when it changes, until you stop it. That means writing over keys it has already copied, so add this, or it stops at the first changed key with a BUSYKEY error:

[advanced]
rdb_restore_command_behavior = "rewrite"

Turn the notifications on at the source first (CONFIG SET notify-keyspace-events KEA, or the provider's setting). Keyspace notifications are fire-and-forget: a key changed while the tool is disconnected is missed, so run the verify script after the switch.

Filters. The [filter] section skips keys or databases, for example module keys KiviDB does not read:

[filter]
block_key_prefix = ["ts:", "bf:"]
block_db = [5]

Into an existing database. RedisShake stops at the first key that already exists on KiviDB (Target key name is busy). Set rdb_restore_command_behavior = "rewrite" under [advanced] to overwrite, or "skip" to keep KiviDB's copy.

Copy with RIOT

RIOT (open source, Apache 2.0) does the same from a single command. The riot-standalone-… downloads include their own Java runtime.

riot replicate redis://:$REDIS_PASSWORD@redis.internal:6379 \
  redis://kividb.internal:6380 --target-pass "$KIVIDB_PASSWORD" \
  --compare NONE
  • --mode SCAN (the default) copies once; --mode LIVE keeps following writes through keyspace notifications until you stop it.
  • It copies one logical database per run: add /2 to both URIs for database 2.
  • --compare QUICK and FULL run a Lua script on the target after the copy, so they need Lua there. Into the default build, or a KiviDB Cloud database without Lua, use --compare NONE and the verify script.

4. Copy keys with MIGRATE

To move some keys rather than everything, run MIGRATE on Redis with KiviDB as the destination. It sends each key as a RESTORE, and KiviDB accepts Redis's payloads:

redis-cli -h redis.internal MIGRATE kividb.internal 6380 "" 0 5000 COPY REPLACE \
  AUTH "$KIVIDB_PASSWORD" KEYS user:1 user:2 user:3
# with a KiviDB user: AUTH2 <username> <password>

COPY leaves the key on Redis; without it Redis deletes the key once KiviDB has it. The fourth argument is the destination database. The Redis server itself opens the connection to KiviDB, and MIGRATE has no TLS options of its own, so for KiviDB Cloud use RedisShake or RIOT instead.

To move a whole database this way, loop over SCAN and send batches:

redis-cli -h redis.internal --scan | xargs -n 100 \
  redis-cli -h redis.internal MIGRATE kividb.internal 6380 "" 0 10000 COPY REPLACE AUTH "$KIVIDB_PASSWORD" KEYS

From Redis Cluster

KiviDB can't join a Redis cluster, and an RDB file is per shard, so use a copy tool that reads every shard.

Into a self-hosted KiviDB cluster: build the KiviDB cluster first (see Set up a cluster), then let RedisShake read each Redis shard and route each key to the KiviDB node that owns its slot:

[sync_reader]
cluster = true
address = "redis-node-1.internal:6379"   # any node; it finds the rest
password = "redis-password"

[redis_writer]
cluster = true
address = "kividb-node-1.internal:6380"  # any node
password = "kividb-password"

The KiviDB cluster does not need the same number of shards or the same slot layout: keys are written to whichever node owns their slot. Use scan_reader with cluster = true where PSYNC is blocked.

Into one KiviDB server (when the data fits on one machine): the same, with cluster = false under [redis_writer]. Multi-key commands then work across all keys, which they did not on the cluster.

With RIOT: add --source-cluster, and --target-cluster for a cluster target.

Hash tags ({user:42}:cart) keep working on a KiviDB cluster: it uses the same slot function.

From a Sentinel setup

Sentinel only decides which Redis server is the primary. Ask it, then use any method above against that address:

redis-cli -h sentinel-1.internal -p 26379 SENTINEL get-master-addr-by-name mymaster

Do not attach KiviDB as a replica of a Sentinel-monitored primary. Sentinel discovers every replica of the primary and treats it as a failover candidate. If the primary fails while KiviDB is attached, Sentinel can promote the KiviDB server and reconfigure the other Redis replicas to follow it. In Redis you would prevent that with replica-priority 0; KiviDB 1.2.0 accepts that setting but does not report it to Sentinel, so it does not help.

Two ways that stay out of Sentinel's sight:

  • Replicate from a Redis replica rather than the primary. Sentinel learns replicas from the primary only, so it never sees KiviDB. Redis replicas serve their own replicas. When Sentinel promotes that replica, KiviDB keeps following it and continues with a partial resync, without a new full copy.

    kividb --replicaof redis-replica-1.internal 6379 --masterauth "$REDIS_PASSWORD"
    
  • Use RedisShake (sync_reader) against the primary. It is a client, not a server Sentinel can connect to and promote. If Sentinel fails over during the copy, restart RedisShake against the new primary.

After the move, clients connect to KiviDB directly: Sentinel-aware clients (Redis.sentinel in redis-py, sentinels in ioredis, JedisSentinelPool) are replaced by plain connections, or by cluster clients if you move to a KiviDB cluster. KiviDB has no Sentinel; for failover, run a KiviDB cluster with replicas, the Kubernetes operator, or KiviDB Cloud Pro.