Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ノート数が多いインスタンスで、フォローが少ないとホームタイムラインの読み込みでクエリタイムアウトになる #9205

Closed
syuilo opened this issue Nov 20, 2022 · 16 comments · Fixed by #10106
Labels
🔥high priority packages/backend Server side specific issue/PR 🐢Performance Efficiency related issue/PR

Comments

@syuilo
Copy link
Member

syuilo commented Nov 20, 2022

💡 Summary

🥰 Expected Behavior

🤬 Actual Behavior

📝 Steps to Reproduce

📌 Environment

Misskey version:
Your OS:
Your browser:

@syuilo syuilo added 🐢Performance Efficiency related issue/PR packages/backend Server side specific issue/PR labels Nov 20, 2022
@syuilo syuilo changed the title フォローが少ないとホームタイムラインの読み込みでクエリタイムアウトになる ノート数が多いインスタンスで、フォローが少ないとホームタイムラインの読み込みでクエリタイムアウトになる Nov 20, 2022
@syuilo
Copy link
Member Author

syuilo commented Nov 20, 2022

インデックス張ってあるのに何故だろう

@tamaina
Copy link
Contributor

tamaina commented Nov 22, 2022

リストやローカルもめっちゃ遅い感じが?

@tamaina
Copy link
Contributor

tamaina commented Nov 22, 2022

日付順にソートするのに時間かかってるとか(適当)

@acid-chicken
Copy link
Member

acid-chicken commented Nov 25, 2022

  • フォローが少ない(厳密にはフォローしているユーザーの書き込みが少ない)ユーザーのホーム
  • リモートの投稿割合がかなり高いサーバー(所謂お一人様など)のローカル

みたいな、最新から数えていってタイムラインに表示すべき投稿を埋めるまでかなりの数をシークする必要があるケースで遅くなっている気がする(つまりシーケンシャルスキャンしている→インデックスが効いてない)

@acid-chicken
Copy link
Member

ミュートなりブロックなりでインデックスが効かなくなる説

@syuilo
Copy link
Member Author

syuilo commented Dec 10, 2022

例えば一ヶ月以内の投稿しか対象にしないようにしたら改善するかな

@syuilo syuilo pinned this issue Dec 11, 2022
@syuilo
Copy link
Member Author

syuilo commented Jan 1, 2023

例えば一ヶ月以内の投稿しか対象にしないようにしたら改善するかな

効果なかった

@tamaina
Copy link
Contributor

tamaina commented Jan 26, 2023

MastodonとかはRedisにタイムラインを保持している(DBの検索はやらないとかなんとか)

@syuilo
Copy link
Member Author

syuilo commented Jan 26, 2023

Issueはこちら
#9325

@tamaina
Copy link
Contributor

tamaina commented Jan 26, 2023

あるんだ

@tamaina
Copy link
Contributor

tamaina commented Jan 26, 2023

でもシークが問題なら、あまり参照されない古い投稿を取り出すのも遅いんじゃね?

@tamaina
Copy link
Contributor

tamaina commented Jan 26, 2023

いやノートを検査しまくらないから早くなるか

@tamaina
Copy link
Contributor

tamaina commented Feb 1, 2023

ユーザーのファイル付き投稿一覧を取得するのもしんどかったりする

@xianonn
Copy link
Contributor

xianonn commented Feb 11, 2023

const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.createdAt > :minDate', { minDate: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) // 30日前まで
.andWhere(new Brackets(qb => { qb
.where('note.userId = :meId', { meId: me.id });
if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`);
}))

L78 と L79 が OR で繋がっているのでインデックスが使えていないのだと思います。

この部分を SQL で書くと

WHERE
  "note"."createdAt" > $1 AND
  (
    "note"."userId" = $2 OR
    "note"."userId" IN (SELECT
        "following"."followeeId" AS "following_followeeId"
      FROM
        "following" "following"
      WHERE
        "following"."followerId" = $3)
  )

のようになりますが、これを OR を使わない形に変える、例えば UNION を使って

WHERE
  "note"."createdAt" > $1 AND
  (
    "note"."userId" IN (SELECT
        "following"."followeeId" AS "following_followeeId"
      FROM
        "following" "following"
      WHERE
        "following"."followerId" = $3
      UNION ALL
      SELECT $2)
  )

とすると速くなります。

ですがまだ数秒かかってしまう感じ(ちゃんと調べてませんが、多分サブクエリが複数回呼ばれてその分遅くなっている?)なので、この部分を内部結合にして

FROM
  "note" "note"
  INNER JOIN (SELECT
        "following"."followeeId" "id"
      FROM
        "following" "following"
      WHERE
        "following"."followerId" = $3
      UNION ALL
      SELECT $2) "followeeOrMe" ON
    "followeeOrMe"."id"="note"."userId"

(略)

WHERE
  "note"."createdAt" > $1

とすると一瞬で返るようになります(内部結合にしたことで1回だけ呼ばれるようになるはず)。

ただ TypeORM では UNION がサポートされていないっぽい。

@Ryokusa
Copy link

Ryokusa commented Feb 22, 2023

あまり詳しくなくて恐縮なのですが、フォローがあるユーザーはIN句で自身もまとめて検索するのはどうでしょうか?
L77~L79の範囲

if (hasFollowing){
    //IN句で自身ID、フォローIDからノート取得
}else{
    //自身IDからノート取得
}

冗長かもしれませんがORでインデックスが外れることを考えるとこっちでも良いのではと思いました
Brackets の書き方がわからず申し訳ない…

@xianonn
Copy link
Contributor

xianonn commented Feb 26, 2023

フォローがあるユーザーはIN句で自身もまとめて検索、のところは上で書いたやつの 1 つ目の案になりますね。

UNION は TypeORM で対応しておらずクエリ直書きの力技になってしまうので他の方法を考えているところ。
2 つ目の案の followeeOrMe の部分を VIEW で作ってしまえば普通のテーブルのように扱える(TypeORM が VIEW に対応していれば。未確認)が、これだけのために VIEW を作るのもなんだか。

1 クエリになっているのを崩さないように考えていたが、先にフォロイーを取得してしまって 2 クエリにすれば解決しそうなのでやってみる。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔥high priority packages/backend Server side specific issue/PR 🐢Performance Efficiency related issue/PR
Projects
None yet
5 participants