redis-on-mysql 0.0.1

A Redis-compatible proxy that stores all data and Pub/Sub state in MySQL
Documentation

redis-on-mysql

redis-on-mysql is a Redis-compatible proxy that stores all data and Pub/Sub state in MySQL. It speaks RESP2 via resp-async and translates Redis commands into SQL while aiming for Redis 4.0 command family coverage.

What it does

  • Stateless proxy: all data lives in MySQL tables.
  • Per-user MySQL pools after AUTH; credentials are validated but not stored.
  • Redis-style TTL handling and background cleanup.
  • Pub/Sub backed by mailbox tables with server-side polling.

Command coverage (high level)

  • Connection/basic: PING, QUIT, AUTH, ECHO, SELECT, CLIENT
  • Strings/TTL: GET, SET, SETNX, SETEX, PSETEX, INCR, DECR, DEL, EXISTS, EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT, PERSIST, TTL, PTTL
  • Hash, Set, List, Sorted Set families
  • Bitmap, Bitfield, HyperLogLog, Geo (string or zset backed)
  • Generic keyspace: TYPE, KEYS, SCAN, RANDOMKEY, RENAME, RENAMENX, DBSIZE, FLUSHDB, FLUSHALL, UNLINK
  • Cluster compatibility: INFO, NODES, SLOTS, KEYSLOT plus READONLY/READWRITE/ASKING
  • Pub/Sub: SUBSCRIBE, UNSUBSCRIBE, PUBLISH

For the exact routing list, see src/handlers/mod.rs.

Quickstart

  1. Create the schema in MySQL using the migration file:
# option 1: mysql client
mysql -u <user> -p <database> < migrations/20260208231600.sql

# option 2: sqlx cli (requires DATABASE_URL)
export DATABASE_URL="mysql://user:password@127.0.0.1:3306/redis"
sqlx migrate run
  1. Run the proxy:
export REDIS_MYSQL_ADDR=127.0.0.1:3306
export REDIS_MYSQL_DATABASE=redis
# optional
export REDIS_MYSQL_BIND=0.0.0.0:6380

cargo run --release
  1. Connect with redis-cli:
redis-cli -p 6380
AUTH tenantA:alice password
SET key value
GET key

Authentication and tenant routing

  • AUTH is required for all data commands.
  • Supported formats:
    • AUTH user password
    • AUTH user:password (single argument)
  • Tenant id is derived from the username via regex; the no-match policy decides whether to reject, keep the username, or use a literal tenant id.
  • The proxy validates credentials by opening a MySQL connection, then reuses a per-user pool for subsequent commands.

Configuration

Configuration can be loaded from a YAML file and/or environment variables. If REDIS_MYSQL_CONFIG is set, the file is loaded first and any environment variables override it. Defaults apply when a value is not set anywhere.

Precedence:

  • Defaults
  • YAML file (if REDIS_MYSQL_CONFIG is set)
  • Environment variables

YAML keys are snake_case and use the same units as their env counterparts (milliseconds or seconds in the name).

YAML example

bind_addr: "0.0.0.0:6380"
mysql_addr: "127.0.0.1:3306"
mysql_database: "redis"
mysql_options: ""
auth:
  tenant_regex: "^(?P<tenant>[^:]+):"
  tenant_no_match: "username"
pool:
  max_connections: 16
  connect_timeout_ms: 5000
  idle_ttl_secs: 600
cleanup:
  expire_cleanup_interval_secs: 60
  expire_cleanup_batch: 1000
  pubsub_cleanup_interval_secs: 60
  pubsub_idle_secs: 600
  pubsub_message_ttl_secs: 3600
pubsub:
  poll_ms: 200
  poll_batch: 128
  heartbeat_secs: 30
string_set_retry:
  max_attempts: 3
  backoff_base_ms: 1
  backoff_max_ms: 16
string_incr_retry:
  max_attempts: 64
  backoff_base_ms: 1
  backoff_max_ms: 16
  deadline_ms: 2000

Enable the file with:

