ElectraDB
PricingDocsArticlesLog in
Get started
Articles

Moving a Redis-compatible dataset to ElectraDB

Migration24 October 2026

Copy every key, value and expiry from a Redis-compatible server into an ElectraDB key-value database with a short script, over verified TLS.

Before you start

This guide copies the keys of your current Redis-compatible server, whether you run it yourself or rent it from a provider, into an ElectraDB Redis-compatible (Valkey) database. It uses about 40 lines of Python and nothing on the server side but standard commands, so it works wherever you can connect to both ends.

What is copied, and what is not

  • Every key with its value and its remaining expiry is copied, whatever its type.
  • Pub/Sub messages are not data and are not copied; your subscribers simply reconnect to the new database.
  • Users, passwords and settings stay behind. An ElectraDB database has one user, u_<id>, and you choose its eviction policy and replication mode on the database page.
  • Keys of module types, such as JSON documents, search indexes, time series or Bloom filters, cannot be copied: our Valkey runs without modules.

We do not import an RDB file of your own, and your database cannot be made a replica of your current server. The copy goes through the network, key by key, as described below.

Pick the plan by dataset size

Key-value plans are sold by the size of the dataset they hold (see pricing). On the source, used_memory_human in INFO memory says how much yours takes now:

redis-cli -h <current host> -p 6379 --user <user> --askpass INFO memory | grep used_memory_human

Choose a plan with room above that figure: the same data can take a little more or less memory on another server version, and your dataset will grow. Create the database (New database, then Redis-compatible) and keep its eviction policy at No eviction for the copy. A dataset too large for the plan then shows up as out-of-memory errors in the script, instead of keys silently evicted to make room. You can pick another policy afterwards.

When the database is live, copy its connection string and password from its page. The string has this form:

rediss://u_<id>:<password>@<id>.db.electradb.com:<port>

How the copy works

Every Redis-compatible server has three commands that together copy a key exactly:

  • DUMP key returns the key's value in the server's own serialized form, whatever its type: a string, a hash, a list, a set, a sorted set or a stream.
  • PTTL key returns the time left before the key expires, in milliseconds: -1 when it has no expiry, -2 when it no longer exists.
  • RESTORE key ttl payload REPLACE creates the key from that payload on the target, with the given expiry (0 for none). REPLACE overwrites a key that is already there, so the copy can run as often as you like.

The script walks the source with SCAN, which visits every key in small steps without blocking the server the way KEYS would, and sends the commands in batches of 500 to save round trips. Keys that expire between the scan and the copy are skipped.

RESTORE accepts payloads written by the same or an older version of a Redis-compatible server. Newer servers can write a format Valkey does not read; the target then refuses the key with DUMP payload version or checksum are wrong. The script reports such keys and carries on: see When a key is refused.

The script

It needs Python 3 and redis-py:

pip install redis
# copy_keys.py: every key, value and expiry from SOURCE_URL to TARGET_URL
import os
import sys

import redis

src = redis.from_url(os.environ["SOURCE_URL"], socket_connect_timeout=10, socket_timeout=60)
dst = redis.from_url(
    os.environ["TARGET_URL"], ssl_check_hostname=True, socket_connect_timeout=10, socket_timeout=60
)
BATCH = 500


def copy(keys):
    """DUMP and PTTL each key on the source, RESTORE it on the target. Returns (copied, failed)."""
    read = src.pipeline(transaction=False)
    for key in keys:
        read.pttl(key)
        read.dump(key)
    found = read.execute()
    write = dst.pipeline(transaction=False)
    sent = []
    for key, ttl, payload in zip(keys, found[0::2], found[1::2]):
        if ttl == -2 or payload is None:
            continue  # expired or deleted since the scan
        write.restore(key, ttl if ttl > 0 else 0, payload, replace=True)
        sent.append(key)
    failed = 0
    for key, result in zip(sent, write.execute(raise_on_error=False)):
        if isinstance(result, Exception):
            failed += 1
            print(f"{key!r}: {result}", file=sys.stderr)
    return len(sent) - failed, failed


copied = failed = 0
batch = []
for key in src.scan_iter(count=1000):
    batch.append(key)
    if len(batch) == BATCH:
        c, f = copy(batch)
        copied, failed, batch = copied + c, failed + f, []
if batch:
    c, f = copy(batch)
    copied, failed = copied + c, failed + f
print(f"copied {copied} keys, {failed} refused")

The target URL uses rediss://, so the connection is encrypted, and the certificate and the host name are checked against the certificate authorities your system already trusts. Use rediss:// for the source too if it requires TLS.

Run it and check the result

