エピソードからの話数選択の読み込み時間&エラーについて

お世話になっております。
いつも楽しく利用させていただいております。

早速ですが、
エピソードからの話数選択の読み込み時間が非常に長く
また頻繁にエラーが起こるようです。
解決方法などありますでしょうか。
お忙しいところ恐縮ですがよろしくお願いします。

Shimba, Koji管理者
2024-05-27 14:37

ご利用ありがとうございます。
具体的にはどの作品のエピソードページが重いでしょうか?

長時間かかる処理が同時期に動いていると別の利用者にも影響が出ることがあるため、そのタイミングで重くなっている可能性もありそうです。
もし特定のページが重いのであれば、その作品に大量にデータが紐づいている等の理由で重くなっている可能性がありそうです。

rinsuki編集者
2024-06-23 18:37

こちらですが、さまざまな検証をしたところ、自分がフォローしているユーザーの合計視聴記録数に依存して遅くなっているように見受けられます。

例として、ダンジョン飯1話 https://annict.com/works/9820/episodes/156458 のページの場合、友人のアカウント (フォロイーの合計記録数4000ほど) の場合は2秒程度で済むそうですが、私のアカウント (フォロイーの合計記録数40000ほど) の場合11秒ほど開くのにかかります。

〜〜ここから技術的な話〜〜

(合計記録数は以下の GraphQL クエリで出てきた recordsCount を計算しています)

query {
  user(username: "rinsuki") {
    following(first: 1000) {
      nodes {
        recordsCount
      }
    }
  }
}

推察ですが、https://github.com/annict/annict/blob/4a73b9e3cd1c49fd14dd3111b29dd8e65f2a487e/app/controllers/concerns/episode_record_list_settable.rb#L27-L28 ここのfollowing_records の取得が (episode_id, deleted_at) のインデックスではなく (user_id) のインデックスを参照してしまい、フォロー中の全ユーザーの記録一覧を見ているのではないでしょうか。

(index は https://github.com/annict/annict/blob/4a73b9e3cd1c49fd14dd3111b29dd8e65f2a487e/app/models/episode_record.rb#L33-L34 で見ているので、実際に EXPLAIN などして確認したわけではありません)

恐らくここは (episode_id, deleted_at, user_id) のインデックスを貼るか (これがうまくいくかまでは検証していません; レコード数によってはインデックスを貼るにはレコードが多すぎるかも)、ページングを諦めて (必要であれば人気作品でブラウザが死なないように Rails 側で疑似ページングなどして) 全件取った後に Rails 側でフィルタリングするのが良いと思います。

また、私の環境ではどの話であるかに関わらず遅く、記録がまだ0件の放送前のエピソード (e.g. https://annict.com/works/10403/episodes/160294 ) では表示に20秒ほどかかります。

Shimba, Koji管理者
2024-06-25 14:12

検証ありがとうございます🙏

僕のフォロイーの合計記録数は14万ほどですが、ダンジョン飯1話のページを本番環境で見ると2, 3秒で返ってきます。まあそれも遅いですが…。

また、ローカルに本番環境のデータを持ってきてrinsukiさんとしてログインしダンジョン飯1話のページを見たんですが、600msくらいで返ってきました。
@following_records のexplainも貼っておきます。なかなか複雑な結果になっていますが、そこまでコストがかかるクエリは無さそうに見えます。

Sort  (cost=527.65..527.65 rows=1 width=2100)
   Sort Key: (CASE WHEN ((episode_records.rating_state)::text = 'bad'::text) THEN '0'::text WHEN ((episode_records.rating_state)::text = 'average'::text) THEN '1'::text WHEN ((episode_records.rating_state)::t
   ->  Nested Loop Left Join  (cost=2.17..527.64 rows=1 width=2100)
         ->  Nested Loop Left Join  (cost=1.88..527.22 rows=1 width=1994)
               ->  Nested Loop Left Join  (cost=1.59..526.72 rows=1 width=902)
                     ->  Nested Loop  (cost=1.44..526.56 rows=1 width=518)
                           ->  Nested Loop  (cost=1.01..273.02 rows=470 width=513)
                                 ->  Nested Loop  (cost=0.58..41.59 rows=4 width=451)
                                       ->  Index Only Scan using follows_user_id_following_id_key on follows  (cost=0.29..8.36 rows=4 width=8)
                                             Index Cond: (user_id = '41331'::bigint)
                                       ->  Index Scan using users_pkey on users  (cost=0.29..8.31 rows=1 width=443)
                                             Index Cond: (id = follows.following_id)
                                 ->  Index Scan using index_records_on_user_id on records  (cost=0.43..40.79 rows=1707 width=70)
                                       Index Cond: (user_id = users.id)
                                       Filter: ((deleted_at IS NULL) AND (user_id <> '41185'::bigint))
                           ->  Index Scan using index_episode_records_on_record_id on episode_records  (cost=0.43..0.54 rows=1 width=13)
                                 Index Cond: (record_id = records.id)
                                 Filter: (episode_id = '156458'::bigint)
                     ->  Index Scan using gumroad_subscribers_pkey on gumroad_subscribers  (cost=0.14..0.16 rows=1 width=384)
                           Index Cond: (id = users.gumroad_subscriber_id)
               ->  Index Scan using profiles_user_id_key on profiles  (cost=0.29..0.49 rows=1 width=1092)
                     Index Cond: (user_id = users.id)
         ->  Index Scan using index_settings_on_user_id on settings  (cost=0.29..0.40 rows=1 width=79)
               Index Cond: (user_id = users.id)
(24 rows)
Loading...