最近、大学のプロジェクト個人的な趣味でWebアプリを作っています。どういうものかというと、「行ってみたいのに一人で行く勇気が持てない」お店に一緒に行ってくれる人を探せるサービスです。
そのなかで、ホームのページを作成する際Twitterのようなタイムラインのように実装しました。そこで、API内部複数回DBを叩く動作を書いてしまった(懺悔)ので言い訳とどうしたら良かったのかを個人的にまとめようと思います。
現状の挙動
APIは以下を満たすようなエントリを返すようになっています。
- 自分が「いいね」していない
- それぞれのエントリには対応する店情報を付与
- そのエントリが自分が投稿している場合
is_owner
フラグがtrue
DBのテーブル上ではentry
テーブルとshop
テーブル、「いいね」用にapplication
テーブル(正式名所はいいねではなく「会いたい!」みたいのが正しい)が存在します。
そのため、ホーム上でこのAPIをfetchした際に、
- 最新50件のエントリを
entry
テーブルから取得 キャッシュ込でshop
エントリから店情報を取得していき、エントリに入れていく application
テーブルから自分が作成したものを取得し、すでに「いいね」している場合は除外entry
カラムのowner_user_id
が自分のユーザIDと同一の場合はis_owner
フラグをtrueにする
のように、すべてのテーブルを叩く用になってしまっています。キャッシュすればある程度は負荷は抑えられますがタイムラインなのでできるだけリアルタイムに反映したいという話題もあります。
また、一度メモリ上に自分が「いいね」したものすべてを格納したりするのでサーバ上のメモリが心もとないです。
解決策1 店情報を含めない
一番最初に考えられるのは、店情報を含めないです。詳しくいうとタイムラインで必要な情報は最低店名のみなので店名をentry
テーブルに突っ込んでしまい詳しい店情報はリンクにすることで別APIから参照可能です。
この実装での問題点は、店名が変わったときにその店のIDを含むentry
すべてのカラムを変更しなければならない点でしょうか。まあ、そうそう店名が変わることは無いでしょうし今の実装もホットペッパーのAPIからの店情報を更新せずそのままshop
テーブルに突っ込んでしまっているので考えなくても良さそうです。
解決策2 すでに「いいね」されているか「オーナーかどうか」はブラウザ側で見る
これをすると何がいいかというと、タイムラインを返すAPIは全ユーザー共通になるので容易にキャッシュをすることができます。
「いいね」されているIDのリストとかは別でfetchしてIndexedDBなどに格納してしまえば簡単に実装ができます。
デメリットとしては、ブラウザ側での処理が多くなってしまう可能性があります。まあ、最新50件程度なら問題なさそうな気もしています。 また、「いいね」数が数万単位になってくるともうブラウザでは処理はできませんので非現実的なのかなと感じます。
Twitterのアーキテクチャを見る
ありがたいことに、Twitterの内部構造について解説してくれている記事があります(2010年)。
この記事をみると、Twitterのタイムラインはフォロワーのツイートを購読するPubSubのようなアーキテクチャを使用しているようです。
まとめ
とりあえず、自分がいいねつけてるかどうかは複合インデックス張ってサブクエリでSELECTするようにしたら良さそう。
趣味アプリ楽しい^〜