◂ feed-research2026-08-29 빌드

피드 다양성 — 점수순 정렬을 일부러 망가뜨리는 레이어

최종 갱신: 2026-08-09

랭커 점수대로만 정렬하면 같은 작성자·같은 주제·같은 영상이 연달아 나온다. 그걸 막는 레이어. 크게 다섯 가지 방식이 있고, 대부분의 서비스가 여러 개를 겹쳐 쓴다.

  1. 감쇠 (decay) — 같은 키(작성자 등)의 n번째 아이템 점수에 f(n) < 1을 곱한다. 순수 pointwise, 싸다.
  2. 탐욕 재순위 (MMR) — 이미 뽑은 것들과의 거리를 점수에 더한다. 리스트 상태에 의존한다.
  3. 슬레이트 최적화 (DPP) — 집합 전체의 행렬식을 최대화한다. 제일 비싸고 제일 원리적이다.
  4. 중복 제거 (dedup) — 같은 것을 두 번 안 보여준다. 다양성이라기보다 위생.
  5. 분포 보정 (calibration) — 리스트의 장르·주제 분포를 유저 이력의 분포에 맞춘다. "서로 다르게"가 아니라 "이력과 비율이 같게" — 목표 자체가 다르다. Netflix가 수식을 공개했다(아래).

단, 이 목록으로 서비스를 비교하려면 먼저 "공개됐는가"를 봐야 한다. X는 네 가지가 전부 소스코드로 읽히는데 Meta는 주요 피드에 대해 넷 중 어느 것도 알고리즘 수준으로 공개한 적이 없다. [추정] — 아래 "케이스: Meta" 절에 검사한 코퍼스를 적었다. 그 부재 자체가 이 노트의 두 번째 발견이다.

케이스: X — 네 개를 다 쓴다

1. 작성자 다양성 감쇠 (2026)

fn diversity_multiplier(decay_factor: f64, floor: f64, position: usize) -> f64 {
    (1.0 - floor) * decay_factor.powf(position as f64) + floor
}

[확인]home-mixer/scorers/ranking_scorer.rs:186-188

position점수 내림차순으로 정렬한 뒤 같은 작성자 안에서 몇 번째인가다. 최종 피드에서의 위치가 아니다. [확인]:198-214

설계 포인트 두 가지:

2025 Scala 세대에는 이 감쇠기가 별도 스코어러 파일로 존재했다가(author_diversity_scorer.rs) 2026에서 랭킹 스코어러 안으로 흡수됐다 [추정] — 2026 레포에 author_diversity_scorer.rs가 남아 있지만 scorers/mod.rs에 선언되지 않아 모듈 트리에서 연결이 끊겨 있다 [확인].

2. MMR — 2025 Scala

score = relevance + diversityWeight × minDistance(후보, 이미 선택된 것들)

[확인]DiversityRescoringFeatureHydrator.scala:102. 임베딩 차원 128 [확인] :32. 거리는 코사인 유사도가 아니라 L2 정규화한 임베딩 사이의 L2 거리[확인] :64-69.

상위 구간에는 다양성이 아예 안 걸린다. minDistance는 조건부다:

if (selected.isEmpty || selected.size < (1 - diversityRatio) * n) None   // → getOrElse(2.0)

[확인] :92-93,102. 즉 기본값 2.0은 "첫 후보일 때"만이 아니라 리스트 앞의 (1-diversityRatio)×n개 전부에 적용된다. 상수 2.0이 모두에게 똑같이 더해지므로 그 구간은 순수 relevance 순서 그대로다. 다양성은 꼬리에만 건다.

ScoreFeature를 통째로 덮어쓴다. [확인] :28,70,91 — relevance를 ScoreFeature에서 읽어 같은 자리에 되쓴다. 즉 이 시점 이후 "랭커 점수"라고 부르는 값은 이미 다양성이 섞인 값이다.

같은 세대에 카테고리 버전이 따로 있다. SimClusters 카테고리 카운트 기반 탐욕 페널티:

