0x00

리서치 / 일일 리포트

  • 읽은 자료 1건
  • 정본 반영 2개 파일
  • 레이더 1건
한 줄 요약X의 노출 판정 서비스(VF)가 액션 제한 사유마다 막을 액션과 안내 문구를 직접 정해, 응답에 액션별 문구까지 담기 시작했다.

오늘 함께 본 다른 변화는 다음과 같다.

  • 같은 X 커밋(2026-10-01, b79b947)에서 abuse-enforcement-service에 운영 중 Kafka 설정이 바뀌면 서비스를 재시작하는 모듈이 들어왔다.
  • 같은 커밋에서 phoenix-rankall 설정이 별도 모듈로 분리되며, SID(아이템을 의미 토큰 시퀀스로 표현한 ID) 백필 QPS 상한 800k가 드러났다.
  • Douyin(ByteDance)의 GEAR 논문이 생성형 광고 retrieval을 Douyin Ads 프로덕션에 배포했다고 밝혔다.

1액션 제한의 대상 액션과 안내 문구를 VF가 직접 정한다 — X

자료 xai-org/x-algorithm@b79b947 — 공개 소스코드, 2026-10-01 02:13 UTC 커밋. 이 노트들은 4세대 공개 뒤의 X 커밋에 차례로 번호를 붙여 부르며, 이 커밋은 4세대+33이다. 전체 변경량은 +2,490/−506, 47파일이다. 그중 신규 파일은 4개로, VF 신설 3개(829줄, 그중 limited-actions 2개 703줄)와 abuse-enforcement 신설 1개(173줄)다. phoenix-rankall config/mod.rs는 +84줄 수정이다.

배경 visibility-filtering(VF)은 포스트마다 이 뷰어에게 보여도 되는지를 판정하는 서비스다. X의 For You 타임라인은 랭킹을 거친 후보에 두 종류의 가시성 결정을 겹쳐 건다. 하나는 드롭과 tombstone을 내리는 Verdict 트리의 Withheld 축이고, 다른 하나는 Shown 상태에서 media interstitial이나 limited engagement를 추가로 거는 제약 축이다. 두 번째 축의 LimitedEngagement는 뷰어가 그 트윗에 reply·like·retweet 같은 액션을 할 수 없다는 뜻이다. 지금까지 코드가 담아 온 정보는 "사유 하나"였고, 액션 26종 가운데 무엇을 막을지와 UI에 뭐라고 표시할지는 하류인 home-mixer(For You 피드를 조립하는 서비스)나 클라이언트가 자체적으로 매핑하는 몫이었다. 이번 커밋은 그 매핑 전체를 VF 안으로 가져와 정책을 데이터(FeatureSwitches와 stringcenter bundle)로 선언하고, wire로 내보내는 payload 자체를 "액션 × 로컬라이즈된 Prompt" 리스트로 승격한다.

새로 알게 된 것A. 제한 사유마다 막을 액션과 안내 문구를 VF가 데이터로 정한다

