X運用 · 公開アルゴリズム · 第1回

Thunderとは?フォロー中の人の投稿が集められる仕組み

なぜ何時間も前の投稿がおすすめに出てくるのか。あなたの投稿はフォロワーに何時間見えているのか。Xが公開したアルゴリズムのコードに、答えがはっきり書いてありました。

Xの公開アルゴリズム(xai-org/x-algorithm)を読み解くシリーズです。

おすすめタイムラインは、大きく3段階で作られます。

  1. 候補集め … 表示するかもしれない投稿を数千件かき集める
  2. ランキング … 「あなたが反応しそうな順」に並べ替える
  3. フィルタ … 表示直前に問題のある投稿を抜く

今回はこのうち「候補集め」の主役のひとつ、Thunder(サンダー)の話です。フォロー中のアカウントの投稿が、どうやってあなたのおすすめ候補に入るのか。コードで確認できた事実だけを書きます。

Thunderの正体は「直近48時間の投稿を並べた巨大な棚」

Thunderは、投稿の作成イベントをリアルタイムで受け取って、メモリ上に保持し続けるサービスです。

  • 保持期間は48時間(デフォルト設定値: 172,800秒)。これより古い投稿は棚から落ちます
  • 投稿は著者ごとに「オリジナル投稿」「リプライ・リポスト」の棚に分けて置かれ、動画付きの投稿はさらに動画用の棚にも追加登録されます
  • 取り込みの時点で除外されるものもあります(例: nullcast=タイムライン非表示指定の投稿)
保持期間48時間の設定コード
保持期間48時間の設定 (thunder/args.rs L48-49)
https://github.com/xai-org/x-algorithm/blob/main/thunder/args.rs#L48-L49

つまりThunderには、直近48時間の通常投稿が広く保持されています。この時点では、誰の候補になるかはまだ決まっていません。

タイムラインを開いた瞬間に起きること

あなたがおすすめタイムラインを読み込むと、システムはThunderにこう問い合わせます。

  • あなたがフォローしているアカウントのIDリスト(上限10,000人)
  • 表示済みの投稿ID(これは除外してほしい、というリスト)
  • 最大取得件数: 1,200件(デフォルト値)

ちなみにフォローが10,000人を超えている場合、超過分は警告ログを残して単純に切り捨てられます。どの1万人が残るかの並び順はコードの外(ソーシャルグラフ側)で決まるため、公開コードからは分かりません。1万人以上フォローしている人のタイムラインでは、フォロー中でもThunder経由の候補には入らない相手が構造的に存在することになります(別の候補取得経路から入る可能性までは否定できません)。

Thunderへのリクエストを組み立てるコード
タイムライン側からThunderへのリクエスト (home-mixer/sources/thunder_source.rs L30-42)
https://github.com/xai-org/x-algorithm/blob/main/home-mixer/sources/thunder_source.rs#L30-L42

Thunderは、フォロー中の各アカウントの棚から投稿を取り出します。このとき著者1人あたりの上限があります。

  • オリジナル投稿: 最大50件
  • リプライ・リポスト: 合わせて最大30件

どれだけ連投している人でも、1回の読み込みで候補に入るのはこの枠までです。

上限値が定義された設定ファイル
上限値の定義 (thunder/config.rs L1-8)
https://github.com/xai-org/x-algorithm/blob/main/thunder/config.rs#L1-L8

意外な事実: Thunderは「選別」をほぼしていない

取り出した投稿をどう絞るのか。最後の処理は、コードでははっきりこう書かれています。

最終処理score_recentの実物コード
最終処理 score_recent (thunder/thunder_service.rs L322-326)
https://github.com/xai-org/x-algorithm/blob/main/thunder/thunder_service.rs#L322-L326

作成日時の新しい順に並べて、上から1,200件切るだけ。

いいね数も、投稿の質も、あなたとの親密度も、Thunderは一切見ていません。「反応されそうか」を判断するのは次の段階(ランキング)の仕事で、Thunderは純粋な新着回収係です。

主なフィルタ・制限は次の通りです。

  • 表示済みと記録された投稿を除外
  • フォロー中の人があなたの投稿をリポストしたものは除外(リポスト経由で自分の書いた投稿が自分のおすすめに出てこないように)
  • リプライは「オリジナル投稿への直接リプライ」など条件を満たすものだけ通過
  • 上限の50件を探すために1人の棚を遡るのは、新しい順に最大500件まで(途中で除外される投稿があるため、拾う数の上限50件とは別に「探す深さ」の上限がある)
  • ハードコードされた除外リスト: @grok、パロディbotの@gork、X社内の実験用アカウントの3つ