penalty += math.log(cnt + 1) / math.log(2)          // log2(cnt+1)
score = math.max(relevance - weight * penalty, 0.00001)

[확인]CategoryDiversityRescoringFeatureHydrator.scala:69,72

MMR은 더하고 카테고리는 뺀다. 전자는 "다른 것에 보상", 후자는 "같은 것에 벌점"이다. 같은 파이프라인에 둘 다 param-gated로 들어가 있다(TwhinDiversityRescoringParam :589-590, CategoryDiversityRescoringParam :593-594). [확인]ScoredTweetsRecommendationPipelineConfig.scala:580-601

둘 다 켜지면 겹쳐 쓴다. 카테고리 쪽이 relevance로 읽는 ScoreFeature는 이미 MMR이 덮어쓴 값이다. 서로 독립적인 두 보정이 아니라 직렬로 합성된다. [확인] — 위 A4의 in-place 덮어쓰기

순서 문제: 이 둘은 postSelectionFeatureHydration이라는 이름의 단계에 실려 있어 top-K 자르기 이후에 도는 것으로 보인다 [추정]. 근거는 단계 이름과 resultSelectors(:574-577, DropRequestedMaxResults 포함) 뒤에 배치됐다는 것뿐이다 — 레포에 벤더링된 product-mixer/coreRecommendationPipelineConfig.scalaresultSelectors(:136) → postSelectionFilters(:142)만 정의하고 postSelectionFeatureHydration이라는 훅 자체가 없다. 즉 실행 위치를 코드로 확인할 수 없다. [확인](부재) — grep -r postSelectionFeatureHydration product-mixer/ → 0건

3. DPP — 2026, 별도 서비스

VMRanker가 gRPC로 외부 서비스를 호출하고, 응답 점수로 기존 점수를 덮어쓴다. [확인]home-mixer/scorers/vm_ranker.rs:47-52

let dpp_params = if dpp_theta > 0.0 || dpp_max_selected_rank > 0 {
    Some(DppParams { theta: dpp_theta, max_selected_rank: dpp_max_selected_rank })
} else { None };

[확인]vm_ranker.rs:112-120. theta는 관련성-다양성 트레이드오프, max_selected_rank는 DPP를 적용할 상위 구간 길이로 보인다 [추정] — 이름과 DPP 관행에 근거하며 코드에 설명은 없다.

서비스 본체는 공개되지 않았다. 레포에 있는 건 클라이언트와 proto 매핑뿐이라, 실제 커널 행렬을 무엇으로 만드는지는 알 수 없다. [확인] — repo-wide, xai_vm_ranker_proto는 외부 크레이트

실패 시 조용히 사라진다. gRPC가 실패하면 모든 후보에 Err를 반환하는데, 파이프라인의 update_allErr로그도 없이 건너뛴다.

fn update_all(&self, candidates: &mut [C], scored: Vec<Result<C, String>>) {
    for (candidate, scored) in candidates.iter_mut().zip(scored) {
        if let Ok(scored) = scored { self.update(candidate, scored); }
    }
}

[확인]candidate-pipeline/scorer.rs (update_all 기본 구현), vm_ranker.rs:33-38

VMRanker가 죽으면 다양성 최적화 없이 순수 점수순 피드가 나가고, 응답에는 아무 표시가 없다. fail-open이며 관측하려면 별도 지표가 필요하다.

EnableVMRanker FeatureSwitch로 통째로 꺼진다. [확인]vm_ranker.rs:18-20

4. 중복 제거 — 실제로는 여기가 제일 촘촘하다

2026 필터 [확인] home-mixer/filters/: drop_duplicates_filter / retweet_deduplication_filter / dedup_conversation_filter

2025 Scala는 dedup 필터만 6종이고, 각각 발동 조건이 좁다 [확인]:

