추천 시스템 설계: 후보 생성·랭킹·실시간 피드백·모델 드리프트
추천 시스템에는 두 개의 시간이 흐른다. 숨김처럼 다음 요청에 즉시 반영해야 하는 시간과, 클릭·완료 로그를 검증한 뒤 모델을 바꾸는 느린 시간이다. 추천 시스템 설계는 두 시간을 분리해 사용자가 빠른 피드를 받으면서도 우연한 반응 하나에 추천 전체가 흔들리지 않게 만드는 일이다.
이 글은 2026년 8월 15일 기준 공개 공식 문서와 원 논문을 대조한 제품 중립 가상 설계다. 모바일·웹의 로그인 사용자와 기록이 거의 없는 신규 사용자를 대상으로 피드 조회, 반응, 재방문을 다룬다. 특정 서비스의 비공개 구조를 설명하지 않으며, 처리량과 저장량은 선택을 비교하기 위한 설계 가정이다.
20초 핵심 요약
- 무엇: 큰 항목 집합을 후보 생성, 랭킹, 재정렬의 세 단계로 줄여 개인화 피드 20개를 만든다.
- 왜: 전 항목을 매번 정밀 계산하면 지연과 비용이 커지고, 노출 맥락 없는 클릭을 학습하면 상단 편향과 반복 추천이 강화될 수 있다.
- 어떻게: API(Application Programming Interface, 응용 프로그램 프로그래밍 인터페이스)는 사용자 앱과 추천 서버가 정해진 형식으로 요청과 결과를 주고받는 접점이다. 이 설계에서는 피드 조회와 행동 전송을 두 API로 분리해 요청을 추적하고 실패를 격리하며, 숨김은 즉시 반영하되 모델 변경은 노출 로그·평가·단계적 검증을 거쳐 배포한다.
두 개의 시간이 지켜야 할 사용자 약속
첫 행동은 피드를 여는 것이다. 사용자는 짧은 대기 뒤 관련 항목 20개를 받고 싶다. 개인정보 설정과 차단 목록이 지켜져야 하며, 추천 모델이 실패해도 빈 화면보다 안전한 기본 피드를 기대한다.
둘째 행동은 항목을 보거나 클릭·숨김·완료하는 것이다. 실제 화면에 보이지 않은 항목의 무반응은 선호 신호가 아니다. 방금 숨긴 항목은 다음 페이지부터 사라져야 한다.
셋째 행동은 다시 방문하는 것이다. 최근 반응이 어느 정도 반영돼야 하지만 우연한 클릭 하나가 장기 선호 전체를 덮어쓰면 안 된다. 따라서 최근 세션 신호와 안정된 장기 신호를 분리한다.
정확성도 “클릭할 확률” 하나로 정의하지 않는다. 관련성, 신선도, 다양성, 반복 억제, 안전·차단 규칙과 장기 만족을 함께 본다. Google의 추천 시스템 교육 자료도 클릭만 최적화하면 클릭을 유도하는 항목이, 시청 시간만 최적화하면 긴 항목이 과도하게 유리해질 수 있다고 설명한다.Google Scoring 문서
범위는 개인화 홈 피드와 다음 페이지, 노출·클릭·숨김·좋아요·완료 이벤트, 신규 사용자와 신규 항목, 평가와 재학습이다. 광고 입찰, 결제, 콘텐츠 검색과 모델 수식은 제외한다. 가용성은 정밀 개인화보다 우선하고, 명시적 숨김·차단에는 즉시 일치하는 결과를 요구한다. 최근 클릭 집계와 모델 입력값은 수 초에서 수 분 늦어질 수 있다.
1억 개 항목 앞에서 갈라지는 계산
아래 수치는 실측 성능이 아니라 설계 가정이다. 입력이 바뀌면 구성 선택도 다시 검토해야 한다.
| 입력 가정 | 계산 | 결과 | 설계 판단 |
|---|---|---|---|
| 일간 활성 사용자 1,000만 명, 1인당 10회 요청 | 1억 ÷ 86,400 | 평균 약 1,157 QPS | 피크를 10배로 잡아 약 12,000 QPS의 조회 경로를 검토한다. QPS(Queries Per Second)는 1초에 받는 요청 수이며 조회 경로의 필요한 처리 용량을 잡기 위해 쓴다. |
| 요청당 20개 노출, 피크 12,000 QPS | 20 × 12,000 | 초당 24만 노출 | 항목마다 데이터베이스에 동기 기록하지 않고 묶어서 비동기 수집한다. |
| 하루 노출 20억 건, 압축 후 건당 200바이트 | 20억 × 200B | 원시 약 400GB/일 | 복제와 인덱스 비용은 별도다. 보존 기간과 집계 전환이 비용을 좌우한다. |
| 활성 항목 1억 개, 값 하나를 4바이트로 저장하는 128차원 표현 | 1억 × 128 × 4B | 원본 약 51.2GB | 전체 항목을 한 대에서 전수 비교하지 않고 분할 검색을 검토한다. |
| 후보 2,000개, 정밀 랭킹 200개, 최종 20개 | 단계별 축소 | 1억 → 2천 → 200 → 20 | 비싼 계산은 소수 후보에만 적용한다. |
요청량이 10배가 되면 이벤트 저장과 후보 조회도 거의 선형으로 늘어난다. 항목 수가 10배가 되면 전수 비교 비용이 더 커진다. 실제 설계 전에는 시간대별 트래픽, 응답 크기, 모델 입력값 조회 지연과 후보원이 관련 항목을 얼마나 놓치는지 측정해야 한다.
열기·반응·재방문을 한 요청으로 잇기
피드를 여는 요청은 아홉 단계를 지난다
사용자가 피드를 열 때의 계약부터 고정한다. cursor는 다음 페이지 위치를 서버만 해석할 수 있게 감춘 토큰이다. 사용자·피드 세션·만료 시각을 서명해 담으면 클라이언트가 페이지 번호를 바꿔 중복과 누락을 키우는 일을 막을 수 있다.
GET /v1/feed?surface=home&limit=20&cursor=opaque_cursor
Authorization: Bearer <redacted>
200 OK
{
"request_id": "r_...",
"items": [
{"item_id": "i_...", "position": 1, "reason_code": "recent_interest"}
],
"next_cursor": "opaque_cursor",
"model_version": "ranker_..."
}
- API 경계는 인증, 요청 속도, 개인정보 동의 상태를 확인하고 요청 식별자인
request_id를 만든다. - 추천 조정 계층은 최근에 본 항목과 숨김·차단 목록, 요청 시점의 맥락을 읽는다.
- 후보 생성은 1억 개 항목을 정밀 평가하기 전에 가능성 있는 수천 개로 줄이는 단계다. 협업 신호, 콘텐츠 유사도, 최신·인기 후보를 병렬로 받아 신규 사용자와 후보원 장애를 보완한다.
- 임베딩은 사용자와 항목의 성질을 여러 숫자로 표현해 가까움을 계산할 수 있게 한 값이다. 전체를 정확히 비교하기 비싸면 근사 최근접 이웃 검색, 즉 일부 정확도를 양보하고 가까운 벡터를 빠르게 찾는 방법을 쓴다.Google Retrieval 문서
- 같은
item_id를 합친 뒤 특성 저장소(feature store)에서 최신 사용자·항목·맥락 값을 한 번에 읽는다. 특성 저장소는 모델 입력값을 같은 정의로 관리해 서빙과 학습의 차이를 줄이는 계층이라 필요하다. - 랭킹은 후보마다 클릭·완료·숨김 가능성 같은 점수를 계산해 공통 순서를 정한다. 후보원마다 점수 척도가 다르므로 각 후보원의 원래 점수를 최종 순서로 바로 쓰지 않는다.
- 재정렬은 점수 뒤에서 숨김, 중복, 안전 위반을 제거하고 신선도·다양성 규칙을 적용한다. 이 규칙을 모델이 알아서 배웠다고 가정하지 않는다.
- 결과 묶음을 짧게 캐시하고 모델·특성·정책 버전, 후보원, 최종 위치를 기록한다. 캐시는 계산한 결과를 잠시 보관해 반복 요청을 빠르게 돌려주는 공간이다.
- 응답 뒤 클라이언트는 실제 화면에 보인 항목만 노출 이벤트로 전송한다. 서버가 보낸 것과 사용자가 본 것을 구분해야 무반응을 잘못 학습하지 않는다.
아래 이미지는 왼쪽 위의 피드 요청부터 번호 순서로 읽고, 응답 뒤 오른쪽의 이벤트가 두 갈래로 나뉘는 지점을 보면 된다.