自社AIのアカウントが名指しでコードに書かれて除外されているのは、ちょっと面白いポイントです。

補足: リポストと引用はどう扱われるのか

あなたが誰かの投稿をリポストすると、それは「あなたの投稿」としてThunderの棚に入ります。ただし入るのはオリジナルの棚ではなく、リプライと同じ2つ目の棚です。

  • フォロワーへの配信枠は、リプライと共用の「1人あたり30件」。オリジナル投稿の50件枠は消費しません
  • リポスト経由で、元の投稿はあなたのフォロワー(元の投稿者をフォローしていない人も含む)の候補に入ります。投稿がフォロー外へ届く、いちばん素朴で確実なルートです
  • ただし、あなたをフォローしていない人に届くことはありません。おすすめには「フォローしていない人の投稿」を拾ってくる別の経路もあるのですが(次回解説します)、その経路で拾われた候補がリポストやリプライだった場合、表示前に一律で弾くフィルタが存在します。つまり、あなたのリポストを見られるのは、あなたのフォロワーだけです

https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/oon_retweet_reply_filter.rs

一方、引用は扱いが大きく違います。引用ポストはリポストでもリプライでもない「オリジナル投稿」として処理されるためです。

  • 入る棚はオリジナル投稿の棚(50件枠)。リポストの30件枠より広い
  • 上のフィルタは「リポストとリプライ」だけを見ているので、引用は弾かれません。つまり引用ポストはフォロー外の候補集め経路にも乗れます
  • リポストの射程が「自分のフォロワーまで」なのに対し、引用はオリジナル投稿と同じ扱いで広がる余地がある

「そのまま転送(リポスト)」より「一言添えた再発信(引用)」の方が、候補集めの段階から一貫して広い扱いを受けている、というのがコードから読み取れる設計です。

最大10,000人 × 50件が、どうやって1,200件になるのか

ここまでの数字を整理すると、疑問が湧くはずです。フォロー10,000人 × 1人50件なら理論上は数十万件。それがなぜ1,200件に収まるのか。

絞り込みは3ステップで、順番が重要です。

ステップ1: 棚から取り出す。フォロー中の各アカウントの棚から、48時間以内かつ未表示の投稿を、1人あたりの上限(オリジナル50件、リプライ・リポスト合わせて30件)まで取り出します。フォロー先が多く活発なほど、この時点の山は大きくなります。

ステップ2: 全部まとめて新着順に並べる。誰の投稿かに関係なく、1つの山にして作成日時順にソートします。

ステップ3: 上から1,200件で切る。これで終わりです。つまり、ここまでの条件(著者ごとの上限や各種除外)を通過した投稿を最後に全部まとめて、「新着上位1,200件に入れるか」が実質的な最後の関門になります。

これが意味することを、単純化した概算で見てみます(全員が均等に投稿すると仮定した、あくまで理解用の数字です)。

  • フォロー1,000人、平均20投稿/日の場合 → 1日約20,000件 → 新着1,200件は直近約1.5時間分
  • フォロー100人、平均20投稿/日の場合 → 1日約2,000件 → 新着1,200件は約14時間分

つまり「48時間残る」は棚の話であって、実際に候補に入れる時間の長さは、読み手のタイムラインに流れ込む新着投稿の量で決まります。フォロー先が多く活発な人のタイムラインでは、あなたの投稿は数時間で新着1,200件から押し出されます。逆に流量の少ないタイムラインでは、丸1日前の投稿でも候補に残っています。

投稿する側から見ると: あなたの投稿がフォロワーの候補に何時間残るかは、そのフォロワーのフォロー先の数と、その人たちの投稿頻度(=タイムラインの流量)で人ごとに違う、ということです。フォロー数が多くても大半が休眠アカウントなら流量は少なく、フォロー数が少なくても全員が高頻度投稿なら流量は多くなります。

「何時間も前の投稿」がおすすめに出てくる理由

この仕組みから、多くの人が不思議に思う現象が説明できます。