필터 규칙 위치
RetweetDeduplicationFilter 후보 풀의 비(非)리트윗 전부를 먼저 seen에 넣고, 리트윗은 자기 id 또는 원본 id가 seen에 있으면 제거 :27-36
MediaIdDeduplicationFilter 미디어가 정확히 1개인 후보만 대상. 다중 미디어는 손도 안 댄다 :26,33-35
ClipClusterDeduplicationFilter 클러스터가 1종일 때만. Map[Long,Long]distinct 값 개수라 이미지 4장이 같은 클러스터면 이것도 걸린다 :24-27,29
DuplicateConversationTweetsFilter 조상 제거 후 같은 루트의 답글은 maxBy(ScoreFeature) 하나만. 루트가 없는(대화 아닌) 후보는 전부 통과 :33-40
QuoteDeduplicationFilter 인용된 원본 쪽을 지운다 (인용 포스트가 아니라) :23-26, 배선 :603,609
KeepTopKCandidatesPerCommunity 커뮤니티당 1개 + 전체 커뮤니티 후보 3개 상한 (MaxCandidatesPerCommunity = 1, MaxCommunityCandidates = 3) :14-15,30,32

MediaIdDeduplicationFilterif (mediaIds.size == 1) 조건은 의도적 보수성으로 보인다 — 이미지 4장짜리 포스트 두 개가 한 장만 겹칠 때 지우면 과하다 [추정]. 코드에 이유는 없다. (파일명은 MediaDeduplicationFilter.scala인데 object명은 MediaIdDeduplicationFilter[확인].)

QuoteDeduplicationFilter의 방향은 직관과 반대다. "인용 중복 제거"라고 읽으면 인용 포스트를 지울 것 같지만, 실제로는 인용당한 원본을 피드에서 뺀다 — 인용문이 원본을 이미 포함해 보여주기 때문 [추정].

tweet-mixer 쪽에는 저신호 유저 전용으로 작성자당 1개 규칙이 따로 있다. DropDuplicateCandidates(duplicationKey = _.features.getOrElse(AuthorIdFeature, None)) [확인]HomeRecommendedTweetsRecommendationPipelineConfig.scala:364

케이스: Meta — 주요 피드에 대해서는 아무것도 공개하지 않았다

이건 우리 조사의 구멍이 아니라 발견이다. Meta는 지면별 AI system card 30종, DSA 리스크 평가 문서, 엔지니어링 블로그를 계속 내면서 예측 헤드 이름·개별 피처 문장·단계별 후보 수·가중치를 정하는 방법까지 공개했는데, 다양성과 dedup만은 알고리즘 수준으로 한 번도 쓰지 않았다. X 쪽에서 감쇠 함수 형태, MMR 수식, dedup 필터 6종의 발동 조건이 줄 단위로 읽히는 것과 대비된다.

Meta 주요 피드에서 얻을 수 있는 다양성 서술은 아래가 전부다.

지면 공개된 문장 전부 등급
Facebook Feed pass 2 = "the contextual pass. Here, contextual features, such as content-type diversity rules, are added to help diversify Juan's News Feed." (2021) + "tries to ensure that your Feed has a balanced mix of content types. That means, for example, you wouldn't see multiple video posts in a row" (카드) [확인]
Instagram Feed "The system also tries to ensure your feed has a balanced mix of content types." — Facebook 문장보다 오히려 덜 구체적이다 [확인]
Instagram (Mosseri) "we try to avoid showing too many posts from the same person in a row, or too many suggested posts back to back" [확인]
Instagram Explore *"in case we would like to increase the diversity of results, we might shuffle items based on some business rules (e.g., 'Do not show items from the same authors in a sequence')"* — Meta 유기 지면에서 유일하게 규칙을 예시로 든 문장이다. 다만 *"in case we would like to"*라 실제 발동 여부는 미확정 [확인]
Threads 한 문장도 없다. "multiple video posts in a row" 수준의 서술조차 없다 [관찰] 2026-08-09 (ig-threads-feed 카드 본문 1건)

[확인] 순서대로 — engineering.fb.com, 2021 · fb-feed 카드 · ig-feed 카드 · Instagram Ranking Explained, 2023 · Scaling the Instagram Explore recommendations system, 2023 · ig-threads-feed 카드

