インデックスとは?最初の1いいねが開けるもの
番地付きの棚への扉は、最初の1いいねが開ける。
※これはテキストの主経路の話です。動画と、フォロワー1,000人未満の小さいアカウントには別の扉があります。本文で説明します。
Xの公開アルゴリズムを読み解くシリーズ、第6回です。
最初に、前回のお詫びから始めさせてください。
第5回で「おすすめができるまでの9ステップ」を書きました。候補を最大3,000件集めて、ふるいにかけて、点数を付けて、並べ替えて、最後に35件へ。そういう流れです。
あれを読んで、こう受け取った方が多いと思います。
「じゃあ自分の投稿も、とりあえず3,000件の候補には入っているんだな」
すみません。そうとは限りませんでした。
9ステップの、さらに手前に関門がありました。候補を集める前に、そもそも「集められる対象」になっているかどうか。そしてテキスト投稿の主経路では、その関門を開けるのは、投稿することではありませんでした。
第5回は「候補に入ってから先」の話として正確ですが、「候補に入るまで」を丸ごと省いていました。今回はそこを埋めます。
もう1つ、繋がっている宿題があります。第2回でPhoenixを書いたとき、1つだけ断定を避けました。「新しい投稿は、いつからPhoenixの検索対象になるのか」という点です。公式文書と公開コードの繋がりが確定できなかったからです。
その答えの在り処は、「インデックス」でした。READMEではたった2行で済まされている場所で、どうやら見落としてしまっていたようです。そこを深掘りしてみると……
第5回の抜けと、第2回の保留。調べてみたら、どちらも同じ場所にありました。
結論を先に言います。
- Phoenixが検索するための「読む人によらない共通の図書館」が存在する
- テキストの主経路では、投稿しただけでは番地(セマンティックID)付きの一覧に載らない。扉を開けるのは「最初の1いいね」
- 例外は2つ。動画(いいね0件でも担当棚に入る。ただし条件は厳しい)と、フォロワー1,000人未満の小さいアカウント(いいね不要の「すそ野」棚が既定で用意されている)
順番に説明します。
インデックスとは何か。司書は本文を読まない
まず、今回の主役である「インデックス」の役割をはっきりさせます。
Phoenixは、おすすめ作りの中で「この読者に合いそうな投稿を1,000件持ってこい」と注文される係です。問題は母数です。相手はX上の全投稿。毎秒増え続ける山の中から、一瞬で1,000件を選ばなければいけません。
注文が来るたびに投稿の本文を1件ずつ読んでいたら、何年あっても終わりません。
だから、先に準備しておきます。検索しやすい形に整えた投稿のリストを、あらかじめ作って並べておく。これがインデックスです。
図書館で言えば「蔵書目録」です。そして、ここが今回いちばん大事な性質です。
この図書館は、読む人によって中身が変わりません。
コードのデータ構造がそのまま証拠になっています。図書館の中身はこう定義されています。
HashMap<PostId, StoredValue>
キーは「投稿ID」だけ。閲覧者のIDは、どこにも登場しません。あなた用の棚、私用の棚、といった区別は存在しない。全員が同じ1つの図書館を見に来ます。
「じゃあ、なぜ人によっておすすめが違うのか」
図書館が違うのではなく、司書に渡すリクエスト票が違うからです。あなたの行動履歴から作られた「この読者はこのあたりの本が好き」という注文票。図書館は1つ、注文票が人の数だけある。これが全体の構図です。
もう1つ、司書の重要な性質。
司書は本文を読みません。背表紙のラベルだけを見て探します。
そのラベルが、第3回で扱ったセマンティックID(投稿の住所)です。インデックスの1行は、突き詰めると「投稿ID→背表紙のコード列」第3回で「住所」と呼んだものが実際に使われる現場が、ここでした。
(正確に言うと、コードから確認できるのは「検索用に、番地付きの一覧が用意されている」ところまでです。司書が実際に何を見て探しているかは非公開なので、「本文を読まない」は比喩として読んでください。この記事では、この線引きを最後にもう一度整理します)
もう1つ。「共通の図書館」と書きましたが、これは「読む人によって中身が変わらない」という意味です。世界に物理的に1つしかない、という意味ではありません。コードから言えるのは「データの持ち方が閲覧者ごとに分かれていない」ところまでです。
登場人物。図書館で働く4人
今回はコードの部品を、図書館の係員に置き換えて話します。名簿です。
- 受付係……いいねが付くたびに「この本の情報を更新して」という申請を出すか判断する係。ただし毎回は出さない(後述)
- ラベル係……本の背表紙に番地(セマンティックID)を書き込む係。通常のテキスト経路ではいいね1件目から、動画と小規模アカウントの「すそ野」経路では投稿直後から担当する
- 棚卸し係……3分に1回だけ動く係。溜まった申請をまとめて反映し、期限切れの本を捨て、棚を丸ごと書き直す
- 司書……読者のリクエスト票を受け取り、棚から候補を選ぶ係。仕事ぶりは非公開(後述)
そして注文主がhome-mixer(第5回の工場ライン)、リクエスト票があなたの行動履歴です。
1冊の本の旅。投稿からフォロー外に届くまで
先に、いちばん標準的な流れ(テキストの主経路)を1本のストーリーで通します。動画と小さいアカウントの例外は、後の章で扱います。
あなたが投稿した瞬間。
本が1冊、図書館に納品されます。「新着」の棚に置かれ、メタ情報(動画の有無、動画の長さ、あなたのフォロワー数)も記録されます。
でも、背表紙が白紙です。
司書は背表紙しか見ません。だからこの時点のあなたの本は、棚には確かにあるのに、探しようがない。
同じ頃、Thunder(第1回)があなたの投稿を、フォロワー向けの候補に載せています。そして誰かが、いいねを1件押した瞬間。
テキストの本なら、ここでラベル係が動き出します。番地を管理している別のサービスに問い合わせて、あなたの本の番地を受け取り、この図書館の索引に書き込む。それがラベル係の仕事です(ただし投稿直後は問い合わせが少し待たされます。後述)同時に、本の中身(本文+画像+動画)をAIが要約したカードの取り寄せも動き出します。
こうして、番地付きの索引へ進む経路が開きます。フォロー外の誰かのリクエスト票と背表紙が近ければ、司書に選ばれる可能性が生まれる。
いいねが増えていくと、受付係が数を見ています。2件、4件、8件、16件……と、倍になるたびに「この本の情報を最新にして」という更新申請を出します。棚卸し係が3分ごとに回ってきて、申請をまとめて反映します。
48時間経つと、受付係は申請を受け付けなくなります。そして棚の期限が来ると、本は撤去されます。いいねがまだ増え続けていても、です。
これが旅の全体です。ここから、係員ごとに細部を見ていきます。
棚の正体。「条件」と「期限」のリスト
図書館の中は、いくつもの棚に分かれています。棚が持っている情報は2つだけです。
- どんな本を入れるか(条件)
- 何日で撤去するか(期限)
コード上の名前もそのまま「条件_日数」です。主な棚を並べます。
- post_creation_1day……投稿されたもの全部。1日で撤去
- 1fav_1day……いいね1件以上。1日
- 1fav_2day……いいね1件以上。2日
- 32fav_1day……いいね32件以上。1日
- 1fav_topic_1day……いいね1件以上を、トピック別に。1日
- video_2day〜video_30day……10秒を超える動画。2日・4日・7日・14日・30日の5本
- evergreen_video_1825day……「常緑」扱いの動画。5年
- metadata_1〜3day……本のメタ情報(動画の有無・尺・著者フォロワー数・反応数)1〜3日
ここで大事なのは、棚は「どれか1つに振り分けられる」ものではないことです。
1fav_1dayと1fav_2dayは、条件がまったく同じで、期限だけ違います。つまり同じ本が両方に入る。テキスト投稿にいいねが33件付けば、post_creation・1fav×2・1fav_topic・32favに同時に載っています。
同じ本が「新刊コーナー」にも「話題の本コーナー」にも置かれている、あの状態です。
なぜ棚を分けるのか。司書が「どの範囲から探すか」を選べるようにするためです(ここは解釈です)直近1日の広くて薄い棚を見るか、いいね32件以上の狭くて濃い棚を見るか。用途の違う売り場を、同じ在庫から作っている。そう読むと、条件が同じで期限だけ違う棚が並んでいる理由に筋が通ります。
【そもそも納品を断られる本】
棚に入る以前に、受け入れ自体を断られる本があります。
- リプライ
- リポスト
- コミュニティポスト
- 表示可否の判定(第5回⑨のVF)でdropになる本、NSFW判定の本
つまりリプライは、Phoenixの主要な棚には最初から並びません。リプライがフォロー外のおすすめに広がりにくいのは、点数が低いからではなく、そもそも図書館に置かれないからです。
(細かい正確さのために: リプライについても「search_unfiltered」という検索用の登録イベントは発行されます。ただし、その受け皿になる棚の実体は、この公開リポジトリの中にはありません。断られるのは1favなどの主要棚です)
ラベル係。いいね1件目で、番地付きの一覧に載る
ここが今回の山場です。
投稿した瞬間に走る処理を全部並べると、こうなります。
- post_creation_1dayの棚に入る
- metadataの棚に入る
- (動画があれば)videoの棚に入る
このうちpost_creation_1dayに書き込まれるのは「投稿IDと投稿者ID」だけです。番地を書き込むラベル係(Sidパイプライン)の担当棚リストに、post_creationは入っていません。
つまり主経路では、投稿しただけの本は「棚にはあるが、背表紙が白紙」
そして、いいねが1件付いた瞬間に走る処理がこれです。
- 1favの棚(1日と2日)に入る。ここはラベル係の担当棚。番地の書き込み対象になる
- トピック別の棚に入る
- 検索用の登録イベント(search_unfiltered)が発行される
- 本の中身を要約したAIのカード(マルチモーダル埋め込み)が取り寄せられ、専用の登録イベントとして送り出される
最後の1つは補足が要ります。要約カードそのものは、この処理が作っているのではありません。別の場所で作られたものを取ってきて、要約カード用の登録イベントとして送り出す処理です。受け皿になる専用の棚は定義として存在しますが、そこへ書き込む係は、この公開リポジトリの中では実装されていません。同じように、背表紙の番地も、この処理が計算しているのではなく、番地を管理しているサービスに問い合わせて受け取っています。番地そのものは、いいねより前に別サービス側で計算されている可能性があります(そこは公開範囲の外です)
ただし、その取り寄せが走るのはいいねイベントのときだけです。コード上、投稿イベントからはこの処理が呼ばれていません。つまり「番地が生まれるかどうか」ではなく、「この図書館の、番地付きの一覧に載るかどうか」が、主経路ではいいねを境に変わります(例外2つは、この後の章で)
もう1つ、時間のゲートがあります。番地の問い合わせは、投稿から180秒(3分)未満の本を「若すぎる」として対象外にします。その場合、本は番地が空のまま棚に載り、30秒ごとの埋め直し処理(対象は投稿から1時間以内)が、あとから番地を付けます。つまり投稿直後にいいねが付いても、番地が付くのは早くて投稿の3分後です。
(ややこしいことに、この「3分」は後で出てくる棚卸しの「3分」とは別の時計です。偶然同じ数字なだけで、片方は番地の問い合わせの最短年齢、もう片方は棚のファイルの書き直し間隔です)
なお、この180秒・30秒・1時間は公開コードの既定値で、いずれも実行時の設定で上書きできます。本番が必ずこの数字で動いているとまでは断定できません(棚卸しの3分も同様です)
0件と1件の差を並べてみてください。
- 0件→1件: 番地付きの棚、トピック棚、検索用の登録イベント、要約カードの取り寄せ。別々の経路が、まとめて開く
- 1件→32件: 棚が1つ増えるだけ(次の節)
ここで確認できる最初の1いいねの役割は、順位の加点ではなく、番地付きの棚に載る経路を開くこと。いわばスイッチです。
第4回で見たSimClusters側と比べてみてください。あちらは、投稿の座標が長期の記録に残るのに8件、候補として拾われるには界隈ランキングの上位800件、という壁でした。Phoenixの扉はその手前、1件です。そして、その1件を境に、開く処理の幅が大きく変わります。
ここから読める構図があります(ここは解釈です。テキストの主経路(後述のすそ野棚に入らないアカウント)に絞った話です)
その場合、投稿した直後に無条件で動いているのは、Thunder(第1回)のフォロワー向け候補集めだけです。
一方で、その時点の本には番地がありません。番地付きの棚に載るのは、いいねが1件入ってからです。
つまり、番地を使った検索で見つけてもらうより前に、誰かの反応が要る。そしてその反応が最も届きやすい場所が、フォロワーのタイムラインです。
「まずフォロワーに刺さらないものは、外にも出にくい」体感で言われてきた話と、この構造は噛み合います。
ただし断定は避けます。コードは「誰がいいねを押したか」を見ていません。フォロワーでなくても、プロフィールから、検索から、あるいはフォロワーがリポストした先から見つけた人が、最初の1いいねを押すことはありえます。「フォロワーでなければならない」わけではありません。
ついでに言うと、「自分で押した1件」を除外する処理も見当たりません。受付係が見ているのは投稿ID・いいね数・投稿時刻だけです。
面白いのは、第4回のSimClusters側には除外がはっきり書いてあることです。投稿の座標を動かす処理は、取り込む条件として「その投稿の作者が、いいねを押した本人でないこと」を明示的に確認しています。自分で押したいいねは、あちらでは座標を1ミリも動かせません。
片方は除外を書き、もう片方は書いていない。ただし、いいねのイベントが受付係に届くまでの経路は公開されていないので、その手前で弾かれている可能性は残ります。ここは「除外するコードが見当たらない」までです。
例外①。動画は、反応を待たない
例外の1つ目は、動画です。
動画を棚に入れる処理には、いいねの条件がありません。しかもこの処理は投稿した瞬間のイベントからも呼ばれ、videoの棚はラベル係の担当に入っています。
つまり条件を満たす動画付きの投稿は、いいね0件のまま、投稿した瞬間からラベル係の担当棚(video)に並びます。番地の書き込み自体はテキストと同じ時計(後述の3分ゲート)を通りますが、誰かの反応を待つ必要がない。ここが決定的な差です。
テキストの本が誰かの最初の反応を待っている間、動画の本はもう番地付きの索引へ進む経路に入っている。ここはテキストと決定的に違います。
ただし、ここから先は期待しすぎないでください。3つ、冷や水があります。
【冷や水1: 条件がかなり厳しい】
「動画を貼れば動画棚」ではありません。コード上の条件を展開するとこうです。
- 動画の種類がTweetVideoかAmplifyVideoであること
- GIFは対象外(許可リストに入っていません)
- 10秒を「超える」こと(10秒ちょうどは不可)
- NSFWでないこと
- そして、添付メディアが全部この条件を満たすこと
最後が効きます。動画1本+画像1枚のような混在投稿は、丸ごと対象外です。
【冷や水2: 長い棚は、おすすめには効かない】
棚の期限だけ見ると、動画は桁違いに長く在庫に残ります。
- テキスト: 最長2日(1fav_2day)
- 動画: 2日・4日・7日・14日・30日の5本
- 常緑扱いの動画: 5年
ところが、おすすめ側にはこの定数があります。
MAX_POST_AGE = 48 * 60 * 60
そして第5回④で見た年齢フィルタが、投稿から48時間を超えた候補を問答無用で落とします。
つまり司書が30日棚から20日前の動画を持ってきても、おすすめの工場ラインで捨てられる。長い棚は、おすすめのタイムラインに対しては機能していません。
では何のためにあるのか。顔ぶれ(video / nsfw_video / evergreen_video / imagine)と期限を素直に読むなら、48時間で切る必要がない別の売り場(動画タブや、没入型の縦スクロール再生)向けだと考えられます(ここは解釈です)同じ図書館を複数の売り場が共有していて、おすすめはその中で一番賞味期限にうるさい客、という構図です。
【冷や水3: 5年の棚は、投稿者からは到達できない】
常緑動画の棚に入る処理は、「常緑動画」という別のイベント種別でしか呼ばれません。投稿イベントでも、いいねイベントでもない。どこか別の仕組みが「これは常緑」と指定したものだけが入ります。投稿しても、いいねが増えても、自力では入れません。
【まとめると】
動画が優遇されているのは入場条件です。フォロー外への入口が、テキストより1段早く開く。
一方で「動画は長く残るから有利」は、少なくともおすすめのタイムラインについては成り立ちません。48時間の壁は動画にも同じようにかかります。
以前「動画は新しい層への架け橋になるかも」と投稿しました。構造としては当たっていましたが、効いているのは滞在期間ではなく入口の早さのほうでした。
例外②。「すそ野」棚(既定でフォロワー1,000人未満)
実は、この経路は公開直前のチェックで見つかりました。動画に続く、2つ目の例外です。
「tail(すそ野)」という名前の棚があります。入り方が独特です。
- 対象: 著者のフォロワーが1,000人未満の投稿(公開コードの既定値)
- いいねの条件: なし(最低いいね数の既定値は0)
- 入り方: 投稿直後に出るメタ情報のイベントを受けて、番地を問い合わせ、番地付きで載る
- 期限: 1日だけ
- 番地の問い合わせは、他と同じ「投稿から3分未満は対象外」のゲートを通る
つまり、フォロワー1,000人未満のアカウントに限っては、テキスト投稿でも、いいね0件のまま番地付きの棚に載る経路が実装されています。
これで、この記事の芯は1段階正確になります。
- フォロワー1,000人以上(既定値): 公開コードで確認できる通常のSID付き経路では、入口は「最初の1いいね」
- フォロワー1,000人未満: いいね不要の「すそ野」棚が別に用意されている。ただし1日限りで、この棚の使われ方は例によって非公開
この「1,000人」という数字、見覚えがないでしょうか。第5回の補足2で見たコールドスタート枠(フォロワー1,000人以下の投稿を1枠すくい上げる仕組み)と同じ数字です(細かく言うと不等号が違います。すそ野は「未満」、コールドスタートは「以下」で、ちょうど1,000人の人は後者だけ対象です)小さいアカウントを別枠で拾う思想が、点数付けの側だけでなく索引の側にもある。そう読みたくなりますが、使われ方が非公開なので、ここは解釈に留めます。
(このすそ野棚の数字も既定値です。本番の設定は分かりません)
ただし、この2つは確からしさの段階が違うので、そこは分けておきます。
- 点数付けの側(第5回のコールドスタート枠)は、効果まで確認できます。既定でオンになっていて、条件を満たした1件の点数を16位相当まで引き上げる、という処理がコードに書いてあります
- 索引の側(すそ野棚)は、棚があるところまで。その棚を検索がどう使うかは公開されていません
つまり「フォロワー1,000人前後で線を引いて、小さいアカウントを別枠で扱う仕組みが、点数付けと索引の2か所にある」ということ。これはコードの作りとして見えます。
そして点数付けの側については、はっきり書いておきます。小さいアカウントは、ある側面で確かに優遇されています。既定でオンの仕組みが、条件を満たした投稿の点数を実際に持ち上げているからです。ここは推測ではありません。
言い切れないのは索引の側だけです。すそ野棚という器はあるが、検索がそれをどう使うかが見えない。優遇の意図があるように見えて、効果までは追えない、という状態です。
受付係。2の累乗と、1分ルール
次に、いいねが増えたとき棚の情報がどう更新されるかです。
いいねが1件付くたびに、受付係のところにイベントが届きます。ただし受付係は、毎回更新申請を出すわけではありません。本ごとに「次の申請ライン」を持っていて、それが2の累乗で決まっています。
1 → 2 → 4 → 8 → 16 → 32 → 64 → 128 → …
いいね数がラインを超えたときだけ申請を出し、その時点の数で次のラインを引き直す。序盤は2件、4件、8件と頻繁に更新され、後半は256件、512件と間隔が開いていきます。
なぜ倍々なのか。いいね1,000件の本でも、生涯の更新申請は10回程度で済むからです。全投稿×全いいねを毎回処理するのは重すぎる。かといって更新しないと、棚の情報(いいね数や要約カード)が古いままになる。「若い本ほど細かく、育った本ほど粗く」という間引き方は、変化が激しい時期に更新を集中させる設計です。
ここで1つ、はっきりさせておきます。中間の段(2, 4, 8, 16…)で新しい棚が開くことはありません。開くのは1件目(番地付きの棚に載る)と32件目(専用棚が増える)の2か所だけです。
では中間の段は何をしているのか。処理としては、1件目と同じ一式(番地の引き直し・要約カードの取り寄せ・反応数の記録)が、毎回まるごと走り直します。ただし、番地も要約カードも投稿の中身から決まるので、何度引き直しても基本的に同じ値です。著者のフォロワー数もほぼ動かない。
つまり、値として実際に変わるのは、ほぼ反応の数だけです。
- いいね・リポスト・リプライ・引用・ブックマーク・表示回数
- そして、興味なし・報告・ブロックの数
つまりこの仕組みは、主に「棚に置いてある本の反応数を、なるべく最新に保つ」ために動いていると読めます(投稿直後に番地が空だった本が、後の段で埋まるケースもあります)しかもポジティブな数字だけでなく、ネガティブな数字も一緒に運んでいる。
使われ方は公開範囲の外です。ただ、2の累乗のラインを引き、1分のクールダウンを設け、48時間で打ち止めにする。ここまで設計して運び続けている情報が、どこにも使われていないとは考えにくい(ここは解釈です)
そしてもう1つ。背表紙のコードは投稿の中身から決まるので、いいねで棚の中の位置が動くことはありません。だから反応の数が効くとすれば、「棚のどこに置かれるか」ではなく「どの棚に入れるか、どう優先するか」の側です。いいね32件で入る専用棚が、まさにその使い方の実例になっています。
申請にはさらに条件が付いています。
- 前回の申請から1分以上空いていること
- 最初の申請から48時間以内であること
- 投稿から49時間以内であること
48時間を過ぎると、いいねがどれだけ増えても棚の情報は更新されなくなります。そして棚の期限(テキストなら1〜2日)が来れば撤去。「古い投稿がいいねの力で掘り起こされる」仕組みは、少なくともこの図書館にはありません。
棚卸し係。図書館の時計は3分刻み
受付係の申請は、すぐには棚に反映されません。もう1人、独立した時計で動く係がいます。
棚卸し係は、いいねと無関係に、3分に1回だけ動きます。起きたらやることは3つ。
- 溜まっていた申請をまとめて棚に反映する
- 期限切れの本を撤去する(判定は投稿IDに埋まった時刻。第5回の年齢フィルタと同じ方式です)
- 棚を丸ごとファイルに書き直す
ポイントは「丸ごと書き直す」ところです。1冊ずつ差し込むのではなく、3分ごとに棚全体を作り直しています。
ここで、2つの時計が噛み合います。受付係のクールダウンは1分、棚卸しは3分。つまり3分の間に同じ本の申請が2〜3回通ることがある。ところが申請の置き場は「本ごとに1枠」で、2回目の申請は1回目を上書きします。
結果、何回申請しても、棚に出るのは3分に1回、その時点の最新状態だけ。
猛烈にバズっている本も、ゆっくり伸びている本も、図書館から見える解像度は同じ「3分刻み」です。秒単位の初速は、この仕組みには映りません(ここは解釈です: 初速を競うなら、意味を持つ単位は秒ではなく「次の棚卸しまでに何件か」です)
あなたの投稿が棚のファイルに書き出されるタイミングも同じ理屈で決まります。棚卸しの直前に条件を満たせば数秒で書き出され、直後なら3分近く待つ。平均して1分半です。
ただし、ここで話が終わらない点に注意してください。3分というのは「棚のファイルが書き直される間隔」であって、「検索できるようになるまでの時間」ではありません。司書がその新しいファイルをいつ読み込むかは、公開コードから確認できません。読み込みの間隔が別にあれば、実際に検索対象になるまではもっと後になります。
32冊の棚。区切りは実在する、用途は書かれていない
さて、棚の一覧に1つだけ異質なものがありました。32fav_1day、いいね32件以上の本だけを集めた棚です。
インデックスの処理全体を見渡すと、いいね数のしきい値は「1」と「32」の2つしかありません。そして32のほうは、コードの中で定数として名前まで付けられています。
minEngagements = 32L
専用の棚があり、定数として切り出されている。「いいね32件」という区切りが設計として実在することは、事実として言えます。
言えないのは、この棚がどう使われるかです。司書がいつこの棚を見るのかは、公開コードに書かれていません。
そのうえで、予想を書きます(ここは解釈です)
1favの棚には、直近1日の「いいね1件以上」が全部入っています。膨大です。32favの棚は、そこから32件のハードルを越えた本だけを抜き出した、母数がずっと小さいリスト。
検索の世界では、「広い棚を精密に探す」より「絞り込み済みの棚をさっと探す」ほうが速くて安い。だからこの棚は、司書が「実績のある本から手早く選びたい」ときの話題書コーナーとして使われている可能性が高い、と私は読んでいます。
もしそうなら、いいね32件は「そこを超えると、別の土俵でも戦えるようになる」ラインです。1件目が「番地付きの棚に載るスイッチ」だとすれば、32件は「棚の格が1つ上がる(かもしれない)ライン」
ただし繰り返しますが、ここは公開コードの外側です。事実は「32という区切りと専用の棚が存在する」まで。
司書の部屋には、入れない
最後に、正直な線引きをしておきます。今回追えたのは、両端だけです。
- 棚に載るまで: 完全に見える(ここまで書いてきた内容)
- 司書が棚から探す処理: 非公開
- 返ってきた候補のその後: 完全に見える(第5回の③〜⑨)
注文側のコードから、司書に何を渡しているかは全部分かります。あなたの行動履歴の並び、年齢層・言語・IPから割り出した位置などの属性、そして「何件返してほしいか」返ってくるのは投稿IDのリストです。
1つ気になる形があります(ここは解釈です)司書からの返事は1本のリストではなく「束の配列」になっていて、注文主側でそれを1本に潰しています。棚ごとに探した結果が束で返ってきている、と読むと、棚が何種類もある理由とこの形が噛み合います。
もう1つ、確定していることを。取ってくる件数には、サーバーの混み具合に応じた調整が入ります。混雑時は、候補の数そのものが絞られます。
3つの係を並べると、階段になる
ここまでの話を、シリーズ全体の中に置き直します。
おすすめの候補を集める係は3つありました(第5回の②)この3つを「いいね0件の投稿から見るとどうか」で並べると、きれいな階段になります。
- Thunder(第1回): いいね不要。フォロワー向けの候補集めには無条件で入る(表示までは第5回の選別を通ります)
- Phoenix(今回): 主経路では番地が白紙のままで、番地に基づく検索では拾われない。いいね1件で番地付きの一覧に載る経路が開く(動画とすそ野は別)
- SimClusters(第4回): そもそも地図に載っていない。座標を動かすのはいいねだけなので、0件の投稿には座標が存在しない
Phoenixが「棚にはあるが背表紙が白紙」なのに対して、SimClustersはもっと手前です。位置が未定なのではなく、位置を決める材料が1つもない。
しかも第4回で見た通り、SimClustersで候補として拾われるには、そのクラスターのランキング上位800件に入る必要がありました。何件あれば入れるという固定の境界はコードになく、押した人の濃さと、その界隈の競争次第です。
ここで言葉を選びます。
SimClustersについては「いいね0件では拾われない」と言い切れます。座標を作る材料が存在しないからです。
Phoenixについては、そこまで言えません。番地に基づく検索では拾えないはずですが、post_creation_1dayという棚が実在していて、その用途が公開コードに出てこないからです。使われていないなら、なぜ24時間の棚としてわざわざ用意されているのか、という疑問が残ります。
なので、こう書きます。
いいねが0件の投稿で、確実に動いているのはフォロワー向けの候補集め(Thunder)だけです。これはフォロワー1,000人以上のアカウントの話。1,000人未満(既定値)には、いいね不要の「すそ野」棚が加わります。
そして、その最初の1いいねを境に、番地付きの棚に載るようになります。SimClustersの扉は、さらに奥にあります。
(動画は、この階段のうちPhoenixの段だけを飛ばせます。前の章で書いた通りです)
だからどうなる
投稿する側の目線でまとめ直します。
【1. 0→1が、処理の大きく変わる境界】
0→1で開くもの(番地付きの棚・トピック棚・検索用イベント・要約カードの取り寄せ)と、1→32で開くもの(棚1つ)の差は歴然です。フォロー外に届くための最初の関門は、バズることではなく、誰か1人が反応してくれることでした(フォロワー1,000人未満には、すそ野棚という別口も既定で用意されています)
【2. その1いいねが最も届きやすいのは、フォロワーのタイムライン】
投稿した直後、無条件で動くのはフォロワー向けの候補集めだけです(第1回のThunder。表示までは選別があります)そこで反応が起きれば、番地付きの棚に載る。
第5回で「アカウントが強いのではなく、あなたとその人の関係が強い」と書きましたが、その関係は「外への入口」にも効いてきます。
(繰り返しになりますが、コードは「誰が押したか」を見ていません。フォロワー以外が最初の1いいねを押すこともあります)
【3. いいねの「数」が効く場所は、係によって違う】
Phoenixの番地は投稿の中身から計算されるので、いいねが1件でも1万件でも変わりません。必要なのは1件目だけ。
一方SimClustersは、クラスター内のランキング上位800件という壁があります。何件で入れるという固定の境界はなく、要るのは「同じ方向を向いた」反応です。
【4. 動画が優遇されているのは「入口の早さ」だけ】
いいね0件でラベル係の担当棚に入れる。番地の書き込みは同じ時計だが、ここはテキストにない利点です。
ただし「長く残るから有利」は成り立ちません。おすすめ側に48時間の壁があり、動画にも同じようにかかります。しかも条件は厳しく(10秒超・GIF不可・画像との混在不可)、5年の棚は自力では入れません。
【5. 少なくとも索引の更新側は、3分刻み】
秒単位で反応が積み上がっても、棚のファイルが書き直されるのは3分に1回です(既定値)番地の問い合わせにも「投稿から3分未満は対象外」という既定のゲートがあります。ただし司書がそのファイルをいつ読み込むかは公開されていないので、「検索できるようになるまでが3分」ではありません。
確実なのは、更新が48時間で止まり、テキストは1〜2日で棚から消えるということ。勝負の時間はかなり短いです。
【6. リプライは主要な棚に置かれない】
フォロー外への露出を狙う投稿は、リプライではなく単独ポストで、という判断の根拠がここにあります。
まとめ
- Phoenixの検索対象は、読む人によって中身が変わらない共通の図書館。人ごとに違うのはリクエスト票だけ
- 棚=「条件」と「期限」のリスト。同じ本が複数の棚に同時に載る
- テキストの主経路では、投稿しただけでは番地付きの一覧に載らない。いいね1件目を境に、番地付きの棚とトピック棚に載り、検索用・要約カード用の登録イベントも発行される
- 動画は例外。いいね0件で担当棚に入る(番地の書き込みは同じ時計)条件は厳しく、棚の期限が長くてもおすすめ側の48時間の壁は変わらない
- もう1つの例外が「すそ野」棚。フォロワー1,000人未満(既定値)なら、いいね不要で番地付きの棚に載る。1日限りで、使われ方は非公開
- 更新は受付係(2の累乗のライン、1分クールダウン、48時間で打ち止め)と棚卸し係(3分ごとに棚を丸ごと書き直す)の2つの時計で動く
- いいね32件の区切りと専用の棚は実在する。用途は公開範囲の外(私の予想は「話題書コーナー」)
- 司書の中身は非公開。棚に載るまでと、候補が返ってきた後は、全部コードで追える
- いいね0件の投稿で確実に動くのは、フォロワー向けの候補集め(Thunder)Phoenixの主経路は1件を境に開き(小規模にはすそ野棚)、SimClustersは0件では座標そのものが無い
冒頭のお詫びに戻ります。第5回の9ステップは「候補に入ってから」の話でした。その手前に、この記事で書いた工程があります。テキストの主経路では、いいねが1件付くまで番地付きの索引に載りません。小さいアカウントには、すそ野棚という別口が既定で用意されています。
そして第2回で「新着がいつ検索対象になるか断定できない」と書きました。今回、半分だけ答えが出ました。
分かったのは、投稿側の工程です。テキストは最初の1いいねを境に番地付きの棚へ(小さいアカウントには、いいね不要のすそ野棚も)、動画は投稿の直後から担当棚へ入る。番地の問い合わせには「投稿から3分未満は対象外」のゲートがあり、棚のファイルは3分ごとに書き直される。
分からないままなのは、司書側です。書き直された棚をいつ読み込むのか。だから「投稿の何分後に検索対象になるか」は、今回も断定できません。第2回の保留は、完全には解けませんでした。
追記(公開直前の更新について)
この記事はコミット28e414f(8月21日)を基準に書いています。公開直前の最新版(0d3cdd8・8月25日)でも、本文で扱った仕組みが同一であることを確認しました。
1点だけ、8月25日の更新で棚が増えています。「いいね1件以上の動画」専用の棚(1fav_video)が新設され、2日・2〜4日・4〜14日・4〜30日という階層付きで保持されるようになりました。事実として言えるのは「動画向けの棚の構成がさらに増えた」ところまでです(使われ方は例によって非公開です。この記事の本文には反映していません。詳細は続報で扱います)
参照(クリックすると該当箇所が開きます)
いいねイベントの受付と2の累乗のしきい値:
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall-strato/columns/favoriteEventProcessor.strato
申請の条件(1分クールダウン・48時間・49時間・32件の定数):
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall-strato/lib/eventProcessing.strato
棚への振り分け(1fav/32fav/video/納品を断られる条件):
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall-strato/columns/phoenix_rank_all/phoenixRankAllCandidateProcessor.strato
棚の一覧と保持期間・棚卸しの間隔(3分):
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall/src/config/mod.rs
インデックスの1行の中身(投稿ID・背表紙のコード列など):
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall/src/processor/record.rs
すそ野(tail)棚 — フォロワー1,000人未満・いいね不要の番地付き経路(既定値):
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall/src/processor/sid_tail_processor.rs
背表紙のラベル(セマンティックID)の引き当て:
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall/src/processor/sid_processor.rs
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall/proto/sid_lookup.proto
棚の実体(閲覧者IDを持たない構造・期限切れの撤去・丸ごと書き直し):
https://github.com/xai-org/x-algorithm/blob/28e414f/phoenix-rankall/src/store/base.rs
おすすめ側の48時間の壁(年齢フィルタとMAX_POST_AGE):
https://github.com/xai-org/x-algorithm/blob/28e414f/home-mixer/filters/age_filter.rs
https://github.com/xai-org/x-algorithm/blob/28e414f/home-mixer/params/config.rs
注文主が司書に渡すもの・返ってくる形:
https://github.com/xai-org/x-algorithm/blob/28e414f/home-mixer/sources/phoenix_source.rs
セルフいいねの扱い(SimClusters側は明示的に除外):
https://github.com/xai-org/x-algorithm/blob/28e414f/simclusters/simclusters_v2/summingbird/storm/TweetJob.scala
前回まで:
第1回(Thunder): https://x.com/FFBuncho/status/2089576142814728301
第2回(Phoenix): https://x.com/FFBuncho/status/2090012510409863499
第3回(セマンティックID): https://x.com/FFBuncho/status/2090276308844708093
第4回(SimClusters): https://x.com/FFBuncho/status/2091314098948686194
第5回(9ステップ): https://x.com/FFBuncho/status/2091712865015304550
この記事のもとになったX記事
Xで公開した第6回の記事を、サイト用に読みやすく再構成したものです。
ふくふく文鳥