FREE · TOPIC 169

Newly-Unread Indicator

476 words·Updated 2026-05-14·
#system-design#messaging#redis#realtime#notifications
# Newly-Unread Indicator A newly-unread badge is not the same as total unread messages. Example product behavior: - You receive messages from three unique people while away. - The badge shows `3`. - You open the messages screen. - The badge clears, even if you did not open every conversation. This is an acknowledgement indicator, not the canonical per-message read state. ## Read Path First The app needs a cheap call: ```text GET /me -> { newly_unread_count: 3 } ``` Near-real-time badge reads should not scan messages or replay Kafka events. Precompute the state. ## Redis Sorted Set Model Use one sorted set per recipient: ```text key = newly_unread:{recipient_user_id} member = sender_user_id score = latest_message_timestamp ``` Operations: ```text ZADD newly_unread:B now A # A sent B a message ZCARD newly_unread:B # badge count ZREVRANGE newly_unread:B 0 9 # recent senders for display DEL newly_unread:B # B acknowledged messages screen ``` Why sorted set: - members are unique sender IDs, - score keeps latest sender order, - cardinality gives the badge count, - a sender can update their timestamp without duplicating the count. ## Write Flow ```mermaid sequenceDiagram participant A as Sender A participant API participant DB as Message DB participant R as Redis participant WS as WebSocket Gateway participant B as Recipient B A->>API: send message to B API->>DB: persist message API->>R: ZADD newly_unread:B timestamp A alt B online API->>WS: push badge update WS->>B: newly_unread_count changed end ``` Persist the message first, then update the badge projection. If the projection update fails, it can be retried or rebuilt from recent messages. ## Acknowledgement Flow ```mermaid sequenceDiagram participant B as User B participant API participant R as Redis B->>API: open messages screen API->>R: DEL newly_unread:B API-->>B: messages screen payload ``` This clears "newly unread" only. It does not necessarily mark all message threads read. ## Separate States Do not confuse these: | State | Meaning | Storage | |---|---|---| | Newly unread | Senders since last acknowledgement | Redis sorted set | | Thread unread | Conversation has unread messages | message/thread read receipts | | Message read | Specific message seen/read | message receipt table or per-thread cursor | Product copy and API names should reflect the distinction. ## Failure Modes | Failure | Effect | Mitigation | |---|---|---| | Redis loses key | badge undercounts | rebuild from recent messages or accept as ephemeral | | Retry sends same event | duplicate badge risk | sorted set member uniqueness by sender ID | | User opens messages while new message arrives | race around clearing | use acknowledgement timestamp or compare scores | | WebSocket disconnected | badge stale until next poll | `/me` reconciliation | | Sender changes display name | stale tooltip name | store IDs in Redis, fetch names from user/profile cache | ## Useful References - [Redis sorted sets](https://redis.io/docs/latest/develop/data-types/sorted-sets/) - [[wiki/realtime-database-and-websocket-scaling]]
Primary References & Engineering Sources
  • ·[[wiki/arpit-social-networks-ii]]
  • ·[[wiki/realtime-database-and-websocket-scaling]]