タイムライン実装するのって意外と難しいよねって話

最近、大学のプロジェクト個人的な趣味でWebアプリを作っています。どういうものかというと、「行ってみたいのに一人で行く勇気が持てない」お店に一緒に行ってくれる人を探せるサービスです。

そのなかで、ホームのページを作成する際Twitterのようなタイムラインのように実装しました。そこで、API内部複数回DBを叩く動作を書いてしまった(懺悔)ので言い訳とどうしたら良かったのかを個人的にまとめようと思います。

現状の挙動

APIは以下を満たすようなエントリを返すようになっています。

  • 自分が「いいね」していない
  • それぞれのエントリには対応する店情報を付与
  • そのエントリが自分が投稿している場合is_ownerフラグがtrue

DBのテーブル上ではentryテーブルとshopテーブル、「いいね」用にapplicationテーブル(正式名所はいいねではなく「会いたい!」みたいのが正しい)が存在します。

そのため、ホーム上でこのAPIをfetchした際に、

  1. 最新50件のエントリをentryテーブルから取得 キャッシュ込でshopエントリから店情報を取得していき、エントリに入れていく
  2. applicationテーブルから自分が作成したものを取得し、すでに「いいね」している場合は除外
  3. entryカラムのowner_user_idが自分のユーザIDと同一の場合はis_ownerフラグをtrueにする

のように、すべてのテーブルを叩く用になってしまっています。キャッシュすればある程度は負荷は抑えられますがタイムラインなのでできるだけリアルタイムに反映したいという話題もあります。

また、一度メモリ上に自分が「いいね」したものすべてを格納したりするのでサーバ上のメモリが心もとないです。

解決策1 店情報を含めない

一番最初に考えられるのは、店情報を含めないです。詳しくいうとタイムラインで必要な情報は最低店名のみなので店名をentryテーブルに突っ込んでしまい詳しい店情報はリンクにすることで別APIから参照可能です。

この実装での問題点は、店名が変わったときにその店のIDを含むentryすべてのカラムを変更しなければならない点でしょうか。まあ、そうそう店名が変わることは無いでしょうし今の実装もホットペッパーAPIからの店情報を更新せずそのままshopテーブルに突っ込んでしまっているので考えなくても良さそうです。

解決策2 すでに「いいね」されているか「オーナーかどうか」はブラウザ側で見る

これをすると何がいいかというと、タイムラインを返すAPIは全ユーザー共通になるので容易にキャッシュをすることができます。

「いいね」されているIDのリストとかは別でfetchしてIndexedDBなどに格納してしまえば簡単に実装ができます。

デメリットとしては、ブラウザ側での処理が多くなってしまう可能性があります。まあ、最新50件程度なら問題なさそうな気もしています。 また、「いいね」数が数万単位になってくるともうブラウザでは処理はできませんので非現実的なのかなと感じます。

Twitterアーキテクチャを見る

ありがたいことに、Twitterの内部構造について解説してくれている記事があります(2010年)。

atmarkit.itmedia.co.jp

この記事をみると、Twitterのタイムラインはフォロワーのツイートを購読するPubSubのようなアーキテクチャを使用しているようです。

まとめ

とりあえず、自分がいいねつけてるかどうかは複合インデックス張ってサブクエリでSELECTするようにしたら良さそう。

趣味アプリ楽しい^〜