VF에 limited-actions policy 시스템이 새로 생겼고, 이를 이루는 새 파일 두 개가 703줄이다. 제한 사유는 ConversationControl, ReadonlyViewer, BlockedViewer, RootAuthorBlockedViewer, StaleTweet의 5개다. 코드에서는 이 다섯이 LimitedEngagementReason의 variant다.

  • 새 정책 파일이 제한 사유를 받아, 그 사유로 막을 액션 목록과 문구 설정 조합을 FeatureSwitches를 통해 해석한다 확인 — limited_actions_policy.rs:14-115.

    • 파일은 VF visibility-filtering/params/limited_actions_policy.rs이고, 306줄이 새로 생겼다.
    • 입력은 LimitedEngagementReason이다. 해석하는 것은 그 사유에 걸리는 LimitedActionType 리스트와 copy namespace / prompt_type / learn_more_url 조합이다.
    • 반환값은 LimitedActionsPolicies(reason → Vec<LimitedAction>)다.
  • 테스트는 5개 사유마다 막을 액션 목록을 고정해 둔다 확인 — limited_actions_policy.rs:143-230.

    • ConversationControl → [Reply]
    • BlockedViewer → 19개 액션(Reply·Retweet·QuoteTweet·Like·React·SendViaDm·AddToBookmarks·AddToMoment·PinToProfile·ShareTweetVia·Follow·ListsAddRemove·Embed·CopyLink·VoteOnPoll·ShowRetweetActionMenu·ReplyDownVote·EditTweet·Highlight)
    • RootAuthorBlockedViewer → [Reply]
    • ReadonlyViewer → 14개 액션(viewer가 read-only 모드일 때 쓰기 액션 전부 포함)
    • StaleTweet → 15개 액션(복제·공유·view engagement 포함)
  • 안내 문구는 새 문구 파일이 로컬라이즈 UI 문구 번들에서 불러오고, 번들 해시를 고정해 둔 메타데이터와 비교하는 장치도 함께 갖는다 확인 — limited_actions_copy.rs:1-85.

    • 파일은 VF visibility-filtering/limited_actions_copy.rs이고, 397줄이 새로 생겼다.
    • 로컬라이즈 UI 문구를 담는 StringCenter 번들을 /config/stringcenter/visibility-ltd-actions.json에서 로드한다. 번들 해시를 pinned metadata와 비교하는 장치는 DriftCheck다.
    • 번들 로더는 LimitedActionType과 PromptCopy에서 Prompt { headline, subtext, kind }를 조립한다.
    • LimitedActionType은 26-variant enum이다: Reply·Retweet·QuoteTweet·Like·React·SendViaDm·AddToBookmarks·AddToMoment·PinToProfile·ViewTweetActivity·ShareTweetVia·Follow·ListsAddRemove·MuteConversation·Embed·ViewHiddenReplies·HideCommunityTweet·CopyLink·VoteOnPoll·RemoveFromCommunity·ShowRetweetActionMenu·ReplyDownVote·Autoplay·EditTweet·Highlight·ViewPostEngagements.
    • PromptCopy는 namespace, prompt_type, optional learn_more_url로 이뤄진다.
    • kind는 Basic, SeeConversation(CTA), LearnMore { language, link_text, url }의 3-variant다.
  • 판정 결과를 Thrift 액션으로 바꾸는 함수가 이 정책을 세 번째 인자로 받도록 시그니처가 바뀌었다 확인.

    • 바뀐 함수는 treatment.rs의 thrift_action(verdict, level, policies)이고, 추가된 인자는 &LimitedActionsPolicies다.

    • 직전 커밋(4세대+32, treatment.rs 신설)에서 격리된 rules → thrift/proto 사영 레이어가, 이번 커밋에서 그 역할을 확장받는 모양새다.

    • 바뀐 시그니처는 다음과 같다.

      pub(crate) fn thrift_action(
          verdict: &Verdict,
          level: SafetyLevel,
          policies: &LimitedActionsPolicies,
      ) -> Action {
      

      — treatment.rs:16-20

  • 제한이 걸린 트윗의 응답은 이제 새 Thrift 타입으로 조립되고, wire는 "제한 사유 enum만 넘기던" 형태에서 "액션별 로컬라이즈 prompt까지 담은 payload"로 승격된다 확인.

    • Thrift import에 BasicLimitedActionPrompt · CtaLimitedActionPrompt · LimitedAction · LimitedActionCtaType · LimitedActionPrompt · LimitedActionsPolicy · LocalizedMessage · LocalizedMessageLimitedActionPrompt · MessageLink 등 9개 심볼이 추가됐다.
    • 조립하는 경로는 Verdict의 Shown{engagement: Some(...)}이고, 조립 결과는 LimitedEngagements다.
  • VF의 EvaluateTweets 엔드포인트는 3개 필드를 들고, 그중 하나가 제한 액션 문구다 확인 — evaluate_tweets.rs:22-40.

    • VF evaluate_tweets.rs의 EvaluateTweetsEndpoint가 가진 필드는 filter_tweets, client_switches, limited_actions_copy다.

새로 알게 된 것B. 한 트윗에 제한 사유가 여러 개 쌓일 수 있게 됐다

  • 제한 정보가 사유 하나만 감싸던 구조에서 여러 사유를 쌓는 구조로 바뀌었다 확인.

    • VF models/verdict.rs의 LimitedEngagement가 단일 reason wrapper에서 first와 rest Vec을 축적하는 구조로 전환됐다.

      #[derive(Clone, Debug, PartialEq, Eq)]
      pub struct LimitedEngagement {
          first: LimitedEngagementReason,
          rest: Vec<LimitedEngagementReason>,
      }
      
      impl LimitedEngagement {
          pub fn add(&mut self, reason: LimitedEngagementReason) {
              if !self.reasons().any(|held| held == reason) {
                  self.rest.push(reason);
              }
          }
      }
      

      — verdict.rs:67-98

  • 규칙 쪽도 이미 제한이 걸린 트윗에서 새 사유를 만나면 Vec에 덧붙인다 확인 — rules/registry.rs.

    • collect_specs가 ActionSpec::LimitedEngagement(reason)에 매치될 때 이미 engagement가 있으면 limit.value.add(*reason)로 Vec에 append 한다.
    • 즉 같은 트윗에 BlockedViewer와 ReadonlyViewer가 동시에 걸리면, 두 사유에 걸린 액션 집합의 합집합을 하류가 아니라 VF가 만든다 확인 — treatment.rs의 limited_engagements.

새로 알게 된 것C. VF가 브라질 선거법원 라벨을 공식 drop 라벨로 등록했다

  • VF 규칙의 국가별 NSFW 콘텐츠 게이팅 정책 트리에 브라질 선거법원 라벨이 두 자리에 추가됐다 확인 — registry.rs:513-517, 648-652.
    • 추가된 라벨은 brazil_election_legal/drop/undesirable이고, 정책 트리는 rules/registry.rs의 country_specific_nsfw_content_gating이다.
    • 두 자리는 일반 drop 리스트와 라벨 drop 리스트다.
  • 직전 커밋(4세대+32)에서 홈믹서 측 Brazil 2026 election handle 리스트가 2,786 → 2,795로 9개 늘어난 데 이어, VF 쪽에도 Brazil 선거법원 라벨이 공식 drop 라벨로 등록된다.
  • VF가 라벨링 서비스에서 brazil_election_legal 라벨이 붙은 트윗을 받으면, 라벨이 Grok 생성이든 사람이 지정한 것이든 drop/undesirable 경로로 보낸다는 뜻이다.

새로 알게 된 것D. VF가 요청마다 어느 서비스가 호출했는지 기록한다

  • 새 파일이 gRPC 요청 피어의 x509 인증서에서 호출 서비스의 identity를 뽑는다 확인 — caller_identity.rs:1-56.
    • VF caller_identity.rs가 126줄로 새로 생겼다.
    • SAN URI 또는 subject CN 가운데 twtr:svc: 접두사로 시작하는 것을 뽑아 service-to-service identity로 쓴다.
    • 메트릭 vf_requests_by_caller가 세 라벨(caller_identity, rpc: FilterTweets|EvaluateTweets|GetSafetyLabels, deadline: present|absent)로 집계된다.
  • 테스트가 3가지 경우를 고정한다 확인.
    • SAN URI가 우선이다.
    • SAN이 없으면 CN으로 폴백한다.
    • 둘 다 twtr:svc: 접두사가 아니면 identity가 없다.
  • 모든 EvaluateTweets 요청이 caller identity 메트릭에 집계된다 확인(A 카드에 링크한 엔드포인트 파일).
    • 요청 핸들러가 맨 앞에서 caller_identity::record(Endpoint::EvaluateTweets, &request)를 호출한다.

새로 알게 된 것E. abuse-enforcement-service는 Kafka 설정 변경을 재시작으로 반영한다

abuse-enforcement-service는 실제 조치(label/challenge/suspension)를 판단하는 서비스이고, 2026-09-23에 Rust 재구성이 공개됐다. 이번 커밋부터 이 서비스는 기동 시점 config를 캡처해(boot_config) 그 위에서 Kafka producer를 빌드한다. 캡처한 값은 serde_json::Value다. 이전에는 lib.rs가 DynamicConfig.kafka_producers()로 producer를 꺼냈다. 운영 중에 kafka 설정이 바뀌면 서비스를 재시작시키는 전용 모듈(restart_on_config_change.rs)도 새로 들어왔다. kafka producer 바인딩이 "boot 시점 스냅샷에만 반응"하는 쪽으로 좁혀진 결과다.

  • 새 모듈은 기동 시점에 캡처한 설정과 운영 중 live config를 비교해, 지정된 경로가 바뀌면 서비스를 재시작하도록 유도한다 확인 — restart_on_config_change.rs:1-85.
    • abuse-enforcement-service의 restart_on_config_change.rs가 173줄로 새로 생겼다.
    • boot 시점에 캡처한 값은 serde_json::Value이고, 감시 대상은 지정된 JSON pointer 경로다.
    • 기본 감시 경로는 3개다: /kafka/consumer/topic_labels, /topic_labels, /kafka/producer.
    • 판정 결과는 세 상태(Check::{Unchanged, Invalid, Restart(Vec<String>)})다. 변경이 없으면 Unchanged다.
    • 변경이 있으면 validate(live)를 돌린다. 통과하면 Restart(변경 경로 리스트)이고, 실패하면 Invalid다. Invalid에서는 재시작하지 않고 로그만 남긴다.
  • 재시작 대기를 알리는 메트릭이 valid/invalid 두 라벨로 보고된다 확인.
    • 메트릭 이름은 CONFIG_RESTART_PENDING이다.
  • Kafka producer를 만드는 함수가 live 설정 대신 기동 시점 설정 스냅샷을 받도록 바뀌었다 확인 — lib.rs:831-840, 1385-1392.
    • lib.rs(+325/−55)에서 build_kafka_producers(cfg, dynamic_config) → build_kafka_producers(cfg, boot_config)로 시그니처가 바뀌었다.
    • boot_config는 dynamic_config.config().unwrap_or(serde_json::Value::Null)로 캡처한 기동 시점 설정 스냅샷이다. producer 빌드는 이 스냅샷 안의 kafka_producers 섹션만 참조한다.
    • growthbook.rs(+132/−32)에는 새 함수 kafka_producers_from_config(Option<&Value>)가 추가됐다. producer 설정을 얻는 방식이 "DynamicConfig 메서드 호출"에서 "JSON Value 직접 파싱"으로 바뀐 결과다.
    • rules.rs(+65/−51)에서는 rules_cache.resolve(entity_type, rules_override.as_deref()) → as_ref()로 lifetime을 미세 조정했다(의미 변화 없음).

새로 알게 된 것F. phoenix-rankall의 Kafka 설정이 기존 설정 모듈로 옮겨졌고, 그 모듈의 SID 백필 수치를 확인했다

phoenix-rankall은 SID 백필과 랭킹 점수를 다루는 Kafka 소비자다. 이전에는 kafka.rs 하나가 topic·그룹·bootstrap·SASL·SSL·wily·SID 설정을 전부 안고 있었다. 이번 커밋에서 그 책임이 4세대 공개 때부터 있던 phoenix-rankall/src/config/mod.rs로 넘어가면서(+84줄), kafka.rs에는 KafkaConsumerBuilder를 호출하는 얇은 어댑터만 남는다. 설정 소스는 env var와 CLI 양쪽에서 명시적으로 선언된다(xai-recsys-rankall 바이너리 이름). 이 모듈에 이미 있던 SID 백필 상수도 눈에 띈다. QPS 상한 800k, batch 5,000, 최소 포스트 나이 180초다.

  • kafka.rs가 들고 있던 Kafka 설정이 기존 CLI 모듈 config/mod.rs로 옮겨졌다 확인 — config/mod.rs:1-98.
    • phoenix-rankall src/config/mod.rs는 +84/−0 수정이다. 이 파일은 4세대 공개(47c1bcd) 때부터 있었고, 2026-08-16 daily도 이 파일을 인용한다.

    • 바이너리 이름은 xai-recsys-rankall이고, 환경 변수로 값을 받는 CLI 인자가 20여 개다.

      #[derive(Debug, Parser)]
      #[command(name = "xai-recsys-rankall")]
      pub struct Cli {
          #[arg(long, value_enum, env = "PIPELINE")]
          pub pipeline: PipelineKind,
          #[arg(long, default_value = "/s/kafka/main-2:kafka-tls", env = "KAFKA_BOOTSTRAP")]
          pub bootstrap: String,
          #[arg(long, default_value_t = 12, env = "SEEK_HOURS")]
          pub seek_hours: u64,
          #[arg(long, default_value = "phoenix.HomeExperiment1Lap7.", env = "SCORE_PREFIX")]
          pub score_prefix: String,
          #[arg(long, default_value_t = 180, env = "DUMP_INTERVAL_SECS")]
          pub dump_interval_secs: u64,
          #[arg(long, default_value_t = 180.0, env = "SID_MIN_POST_AGE_SECONDS")]
          pub sid_min_post_age_seconds: f64,
          #[arg(long, default_value_t = 5000, env = "SID_BACKFILL_BATCH_SIZE")]
          pub sid_backfill_batch_size: usize,
          #[arg(long, default_value_t = 800_000, env = "SID_BACKFILL_QPS_CAP")]
          pub sid_backfill_qps_cap: usize,
          #[arg(long, default_value_t = 1.0, env = "SID_BACKFILL_MAX_AGE_HOURS")]
          pub sid_backfill_max_age_hours: f64,
      

      — config/mod.rs:9-96

    • 이 가운데 눈여겨볼 수치는 SID_BACKFILL_QPS_CAP = 800_000과 SCORE_PREFIX = "phoenix.HomeExperiment1Lap7."다.

    • 전자는 SID(Semantic ID) 백필 QPS 상한이 800k임을 명시한다.

    • 후자는 phoenix-rankall이 Home 지면 Experiment1 Lap7이라는 특정 실험 그룹의 Phoenix(Grok 기반 Transformer 랭커) 랭킹 점수를 소비한다는 뜻이다.

    • 추가 수치도 있다. SID 백필은 batch 5,000에 간격 30초이고, 최소 포스트 나이 180초, 최대 포스트 나이 1시간이다.

    • Kafka dump 간격은 180초, 보존 버전은 3개, 재시작 시 되돌릴 Kafka 시간 상한은 12시간이다.

새로 알게 된 것그 밖의 변경

  • VF의 fallback cache가 직접 관리하던 64개 shard 구조를 quick_cache sync Cache에 맡기도록 완전히 재작성됐다 확인 — fallback_cache.rs:1-50.
    • 바뀐 파일은 VF hydration/fallback_cache.rs(+69/−173)다.
    • 이전의 64개 shard × Mutex-wrapped unsync Cache 구조(제너레이션 카운터와 CacheEntry 래퍼)가 quick_cache sync Cache와 OptionsBuilder로 교체됐다. shard 분배와 생성 로직은 라이브러리에 위임된다.
    • generic parameter도 FallbackCache<K, V> → FallbackCache<V>로 좁아졌다. 키는 이제 u64로 하드코딩된다.
    • 매번 with_locked_shard 패턴으로 Mutex를 잡던 호출이 사라져서, fallback cache 자리의 Mutex 접촉이 전부 걷히는 리팩터다.
  • 그 밖의 소소한 변경도 있다.
    • VF rules/golden_corpus/oon_tweet_label.rs(+76/−3)에서 oon_tweet 라벨 그룹의 golden corpus가 소폭 확장됐다.
    • VF params/client_switches.rs(+63/−13)의 테스트 fixture가 scala_client_switches.yml과 limited_actions_policy.yml 두 파일을 로드하도록 확장됐다.
    • VF hydration/execute.rs(+27/−24)가 Fetcher 변경에 따라 미세 조정됐다.
    • Phoenix python/training/xai-checkpointing/xai_checkpointing/orbax_encrypted.py(+42/−11)에서 체크포인트 암호화가 리팩터됐다. 피드 파이프라인이 아니라서 생략한다.
    • grox(Grok 기반 nearline 분류기) flows/reply_spam/task_write.py(+13/−1)가 바뀌었다.
    • home-mixer query_hydrators/served_history_query_hydrator.rs(+5/−7)가 바뀌었다.
    • home-mixer params/param.rs(+4/−4)에서는 cron sync 시각만 바뀌었다(실질 값 변화 없음). 다음 커밋에서 반영 여부를 다시 확인한다.

정본 반영 services/x.md 세대 흐름 표에 4세대+33 행을 추가했고, 세대 상세 섹션(2043행 뒤)에 4세대+33 전체 분석을 추가했다. 서비스 정본 "출처" 섹션은 바꾸지 않았다. 기존 커밋 레퍼런스가 그대로 유효하다.

의미 제한 사유에서 막을 액션과 안내 문구까지 정하는 일이 VF 안으로 들어왔다. 이 커밋에서는 네 겹의 사실이 겹친다.

  1. "제한 사유" 결정권이 VF 안으로 끌어들여졌다. 지금까지 VF가 Verdict의 engagement 축에 담은 정보는 사유 enum 하나였고, 그 사유로 어떤 액션을 막을지와 UI 문구는 하류(home-mixer, 클라이언트)의 몫이었다. 이번 커밋부터는 VF 안에서 다음 세 가지가 이뤄진다.

    • LimitedActionsPolicies로 사유 → 액션 리스트 매핑을 FeatureSwitches 기반으로 소지한다.
    • LimitedActionsCopy로 (액션, 사용자 locale, country) → 로컬라이즈된 Prompt를 소지한다.
    • treatment.rs::thrift_action이 두 정보를 조합해 Thrift payload LimitedEngagements 안에 액션별 Prompt 리스트를 직접 담는다.

    결과적으로 클라이언트는 자체 로컬라이제이션이나 액션 매핑 테이블을 유지할 필요가 없고, 사유 → 액션 → 문구의 모든 결정이 서버 쪽 데이터로 선언된다. 이 변화는 4세대+32에서 treatment.rs가 rules → thrift/proto/metric 사영 레이어로 격리된 것과 짝을 이룬다. 직전 커밋이 "wire 사영 지점을 한 파일로 모은" 것이고, 이번 커밋이 "그 사영 지점을 통과하는 데이터의 풍부함을 UI 수준까지 끌어올린" 것이다.

  2. LimitedEngagement가 복수 사유 축적 구조로 바뀌면서, 같은 트윗이 여러 제한 사유를 동시에 가질 수 있는 상황이 명시적으로 모델링된다.

    • 이전에는 rules가 engagement를 한 번만 결정하면, 그다음 ActionSpec::LimitedEngagement 매치는 drop(dead-code 경로)이었다.
    • 이번 커밋부터는 rules가 여러 번 engagement를 결정하면 limit.value.add(*reason)로 Vec에 축적된다.
    • 즉 BlockedViewer(19개 액션 제한)와 ReadonlyViewer(14개 액션 제한)가 겹치면, VF가 두 사유의 액션 목록을 액션 종류 기준 합집합으로 합쳐 첫 사유와 함께 payload에 싣는다 확인 — treatment.rs:128-148과 테스트 a_limit_sends_its_first_reason_and_the_union_of_its_reasons_policies.
    • ConversationControl과 StaleTweet, RootAuthorBlockedViewer와 BlockedViewer 같은 조합도 가능해진다.
  3. abuse-enforcement-service가 "boot 시점 설정 스냅샷"을 모델로 전환했다.

    • Rust 재구성이 공개된 2026-09-23 이후 이 서비스는 DynamicConfig 객체를 통해 live config를 쓰고 있었다. 이번 커밋부터 Kafka producer 바인딩은 boot 시점의 JSON Value 스냅샷에만 의존한다.
    • 그 대신 운영 중 kafka 설정(토픽, SASL 인증, bootstrap 서버)이 바뀌면, restart_on_config_change 모듈이 서비스를 재시작시켜 새 설정을 반영하는 패턴으로 간다.
    • 이 패턴은 kafka producer 라이프사이클을 간단하게 유지한다(hot swap 없음).
    • 또 설정 변경을 로그/메트릭에 명확히 드러낸다(재시작 이벤트).
    • 마지막으로 설정 유효성 검증(validate(live))을 통과하지 못하면 재시작하지 않는 fail-safe를 제공한다.
    • 이 방식은 핵심 설정(topic labels, kafka producer config) 변경에만 적용되고, 그 외는 여전히 live reload 경로다.
  4. phoenix-rankall이 실험 그룹 식별자와 QPS 상한을 공개했다. CLI config 모듈 분리는 조직적인 리팩터로 보이지만, 그 과정에서 두 수치가 노출된다.

    • 첫째는 SCORE_PREFIX = "phoenix.HomeExperiment1Lap7."다. Phoenix 랭킹 서비스가 Home 지면의 "Experiment1 Lap7"이라는 특정 실험 그룹의 점수를 Kafka 토픽으로 발행하고, phoenix-rankall이 그 특정 접두사를 가진 점수만 소비한다는 뜻이다.
    • "Lap7"이라는 명명은 실험 iteration 번호로 보인다 추정.
    • 둘째는 SID_BACKFILL_QPS_CAP = 800_000이다. SID 백필의 QPS 상한이 800k라는 숫자다.
    • SID 백필은 모든 포스트에 적용되는 게 아니라 특정 창(최대 1시간, 최소 3분)의 포스트만 처리하도록 설계돼 있다. 앞선 커밋에서 SID_BACKFILL_MAX_AGE_HOURS = 1이 보였고 이번에도 유지된다.
    • 하루에 보여지는 포스트 수가 수억~수십억 규모라는 걸 감안하고 이 QPS 상한을 창 설계와 함께 보면, SID 임베딩 테이블이 최근 1시간 신선도의 포스트 중 QPS 800k 범위로 커버된 서브셋에만 유지된다는 그림이 선다.
    • 신선도 창을 벗어난 포스트는 SID가 없거나 stale 상태로 남는다.

Brazil 선거법원 라벨을 VF에 등록한 변화는 로직 추가가 아니라 라벨 vocabulary 확장이다. Brazil 2026 election filter(홈믹서 측 handle 하드코딩)는 4세대+1에 신설됐다. 이번 등록은 VF가 라벨링 서비스에서 brazil_election_legal 라벨이 붙은 트윗을 받으면, 라벨 출처와 상관없이 drop 경로로 처리한다는 뜻이다. 규칙은 라벨 종류(BRAZIL_ELECTION_LEGAL)와 요청 국가(br)를 보고 작성자 본인은 예외로 둔다 확인 — tweet_rules.rs:570-579. 홈믹서 측 handle 리스트와 VF 측 라벨, 두 축이 함께 작동한다. 직전 커밋에서 Brazil handle 리스트가 2,795개로 늘어난 데 이어 VF 라벨 등록까지 붙으면서, 선거 기간에 두 경로가 공조한다는 선언이 코드로 명시된 셈이다.

2생성형 광고 retrieval GEAR가 Douyin Ads 프로덕션에 배포됐다 — radar

자료 arXiv 2609.39327 — arXiv, 2026-09-30 v1, 논문. 제목은 "Generative End-to-end Ad Retrieval at Douyin"이다. 저자 11인 전원이 Douyin(ByteDance) 소속이며, HTML 판에서 소속을 확인했다.

배경 생성형 retrieval은 아이템을 ID 임베딩이 아니라 의미 토큰(Semantic ID) 시퀀스로 표현하고, autoregressive 생성으로 검색을 대체하는 패러다임이다. 산업 규모에서는 두 가지 병목이 알려져 있다. 표현 붕괴(representation collapse)는 아이템 tokenizer가 지속적인 분포 변화 아래서 degenerate한 결과로 수렴해 end-to-end 적응이 불안정해지는 문제다. 아이템 충돌(item collision)은 후보 풀이 거대해지면 서로 다른 아이템이 같은 토큰 시퀀스를 공유하게 되어 retrieval 정밀도가 떨어지는 문제다. 두 문제는 서로 결합된다. 충돌을 완화하려고 codebook 용량을 키우면 붕괴가 심화된다. 이번 논문의 GEAR는 두 병목을 함께 다루는 생성형 광고 retrieval이고, Douyin Ads에서 서빙 중이라고 밝힌다.

새로 알게 된 것A. GEAR는 tokenizer·generator·reranker를 함께 end-to-end로 최적화한다

GEAR는 tokenizer, generator, reranker를 함께 end-to-end로 최적화하는 프레임워크다. 핵심 메커니즘은 두 가지다.

  • BasisVQ는 codebook을 orthogonal basis로 reparameterization한다. 이로써 global gradient sharing과 rigid spatial rotation of latent space가 가능해지고, ad-hoc heuristic 없이 gradient dynamics를 안정화한다.
    • BasisVQ를 prefix-aware BasisRQ로 확장해서, 같은 asymptotic time complexity 안에서 codebook 표현력을 크게 끌어올린다.
  • Context-conditioned reranking head는 생성 프로세스 안에 들어가, 충돌하는 아이템을 최소 계산 오버헤드로 분리(disambiguate)한다.

"It currently serves hundreds of millions of daily active users on Douyin Ads, yielding substantial empirical improvements in extensive online A/B tests." — arXiv 2609.39327, 2026-09-30

정본 반영 radar.md "이번 달 신규"에 GEAR 포인터를 2~4줄로 추가했다. Douyin(ByteDance)이 추적 서비스 밖이라 서비스 정본에는 반영하지 않는다.

의미 GEAR는 Douyin Ads의 광고 지면에 SID 생성 retrieval을 안착시킨 첫 명시적 진술이다. SID 계열 선행인 Kuaishou HD-Rec/PushDualGen, Spotify GLIDE, Tencent BARGE는 이미 SID 생성 retrieval을 다양한 modality와 지면에 배포한 상태다. Alibaba PailitaoGR은 Pailitao 온라인 검색 로그로 데이터를 만든 오프라인 실험만 보고했고, 배포나 A/B 진술은 없다 확인 — arXiv 2608.26658. 최근 한 달 사이에 "생성형 retrieval이 industrial-scale에 자리잡는 중"이라는 각도로 나온 진술로는 네 번째이고, 지금까지의 흐름은 다음과 같다.

  • (a) Snap GRP v0.1(2026-09-29)은 retrieval · ranking · reward를 단일 encoder-decoder로 통합했고, 서빙 latency −69%, A/B view time +0.82%를 냈다.
  • (b) Tencent PCG TGR(2026-09-01)은 랭킹·retrieval·slate 생성·reason token을 한 프레임에 묶어 5개 지면에 배포했다.
  • (c) Kuaishou OneTrans-V2(2026-09-23)는 3단 cascade를 단일 Transformer로 통합했고, GMV +9.74%를 냈다.
  • (d) 오늘의 GEAR는 generative retrieval 자체의 두 가지 구조적 병목(representation collapse, item collision)을 명시적으로 다루고, Douyin Ads 수억 DAU에 서빙 중이다.

GEAR는 (a)·(b)·(c)와 달리 retrieval에만 집중하고, 그 자리의 알려진 두 문제를 BasisVQ(codebook orthogonal basis reparam)와 context-conditioned reranking head로 공략한다. 광고 도메인에서 generative retrieval을 다룬 선행으로는 Taobao TransRetrieval(2026-08-28)이 있다. 다만 TransRetrieval은 scaling 문제를 field heterogeneity로 다룬 Transformer scaling 쪽이지, SID 생성 쪽이 아니다. GEAR가 광고에서 SID 생성 retrieval의 Douyin 라인을 열어서, 광고 retrieval이 generative 쪽으로 수렴할지가 다음 관측 지점이 된다.

레이더

  • GEAR(Douyin / ByteDance)가 generative ad retrieval을 Douyin Ads 수억 DAU 프로덕션에 배포했다. 소속, 배포 규모, 메커니즘은 확인한 사실이다 확인 — arXiv 2609.39327.
    • 두 병목을 end-to-end로 해결한다. representation collapse는 BasisVQ(orthogonal basis codebook)로, item collision은 context-conditioned reranking head로 다룬다.
    • online A/B에서 "substantial empirical improvements"를 얻었다고 밝혔다.
    • radar.md 이번 달 신규에 편입했다.

다음에 볼 것