# Redis Compatibility

How to connect Redis clients to ParticleDB, what ships, how Redis keys are stored and made durable, and how it differs from Redis.

Source: https://particledb.ai/redis/overview/

ParticleDB speaks the Redis protocol (RESP2). You can point `redis-cli` or a Redis
client library (configured for RESP2) at it and use strings, lists, sets, sorted sets, hashes, streams, HyperLogLog,
geo indexes, Pub/Sub and `MULTI`/`EXEC` transactions. Every write is logged to the
same write-ahead log as SQL, so Redis keys survive a restart, and SQL can read and
write the same key space.

For the per-command reference, see [Redis Commands](/connectivity/redis-commands/).

## Enable and connect

The Redis listener is on by default on port **6379**. It listens on the same
interface as the PostgreSQL listener (`--pg-addr`, all interfaces by default).
To choose the interface and port explicitly, use `--redis-addr`:

```bash
particledb start --data-dir ~/.particledb/data --redis-addr 127.0.0.1:6379
```

| Flag | What it does |
|---|---|
| `--redis-addr host:port` | Listen address for the Redis protocol. Default: the `--pg-addr` interface, port 6379. |
| `--tls-cert`, `--tls-key` | Serve TLS on the Redis port (and the other listeners). Plain-text connections are then refused. |
| `--connection-timeout seconds` | Close idle Redis connections after this many seconds. Default `300`; `0` disables it. |
| `--resp-commit-mode sync\|async` | When a write is acknowledged; see [Durability](#durability). |

```bash
redis-cli -p 6379 PING
# PONG

# With TLS
redis-cli -p 6379 --tls --cacert ca.pem PING
```

## Authentication

Authentication is on by default. Until a client authenticates, every command except
`AUTH`, `HELLO`, `PING` and `QUIT` fails with `NOAUTH Authentication required.`

Redis users are ParticleDB users: authenticate with a role created in SQL (or the
initial superuser, `postgres` by default, whose password you set with
`--pg-password`). Use the two-argument form of `AUTH`:

```bash
redis-cli -p 6379 --user postgres --pass your_password GET greeting
```

```
127.0.0.1:6379> AUTH postgres your_password
OK
127.0.0.1:6379> AUTH postgres wrong
(error) WRONGPASS invalid username-password pair or user is disabled.
```

- The one-argument form `AUTH password` authenticates as a user named `default`, so
  it only works if you created such a role.
- Commands that read or write keys require a **superuser** role. A non-superuser can
  authenticate and run `PING`, but its data commands fail with
  `NOPERM permission denied: this command requires superuser privilege`.
- `HELLO` answers with server information but **does not authenticate**: its
  `AUTH` and `SETNAME` options are ignored. Configure your client to send `AUTH`
  (most clients do this when you give them a user name and password with RESP2).
- `--no-auth` disables authentication on every listener; use it only for local
  development.

## Protocol and databases

- **RESP2 only.** `HELLO 3` is accepted but the server answers in RESP2
  (`proto` is `2` in the reply). Clients that require RESP3 must be configured for
  RESP2.
- **One database.** `SELECT 0` is accepted; any other index returns
  `ERR DB index is out of range`. `MOVE` and `SWAPDB` are refused. Use key prefixes
  to separate applications.
- **No cluster mode.** The server reports `cluster_enabled:0`; connect with a
  standalone (non-cluster) client configuration.
- **Binary safe.** Keys, values, members, hash fields, channels and messages can hold
  any bytes and are returned exactly as sent.

## What ships

| Family | Commands |
|---|---|
| Strings | `SET` (EX, PX, NX, XX), `GET`, `GETSET`, `GETDEL`, `GETEX`, `SETNX`, `SETEX`, `MGET`, `MSET`, `MSETNX`, `INCR`, `INCRBY`, `DECR`, `DECRBY`, `APPEND`, `STRLEN`, `GETRANGE`, `SUBSTR`, `SETRANGE` |
| Lists | `LPUSH`, `RPUSH`, `LPUSHX`, `RPUSHX`, `LPOP`, `RPOP`, `LLEN`, `LRANGE`, `LINDEX`, `LSET`, `LINSERT`, `LREM`, `LTRIM`, `LPOS`, `LMOVE`, `RPOPLPUSH`, and the non-waiting forms of `BLPOP`, `BRPOP`, `BLMOVE`, `BRPOPLPUSH` |
| Sets | `SADD`, `SREM`, `SMEMBERS`, `SISMEMBER`, `SMISMEMBER`, `SCARD`, `SPOP`, `SRANDMEMBER`, `SMOVE`, `SUNION`, `SINTER`, `SDIFF`, `SUNIONSTORE`, `SINTERSTORE`, `SINTERCARD`, `SSCAN` |
| Sorted sets | `ZADD` (NX, XX, GT, LT, CH, INCR), `ZINCRBY`, `ZSCORE`, `ZMSCORE`, `ZRANK`, `ZREVRANK`, `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`, `ZREVRANGEBYSCORE`, `ZCOUNT`, `ZLEXCOUNT`, `ZCARD`, `ZREM`, `ZREMRANGEBYSCORE`, `ZREMRANGEBYRANK`, `ZPOPMIN`, `ZPOPMAX`, `ZRANDMEMBER`, `ZUNIONSTORE`, `ZINTERSTORE`, `ZINTERCARD`, `ZSCAN` |
| Hashes | `HSET`, `HGET`, `HMSET`, `HMGET`, `HSETNX`, `HGETALL`, `HDEL`, `HEXISTS`, `HKEYS`, `HVALS`, `HLEN`, `HSTRLEN`, `HINCRBY`, `HRANDFIELD`, `HSCAN` |
| Streams | `XADD`, `XLEN`, `XRANGE`, `XREVRANGE`, `XREAD`, `XTRIM`, `XDEL`, `XINFO STREAM` |
| HyperLogLog | `PFADD`, `PFCOUNT`, `PFMERGE` (Redis-compatible counts) |
| Geo | `GEOADD`, `GEODIST`, `GEOPOS` |
| Keys | `DEL`, `UNLINK`, `EXISTS`, `TYPE`, `KEYS`, `SCAN`, `RENAME`, `COPY`, `TOUCH`, `RANDOMKEY`, `EXPIRE`, `PEXPIRE`, `EXPIREAT`, `PEXPIREAT`, `TTL`, `PTTL`, `EXPIRETIME`, `PEXPIRETIME`, `PERSIST` |
| Pub/Sub | `SUBSCRIBE`, `UNSUBSCRIBE`, `PUBLISH` |
| Transactions | `MULTI`, `EXEC`, `DISCARD` |
| Scripting | `EVAL`, `EVALSHA`, `SCRIPT LOAD`, `SCRIPT EXISTS` for a fixed set of built-in scripts (see [Differences from Redis](#differences-from-redis)) |
| Connection and server | `AUTH`, `HELLO`, `PING`, `ECHO`, `QUIT`, `RESET`, `SELECT 0`, `DBSIZE`, `FLUSHDB`, `FLUSHALL`, `INFO`, `CONFIG GET`, `CONFIG SET pdb-resp-commit-mode`, `TIME`, `WAIT` |

Commands not in this table either return `ERR unknown command` or are accepted for
client compatibility without doing anything useful (for example `CLIENT`,
`ACL`, `COMMAND`, `SAVE`, `BGSAVE`, `SLOWLOG`). Do not rely on their replies.

## Durability

Every Redis write is appended to ParticleDB's write-ahead log before it is
acknowledged, and keys, values, expirations and streams are
restored on restart. `SAVE`, `BGSAVE` and `BGREWRITEAOF` are not needed and do
nothing.

`--resp-commit-mode` decides how long a Redis write waits:

| Mode | A reply is sent after | Acknowledged writes can be lost by |
|---|---|---|
| `sync` (default) | the persistence flush completes (`--wal-sync-mode sync` or `groupsync`, the default) | storage or hardware failures outside the persistence guarantee |
| `async` | the WAL record is written to the operating system | an operating-system crash or power loss (not a ParticleDB process crash) |

If you know Redis persistence: `sync` corresponds to `appendfsync always`, and
`async` is close to `appendfsync everysec`, except that the flush starts as soon
as the previous one finishes rather than once a second.
`CONFIG GET appendfsync` reports `always` or `everysec` to match;
`CONFIG SET appendfsync` is refused. See
[Transaction and Durability Modes](/operations/durability/).

With `--wal-sync-mode nosync` the server writes no WAL, so Redis writes are not
durable at all.

You can switch the mode at runtime; it returns to the startup value after a
restart:

```
127.0.0.1:6379> CONFIG SET pdb-resp-commit-mode async
OK
127.0.0.1:6379> CONFIG GET pdb-resp-commit-mode
1) "pdb-resp-commit-mode"
2) "async"
```

`async` is refused when the server runs with `--wal-sync-mode nosync`, with
`--num-wal-stripes` above 1, or with replication.

## Memory reporting

The Redis memory commands do not report real memory use in this release:

- `MEMORY USAGE key` returns the same fixed value (`64`) for every existing key,
  whatever its size, and `nil` for a missing key.
- `MEMORY STATS` returns an empty array.
- `INFO` has no `Memory` section; `INFO memory` returns the same output as `INFO`
  (`Server`, `Persistence` and `Keyspace` sections).

Use ParticleDB's server metrics and memory settings (`--max-server-memory`) to monitor and bound memory instead.

## Reaching Redis keys from SQL

SQL functions read and write the same key space, over any protocol that runs SQL
(PostgreSQL wire, native wire, HTTP, gRPC):

```bash
redis-cli SET greeting hello
redis-cli RPUSH tasks a b c
redis-cli HSET user:1 name Alice
```

```sql
SELECT KV_GET('greeting');             -- hello
SELECT LIST_RANGE('tasks', 0, -1);     -- [a,b,c]
SELECT HASH_GET('user:1', 'name');     -- Alice
SELECT KV_SET('from_sql', '42');       -- OK; redis-cli GET from_sql returns "42"
SELECT KV_TTL('session:abc');          -- remaining seconds, as TTL
```

Other functions follow the same naming: `KV_*` for strings and keys, `LIST_*`,
`SET_*`, `HASH_*`, `ZSET_*`, `STREAM_*`. SQL functions return text: reading a
value that is not valid UTF-8 fails with SQLSTATE `22021` instead of altering it.

## Differences from Redis

- **No Lua.** `EVAL`, `EVALSHA`, `SCRIPT LOAD` and `SCRIPT EXISTS` accept only a
  fixed set of built-in scripts, matched by their exact text or SHA1 and run
  natively and atomically: the Orkes Conductor Redis queue's `pop_batch.lua`
  (orkes-redis-queues 1.0.9) and the Redisson 3.13.3 `RLock` scripts (lock, renew,
  unlock, force-unlock). Any other script returns `ERR unknown script`;
  `EVALSHA` of an unknown SHA returns `NOSCRIPT`. `EVAL_RO`, `EVALSHA_RO`,
  `SCRIPT FLUSH` and `FUNCTION` are not supported.
- **Blocking commands do not wait.** `BLPOP`, `BRPOP`, `BLMOVE`, `BRPOPLPUSH`, and
  `XREAD` with `BLOCK` return at once (`nil` when there is nothing
  to return). Poll instead.
- **No `WATCH`.** `MULTI`/`EXEC` run the queued commands atomically, but optimistic
  locking with `WATCH`/`UNWATCH` is not available.
- **Pub/Sub** supports channels only: `PSUBSCRIBE`, `PUNSUBSCRIBE`, `SSUBSCRIBE` and
  `PUBSUB` are not supported. A subscriber that falls more than 32 MB behind is
  disconnected (like Redis's `client-output-buffer-limit pubsub`).
- **Streams:** Consumer groups (`XGROUP`, `XREADGROUP`, `XACK`, `XPENDING`, `XCLAIM`, `XAUTOCLAIM`), `XINFO GROUPS` and `XINFO CONSUMERS` are not
  supported. `XTRIM ... ~` trims exactly to the threshold.
- **Expiry options:** `EXPIRE` and related commands do not accept `NX`, `XX`, `GT`
  or `LT`.
- **Not supported** in this release: `INCRBYFLOAT`, `HINCRBYFLOAT`, `RENAMENX`,
  `SDIFFSTORE`, `ZRANGEBYLEX`, `ZUNION`, `ZDIFF`, `GEOSEARCH`, `GEORADIUS`, `GEOHASH`, `SORT`,
  `DUMP`/`RESTORE`, `MONITOR`; `ZRANGE` supports only `start stop [WITHSCORES]`
  (use `ZREVRANGE` and `ZRANGEBYSCORE` for the other forms); `LPOP`/`RPOP` pop one
  element (no count argument). Bitmap commands (`SETBIT`, `GETBIT`, `BITCOUNT`,
  `BITPOS`, `BITOP`, `BITFIELD`) are not compatible with Redis: they do not
  operate on the string value that `GET` returns.
- **Server information.** `CONFIG GET` reports fixed values for most parameters
  and `CONFIG SET` changes only `pdb-resp-commit-mode`; configure the server with
  `particledb start` flags.

## Related

[Documentation home](/docs/) · [Troubleshooting](/guides/troubleshooting/)