그림의 요점은 피드백 속도가 하나가 아니라는 데 있다. 숨김과 최근 세션 값은 다음 요청에 바로 쓰는 길로 보내고, 모델 자체의 변화는 오프라인 평가와 제한된 온라인 검증을 거치는 길로 보낸다.
반응 이벤트는 발생 시각과 도착 시각을 함께 남긴다
사용자가 항목에 반응하면 다음 계약을 쓴다.
POST /v1/events
Idempotency-Key: e_...
{
"event_id": "e_...",
"request_id": "r_...",
"item_id": "i_...",
"event_type": "impression|click|hide|complete",
"position": 1,
"occurred_at": "2026-08-15T01:00:00Z"
}
멱등성은 같은 요청을 여러 번 받아도 한 번 처리한 것과 같은 결과를 만드는 성질이다. 모바일 재시도와 네트워크 복구가 같은 이벤트를 다시 보낼 수 있어 event_id나 Idempotency-Key가 필요하다.
- 수집 API가 형식, 인증, 이벤트 시각 범위, 요청과 항목의 관계를 확인한다.
hide는 강한 제외 저장소에 먼저 기록해 다음 피드에서 바로 뺀다.- 유효한 이벤트를 내구성 있는 이벤트 스트림에 추가한다. 스트림은 사건을 순서대로 이어 저장하고 여러 후속 처리가 각자 읽게 하는 통로다.
- 처리기는 최근 클릭 수와 세션 관심 같은 온라인 특성을 갱신하고, 이미 본
event_id는 무시한다. - 원시 이벤트와 처리 위치는 오프라인 저장소에 남긴다. 학습 데이터 생성기는 실제 노출과 뒤따른 행동을 정해진 시간 창 안에서 결합한다.
occurred_at은 기기에서 행동이 일어난 때이고 received_at은 서버가 받은 때다. 둘을 함께 두면 오프라인이었던 기기에서 늦게 온 클릭을 수집 장애와 구분할 수 있다. 사용자 전체 행동의 완전한 순서를 맞추기보다 같은 request_id 안에서 노출 뒤 반응이 왔는지를 우선한다.
Apache Kafka 문서가 설명하는 at-least-once는 손실을 줄이는 대신 같은 메시지가 다시 전달될 수 있는 방식이다. Kafka 안의 토픽 사이에서는 트랜잭션으로 한 번만 처리한 효과를 만들 수 있지만 외부 특성 저장소나 분석 데이터베이스까지 자동으로 확장되지는 않는다. 그래서 이 설계는 막연한 “정확히 한 번”보다 중복 제거 키와 처리 위치를 결과와 함께 저장한다.Apache Kafka 4.1 설계 문서
다시 방문할 때는 최근 신호와 장기 선호를 합친다
새 피드 요청은 즉시 갱신된 숨김 목록과 세션 특성을 먼저 읽는다. 장기 사용자 임베딩은 검증된 배치 또는 준실시간 학습 결과에서 가져온다. 모델과 특성 버전이 맞지 않으면 직전 안정 버전이나 인기 피드로 낮춘다.
직전 피드의 항목은 억제한다. 아직 유효한 커서가 있으면 같은 피드 세션의 결과 묶음을 이어서 중복과 순서 변화를 줄인다. 신규 사용자는 지역·언어·시간대별 인기 항목과 직접 고른 관심사를 섞고, 신규 항목은 콘텐츠 자체의 특징으로 후보에 들어갈 기회를 준다.
빠른 상태와 느린 학습이 만나는 경계
데이터 모델은 노출 기회와 사용자 반응을 분리한다
| 데이터 | 핵심 키와 필드 | 조회·인덱스 | 보존과 일관성 |
|---|---|---|---|
recommendation_request |
request_id, user_key, surface, model_version, created_at |
request_id, 제한된 사용자별 최근 요청 |
사용자 키를 가명화하고 목적 기간 뒤 집계하거나 삭제한다. |
recommendation_impression |
(request_id,item_id,position), event_id, shown_at, propensity |
event_id 유일, request_id 결합 |
실제 노출만 기록한다. propensity는 선택될 확률이며 탐색의 편향을 보정할 때 쓴다. |
interaction_event |
event_id, request_id, item_id, type, occurred_at, received_at |
event_id 유일, 이벤트 시각별 분할 |
늦은 도착 허용 창 뒤의 이벤트는 보정 작업으로 처리한다. |
user_exclusion |
(user_id,item_id), reason, updated_at |
사용자 기준 빠른 조회 | 숨김·차단은 즉시 일치시키고 삭제 요청과 연결한다. |
online_feature |
(entity_key,feature_name), 값, feature_time, version |
사용자·항목 묶음 조회 | 최신 서빙 값이며 너무 오래되면 기본값으로 낮춘다. |
offline_feature |
entity_key, feature_time, 값, 정의 버전 |
날짜별 분할, 시점 일치 결합 | 그 학습 시점에 알 수 있었던 값만 붙인다. |
item_catalog |
item_id, 상태, 주제, 안전 상태, updated_at |
상태·언어·지역 필터와 벡터 인덱스 | 삭제·차단을 후보 인덱스와 캐시에 전파한다. |
TTL(Time To Live, 자동 만료까지의 시간)은 추천 캐시와 임시 중복 제거 키가 너무 오래 남지 않게 하는 값이다. 이 글에서는 오래된 결과가 다시 보이는 일을 줄이기 위해 필요하다. 개인정보의 법적 보존 기간과는 다른 개념이며, 숨김과 삭제 상태를 TTL에만 맡기지 않는다.
온라인 특성 저장소는 즉시 응답에 필요한 최신 값을 제공한다. 오프라인 저장소는 과거 시점의 학습 값을 제공한다. 시점 일치 결합은 과거 학습 행에 그 뒤에 생긴 미래 정보를 넣지 않는 방법이다. 이를 지키지 않으면 학습 때만 알 수 있는 값으로 좋아 보였던 모델이 실제 요청에서는 재현되지 않는다.Feast Feature Retrieval Feast Point-in-time Joins
후보 생성·랭킹·캐시는 서로 다른 비용을 줄인다
후보 생성에는 콘텐츠 유사도, 다른 사용자와의 공통 반응, 인기·최신 항목을 주는 여러 후보원을 선택한다. 한 후보원이 실패하거나 신규 사용자 기록이 부족해도 다른 길이 남는 장점이 있다. 대신 중복 제거, 후보원별 개수와 서로 다른 점수를 관리해야 한다.
랭커는 여러 후보원의 항목을 공통 척도로 비교한다. 정밀 모델은 후보 수, 특성 수와 모델 복잡도만큼 비용이 늘어 입력 후보에 상한을 둔다. 재정렬은 모델 점수 뒤에 안전·숨김·중복 제거, 신선도와 다양성을 적용한다. 클릭·완료·숨김·장기 만족의 가중치도 모델과 별도의 정책 버전으로 기록한다.
사용자별 결과 캐시는 반복 요청을 빠르게 하지만 최근 반응과 삭제 반영을 늦출 수 있다. 짧은 TTL, 응답 직전 숨김 재검사, 항목 상태 버전을 함께 쓴다. 세그먼트별 인기 캐시는 신규 사용자와 장애 시 대체 결과에 유리하고, 특성 캐시는 랭킹 지연을 줄인다. 특성 값에는 생성 시각인 feature_time을 붙여 너무 오래되면 기본값이나 더 작은 후보군으로 낮춘다.
실시간 피드백은 즉시 반영과 모델 재학습으로 나눈다
실시간을 클릭할 때마다 모델 파라미터를 바꾸는 뜻으로 쓰지 않는다.
- 즉시 경로: 숨김·차단, 최근에 본 항목, 세션 주제 횟수처럼 뜻이 분명하고 되돌리기 쉬운 값을 수 초 안에 갱신한다.
- 학습 경로: 노출과 늦게 확정되는 클릭·완료·전환을 결합하고 품질 검사 뒤 새 모델을 학습한다.
암묵적 피드백은 별점처럼 직접 선호를 묻지 않고 노출·클릭·시청에서 선호를 추정하는 데이터다. 상단 항목은 보일 기회부터 많으므로 클릭만 모으면 위치 편향이 학습된다. 요청, 위치와 실제 노출을 함께 남기고, 제한적 탐색에는 선택 확률도 기록해야 사후 평가의 편향을 줄일 수 있다.Google Scoring 문서 Qin et al., 2020
평가는 세 층으로 나눈다.
- 시스템 상태: 중앙값인 p50뿐 아니라 느린 쪽을 보여 주는 p95와 p99 지연, 타임아웃, 후보원 오류율, 캐시 적중률, 특성 나이, 이벤트 지연과 중복률을 본다.
- 오프라인 품질: Recall@K는 관련 항목을 상위 K개 안에서 얼마나 찾았는지 본다. NDCG(Normalized Discounted Cumulative Gain, 정규화 할인 누적 이득)는 관련 항목이 목록 위쪽에 있을수록 높은 점수를 주는 순위 지표다. 노출 범위와 다양성, 신규·기존 사용자군 차이도 함께 본다.
- 온라인 사용자 결과: 클릭 외에 완료, 숨김, 세션 깊이, 재방문, 불만과 안전 지표를 본다. 가드레일은 주요 지표가 좋아져도 악화하면 실험을 멈추는 보호 지표다.
오프라인 결과가 온라인 만족을 보장하지 않는다. 2020년 ACM(Association for Computing Machinery, 컴퓨터 분야 학술 단체) 연구에서도 여러 오프라인 지표의 결과가 크게 달랐고 사용자 숙련도에 따라 온라인 상관도도 달랐다. 이 자료는 오프라인 점수만으로 배포를 결정하지 않고 실제 사용자 결과를 별도로 확인해야 한다는 근거로 쓴다. 한 연구 환경의 결과를 보편 수치로 쓰지 않고, 오프라인 평가는 나쁜 모델을 거르는 관문으로, 온라인 실험은 실제 사용자 결과를 확인하는 관문으로 둔다.Off-line vs. On-line Evaluation, ACM 2020
탐색은 한 슬롯에서 시작하고 언제든 끌 수 있어야 한다
활용(exploitation)은 현재 가장 좋아 보이는 항목을 보여 주는 선택이다. 탐색(exploration)은 정보가 부족한 항목도 제한적으로 보여 학습 기회를 만드는 선택이다. 활용만 하면 인기 항목에 노출이 몰리고 신규 항목은 평가 기회조차 얻지 못할 수 있어 둘의 균형이 필요하다.
이 설계에서는 최종 20개 중 위험이 낮은 1개 슬롯에만 탐색을 허용한다. 설계 가정이며 모든 서비스에 맞는 비율은 아니다. 안전·차단 필터 뒤에서 실행하고 무작위 비율, 후보 선택 확률과 정책 버전을 노출 로그에 남긴다. 사용자 만족이나 안전 지표가 나빠지면 즉시 탐색을 0으로 내리는 중단 스위치를 둔다.
작은 확률로 무작위 후보를 고르는 epsilon-greedy는 설명과 운영이 쉽지만 사용자 맥락을 덜 쓴다. 신뢰도 상한으로 고르는 UCB(Upper Confidence Bound)나 후보 가능성을 확률적으로 뽑는 Thompson Sampling은 정보를 더 활용하지만 로그와 사후 평가, 안정성 운영이 복잡하다. 알고리즘 이름보다 탐색 예산과 사용자 피해 상한을 먼저 정한다.
모델 드리프트 경보는 품질 저하의 확정 판정이 아니다
모델 드리프트(model drift)는 시간이 지나 입력이나 사용자 반응 관계가 달라져 기존 모델 판단이 맞지 않게 되는 현상을 넓게 가리킨다. 운영에서는 다음 세 신호를 섞지 않는다.
- 데이터 드리프트: 현재 입력값 분포가 과거 서빙 분포와 달라진 상태다. 계절, 신규 콘텐츠, 화면 변경이나 수집 오류가 원인일 수 있다.
- 학습-서빙 불일치(training-serving skew): 학습 때 만든 특성과 실제 요청에서 만든 특성의 정의나 분포가 다른 상태다. 코드 버전과 시점 결합 오류를 확인한다.
- 개념 변화: 같은 입력과 점수에 대한 실제 사용자 만족의 관계가 달라진 상태다. 클릭·완료·재방문처럼 늦게 확정되는 결과가 필요하다.
입력 변화는 조기 경보이지 품질 저하의 증명은 아니다. 반대로 입력이 안정돼도 사용자와 반응의 관계는 바뀔 수 있다. Google Cloud 문서도 학습 데이터와 서빙 데이터 차이, 시간에 따른 서빙 분포 변화를 구분하고 통계 이상 뒤에 원인 조사와 재학습을 둔다.BigQuery ML 모델 모니터링
운영 순서는 다음과 같다.
- 특성 누락률과 값 범위, 범주 비율, 예측 점수, 후보원 비중, 사용자군별 성과를 관찰한다.
- 기준 분포와 최근 기간을 비교한다. 임계값은 과거 변동과 오류 비용으로 정하고 보편 숫자를 가져오지 않는다.
- 경보가 나면 스키마·수집·배포 변경, 특정 사용자군과 새 콘텐츠 유입을 먼저 나눈다.
- 수집 오류면 기본값으로 낮추거나 문제 후보원을 끈다. 실제 선호 변화면 최신 데이터로 재학습 후보를 만든다.
- 새 모델은 오프라인 평가, shadow, canary, A/B 테스트 순으로 확인한다. shadow는 사용자 응답에는 쓰지 않고 뒤에서 결과만 비교하는 방식이고, canary는 작은 트래픽에 먼저 적용해 피해 범위를 제한하는 방식이다. A/B 테스트는 사용자를 두 집단으로 나눠 기존안과 새 안의 결과를 비교한다.
- 성과나 보호 지표가 악화되면 직전 모델·특성 정의·정책 버전으로 되돌리고, 늦게 도착한 결과로 영향 범위를 다시 계산한다.
자동 재학습과 자동 배포는 한 동작으로 묶지 않는다. 새 데이터를 썼다는 사실만으로 안전하고 더 나은 모델이 되지는 않는다.
어느 시간에서 먼저 막히는가
- 온라인 특성 핫키: 핫키는 유명 항목 하나처럼 요청이 몰리는 키다. 항목 특성을 복제 캐시하고 한 요청의 값을 묶어 읽는다.
- 후보 검색: 항목 수와 벡터 차원이 커지면 메모리와 인덱스 재구축이 막힌다. 새 인덱스를 별도 버전으로 준비한 뒤 전환하고 최신 항목은 작은 추가 인덱스로 보완한다.
- 랭킹 추론: 후보 수 × 특성 수 × 모델 복잡도가 비용을 만든다. 후보 수 상한, 작은 대체 모델과 단계별 시간 예산을 둔다.
- 이벤트 쓰기: 노출 건수는 피드 요청보다 훨씬 많다. 비동기 묶음 전송과 분산 저장을 쓰고, backpressure를 둔다. backpressure는 뒤 단계가 처리하지 못할 때 앞의 유입을 늦춰 전체 붕괴를 막는 제어다.
- 캐시 무효화: 항목 삭제·숨김·안전 상태는 TTL을 기다리지 않고 상태 버전이나 무효화 이벤트로 제거한다. 최근 개인화 특성은 짧은 지연을 허용해 비용을 낮춘다.
평균 지연만 보면 일부 사용자가 겪는 긴 대기를 숨긴다. 후보원마다 시간 예산을 나누고 p95·p99, 타임아웃과 대체 피드 비율을 함께 본다.
사용자가 먼저 알아채는 여섯 가지 어긋남
p95와 p99는 각각 요청의 95%, 99%가 그 시간 안에 끝났음을 나타내는 지연 경계다. 평균에 가려진 느린 사용자를 찾고 후보원별 시간 예산을 정하기 위해 함께 본다.
| 사용자가 보는 문제 | 감지 | 즉시 완화 | 복구 | 데이터 일치 확인 |
|---|---|---|---|---|
| 피드가 늦거나 빈 화면 | 단계별 p95·p99, 타임아웃, 후보 수 | 느린 후보원을 시간 예산에서 빼고 캐시·인기 피드를 반환 | 작은 트래픽부터 후보원을 복원 | 요청별 후보 수와 오류율 대조 |
| 같은 항목이 반복됨 | 요청·세션 중복률, 후보원 중복 비율 | item_id 중복 제거와 최근 노출 필터 |
커서·캐시 키·병합 오류 수정 | 영향 세션을 다시 계산 |
| 숨긴 항목이 다시 보임 | 숨김 뒤 재노출률 | 응답 직전 강한 제외 저장소 재조회 | 이벤트 지연과 캐시 무효화 보정 | 삭제·차단 전파 완료 확인 |
| 오래된 관심사만 보임 | 특성 나이, 최근 행동 반영 지연 | 세션 특성이나 최신·인기 후보 비중 증가 | 스트림 지연 해소 뒤 발생 시각 순 재처리 | 온라인·오프라인 특성 비교 |
| 품질이 갑자기 나빠짐 | 점수·특성 분포, 온라인 보호 지표 | 직전 모델로 복귀하고 탐색 중단 | 수집 변경과 선호 변화 분리 | 늦은 사용자 결과로 재평가 |
| 삭제된 항목이 보임 | 카탈로그 상태와 노출 로그 대조 | 응답 직전 상태 필터와 캐시 제거 | 후보 인덱스와 사전 계산 결과 갱신 | 모든 복제본에서 삭제 확인 |
재시도만 늘리면 이미 느린 의존성에 부하를 더할 수 있다. 전체 요청 마감 시간 안에서 재시도 간격을 점차 늘리는 지수 백오프와 횟수 상한을 둔다. 복구가 끝났다는 판단도 서버가 살아났다는 신호가 아니라 사용자 요청과 저장 데이터가 다시 맞는지까지 확인한 뒤 내린다.
정확도를 더 사지 않는 선택
개인정보와 비용은 빠른 경로의 범위를 제한한다
이벤트 API는 다른 사용자의 request_id를 제출하지 못하도록 소유 관계를 확인한다. 허용한 이벤트 종류와 시각 범위만 받고, 외부 item_id와 내부 사용자 키를 분리한다. 분석 저장소에는 가명 키를 쓰고 전송·저장 암호화, 최소 권한, 접근 기록과 삭제 요청 전파를 둔다.
상세 행동은 민감한 관심사를 드러낼 수 있다. 목적별 보존 기간을 정하고 원시 데이터가 필요 없어진 뒤 집계값으로 바꾼다. 유럽연합 일반개인정보보호법(GDPR, General Data Protection Regulation) 제5조의 목적 제한, 데이터 최소화, 정확성과 보존 제한은 이 설계 원칙의 근거다. 실제 의무는 관할과 처리 목적에 따라 달라지므로 법률 자문으로 해석해서는 안 된다.EUR-Lex GDPR Article 5
주요 비용은 벡터 인덱스의 메모리와 복제, 온라인 특성 조회, 랭킹 추론, 원시 이벤트 보존, 모델 학습과 실험에서 나온다. 모든 신호를 실시간으로 만들지 않고 숨김과 세션 특성만 빠른 길에 두면 비용과 복잡도를 줄일 수 있다. 비용은 결과·인기 캐시, 후보 수 상한, 특성 묶음 조회, 이벤트 압축과 보존 계층화 순으로 줄이되 사용자군별 정확도 손실을 함께 측정한다.
즉시성·정확도·비용을 모두 최대화할 수는 없다. 사용자 행동이 최근 상태를 얼마나 빨리 바꿔야 하는지에 따라 다음 대안을 고른다.
| 선택 | 장점 | 단점 | 채택 조건 |
|---|---|---|---|
| 여러 후보원 병합 | 신규 사용자와 일부 장애에 강하다. | 병합과 할당량 운영이 복잡하다. | 후보 원인이 서로 다르고 대체 경로가 필요할 때 |
| 단일 거대 모델 | 겉으로 보이는 경로가 단순하다. | 전체 항목 정밀 계산 비용과 장애 범위가 크다. | 항목과 트래픽이 작아 전수 계산이 가능할 때 |
| 사전 계산 후보 | 빠르고 싸다. | 최근 세션을 늦게 반영한다. | 인기·장기 선호가 안정적일 때 |
| 온라인 근사 검색 | 최신 사용자 표현을 반영한다. | 인덱스 갱신, 메모리, 검색 누락과 지연을 맞바꾼다. | 항목이 커지고 최근 행동 가치가 높을 때 |
로그부터 드리프트 운영까지 확장한다
- 초기: 지역·언어별 인기·최신 피드, 명시적 숨김, 노출·클릭 이벤트 형식, 지연과 중복 관측을 만든다. 복잡한 모델보다 실제 노출을 정확히 기록하는 일이 먼저다.
- 개인화 시작: 사전 계산한 협업 후보와 콘텐츠 후보를 섞고 단순 랭커와 결과 캐시를 둔다. 오프라인 평가와 작은 A/B 테스트를 시작한다.
- 트래픽 증가: 근사 벡터 검색, 온라인 특성 저장소, 비동기 스트림 집계와 후보원별 시간 예산을 도입한다. 모델·특성·정책 버전을 추적한다.
- 빠른 피드백: 숨김과 세션 신호를 수 초 안에 반영하고 제한적 탐색과 선택 확률 로그를 추가한다. 이벤트 중복·지연과 노출 편향을 운영 지표로 만든다.
- 성숙 단계: 사용자군별 입력 변화와 사용자 성과를 함께 감시한다. shadow→canary→A/B 승격과 자동 복귀를 갖추되 배포는 품질·안전 관문을 통과하게 한다.
숨김은 즉시 경로로 보내고 선호 변화는 검증 경로로 보낸다. 여러 후보원, 공통 랭커, 정책 재정렬, 짧은 캐시와 온라인·오프라인 특성 경계는 이 선택을 구현한다. 전 항목을 하나의 정밀 모델로 매번 평가하는 방식보다 비용과 장애를 나누기 쉽고, 배치 추천만 쓰는 방식보다 최근 행동을 빠르게 반영한다. 그 대가로 특성 시점, 노출 편향, 후보 병합과 여러 버전을 함께 운영해야 한다.
실제 지연 예산, 드리프트 임계값, 탐색 허용 범위와 장기 만족 기준은 실제 트래픽·사용자 연구·온라인 실험으로 보정해야 한다. 시작점은 숫자 세 개를 임의로 고르는 일이 아니다. 자신의 서비스에서 최근 추천 한 건을 골라 응답에 쓰인 모델·특징 시각·실제 노출·후속 행동이 하나의 request_id로 이어지는지 확인하는 일이다.