알고리즘 이름(MMR/DPP), 감쇠 함수, 임계값 N, dedup 규칙 — 전부 no primary source found. [추정] — 검사한 코퍼스는 위 표의 1차 출처 6건과 Transparency Center 시스템 카드 15종, engineering.fb.com·ai.meta.com·about.instagram.com 본문(2026-08-09 열람)이다. 미공개 자료까지 없다는 근거는 아니다. 작성자 다양성만 예외다 — Explore가 *"same authors in a sequence"*를 예시로 들었다(위 표). ⚠ 그리고 Instagram 알림 블로그(2025)는 *"maximal marginal relevance (MMR)"*를 이름으로 부른다 — 피드가 아니라 알림이므로 위 부재 주장의 범위는 피드 지면 한정이다. [확인]

알 수 있는 것은 위치뿐이다: 다양성은 pass 2, 즉 포인트와이즈 스코어링이 끝난 뒤 별도 패스에 붙는다 [확인] 2021. [추정] X가 MMR·DPP를 선택 이후 단계에 두는 것과 구조적으로 같은 자리다. listwise가 존재한다는 진술도 있다 — "pointwise and listwise predictions" + "personalized methodology for delivery frequency control to optimize for long-term user value" [확인] ai.meta.com, 2023. 형태는 미공개.

다만 Facebook은 다양성을 랭킹 피처로도 쓴다

카드에 대응 피처가 실재한다 [확인] fb-feed: "Number of times you have seen posts with the same content type (e.g. video, photo, etc) as the current post in the past hour"

같은 다양성 개념이 재순위 규칙이자 동시에 랭킹 입력이다. X 쪽은 이 저장소가 기록한 범위에서 다양성이 전부 사후 점수 조작(감쇠·MMR·DPP·dedup)이고 "최근에 같은 유형을 몇 번 봤나" 계열 랭커 피처가 등장하지 않는다 — 다만 X 랭커의 전체 피처 목록을 훑어 확인한 것은 아니라 [추정]이다. 확인하면 이 대조가 서거나 무너진다 → 아래 "아직 확인 못한 것".

유일한 예외 — Instagram 알림의 곱셈 페널티 (2025)

Meta 전체에서 가장 기계적인 다양성 공개이고, 피드가 아니라 알림이다.

Score(c) = R(c) × D(c)
D(c) = ∏_{i=1..m} (1 - w_i · p_i(c))
p_i(c) = max_{h ∈ H} sim_i(c, h)

출처: A New Ranking Framework for Better Notification Quality on Instagram, 2025-09-02

위 세 줄은 verbatim 인용이 아니다. 원문에서 이 수식들은 LaTeX 이미지(latex.php?latex=…)로 렌더링돼 있어 본문 텍스트로 존재하지 않는다. 위 표기는 그 이미지를 보고 옮겨 적은 것이다. [관찰] 2026-08-09 (글 본문을 받아 수식 자리가 전부 이미지 태그임을 확인)

다만 각 구성요소는 이미지가 아닌 본문 산문이 그대로 뒷받침한다 [확인] 같은 글:

[추정] 곱 기호와 max 연산자의 정확한 형태만 이미지 의존이고, 나머지(곱셈 구조·R·D의 정의·비교 대상이 최근 발송 이력이라는 점)는 텍스트로 확인된다. 아래 해석은 그 텍스트 위에 선다.

X의 두 형태와 나란히 놓으면 셋이 전부 다르다.

비교 기준 집합 방향 결합
X 2025 MMR 이 요청에서 이미 선택된 후보들 거리에 보상 덧셈
X 2025 카테고리 페널티 이 요청의 카테고리 카운트 같은 것에 벌점 덧셈
IG 2025 알림 최근 발송 이력 H (요청 밖) 유사한 것에 벌점 곱셈

두 가지가 특히 중요하다.

retrieval 단계 다양성 — Instagram은 여기가 주력일 수 있다