理由1: 棚には48時間分ある。
Thunderの候補プールは「直近48時間」。40時間前の投稿でも候補としては現役です。

理由2: 表示順を決めるのはThunderではない。
Thunderが返す新着順1,200件は「候補の束」にすぎません。実際の表示順は、次の段階でAIモデル(Phoenix)が「あなたが反応しそうな確率」で採点して決めます。10時間前でも「この人は反応しそう」と予測された投稿は、10分前の投稿より上に来ます。少なくとも公開されているランキングの重みパラメータに「古いから減点」という明示的な項目は見当たりません(48時間を超えた投稿を一律に落とす足切りはあります。詳細はランキングの回で)。

理由3: 除外されるのは「表示済み」の投稿だけ。
一度もタイムラインに表示されなかった投稿は、「未読の候補」として残り続けます。前回のセッションで下までスクロールしなかった分が、次に開いたとき上位に来るのは、この仕組みの自然な帰結です。ただし前のセクションの通り、未読でも新着1,200件の枠から押し出されれば候補からは外れます。

補足として、体感との答え合わせをもう一歩。フォロー先が多く活発なタイムラインでは、Thunder経由の投稿は数時間で新着1,200件から押し出されるため、実は「何時間も前の投稿」がThunder経由で出てくる余地は小さくなります。その場合に古い投稿が出てくる主なルートは、(a)フォロー外の候補集め(次回解説する仕組み。新着順ではなく内容の近さで拾うため、全候補共通の「48時間以内」の足切りの範囲内なら古さを問いません)、(b)フォロー中の誰かによるリポスト(リポストした時刻が新着として扱われる)です。理由1〜3がフルに効くのは、主に流量の少ないタイムラインです。

1つだけ用語の注意

Thunderが集めるのは「あなたがフォローしている相手」の投稿です。あなたをフォローしてくれている人(フォロワー)の投稿ではありません。コード上も、リクエストに載るのは自分の「フォロー中リスト」です。片思いフォローでも、フォローしている限りその人の投稿は候補に入ります。

なお、相互フォローの相手の投稿が優遇される仕組みは実在しますが、それは候補集めではなくランキング段階の話なので、今回は割愛します(ランキングの回で扱います)。少なくともThunderの内部に相互フォローの優遇はなく、候補集めの時点では片思いも相互も同じ扱いです。

分かっていること・分からないこと

分かっていること

  • Thunderは新着回収のみで、質の判断を一切しない(コードで確認)
  • 著者ごとの上限(オリジナル50件、リプライ・リポスト合わせて30件)と全体上限1,200件
  • 保持期間48時間、表示済み除外、@grok除外

分からないこと

  • 1,200件の候補のうち、実際に何件がタイムライン上位まで残るか(ランキング以降の挙動は次回)
  • 各設定値は「公開コードのデフォルト値」であり、本番で別の値に調整されている可能性

まとめ

  • フォロー中の投稿は、内容の質を評価されることなく、各種の上限・除外を通過したものが新着順で候補に入る
  • 勝負が決まるのは候補集めではなく、その後のランキング
  • 「古い投稿が出てくる」のは不具合ではなく、投稿が最大48時間保持され、未読かつ新着上位1,200件に入っていれば候補になり続ける設計だから

次回は、フォロー外の投稿を拾ってくるもうひとつの仕組み、Phoenix retrieval(あなたの行動履歴から候補を直接検索するAIモデル)を扱います。

参照(行番号付きリンク。開くと該当行がハイライトされます)

新着順ソートして1,200件で切る処理:
https://github.com/xai-org/x-algorithm/blob/main/thunder/thunder_service.rs#L322-L326

上限値の定義(1,200件・10,000人・50件・30件):
https://github.com/xai-org/x-algorithm/blob/main/thunder/config.rs#L1-L8

タイムラインからThunderへのリクエスト:
https://github.com/xai-org/x-algorithm/blob/main/home-mixer/sources/thunder_source.rs#L30-L42

保持期間48時間の設定:
https://github.com/xai-org/x-algorithm/blob/main/thunder/args.rs#L48-L49

投稿の棚(ストア)本体:
https://github.com/xai-org/x-algorithm/blob/main/thunder/posts/post_store.rs

この記事のもとになったX記事

Xで公開した第1回の記事を、サイト用に読みやすく再構成したものです。

次の回:Phoenixとフォロー外の投稿 ブログ一覧へ