Verify: migrate from Amazon ElastiCache to KiviDB

Compare key counts first. On a cluster-mode source, run INFO keyspace on each shard's primary and add them up.

Compare key counts first. On a cluster-mode source, run INFO keyspace on each shard's primary and add them up.

redis-cli -h master.my-cache.xxxxxx.use1.cache.amazonaws.com --tls --user app --pass "$PASSWORD" INFO keyspace
redis-cli -h my-db.cloud.kividb.io -p 6380 --tls --cacert kividb-ca.pem --user kividbapp --pass "$KIVIDB_PASSWORD" INFO keyspace

Then spot-check random keys: their type, their value and their TTL. This script does it, and works against a cluster on either side. It needs pip install redis.

"""usage: python3 verify.py SOURCE_URL TARGET_URL [N]
SOURCE_CLUSTER=1 / TARGET_CLUSTER=1: that side is a cluster.
SOURCE_CA / TARGET_CA: a CA file for a rediss:// URL."""
import os, sys
import redis
from redis.cluster import RedisCluster


def connect(url, side):
    kw = {}
    if url.startswith("rediss://") and os.environ.get(f"{side}_CA"):
        kw["ssl_ca_certs"] = os.environ[f"{side}_CA"]
    if os.environ.get(f"{side}_CLUSTER") == "1":
        return RedisCluster.from_url(url, **kw)
    return redis.Redis.from_url(url, **kw)


def dbsize(r):
    if isinstance(r, RedisCluster):
        n = r.dbsize(target_nodes=RedisCluster.PRIMARIES)
    else:
        n = r.dbsize()
    return sum(n.values()) if isinstance(n, dict) else n


def value(r, key, t):
    if t == "string":
        v = r.get(key)
        return ("hll", r.pfcount(key)) if v and v.startswith(b"HYLL") else v
    if t == "hash":
        return r.hgetall(key)
    if t == "list":
        return r.lrange(key, 0, -1)
    if t == "set":
        return sorted(r.smembers(key))
    if t == "zset":
        return r.zrange(key, 0, -1, withscores=True)
    if t == "stream":
        return r.xrange(key)
    if t == "ReJSON-RL":
        return r.execute_command("JSON.GET", key)
    return None  # other types: compared by type only


src = connect(sys.argv[1], "SOURCE")
dst = connect(sys.argv[2], "TARGET")
n = int(sys.argv[3]) if len(sys.argv) > 3 else 1000

s_size, d_size = dbsize(src), dbsize(dst)
print(f"keys: source {s_size}, target {d_size}")

bad = 0
for _ in range(n):
    key = src.randomkey()
    if key is None:
        break
    t = src.type(key).decode()
    t2 = dst.type(key).decode()
    if t != t2:
        bad += 1
        print(f"TYPE  {key!r}: source {t}, target {t2}")
        continue
    if value(src, key, t) != value(dst, key, t):
        bad += 1
        print(f"VALUE {key!r} ({t}) differs")
    s_ttl, d_ttl = src.pttl(key), dst.pttl(key)
    if (s_ttl < 0) != (d_ttl < 0) or abs(s_ttl - d_ttl) > 60_000:
        bad += 1
        print(f"TTL   {key!r}: source {s_ttl} ms, target {d_ttl} ms")

print(f"checked {n} random keys, {bad} mismatches")
sys.exit(1 if bad or s_size != d_size else 0)
SOURCE_CLUSTER=1 TARGET_CA=kividb-ca.pem python3 verify.py \
  "rediss://app:$PASSWORD@clustercfg.my-cluster.xxxxxx.use1.cache.amazonaws.com:6379" \
  "rediss://kividbapp:$KIVIDB_PASSWORD@my-db.cloud.kividb.io:6380" 2000

HyperLogLogs are compared by their count, not their bytes. Run it from the same EC2 instance as the copy. Against a source that is still taking writes, expect a few differences on keys that changed in between; with writes stopped, expect none.