Put both URLs in the environment, with the passwords URL-encoded if they contain characters such as @ or / (the database page shows the target's already encoded), and run the script from a machine close to the database's region:

export SOURCE_URL='redis://<user>:<password>@<current host>:6379/0'
export TARGET_URL='rediss://u_<id>:<password>@<id>.db.electradb.com:<port>'
python copy_keys.py

The number at the end of the source URL is the logical database it reads, and the target URL selects database 0. If INFO keyspace on the source lists keys in other databases than db0, run the script again for each, with that number at the end of both URLs.

Then compare the two sides. DBSIZE counts the keys; the numbers match when nothing wrote to the source during the copy and no key expired in between:

redis-cli -h <current host> -p 6379 --user <user> --askpass DBSIZE
redis-cli --tls -u "rediss://u_<id>:<password>@<id>.db.electradb.com:<port>" DBSIZE

Spot-check a few keys of each kind your application uses on both sides: TYPE, PTTL, and the value itself (GET, HGETALL, LRANGE key 0 -1). INFO memory on the target shows how much of the plan's dataset size the copy takes. Then point a staging copy of your application at the new database and run your tests.

When a key is refused

If the target refuses keys with DUMP payload version or checksum are wrong, your source writes a newer format than Valkey reads. Copy those keys by type instead, with the commands your Redis clients already use. This function covers the common types; call it for each key the script reported:

def copy_by_type(key):
    kind = src.type(key).decode()
    ttl = src.pttl(key)
    pipe = dst.pipeline()  # MULTI/EXEC: the key appears complete or not at all
    pipe.delete(key)
    if kind == "string":
        pipe.set(key, src.get(key))
    elif kind == "hash":
        pipe.hset(key, mapping=src.hgetall(key))
    elif kind == "list":
        pipe.rpush(key, *src.lrange(key, 0, -1))
    elif kind == "set":
        pipe.sadd(key, *src.smembers(key))
    elif kind == "zset":
        pipe.zadd(key, dict(src.zrange(key, 0, -1, withscores=True)))
    else:
        raise ValueError(f"{key!r}: copy {kind} keys another way")
    if ttl > 0:
        pipe.pexpire(key, ttl)
    pipe.execute()

Streams need XRANGE and XADD with their entry IDs, and their consumer groups have to be created again. If you already know a migration tool such as RIOT, it can copy by type as well.

Switch your application over

The first run is the rehearsal: it shows whether every key is accepted, whether the plan is large enough, and how long a full copy takes. For the real move:

  1. Freeze writes to the source: stop the workers and jobs that write, or put the application in maintenance mode.
  2. Empty the target with FLUSHDB. The script overwrites keys that exist on both sides, but a key deleted from the source since the rehearsal would otherwise stay on the target.
  3. Run the script again and compare DBSIZE on both sides.
  4. Switch the connection string in your application to the ElectraDB one and start it again.
The cut-over, phase by phaseYour appSourceElectraDBRehearsalUses the sourceTakes writesFirst copytimed and testedcopy_keys.pyWrite freezeWriters stoppedNo writesEmptied, copiedDBSIZE comparedcopy_keys.pyAfter the switchUses ElectraDBNo longer usedTakes writes
The first run is the rehearsal, while the source keeps taking writes. During the freeze the target is emptied and the script runs again, which takes about as long as the rehearsal's copy; then your application uses ElectraDB.

The freeze lasts about as long as the rehearsal's copy took. If that is too long for you, you can instead run a final pass during a shorter freeze without emptying the target, and accept that keys deleted from the source in the meantime remain until they expire or you delete them.

For a pure cache, you may not need to copy at all: point the application at the new database and let it fill as requests come in, with an eviction policy such as least recently used. Expect more load on whatever the cache protects while it warms up.

Afterwards, set a connect timeout of 5 to 10 seconds in your client, as the database page's examples do, and choose the replication mode on high-availability plans. Your data is then archived every 30 seconds and snapshotted daily; see Backups and point-in-time restore and MariaDB and Redis-compatible databases are here for what the database keeps for you. To move a database from a MySQL-compatible server as well, see the MariaDB migration guide.

More articles

  • Coming soon: databases that move closer to your applicationTell us where your application runs and we will place your database, and over time move it, to the servers with the lowest latency to it.
  • Migrating a MySQL or MariaDB database to ElectraDBDump a MySQL or MariaDB database, load it into ElectraDB's MariaDB over verified TLS, and switch your application over with a short write freeze.
  • MariaDB and Redis-compatible databases are hereElectraDB now runs MariaDB (MySQL-compatible) and Redis-compatible Valkey databases, with the same failover, backups and restore as PostgreSQL.
  • All articlesEvery article, newest first.
ElectraDBManaged PostgreSQL, MariaDB and Redis-compatible databases in the EU. US and Asia coming soon.99.9% uptime SLA with service credits · Prices in EUR, excluding VAT · For business customers
ProductPricingSLAStatusSign upLog in
DocsAll guidesQuickstartBackups and restoreHigh availabilitySecurityFAQArticles
LegalTerms of ServicePrivacy PolicyCookiesData Processing AgreementService Level AgreementSub-processorsLegal notice
ContactSupport: [email protected]Privacy: [email protected]Report abuse: [email protected]Security: [email protected]
© 2026 ElectraDB · Ruben Gomez Morales · NIF 48089326Y · Calle Mar Mediterráneo 27, 28221 Majadahonda (Madrid), SpainNo advertising cookies. Analytics only if you agree.