misskey icon indicating copy to clipboard operation
misskey copied to clipboard

fix(backend): DBフォールバック有効時、複数のFTTソースから取得するタイムラインで取得漏れが起きる現象の修正

Open tai-cha opened this issue 11 months ago • 15 comments

What

  • FTTにおいて複数のソースからノートを取得する際に複数ソースの中で(そのソース内の)最古のノートが(複数ソースの中で)一番新しいものまでを返すように制限した
  • FTTにおいてDBフォールバックを有効にしている際に、ノートがないソースがある場合はDBにフォールバックするように変更した

Why

(たくさん遡ると起きやすい) 複数のTLソースの中でも保存されている期間に差があり、取得できないノートが発生してしまうため

Fix: #13488 (#13488から再掲) FTT漏れ

Additional info (optional)

Checklist

  • [x] Read the contribution guide
  • [ ] Test working in a local environment
  • [ ] (If needed) Add story of storybook
  • [x] (If needed) Update CHANGELOG.md
  • [ ] (If possible) Add tests

tai-cha avatar Mar 02 '24 06:03 tai-cha

Codecov Report

Attention: Patch coverage is 7.69231% with 12 lines in your changes missing coverage. Please review.

Project coverage is 40.18%. Comparing base (32651ab) to head (48232ca). Report is 8 commits behind head on develop.

Files Patch % Lines
.../backend/src/core/FanoutTimelineEndpointService.ts 8.33% 11 Missing :warning:
...es/backend/src/server/api/endpoints/users/notes.ts 0.00% 1 Missing :warning:
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #13495      +/-   ##
===========================================
+ Coverage    40.08%   40.18%   +0.10%     
===========================================
  Files         1524     1524              
  Lines       188758   188768      +10     
  Branches      3465     2619     -846     
===========================================
+ Hits         75656    75854     +198     
+ Misses      112528   112372     -156     
+ Partials       574      542      -32     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

codecov[bot] avatar Mar 02 '24 06:03 codecov[bot]

このPRによるapi.jsonの差分

差分はこちら

Get diff files from Workflow Page

github-actions[bot] avatar Mar 02 '24 06:03 github-actions[bot]

HTL/STLはこの実装で大丈夫そうですが、タイムラインの更新が緩やかなサーバー、ユーザー個人のタイムラインの場合FTTを利用できずDBにフォールバックしてしまう割合が高くなりそうという懸念点がありそうです

FTT実装当時しゅいろさんに共有した取得漏れの例ですが

ユーザープロフィールのFTTのlimitを4とした時
あるユーザーが
今日は一般のノートを5件、リプライを0件投稿
昨日は一般のノートを5件、リプライを1件投稿したとすると
今日時点でFTTに残るノートは今日の最新の4件、昨日のリプライ1件で
スクロールして遡ると今日の4件の後すぐ昨日のリプライが見えて、その後ずっと昨日のリプライ以降のノートが見えてくるはず
今日のノート1件が表示されないという結果になってしまう

この実装だとこのユーザーは一度もファイルをアップロードしていないため常にDBにフォールバックされてしまいそうです。 すぐフォールバックさせる代わりに次のページング処理で↓の部分をもう一度照会するとフォールバックなしでも解決できそうですがどうでしょう image

u1-liquid avatar Mar 15 '24 02:03 u1-liquid

確かにそうですね!(現在多忙な上に諸事情がありすぐに反映できないかもしれないですがそのほうがパフォーマンス上良さそうなので試してみます)

(ユーザーTLもこの実装を利用しているのをやや忘れていました…)

tai-cha avatar Mar 15 '24 12:03 tai-cha

少し思考が散らかっているのでメモ書きです

(※ここでのDBはPostgreSQLないしRDBを指す)

改めて考えてみているけど一度もファイルをアップロードしていないかどうかはDBを見てみないとわからなくて(FTTのキャッシュがredisの内容が飛ぶなどの理由で不十分な場合もある)、もう一度残った部分のFTTを参照するだけではDBには存在するのにFTTには存在しないノートがあるかどうか(つまり取得漏れがあるかどうか)がわからない

FTTにないだけでDBにはノートがあるケースは割とredisが揮発するものである前提からして(またCacheにMaxが定められているのも含め)起きそうなので悩ましい

全くDBを参照させないというのは仕組み上難しそう(これがそもそも複数のTLのキャッシュが残っている幅の差分の間でフォールバックがきちんと効くようにするPRという趣旨なのもある)

そのFTTソースに対応する条件のノートがDBに本当に存在しないかを確かめるには少なくともCOUNT句か何かでDBを見て比較自体はしないと結局問題が解決しない気がする

もう一度FTTのうちのページングで取得するだけでは取得漏れをきちんと漏らさないことは実現不可能なのでは…?

tai-cha avatar Mar 22 '24 16:03 tai-cha

そもそもFTTに含まれていないノートをDBから持ってくるという趣旨なのでFTTが空な場合にフォールバックするのは自然ではある気がしてきた、ただ一度もファイルを投稿していなければFTTが空で常にフォールバックが起こってしまう(DBへの負荷を減らすためのFTTなのに効果が弱くなる)というのは理解できるのでどうやって落とし所つけようかしら…

tai-cha avatar Mar 22 '24 16:03 tai-cha

タイムラインが穏やかなサーバーでDBへのフォールバック頻度が増えることはあまり大きな問題ではない気がする、それだけアクティブなユーザーも少なくなることが予想されるので(また、DBフォールバックは無効にもできる)

むしろLTLが緩やかなサーバーではより取得漏れが起きやすく深刻

フォローが0人のときもSTLで同様に毎回フォールバックが起きる問題が発生してしまう

フォールバック回数抑えつつ取得漏れをなくすにはよりもっと根本的なところを変える必要がある…?

tai-cha avatar Mar 22 '24 17:03 tai-cha

~なんかいけそうな気がしてきたので改良するか~ なんでいけそうな気がしてきたか忘れた

tai-cha avatar Jun 15 '24 08:06 tai-cha

タイムラインの更新が緩やかなサーバー、ユーザー個人のタイムラインの場合FTTを利用できずDBにフォールバックしてしまう割合が高くなりそうという懸念点がありそうです

ユーザーTLではDBにフォールバックさせないようにしました(これは取得漏れをさせずDBフォールバックをしない実装するのが難しいため) そのうえでタイムラインが緩やかなサーバーに関する懸念に関してはそもそもDBへの参照回数もユーザー数が少なければ落ちるのであまり問題ないと考えています

tai-cha avatar Jul 25 '24 07:07 tai-cha

FanoutTimelineNameごとにdbFallbackの手段があれば単純にその区間の行数のcountをしてくればいいのでもう少し楽になりそうではある

tai-cha avatar Jul 25 '24 07:07 tai-cha

現在のmasterにこれを取り込むと #13978 でSTLに追加したlocalTimelineWithReplyTo:${userId}によりSTLの一部の(db fallbackすると落ちるタイプの)テストが落ちることが確認されています (niri-la#217 等が実際に落ちてる事例になります)

これの対策にもなり、またredistimelineがからになってしまってすべてのノートをdbから取得してしまう問題の対策として、各種TL要素のうちuserTimeline${string}:${userId}${timeline}WithReplyTo:${userId}のようにユーザに関わるものについて、ユーザがアカウントに登録した際に一つだけダミーのデータを追加するというのはどうでしょうか。

懸念点としてはノートを一切しない人のためにuserTimeline系をRedisに保存することになることだと思います。

anatawa12 avatar Jul 31 '24 17:07 anatawa12

Note:

現在のmasterにこれを取り込むと https://github.com/misskey-dev/misskey/pull/13978 でSTLに追加したlocalTimelineWithReplyTo:${userId}によりSTLの一部の(db fallbackすると落ちるタイプの)テストが落ちることが確認されています (https://github.com/niri-la/misskey.niri.la/pull/217 等が実際に落ちてる事例になります)

https://github.com/niri-la/misskey.niri.la/pull/217/commits/92a55ade4300aecc2a4d49e8f82f75893b0534c6 のようにテスト環境で挙動を回避すれば問題なさそうなのは確認したし正しそう

これの対策にもなり、またredistimelineがからになってしまってすべてのノートをdbから取得してしまう問題の対策として、各種TL要素のうちuserTimeline${string}:${userId}や${timeline}WithReplyTo:${userId}のようにユーザに関わるものについて、ユーザがアカウントに登録した際に一つだけダミーのデータを追加するというのはどうでしょうか。

これは確かにこれから作成するユーザに関してはユーザー作成時にやれば解決はできるけど、既存ユーザーのうち対象となるノートが一個もない人では引き続き同じことが起きる上にそれを既存ユーザーにまで適用しようと思うとデータを全部作ってあげるのが大変そうな気が…?(純粋に参照されたときになければ作るでいいかもしれないけど)

tai-cha avatar Aug 19 '24 07:08 tai-cha

参照したときに作るか古い人はある程度負荷を許容するか、サーバー規模で必要ならバッジを叩いてもらうかあたりを考えてました

ある程度期間参加してる人はFTTに一切ノートがないってことは少なさそうなのでそんなに発生するのかなという気がしています

anatawa12 avatar Aug 19 '24 07:08 anatawa12

Redis自体の内容が飛んだ場合や大規模サーバーで休眠ユーザーが多い場合とかはある程度考えた方がいいかも(実際割合どれぐらいいるのかわからないので有識者が意見してくれる方がありがたい)

tai-cha avatar Aug 19 '24 07:08 tai-cha

DBに1件も無い事を示すマーカーをredisに埋め込む事を思いついた。 マーカーが記録されてるとredisが揮発していない、かつDBにもデータが無い事がDBを叩かず認識できるはず。 DBフォールバックした時に無ければredisにマーカー立てる感じで マーカーがTTL落ちしても通常のDBフォールバックが起こるはずだから破損しないはず?

kozakura913 avatar Aug 19 '24 18:08 kozakura913