후보 배분 — 소스가 여럿일 때 무엇을 몇 개 넣는가
최종 갱신: 2026-08-09
후보 생성 소스가 10개, 30개씩 있을 때 "각 소스에서 몇 개를 가져와 최종 리스트에 어떻게 섞을 것인가"를 정하는 레이어. 세 가지 방식이 있고, X는 세대가 바뀌면서 방식을 바꿨다.
- 비율 배분 (ratio) — "이 소스에서 30%" 식으로 슬롯을 나눈다. 결과 구성이 보장된다.
- 상한 배분 (cap) — 소스마다 최대 N개만 가져오고, 이후엔 점수로만 경쟁시킨다. 구성이 보장되지 않는다.
- 인터리브 (round-robin / weave) — 소스를 번갈아 하나씩 뽑는다. 상한과 조합해서 쓴다.
X와 Meta는 이 레이어에 대해 정확히 반대 방향으로 공개했다. X는 배분기 코드가 다 보이는데 상한 값이 전부 레포 밖에 있고, Meta는 단계별 후보 수를 지면마다 숫자로 주는데 그 숫자를 만드는 배분기가 어디에도 없다. 이 비대칭 자체가 아래 전체를 관통하는 주제다.
"X는 인네트워크:아웃오브네트워크를 50:50으로 섞는다"는 틀렸다
가장 널리 퍼진 오해다. 근거는 2023년 공식 블로그의 *"the For You timeline consists of 50% In-Network Tweets and 50% Out-of-Network Tweets on average, though this may vary from user to user"*와 README의 "~50% of Tweets come from this candidate source"(검색 인덱스 행)인데, 둘 다 관측된 결과 비율("on average")이지 코드에 있는 쿼터가 아니다. [확인] — Twitter 블로그 2023-03-31(아카이브 대조 2026-08-09) + twitter/the-algorithm README.md:41
2026 세대 코드에는 in-network / out-of-network 비율을 강제하는 코드가 없다.
- 소스 11개가 병렬로 돌고 결과를 그냥 이어 붙인다.
[확인]—home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs - 최종 선택은 점수 기준 top-K 하나뿐이다.
[확인]—home-mixer/selectors/top_k_score_selector.rs - OON을 조절하는 레버는 쿼터가 아니라 점수 배수다. in-network가 아닌 후보에만
effective_oon을 곱한다.[확인]—home-mixer/scorers/ranking_scorer.rs:220-239,272-275
let final_score = match c.in_network {
Some(false) => after_diversity * effective_oon,
_ => after_diversity,
};
배수 값은 세 갈래로 갈린다: 토픽 요청이면 TopicOonWeightFactor, 신규 유저(계정 나이 < NewUserAgeThresholdSecs 그리고 팔로잉 수 ≥ NEW_USER_MIN_FOLLOWING)면 NEW_USER_OON_WEIGHT_FACTOR, 나머지는 OonWeightFactor. 세 값 모두 레포에 없다. [확인] — ranking_scorer.rs:220-239
→ 즉 "OON을 얼마나 넣을지"는 배분 문제가 아니라 랭킹 문제로 옮겨갔다. 결과 비율은 배수와 후보 풀에 따라 요청마다 달라진다.
세대별 배분 구조 — X
2026 Rust: 배분기 없음, 소스별 상한만
소스 11개(home-mixer/sources/) [확인]:
thunder_source / phoenix_source / phoenix_moe_source / phoenix_topics_source / tweet_mixer_source / cached_posts_source / scored_posts_source / ads_source / who_to_follow_source / prompts_source / push_to_home_source
각 소스가 자기 상한을 FeatureSwitch에서 읽는다 — ThunderMaxResults, PhoenixMaxResults, TweetMixerMaxResults. 값은 전부 레포 밖. [확인] — thunder_source.rs:38, phoenix_source.rs:89, tweet_mixer_source.rs:61
전역 선택은 TopKScoreSelector 하나: 점수 내림차순 TOP_K_CANDIDATES_TO_SELECT개. 이 상수도 params 모듈에 있고 params 모듈은 공개되지 않았다. [확인] — top_k_score_selector.rs:13
캐시 게이트가 사실상 배분기 역할을 한다. 캐시된 포스트가 임계치 이상이면 모든 소스와 Phoenix 자체가 조용히 꺼진다 [확인]. 즉 "새 후보를 얼마나 가져올지"의 1차 결정은 소스별 상한이 아니라 캐시 상태다.
2025 Scala: 상한 + 인터리브 + 우선순위 위브
훨씬 복잡하다. 두 레이어로 나뉜다.
(a) tweet-mixer — OON 후보 생성 전담. Home 요청에 candidate pipeline 34개. [확인] — HomeRecommendedTweetsRecommendationPipelineConfig.scala:271-332 (엔트리 :274-331)
배분 순서 [확인] :362-412:
저신호 유저면 여기서 끝난다. 작성자당 1개로 dedup한 뒤(
DropDuplicateCandidates(duplicationKey = _.features.getOrElse(AuthorIdFeature, None)):364)InsertAppendRatioResults로 명시적 비율 배분을 하고DropAllCandidates():380로 이후 selector를 전부 막는다. 블록 전체가AllowLowSignalUserGate.evaluate로 게이트된다:382. 비율 default: Grok 토픽 0.7 / SimClusters interested-in 0.15 / 인기 지역 0.15 / 나머지 전부 0.01[확인]TweetMixerGlobalParams.scala:225,233,241,249(나머지는filterNot으로 일괄 적용:372-378. default 값이며 프로덕션 값 아님)아니면 블렌더가
BlendingParam으로 선택돼 실행된다. Home 파이프라인에 배선된 것은 3개이고 그중 하나가 뽑힌다[확인]:399,401,402:exclusiveRoundRobinSelector—(소스, 시그널)버킷으로 위브signalPrioritySelector— 버킷에USSFeatures.getPriority추가weightedSignalPrioritySelector— 가중 위브.new Random(0)고정 시드[확인]TweetMixerFunctionalComponents.scala:289
주의:
TweetMixerFunctionalComponents.scala에 정의된 selector는 훨씬 많고(roundRobinSelector:210,inclusiveRoundRobinSelector:237등),BlendingEnum은 값이 6개다[확인]TweetMixerGlobalParams.scala:23-30. "블렌더는 3종"이 아니라 "이 지면이 3개를 배선했다"가 맞다.DropMaxResults(LightRankerMaxResultsParam):404— default 500[확인]HomeRecommendedTweetsParam.scala:9-15그 다음에 u2i(user→post) 파이프라인 7개
:385-393를inclusiveRoundRobinSelector로 덧붙인다:405-406DropRequestedMaxResults:408-411—min(requestedMaxResults, serverMaxResults). 파라미터명은DefaultRequestedMaxResultsParam(300)과ServerMaxResultsParam(500)[확인]HomeRecommendedTweetsParam.scala:18-24,27-33
4번은 특혜처럼 보이지만 특혜가 아니다. "상한을 자른 뒤에 붙으니 슬롯 경쟁을 면제받는다"고 읽기 쉬운데, 두 가지가 그 결론을 뒤집는다 [확인]:
DropMaxResults는result만 자르고remainingCandidates는 그대로 둔다[확인]DropMaxResults.scala:36-38. 그래서 뒤에 붙는 u2i 후보는 리스트 꼬리에 놓인다.- 그리고 home-mixer가 실제로 요청하는 수는 400이다
[확인]ScoredTweetsParam.scala:176(default 400) →ScoredTweetsTweetMixerCandidatePipelineConfig.scala:77→HomeRecommendedTweetsProductPipelineConfig.scala:73. 라이트랭커 상한 500보다 작다.
→ 즉 5번의 최종 절단선(400)이 3번의 상한(500)보다 앞에 있으므로, 비-u2i 블렌드만으로 400개가 채워지면 뒤에 붙은 u2i는 한 개도 안 남는다. u2i는 500 슬롯을 놓고 경쟁하지 않는 대신 400에서 가장 먼저 잘리는 쪽이다. 순서상 뒤에 붙는 것은 우대가 아니라 후순위다.
저신호 유저 판정에 코드/주석 불일치가 있다. 게이트 주석은 "< 25 users"라고 쓰는데 코드는 SmallFollowGraphSize = 5다. tweet-mixer와 home-mixer 양쪽에 똑같이 복제돼 있다. [확인] — tweet-mixer SignalUtils.scala:17,20 vs AllowLowSignalUserGate.scala:12·DenyLowSignalUserGate.scala:12, home-mixer util/SignalUtil.scala:18 vs scored_tweets/gate/DenyLowSignalUserGate.scala:13
주석을 인용하면 임계값이 5배 틀린다. 그리고 이 임계값은 "명시적 비율 배분으로 내려갈지"를 가르는 스위치라서, 5냐 25냐가 영향받는 유저 규모를 크게 바꾼다.
(b) home-mixer — candidate pipeline 9개. 순서 자체가 배분 규칙이다. [확인] — ScoredTweetsRecommendationPipelineConfig.scala:446,451-459
Static → CachedScoredTweets → EarlybirdInNetwork → DirectUteg → TweetMixer
→ ContentExploration → Lists → Backfill → Communities
바로 위 주석: "Order matters. Duplicated candidates take the first occurrence in the pipelines when merger". 병합은 DropDuplicateCandidates(AllPipelines, IdAndClassDuplicationKey, PickFirstCandidateMerger) [확인] :462-469.
→ 비율도 상한도 아니고, 중복 시 앞선 파이프라인이 이긴다는 우선순위다. 같은 포스트가 인네트워크와 tweet-mixer 양쪽에서 오면 인네트워크 것으로 기록된다.
캐시 상태가 여기서도 게이트다: MinCachedTweetsParam(default 30 :272) 미만일 때만 Frs / TweetMixer / Uteg 파이프라인이 돈다. [확인] — ScoredTweetsParam.scala:269-275, ScoredTweetsTweetMixerCandidatePipelineConfig.scala:51
fail-open이 기본이지만 예외가 하나 있다. tweet-mixer는 candidatePipelines.map { … FailOpenPolicy.Always }로 전부 기계적으로 fail-open이다 [확인] HomeRecommendedTweetsRecommendationPipelineConfig.scala:334-337. 그런데 home-mixer는 손으로 나열해서 9개 중 8개만 등록했다 — scoredTweetsContentExplorationCandidatePipelineConfig가 목록에 없다 [확인] ScoredTweetsRecommendationPipelineConfig.scala:456 vs :492-502.
빠진 것은 defaultFailOpenPolicy = FailOpenPolicy(Set(ClosedGate))로 떨어진다 [확인] product-mixer/core/.../RecommendationPipelineConfig.scala:113 → ContentExploration이 죽으면 요청 전체가 실패한다. 나머지 8개가 죽으면 그 몫이 조용히 사라지고 나머지로 채워지는 것과 대비된다.
이게 의도인지 누락인지는 코드에 단서가 없다 [추정] — 8개를 손으로 적고 하나를 빠뜨리기 쉬운 형태이고, 탐색 파이프라인만 fail-closed로 둘 이유를 찾기 어렵다.
소스가 아니라 "슬롯"으로 배분하는 경우
전부 점수 경쟁으로 보내지 않고 위치를 고정하는 장치도 있다.
- 커뮤니티당 후보 1개, 그리고 커뮤니티 후보 총합 3개 —
MaxCandidatesPerCommunity = 1,MaxCommunityCandidates = 3[확인]KeepTopKCandidatesPerCommunity.scala:14-15,30,32 - 콘텐츠 탐색·DeepRetrieval 후보를 특정 위치로 끌어올리기 —
ContentExplorationBoostPosParam,DeepRetrievalBoostPosParam, 각각getNumCandidatesToBoost = 1[확인]ScoreAveragingPositionSelector.scala:121,137,193
이건 랭킹 부스트가 아니라 위치 예약이다. 점수를 올리는 게 아니라 자리를 준다.
케이스: Meta — 숫자는 있는데 배분기가 없다
pass 0 후보 수 — 지면별
| 지면 | pass 0 / retrieval | 다음 단계 | 축소비 |
|---|---|---|---|
| Facebook Feed | 약 500 | 약 500 | 1:1 |
| Facebook Video | 약 1,000 | 상위 200 | 5:1 |
| Facebook Reels | 약 10–100 | (수치 없음) | — |
| Facebook Feed Recommendations | 수치 없음 | — | — |
| Instagram Feed | 약 700 | 약 500 | 1.4:1 |
| Instagram Explore | up to 1,500 | 상위 100 | 15:1 |
| Instagram Reels Chaining | 약 100 | 약 100 | 1:1 |
| Instagram Feed Recommendations | 수치 없음 | — | — |
| Threads For You | 수치 없음 | — | — |
[확인] 각 AI system card verbatim (2023~, 인덱스). 원문 인용과 지면 맥락은 → facebook.md · instagram.md · threads.md
- 같은 이름의 지면인데 숫자가 다르다. FB Feed는 500→500으로 단조 감소가 없고 IG Feed는 700→500이다. Meta 문서가 지면마다 다른 퍼널 형태를 서술한다.
- 비연결(Recommendations) 카드는 FB·IG 양쪽 다 수치가 없다. 그런데 같은 시스템을 설명하는 2023 블로그는 *"narrow billions of pieces of content down to thousands and then to a few hundred"*이라고 쓴다
[확인]ai.meta.com, 2023. Meta 자체 문서 두 개가 다른 해상도로 서술한다. - Threads 카드는 단계 수치를 하나도 안 준다.
[관찰]2026-08-09 (ig-threads-feed 카드 본문 1건) - → "Meta의 pass 0은 500"이라고 한 줄로 쓰면 아홉 줄 중 한 줄만 맞다.
두 숫자의 성격이 다르다 — 이게 진짜 구조적 포인트
- X의 숫자는 소스코드에서 나온다.
LightRankerMaxResultsParam500,DefaultRequestedMaxResultsParam300,ServerMaxResultsParam500, home-mixer 실제 요청값 400,MinCachedTweetsParam30. 정확하고, 파일·줄 번호를 적을 수 있고, 서로 어긋나는 것까지 보인다(위의 500 상한 뒤 400 절단선).[확인] - Meta의 숫자는 system card에서 나온다. 전부 "approximately" / "about" / *"up to"*가 붙은 반올림 값이고, 절단 순서도 누가 어디서 자르는지도 없다.
[관찰]2026-08-09 (검사한 코퍼스: 위 표의 시스템 카드 9종 본문) - →
[추정]공개물의 목적이 다르다. X 레포는 시스템을 읽을 수 있게 만들려던 것이라 **로직은 100%, 값은 0%**로 남았고, system card는 DSA 설명 의무 대응 문서라 **값은 반올림해서 주고 로직은 0%**다. 근거: 두 공개물의 정밀도 패턴이 정확히 반대 방향이다. - 실무 함의: Meta 숫자는 상대 비교(Explore 15:1 vs Feed 1.4:1)에는 쓸 수 있지만 재현에는 못 쓴다. X 숫자는 그 반대다.
Meta 쪽 배분 장치 — 딱 한 지면에서만 기계적으로 보인다
Instagram의 2020년 retrieval DSL과 2023년 Explore 글이 유일하다. 쿼리 전문은 → instagram.md.
- 소스 타입 4종(heuristics / ML / real-time / pre-generated)을 "mix them with tunable weights"
[확인]Scaling the Instagram Explore recommendations system, 2023 — 소스별 가중치가 튜닝 파라미터로 존재한다는 명시적 진술. 값은 미공개. max_media_per_account=10— 계정당 상한. X의 소스별*MaxResults와 같은 rung(상한 배분)인데 키가 소스가 아니라 작성자다.[확인]2020diversify_by(seed_id, method=round_robin)— retrieval 단계 라운드로빈. X의exclusiveRoundRobinSelector(소스×시그널 버킷 위브)와 같은 rung, 다른 키(seed 계정).[확인]2020- seed 소스 병합 우선순위
[확인]2020: "The final merge order is as follows: H >> R > F." (H = Home Feed 유래, R = Explore/Reels 등 다른 추천 지면 유래, F = graph exploration 백업)
→ 마지막 항목이 X와 거의 같은 물건이다. X home-mixer는 파이프라인 9개를 순서대로 배선하고 "Order matters. Duplicated candidates take the first occurrence in the pipelines when merger" 주석을 달았다. Instagram은 같은 규칙을 H >> R > F라는 한 문장으로 공개했다. 비율도 상한도 아닌 "중복 시 앞선 소스가 이긴다"는 우선순위 배분이 양쪽에 다 있다. [추정] 구현 동일성은 확인 불가 — Instagram 쪽은 코드가 없다.
저신호·신규 유저 처리도 대응된다. X는 저신호 유저에게만 명시적 비율 배분으로 내려가고(위 2025 tweet-mixer), Instagram은 이력이 희소한 유저에게 one-hop/two-hop 그래프 확장으로 seed를 만든 뒤 *"the overall distribution is not skewed away from Home-based sources"*를 학습 제약으로 건다 [확인] 2020. 양쪽 다 점수 경쟁이 의미 없을 때만 명시적 분포 제약으로 내려간다.
Facebook 쪽에는 이런 게 없다. Facebook Feed retrieval의 소스별 후보 수 분해는 no primary source found. [추정] — 검사한 코퍼스는 Transparency Center 시스템 카드 15종, engineering.fb.com·ai.meta.com·about.fb.com 본문, 실적발표 전사본(2026-08-09 열람)이다. 연결 콘텐츠는 코퍼스가 이미 친구 그래프로 제한돼 배분 문제 자체가 약하기 때문으로 보인다 [추정].
케이스: Netflix — 배분 단위가 소스가 아니라 행이다
Netflix를 넣으면 이 노트의 전제 하나가 조건부가 된다: "소스가 여럿일 때 몇 개씩 섞나"라는 문제 설정 자체가 코퍼스가 클 때만 성립한다.
아이템 레벨 배분 레이어가 없다. 편성 카탈로그가 수천~수만 타이틀이라 PVR이 *"카탈로그 전체(또는 장르 필터된 부분집합)를 회원별로 정렬"*한다 [확인] TMIS 2015. 전 아이템을 채점할 수 있으면 소스별 상한(X의 *MaxResults)도, 퍼널 수치(Meta의 pass 0)도 필요 없다. X의 500/400 같은 숫자가 등장할 지점이 없다.
대신 같은 문제가 한 층 위에서 다시 나타난다 — 행 배분. 회원당 후보 행이 수만 개("tens of thousands"), 지면은 약 40행 × 최대 75타이틀 [확인] TMIS 2015. "어떤 소스에서 몇 개"가 "어떤 행(장르/BYW/Trending/CW)을 몇 개, 어떤 순서로"가 된다. 그리고 2015년에 이미 비율 배분(템플릿)을 버리고 ML 선택으로 갔다: "BYW 행이 0개인 홈도, 절반이 BYW인 홈도" 허용 [확인]. X가 2026에 한 전환(쿼터 제거 → 점수 경쟁)을 행 단위에서 10년 먼저 한 셈이다 [추정] 문제 구조가 같다는 것이지 계보 관계는 아니다.
행 종류별 랭커가 X의 후보 소스에 대응한다. PVR·Top-N·Trending·Continue Watching·sims가 각자 다른 알고리즘으로 행 내용을 채우고 [확인] TMIS 2015, 페이지 알고리즘이 이들을 조립한다 — X home-mixer가 candidate pipeline 9개를 병합하는 것과 같은 자리다. 차이는 병합 규칙: X는 "중복 시 앞선 파이프라인 승리"라는 우선순위, Netflix는 행 선택이라는 최적화다.
GenPage(2026)는 이 레이어 전체를 단일 모델로 접는다. 행 선택 + 행 내 정렬을 트랜스포머 하나의 autoregressive 생성으로 대체, 엔드투엔드 지연 −20% [확인] arXiv 2606.31031 (실험 단계). SilverTorch가 인덱스를 모델 텐서로 접은 것과 같은 방향 — 배분 로직이 명시적 규칙에서 학습된 분포로 이동한다.
광고: 랭킹 모델 공개물이 없어 오가닉/광고 배분 규칙도 알 수 없다 [확인](부재) → netflix.md.
광고 대 오가닉 — 개수 슬롯이냐 픽셀 거리냐
배분 레이어에서 가장 선명한 두 회사 차이다.
X — 오가닉 랭킹이 끝난 뒤 개수 기준으로 끼워 넣는다. [확인] 2026 소스코드 xai-org/x-algorithm
- 광고를 붙이려면 유기적 포스트가 최소 5개 있어야 한다 (
ads/util.rs:10) - 기본 간격
{requested: 3, min: 2}(ads/util.rs:12-17) - 안전 갭의 절반까지만:
max_from_safe = safe_count / 2(ads/partition_organic_blender.rs:35) BlenderSelector가 프레임워크 정렬을 의도적으로 무력화(score()가 상수0.0)한 뒤 프롬프트·팔로우추천·광고를 위치로 삽입 (blender_selector.rs:25-114)
→ 광고가 랭킹 경쟁에 참여하지 않는다. 정렬이 끝난 리스트에 개수 규칙으로 삽입된다.
Meta — 광고와 오가닉을 공통 단위로 환산해 하나의 랭킹에 넣는다. [확인] US 10,345,993 B2 "Selecting content items for presentation in a feed based on heights associated with the content items", 출원 2015 / 등록 2019, 특허
- "Scores associated with organic news feed stories and scores associated with advertisements are converted into a common unit of measurement, and advertisements and organic news feed stories are together ranked in a single ranking."
- 간격 제약이 개수 기준과 픽셀 높이 기준 둘 다로 표현된다: "at least a threshold number of organic news feed stories are presented between advertisements" 또는 "a combination of heights of the organic feed stories exceeds a gap distance threshold"
- position discount가 위에 놓인 후보들의 예측 높이 합에 의존한다
- 높이 예측 자체가 ML 모델이다 — 콘텐츠 타입/언어/댓글 수 + 클라이언트 화면 크기/OS/해상도/앱 버전으로 *"the likely height of the content item when presented"*를 예측
→ 인터리빙이 슬롯 인덱스가 아니라 기하 제약이다. "3개마다 하나"가 아니라 "직전 광고로부터 픽셀 거리 X 이상"이므로, 같은 유저라도 화면 크기와 콘텐츠 타입 구성에 따라 실제 광고 개수가 달라진다. [추정] — 특허가 결과 비중을 말하지는 않는다. 고정 슬롯 비율(ad load N%)로 모델링하면 틀린다는 뜻이다.
⚠ 주의 두 가지. (1) 이 특허는 출원 2015이고 현재 프로덕션이라는 근거가 아니다. 다만 Meta 광고 인터리빙에 남아 있는 유일한 기계적 1차 출처다. (2) 널리 도는 Total Value = Bid × Estimated Action Rate × Ad Quality는 Meta 1차 출처를 찾지 못했다 — 수식으로 인용 금지. ad load 수치도 인터리빙 규칙도 마찬가지다. [추정] — 검사한 코퍼스는 Marketing API 입찰·게재 문서, Transparency Center 광고 페이지, about.fb.com 본문(2026-08-09 열람)이다. 상세는 → facebook.md
Threads는 2025 Q3에 광고를 글로벌 롤아웃했지만 인터리빙 규칙 미공개이고 픽셀 높이 특허의 적용 여부도 불명이다. [추정](같은 코퍼스, 2026-08-09) → threads.md
이 케이스에서 일반화할 수 있는 것
- 공개된 "비율"은 대개 관측 결과지 코드에 있는 쿼터가 아니다. 소스를 열어 배분기를 직접 찾아야 한다. X는 결국 배분기를 없애고 점수 배수로 옮겼다.
- 캐시가 최대의 배분기다. X는 두 세대 모두 "캐시된 후보가 충분하면 비싼 소스를 안 돈다"가 1차 분기다. 즉 실제 소스 구성은 유저의 요청 빈도에 따라 달라진다.
- fail-open이 기본이면 배분은 보장이 아니라 경향이다. X는 거의 모든 candidate pipeline이 fail-open이라 소스 하나가 죽어도 응답은 나가고, 그 몫은 조용히 다른 소스로 대체된다. 단 정책을 손으로 나열하면 빠뜨린 하나가 fail-closed가 된다 — X home-mixer의 ContentExploration이 그 경우다. 기계적 일괄 적용(tweet-mixer)과 수기 나열(home-mixer)의 차이가 그대로 장애 반경 차이가 된다.
- 상한이 여러 겹이면 "나중에 붙는다"는 우대가 아니라 후순위다. X 2025 tweet-mixer의 u2i 후보는 라이트랭커 상한(500) 절단 뒤에 붙어 경쟁을 면제받는 것처럼 보이지만, 그 뒤의 최종 절단선이 400이라 앞이 다 차면 먼저 사라진다. 절단선을 하나만 보고 결론내면 방향이 반대로 나온다.
- 신규·저신호 유저에게만 비율 배분을 쓴다. 시그널이 없어 점수 경쟁이 의미 없을 때만 명시적 쿼터로 내려간다. X(저신호 유저 명시 비율)와 Instagram(희소 유저 seed 확장 + Home 분포 제약) 양쪽에서 같은 패턴이 관찰된다.
- "후보 수"라는 숫자는 출처 종류를 먼저 봐야 한다. 소스코드에서 나온 수는 정확하고 재현에 쓸 수 있으며 서로 어긋나는 것까지 보인다. 규제 대응 문서에서 나온 수는 반올림돼 있고 상대 비교에만 쓸 수 있다. 정밀도는 시스템의 성질이 아니라 공개물의 목적이 정한다.
- 광고 인터리빙을 슬롯 비율로 모델링하면 안 될 수 있다. X는 개수 기준(간격 3, 최소 5개 오가닉)이라 슬롯 모델이 맞지만, Meta 특허는 픽셀 높이라는 공통 단위 + gap distance 제약이라 화면 크기에 따라 결과가 달라진다. 같은 "N개마다 광고 하나"라는 요약이 한쪽에서는 맞고 한쪽에서는 틀린다.
- 배분 레이어의 존재 자체가 코퍼스 크기의 함수다. 카탈로그가 전 아이템 채점 가능한 크기(Netflix 수천~수만)면 아이템 레벨 배분이 사라지고, 문제는 한 층 위(행 구성)로 이동한다. "이 서비스의 후보 배분은?"이라고 묻기 전에 "배분이 필요한 규모인가"부터 물어야 한다.
- 배분 단위는 지면 구조를 따라간다. 단일 스트림은 아이템을 배분하고, 2차원 페이지는 행을 배분한다. Netflix의 행 종류(장르/BYW/Trending)가 X의 후보 소스와 같은 역할이다 — 병합 규칙만 우선순위(X) vs 최적화(Netflix)로 다르다.
누가 이걸 쓰는가
- X — 2025 Scala(상한 + 위브 + 우선순위 병합), 2026 Rust(배분기 없음, 점수 배수). 값은 전부 레포 밖, 로직은 전부 레포 안. → x.md
[확인] - Facebook — pass 0 약 500(Feed) / 1,000→200(Video) / 10–100(Reels), 비연결 카드는 수치 없음. 소스별 분해는 1차 출처 없음. 광고는 픽셀 높이 공통 단위 특허. → facebook.md
[확인] - Instagram — Feed 700→500, Explore 1,500→100(15:1), Reels Chaining ~100. Meta에서 유일하게 배분 장치가 기계적으로 보이는 지면(tunable weights,
max_media_per_account, seed 라운드로빈,H >> R > F). → instagram.md[확인] - Threads — 단계별 후보 수가 하나도 없다. 인벤토리 정의에 팔로우는 all, 공개 콘텐츠는 a portion이라는 비대칭만 명시. → threads.md
[관찰]2026-08-09 (ig-threads-feed 카드 본문 1건) - Netflix — 아이템 레벨 배분 없음(전 카탈로그 채점), 배분 단위는 행. 행 후보 수만 개 → 약 40행. 2015년에 템플릿(비율 배분) 폐지. → netflix.md
[확인] - 그 외 서비스 — 미조사. 열린 질문.
아직 확인 못한 것
- Meta가 연결(500) 결과와 비연결(수백) 결과를 어떤 규칙으로 인터리브하는가. 두 시스템이 별개라고만 밝히고 병합 규칙은 없다 → facebook.md 열린 질문 4.
- Instagram Explore의 소스별 tunable weight를 무엇으로 튜닝하는가. *"tunable"*이라고만 하고 목적 함수·최적화 방법 미공개.
- Meta 픽셀 높이 특허(2015 출원)가 2026년에도 운용되는가. 특허 외 1차 근거 없음.
- Threads의 후보 생성 방식과 단계별 수치. system card가 숫자를 하나도 주지 않는다.
- Netflix 행 선택 알고리즘의 현재 형태. 2015 논문(row-level 채점+greedy 류 서술) 이후 GenPage(2026 실험)까지 사이 10년의 1차 출처가 없다.
출처
공개 소스코드
- xai-org/x-algorithm — 2026, 공개 소스코드 (Apache 2.0).
home-mixer/sources/,home-mixer/selectors/,home-mixer/ads/. - twitter/the-algorithm — 2025-09-03 커밋
c54bec0, 공개 소스코드.tweet-mixer/,home-mixer/.
Meta Transparency Center (후보 수의 1차 출처)
- Our approach to explaining ranking (AI system card 인덱스) — 2023~, 규제 대응 문서. 지면별 pass 0 수치가 여기서 나온다.
- 개별 카드: fb-feed · fb-video · fb-reels · ig-feed · ig-explore · ig-reels-chaining · ig-threads-feed
Meta 블로그·특허
- Designing a Constrained Exploration System — 2020, 블로그. retrieval DSL,
max_media_per_account,H >> R > F. - Scaling the Instagram Explore recommendations system — 2023, 블로그. 소스 타입 4종 + tunable weights.
- The AI behind unconnected content recommendations — 2023, 블로그. billions → thousands → a few hundred.
- US 10,345,993 B2 — Selecting content items for presentation in a feed based on heights — 등록 2019(출원 2015), 특허. 광고·오가닉 통합 랭킹과 gap distance의 유일한 기계적 출처.
Netflix
- The Netflix Recommender System (TMIS) — 2015, 논문. 전 카탈로그 정렬, 행 수만 개 → 40행, 템플릿 폐지.
- Learning a Personalized Homepage — 2015, 블로그. 행 선택 문제 정식화.
- GenPage — 2026, 논문(RecSys). 페이지 생성의 단일 모델화.