Skip to content

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.