Skip to content

feat: keyspace notifications#313

Merged
kacy merged 1 commit intomainfrom
feat/keyspace-notifications
Feb 26, 2026
Merged

feat: keyspace notifications#313
kacy merged 1 commit intomainfrom
feat/keyspace-notifications

Conversation

@kacy
Copy link
Copy Markdown
Owner

@kacy kacy commented Feb 26, 2026

summary

adds redis-compatible keyspace notifications. clients can subscribe to pub/sub channels to receive real-time events when keys are modified or expire — the most-requested cache invalidation primitive.

enable via config or at runtime:

notify-keyspace-events KEA   # K=keyspace, E=keyevent, A=all event types
notify-keyspace-events Kx    # only expired events

subscribers then receive:

__keyevent@0__:expired  →  <key that expired>
__keyspace@0__:<key>    →  <event name>

what was tested

  • 524 unit tests pass with no regressions
  • cargo clippy -- -D warnings and cargo fmt --check both clean
  • unit tests in keyspace_notifications.rs cover flag parsing (empty/zero, individual flags, A expansion, KEA combo, unknown chars ignored)
  • expiry tests updated to assert on Vec<String> return value

design considerations

zero hot-path overhead: the guard is a single AtomicU32::load(Relaxed) before any work is done. when notify-keyspace-events is "" (the default), every notification site is a branch-predict-always-false check — effectively free.

expiry notifications via broadcast: active expiration (the 100ms timer) collects expired key names into a Vec<String> only when the broadcast channel has active receivers. the background subscriber task in the server translates these to pub/sub publishes. lazy expiry (triggered on GET) is silent for now — consistent with redis's behavior under some configurations.

command notifications: notify_write() is called after successful responses for SET, DEL, EXPIRE, HSET, LPUSH, RPUSH, ZADD, SADD. the event name matches redis's convention (e.g. "set", "expired", "lpush").

runtime reconfiguration: CONFIG SET notify-keyspace-events KEA updates the AtomicU32 immediately — no restart needed.

adds redis-compatible keyspace notifications via the notify-keyspace-events
config parameter. when enabled, ember publishes to:
  - __keyspace@0__:<key> with the event name as the message
  - __keyevent@0__:<event> with the key name as the message

changes:
- ember-core: expire_sample now collects expired key names into a buffer;
  run_expiration_cycle returns Vec<String> of expired keys for callers
- engine: new expired_tx broadcast channel in EngineConfig; shards broadcast
  expired key names to server without touching the GET/SET hot path
- keyspace_notifications: flag parser (K/E/g/$/ l/z/h/s/x/A), notify helper
- config: notify-keyspace-events added to MUTABLE_PARAMS and EmberConfig
- server: keyspace_event_flags AtomicU32 on ServerContext; background task
  subscribes to expired-key broadcast and fires __keyevent@0__:expired
- execute: notify_write helper (single atomic load guard — zero overhead when
  disabled) wired into SET, DEL, EXPIRE, HSET, LPUSH, RPUSH, ZADD, SADD
- config set: updates keyspace_event_flags atomically at runtime
@kacy kacy force-pushed the feat/keyspace-notifications branch from b3a2fc9 to 6392659 Compare February 26, 2026 18:26
@kacy kacy merged commit 8ceee9a into main Feb 26, 2026
4 of 7 checks passed
@kacy kacy deleted the feat/keyspace-notifications branch February 26, 2026 18:26
kacy added a commit that referenced this pull request Feb 26, 2026
implements all five redis-compatible bitmap commands backed by
Value::String storage with big-endian bit ordering (bit 0 = MSB of
byte 0). no new value type — bitmaps reuse the existing string type.

- GETBIT / SETBIT with auto-extension and old-value return
- BITCOUNT with byte-range and bit-range (BIT unit) support
- BITPOS finds first set/clear bit; returns -1 when not found
- BITOP (AND, OR, XOR, NOT) operates across arbitrary source keys,
  result length equals the longest source
- AOF persistence for SETBIT and BITOP (tags 33 and 34)
- recovery replay in ember-persistence without cross-crate dep
  (BitOpKind encoded as u8 in AofRecord)
- fix pre-existing spawn_shard arity mismatch in shard unit tests
  (expired_tx param added in #313 but tests not updated)
- 15 integration tests + 20 unit tests covering edge cases
kacy added a commit that referenced this pull request Feb 26, 2026
implements all five redis-compatible bitmap commands backed by
Value::String storage with big-endian bit ordering (bit 0 = MSB of
byte 0). no new value type — bitmaps reuse the existing string type.

- GETBIT / SETBIT with auto-extension and old-value return
- BITCOUNT with byte-range and bit-range (BIT unit) support
- BITPOS finds first set/clear bit; returns -1 when not found
- BITOP (AND, OR, XOR, NOT) operates across arbitrary source keys,
  result length equals the longest source
- AOF persistence for SETBIT and BITOP (tags 33 and 34)
- recovery replay in ember-persistence without cross-crate dep
  (BitOpKind encoded as u8 in AofRecord)
- fix pre-existing spawn_shard arity mismatch in shard unit tests
  (expired_tx param added in #313 but tests not updated)
- 15 integration tests + 20 unit tests covering edge cases
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant