misskey
misskey copied to clipboard
fix(backend): DBフォールバック有効時、複数のFTTソースから取得するタイムラインで取得漏れが起きる現象の修正
What
- FTTにおいて複数のソースからノートを取得する際に複数ソースの中で(そのソース内の)最古のノートが(複数ソースの中で)一番新しいものまでを返すように制限した
- FTTにおいてDBフォールバックを有効にしている際に、ノートがないソースがある場合はDBにフォールバックするように変更した
Why
(たくさん遡ると起きやすい) 複数のTLソースの中でも保存されている期間に差があり、取得できないノートが発生してしまうため
Fix: #13488
(#13488から再掲)
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
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.
HTL/STLはこの実装で大丈夫そうですが、タイムラインの更新が緩やかなサーバー、ユーザー個人のタイムラインの場合FTTを利用できずDBにフォールバックしてしまう割合が高くなりそうという懸念点がありそうです
FTT実装当時しゅいろさんに共有した取得漏れの例ですが
ユーザープロフィールのFTTのlimitを4とした時
あるユーザーが
今日は一般のノートを5件、リプライを0件投稿
昨日は一般のノートを5件、リプライを1件投稿したとすると
今日時点でFTTに残るノートは今日の最新の4件、昨日のリプライ1件で
スクロールして遡ると今日の4件の後すぐ昨日のリプライが見えて、その後ずっと昨日のリプライ以降のノートが見えてくるはず
今日のノート1件が表示されないという結果になってしまう
この実装だとこのユーザーは一度もファイルをアップロードしていないため常にDBにフォールバックされてしまいそうです。
すぐフォールバックさせる代わりに次のページング処理で↓の部分をもう一度照会するとフォールバックなしでも解決できそうですがどうでしょう
確かにそうですね!(現在多忙な上に諸事情がありすぐに反映できないかもしれないですがそのほうがパフォーマンス上良さそうなので試してみます)
(ユーザーTLもこの実装を利用しているのをやや忘れていました…)
少し思考が散らかっているのでメモ書きです
(※ここでのDBはPostgreSQLないしRDBを指す)
改めて考えてみているけど一度もファイルをアップロードしていないかどうかはDBを見てみないとわからなくて(FTTのキャッシュがredisの内容が飛ぶなどの理由で不十分な場合もある)、もう一度残った部分のFTTを参照するだけではDBには存在するのにFTTには存在しないノートがあるかどうか(つまり取得漏れがあるかどうか)がわからない
FTTにないだけでDBにはノートがあるケースは割とredisが揮発するものである前提からして(またCacheにMaxが定められているのも含め)起きそうなので悩ましい
全くDBを参照させないというのは仕組み上難しそう(これがそもそも複数のTLのキャッシュが残っている幅の差分の間でフォールバックがきちんと効くようにするPRという趣旨なのもある)
そのFTTソースに対応する条件のノートがDBに本当に存在しないかを確かめるには少なくともCOUNT句か何かでDBを見て比較自体はしないと結局問題が解決しない気がする
もう一度FTTのうちのページングで取得するだけでは取得漏れをきちんと漏らさないことは実現不可能なのでは…?
そもそもFTTに含まれていないノートをDBから持ってくるという趣旨なのでFTTが空な場合にフォールバックするのは自然ではある気がしてきた、ただ一度もファイルを投稿していなければFTTが空で常にフォールバックが起こってしまう(DBへの負荷を減らすためのFTTなのに効果が弱くなる)というのは理解できるのでどうやって落とし所つけようかしら…
タイムラインが穏やかなサーバーでDBへのフォールバック頻度が増えることはあまり大きな問題ではない気がする、それだけアクティブなユーザーも少なくなることが予想されるので(また、DBフォールバックは無効にもできる)
むしろLTLが緩やかなサーバーではより取得漏れが起きやすく深刻
フォローが0人のときもSTLで同様に毎回フォールバックが起きる問題が発生してしまう
フォールバック回数抑えつつ取得漏れをなくすにはよりもっと根本的なところを変える必要がある…?
~なんかいけそうな気がしてきたので改良するか~ なんでいけそうな気がしてきたか忘れた
タイムラインの更新が緩やかなサーバー、ユーザー個人のタイムラインの場合FTTを利用できずDBにフォールバックしてしまう割合が高くなりそうという懸念点がありそうです
ユーザーTLではDBにフォールバックさせないようにしました(これは取得漏れをさせずDBフォールバックをしない実装するのが難しいため) そのうえでタイムラインが緩やかなサーバーに関する懸念に関してはそもそもDBへの参照回数もユーザー数が少なければ落ちるのであまり問題ないと考えています
FanoutTimelineNameごとにdbFallbackの手段があれば単純にその区間の行数のcountをしてくればいいのでもう少し楽になりそうではある
現在のmasterにこれを取り込むと #13978 でSTLに追加したlocalTimelineWithReplyTo:${userId}
によりSTLの一部の(db fallbackすると落ちるタイプの)テストが落ちることが確認されています (niri-la#217 等が実際に落ちてる事例になります)
これの対策にもなり、またredistimelineがからになってしまってすべてのノートをdbから取得してしまう問題の対策として、各種TL要素のうちuserTimeline${string}:${userId}
や${timeline}WithReplyTo:${userId}
のようにユーザに関わるものについて、ユーザがアカウントに登録した際に一つだけダミーのデータを追加するというのはどうでしょうか。
懸念点としてはノートを一切しない人のためにuserTimeline
系をRedisに保存することになることだと思います。
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}のようにユーザに関わるものについて、ユーザがアカウントに登録した際に一つだけダミーのデータを追加するというのはどうでしょうか。
これは確かにこれから作成するユーザに関してはユーザー作成時にやれば解決はできるけど、既存ユーザーのうち対象となるノートが一個もない人では引き続き同じことが起きる上にそれを既存ユーザーにまで適用しようと思うとデータを全部作ってあげるのが大変そうな気が…?(純粋に参照されたときになければ作るでいいかもしれないけど)
参照したときに作るか古い人はある程度負荷を許容するか、サーバー規模で必要ならバッジを叩いてもらうかあたりを考えてました
ある程度期間参加してる人はFTTに一切ノートがないってことは少なさそうなのでそんなに発生するのかなという気がしています
Redis自体の内容が飛んだ場合や大規模サーバーで休眠ユーザーが多い場合とかはある程度考えた方がいいかも(実際割合どれぐらいいるのかわからないので有識者が意見してくれる方がありがたい)
DBに1件も無い事を示すマーカーをredisに埋め込む事を思いついた。 マーカーが記録されてるとredisが揮発していない、かつDBにもデータが無い事がDBを叩かず認識できるはず。 DBフォールバックした時に無ければredisにマーカー立てる感じで マーカーがTTL落ちしても通常のDBフォールバックが起こるはずだから破損しないはず?