Commands
Supported Redis commands, Valkey compatibility, and TTL behaviour.
Current surface
| Area | Commands |
|---|---|
| Connection | PING, ECHO, SELECT 0 |
| Strings | SET (NX/XX/EX/PX), SETNX, SETEX/PSETEX, GET, DEL, EXISTS, STRLEN, MGET/MSET/MSETNX, GETSET, APPEND, GETRANGE/SETRANGE |
| TTL | EXPIRE/PEXPIRE, TTL/PTTL |
| Admin | FLUSHDB/FLUSHALL, INFO |
| Harness stubs | CONFIG, FUNCTION FLUSH |
INFO returns server/memory/stats/keyspace plus pg_redis_* metadata;
redis_version matches the Valkey compat pin.
Valkey compatibility
Pinned suite: Valkey 8.1.4 under compat/valkey/.
make compat # Postgres up with in-process RESP gateway + allowlisted TCL
make compat passes --host/--port into Valkey’s harness (“external server”
mode). That does not mean the standalone redis_gateway binary — it means
“use the already-running embedded gateway on :6379” from make postgres-up.
Live status lives in
compat/STATUS.md.
Grow compat/allowlist.txt
as commands land.
What the suite covers
| Covered | Not covered |
|---|---|
| Allowlisted TCL tests via Valkey’s RESP client | Inline wire form (use make bench / raw-byte tests) |
Embedded gateway + redis.* storage |
redis-benchmark throughput / PING_INLINE |
Empty-string SET/GET; TTL (lazy + pg_cron sweeper) |
Full string suite leftovers |
Green compat is necessary for semantic claims; it is not sufficient for inline protocol or benchmark defaults.
Protocol surfaces
Clients do not all speak the same wire form:
| Surface | Example | Who uses it |
|---|---|---|
| RESP array (mbulk) | *1\r\n$4\r\nPING\r\n |
redis-cli, most libraries, Valkey TCL client |
| Inline / telnet | PING\r\n |
redis-benchmark ping / PING_INLINE, telnet |
| Pipelined | many commands back-to-back | redis-benchmark -P, busy clients |
make compat and redis-cli exercise RESP arrays. They will not catch an
inline-protocol regression. Default make bench uses redis-benchmark -t ping,
which runs both PING_INLINE and PING_MBULK.
TTL / expiry
- Lazy —
expires_atcolumns +redis.ensure_aliveon read/write. - Active (SQL modes) —
pg_cronjobpg_redis_ttl_task(* * * * *) callsredis.delete_expired_keys(). - Active (memory mode) — 1s in-gateway tick calls
MemoryStore::purge_expired().
Known limitation — naive active expiry
| Path | What we do now | Gap vs Redis |
|---|---|---|
| Memory | Full HashMap scan every 1s |
Redis samples a small random subset per tick |
SQL (pg_cron) |
Delete up to batch × rounds rows per minute |
Coarser than Redis sampling cadence |
SQL sweeper GUCs:
| GUC | Default | Meaning |
|---|---|---|
pg_redis.ttl_delete_batch_size |
1000 |
Keys per DELETE round |
pg_redis.ttl_delete_max_rounds |
10 |
Rounds per CALL redis.delete_expired_keys() |
Correctness (lazy-on-access + eventual active delete) is fine. Cost under large key counts is not.