MiniRedis Behavior Matrix¶
| Area | Implemented subset | Direct evidence | RESP evidence | Deliberate difference |
|---|---|---|---|---|
| String | GET, MGET, conditional/expiring SET, atomic MSET, INCR, DECR, INCRBY, COMPAREDEL, CHECKDECR |
tests/contract/test_atomic_functions.py::test_checkdecr_is_single_winner_and_queues_inside_transaction |
tests/adapters/test_tcp_async_semantics.py::test_tcp_pipeline_preserves_reply_order_with_invalid_middle_command |
Frozen options only; strict canonical int64 parsing; two explicit atomic functions instead of Lua |
| Hash | HSET, HGET, HDEL, HGETALL, HINCRBY |
tests/contract/test_hashes.py::test_hash_semantics_and_last_field_removal |
tests/adapters/test_direct_resp_parity.py::test_selected_sequence_has_state_and_reply_parity |
HGETALL order is unspecified |
| List | push/pop/range plus direction-aware BLPOP and BRPOP |
tests/mechanisms/test_blpop.py::test_blocked_brpop_preserves_right_pop_direction_when_woken |
tests/adapters/test_tcp_async_semantics.py::test_blpop_does_not_block_another_connection |
Blocking waiters preserve key priority and pop direction |
| Pipeline | ordered Direct and coalesced RESP2 adapter submission | tests/adapters/test_direct_pipeline.py::test_direct_pipeline_preserves_result_slots_and_is_not_atomic |
tests/adapters/test_tcp_async_semantics.py::test_tcp_pipeline_submits_all_decoded_frames_before_first_executes |
Adapter batching only: no atomic execution, rollback, or cross-client isolation |
| Transactions | MULTI, EXEC, DISCARD, WATCH, UNWATCH; one final commit batch |
tests/mechanisms/test_transactions.py::test_exec_reads_prior_write_and_keeps_runtime_error_slots |
Null-array mapping in tests/adapters/test_resp2_mapping.py::test_domain_replies_encode_as_resp2 |
Queue-time errors abort; runtime errors keep result slots; successful commands are not rolled back. Unlike Redis, MiniRedis forbids PUBLISH and BLPOP/BRPOP inside MULTI; WATCH inside MULTI both errors and dirties the transaction, so the later EXEC returns EXECABORT (Redis reports the WATCH error without dirtying it). EXEC deep-copies the whole database before evaluating commands, while Redis executes against the live database. |
| Set | add/remove/membership/members/intersection | tests/contract/test_sets.py::test_smembers_sinter_and_last_member_removal |
tests/adapters/test_direct_resp_parity.py::test_selected_sequence_has_state_and_reply_parity |
Result order is unspecified |
| Sorted Set | plain add/remove/score/rank and score ranges | tests/contract/test_sorted_sets.py::test_zset_orders_equal_scores_by_member_bytes |
tests/adapters/test_direct_resp_parity.py::test_selected_sequence_has_state_and_reply_parity |
Dict storage with deterministic (score, member) sorting. ZSCORE formats with Python repr(float) (for example 1.0), not Redis's %.17g-style output (for example 1). |
| TTL | absolute deadlines, lazy visibility, bounded active cleanup | tests/contract/test_ttl.py::test_expire_ttl_persist_and_bounded_active_cleanup |
Not required | Injected clock/scheduler; no timer per key |
| Eviction | noeviction, exact allkeys-lru, deterministic decaying allkeys-lfu, logical byte budget |
tests/contract/test_eviction.py::test_lfu_planning_projects_without_materializing_survivor_decay |
Not required | Exact ordering and deterministic halving, not Redis sampled LRU/probabilistic LFU/RSS |
| Pub/Sub | exact-channel, bounded, at-most-once fan-out | tests/reliability/test_phase3_invariants.py::test_waiters_and_pubsub_allocate_no_commit_or_durable_state |
tests/adapters/test_tcp_async_semantics.py::test_subscribe_message_ping_unsubscribe_share_one_ordered_outbox |
No patterns, ACK, retry, history, or replay |
| AOF | versioned framed CommitBatch stream; always, everysec, no; bounded online rewrite to state base plus ordered delta |
tests/unit/persistence/test_aof_writer.py::test_parent_fsync_failure_after_rename_is_terminal |
tests/reliability/test_aof_rewrite.py::test_write_during_paused_base_survives_rewrite_and_restart |
Custom format, not Redis AOF; pre-rename failure is non-terminal, post-rename uncertainty is terminal |
| Snapshot | stable checkpoint image and atomic replacement | tests/reliability/test_snapshot_barrier.py::test_snapshot_barrier_captures_seq_and_only_logically_live_keys |
tests/reliability/test_final_acceptance.py::test_final_acceptance_activates_components_then_leaves_no_owners |
Custom format, not Redis RDB |
| Recovery | snapshot-only, legacy AOF, state-base AOF, or combined newest-baseline staged install | tests/unit/persistence/test_recovery.py::test_recovery_prefers_newer_aof_state_base |
Not applicable | AOF base wins an equal snapshot checkpoint; only a final incomplete AOF record is repairable |
| Replica | in-process async sink; logical history ID; bounded batch backlog; manual full/partial reattach; lag, overflow, and fenced promotion | tests/replication/test_partial_resync.py::test_cursor_exactly_before_oldest_backlog_batch_is_partial; tests/replication/test_sink_attach.py::test_replica_logically_hides_expired_key_without_advancing_sequence |
tests/reliability/test_final_acceptance.py::test_final_acceptance_activates_components_then_leaves_no_owners |
New ID on restart/promotion; acknowledged writes can still be lost; no PSYNC wire, heartbeat, WAIT, election, or automatic failover. Like Redis, a replica logically hides expired keys but does not actively delete them or create a local expiry commit; it waits for the primary's propagated deletion. |
| Lifecycle | idempotent close and zero owned resources | tests/reliability/test_reliability_shutdown.py::test_cancelling_one_close_waiter_cannot_cancel_cleanup |
tests/reliability/test_final_acceptance.py::test_final_acceptance_activates_components_then_leaves_no_owners |
Bounded output/replica drain; no delivery guarantee after transport close |
All mutating rows also inherit one implementation-level complexity difference:
Database.apply_batch copies the whole key table and recomputes logical usage
for every commit (O(N) in key count). Real Redis normally mutates the keyspace
and memory accounting in place, making an ordinary single-key update O(1) on
average apart from the command's own data-structure work. The extra MiniRedis
cost buys simple staging and invariant checks; it is not a production
performance model.