[확인] Designing a Constrained Exploration System, 2020 — retrieval DSL에 diversify_by(seed_id, method=round_robin) 한 줄이 있다. seed 계정 단위 라운드로빈이고, 같은 쿼리의 max_media_per_account=10도 같은 성격이다.

[추정] Instagram의 다양성은 랭킹 후 재배열보다 후보 생성 단계에서 미리 섞는 쪽이 주력일 수 있다 — 랭킹 후 서술은 "too many" 수준인데 retrieval 쪽은 메서드 이름과 파라미터 값이 나온다. X도 tweet-mixer의 라운드로빈 위브와 저신호 유저 작성자당 1개 dedup을 retrieval 단계에 두므로 rung 자체는 공통이다 → candidate-allocation.md.

디벙크 — "Instagram은 같은 계정 3연속을 막는다"

널리 반복되지만 Meta 1차 출처를 찾지 못했다. [추정] — 검사한 코퍼스는 위 절과 같다(카드 15종 + Meta 블로그 3계열, 2026-08-09 열람). Mosseri의 실제 문장에 숫자가 없다는 것만은 실측이다 [관찰] 2026-08-09 (아래 인용 글 본문 1건).

[확인] Instagram Ranking Explained, 2023-05-31: "we try to avoid showing too many posts from the same person in a row, or too many suggested posts back to back"

케이스: Netflix — 다양성의 단위가 리스트가 아니라 페이지다

X·Meta와 지면 구조가 달라서(단일 스트림이 아니라 행×열 2차원 홈) 다양성이 걸리는 위치도 다르다.

다양성이 재순위 레이어가 아니라 행 선택 단계에 있다. 회원당 후보 행 수만 개에서 약 40개를 고를 때 *"관련성과 다양성으로 최적화"*한다 [확인] TMIS 2015. 2015년부터 템플릿을 버렸기 때문에("BYW 행이 하나도 없는 홈도, 절반이 BYW인 홈도" 허용) 다양성은 아이템 순서 조작이 아니라 페이지 구성 문제다. X의 MMR·DPP가 "정렬된 리스트를 어떻게 흔들까"라면 Netflix는 "어떤 렌즈(행)들을 나란히 놓을까"다.

calibration — 다섯 번째 방식의 1차 출처. Steck, Calibrated Recommendations (RecSys 2018): 유저가 로맨스 70%/액션 30%를 봤으면 추천 리스트도 그 비율을 유지하도록 KL-divergence 페널티로 탐욕 재순위한다. MMR과 형태는 같은 탐욕 재순위인데 **목표가 "아이템끼리 다르게"가 아니라 "이력 분포와 같게"**다. 정확도만 최적화하면 다수 취향(액션)이 리스트를 독점하는 crowding-out을 막는 장치.

중복 제거는 반대 방향일 수 있다. X는 dedup 필터 6종으로 같은 것을 지우는데, Netflix 홈은 같은 타이틀이 여러 행에 나오는 걸 허용하는 것으로 보인다 — 행마다 추천 이유(장르, BYW, Trending)가 다르고 아트워크 개인화(2017 전면 배포 [확인])까지 겹치면 같은 타이틀의 중복 노출이 "다른 설명의 재시도"로 기능한다 [추정] 행 간 중복 규칙의 1차 출처는 못 찾았고 직접 관찰도 아직 안 했다. 확인되면 "dedup은 위생"이라는 4번의 전제가 지면 구조에 따라 뒤집히는 사례가 된다.

지각된 다양성의 별도 레버 — 아트워크. 같은 타이틀이라도 회원마다 다른 이미지를 밴딧으로 고른다(2017 전면 배포, "we've rolled it out to everyone!" [확인] 블로그). 아이템 집합이 같아도 화면의 시각적 다양성이 달라진다 — X·Meta 문헌에는 대응물이 없는 레이어다.

상세는 → netflix.md

이 케이스에서 일반화할 수 있는 것

누가 이걸 쓰는가

아직 확인 못한 것

출처

공개 소스코드

Meta 블로그

Meta Transparency Center (규제 대응 1차 문서)

Netflix