未読管理
コンテキスト
TwitterやSlackのようなタイムラインで、そのユーザがどこまでメッセージを読んだのかを管理したい。(→ 時系列で既読マークを付けれる場合)
お知らせメッセージのように、詳細を読んだら既読にする管理がしたい (→ メッセージごとに既読かどうかを持たなければならない場合)
ユーザ数が多いと、既読のリクエストが頻繁に飛ぶので、ユーザ毎にできるだけまとめて既読リクエストできるようにしたい。
ソリューション
データの持ち方
時系列で既読マークを付けれる場合
タイムラインのように時系列的メッセージで、ある時点まで表示したことがわかっていれば、そこまでを既読として扱ってよい場合。
code:データモデル
Messages Unread_messages Users
+--------------+ +------------+ +--------------+
|message_id | |user_id | |user_id |
+--------------+ +------------+ +--------------+
|description +----+message_id +---+name |
|posted_at | | | |email |
|posted_by | +------------+ | |
| | | |
+--------------+ +--------------+
設計のポイントはMessagesのmessage_idを時系列性をもったID体系にすることです。
code:未読メッセージ取得SQL
SELECT M.*
FROM messages M
WHERE M.message_id > ? /* unread_messagesのメッセージID */
LIMIT 10 /* カーソルページネーション用のウィンドウサイズ */
ORDER BY M.message_id
メッセージがユーザ毎に変わる場合は、以下のようにメッセージとユーザの関連に対してUnread管理を作ります。
code:データモデル
Unread_messages
+---------------+
|user_id +---+
+---------------+ |
+----+user_message_id| |
| | | |
| +---------------+ |
| |
| |
+ |
Messages User_messages Users |
+--------------+ +--------------+ +-----------+--+
|message_id | |user_message_id |user_id |
+--------------+ +--------------+ +--------------+
|description +--+user_id +---+name |
|posted_at | |message_id | |email |
|posted_by | | | | |
| | | | | |
+--------------+ +--------------+ +--------------+
メッセージごとに既読かどうかを持たなければならない場合
これは時系列で扱うことができないので、メッセージごとに未読のレコードを持つことになります。
code:データモデル
Messages Unread_messages Users
+--------------+ +------------+ +--------------+
|message_id | |user_id | |user_id |
+--------------+ |message_id | +--------------+
|description +---------------------+name |
|posted_at | | | |email |
|posted_by | +------------+ | |
| | | |
+--------------+ +--------------+
したがって、「ユーザ毎にもつことができるUnread_messagesの上限」を決めておくことが、性能を保証するためには必要です。
Messagesにレコード登録する際に、Unread_messagesにもユーザへの配信数分レコードを作ります。既読リクエストにともないUnread_messagesのレコードを削除します。
(例) Mastodonのフォロー、お気に入りなどの通知
TODO 既読リクエストのバッファリング