export REDIS_MYSQL_CONFIG=./redis-on-mysql.yaml

Minimal example

export REDIS_MYSQL_BIND=0.0.0.0:6380
export REDIS_MYSQL_ADDR=127.0.0.1:3306
export REDIS_MYSQL_DATABASE=redis
export REDIS_MYSQL_TENANT_REGEX="^(?P<tenant>[^:]+):"
export REDIS_MYSQL_TENANT_NO_MATCH=username

Full example (defaults)

# Core
REDIS_MYSQL_CONFIG=
REDIS_MYSQL_BIND=0.0.0.0:6380
REDIS_MYSQL_ADDR=127.0.0.1:3306
REDIS_MYSQL_DATABASE=redis
REDIS_MYSQL_DSN_OPTIONS=

# Tenant routing
REDIS_MYSQL_TENANT_REGEX="^(?P<tenant>[^:]+):"
REDIS_MYSQL_TENANT_NO_MATCH=username

# MySQL pool
REDIS_MYSQL_POOL_MAX_CONNECTIONS=16
REDIS_MYSQL_POOL_CONNECT_TIMEOUT_MS=5000
REDIS_MYSQL_POOL_IDLE_TTL_SECS=600

# Cleanup cadence
REDIS_MYSQL_EXPIRE_CLEANUP_INTERVAL_SECS=60
REDIS_MYSQL_EXPIRE_CLEANUP_BATCH=1000
REDIS_MYSQL_PUBSUB_CLEANUP_INTERVAL_SECS=60
REDIS_MYSQL_PUBSUB_IDLE_SECS=600
REDIS_MYSQL_PUBSUB_MESSAGE_TTL_SECS=3600

# Pub/Sub polling
REDIS_MYSQL_PUBSUB_POLL_MS=200
REDIS_MYSQL_PUBSUB_POLL_BATCH=128
REDIS_MYSQL_PUBSUB_HEARTBEAT_SECS=30

# Retry/backoff policies
REDIS_MYSQL_SET_RETRY_MAX_ATTEMPTS=3
REDIS_MYSQL_SET_RETRY_BACKOFF_BASE_MS=1
REDIS_MYSQL_SET_RETRY_BACKOFF_MAX_MS=16
REDIS_MYSQL_INCR_RETRY_MAX_ATTEMPTS=64
REDIS_MYSQL_INCR_RETRY_BACKOFF_BASE_MS=1
REDIS_MYSQL_INCR_RETRY_BACKOFF_MAX_MS=16
REDIS_MYSQL_INCR_RETRY_DEADLINE_MS=2000

Optional server limits (override resp-async defaults)

These can also be provided under server: in the YAML file using the same snake_case names (for example, max_frame_size, read_timeout_ms).

# Request/response limits
REDIS_MYSQL_MAX_FRAME_SIZE=<bytes>
REDIS_MYSQL_MAX_BULK_LEN=<bytes>
REDIS_MYSQL_MAX_ARRAY_LEN=<count>
REDIS_MYSQL_MAX_DEPTH=<count>
REDIS_MYSQL_MAX_INFLIGHT=<count>
REDIS_MYSQL_MAX_CONNECTIONS=<count>

# Timeouts (milliseconds)
REDIS_MYSQL_READ_TIMEOUT_MS=<ms>
REDIS_MYSQL_WRITE_TIMEOUT_MS=<ms>
REDIS_MYSQL_IDLE_TIMEOUT_MS=<ms>

# Queues and IO
REDIS_MYSQL_PUSH_QUEUE_LEN=<count>
REDIS_MYSQL_RESPONSE_QUEUE_LEN=<count>
REDIS_MYSQL_WRITE_BATCH_BYTES=<bytes>
REDIS_MYSQL_TCP_NODELAY=<true|false>
REDIS_MYSQL_BACKLOG=<count>

Non-goals

  • Full Redis feature parity (Lua, transactions, modules, streams, ACLs).
  • RESP3 support.
  • Cluster coordination (single-node cluster responses only).
  • TLS termination inside the proxy.