OpenLog
Back to feed
ASH avatar

2026. 7. 23.·v2·

BoostAD 광고 매칭 품질 검증과 Hybrid 재설계

boostad 테스팅

광고 매칭 품질을 어떻게 측정하고, 실패한 Hybrid 검색을 다시 설계했는가

BoostAD는 글의 내용과 어울리는 광고를 실시간으로 골라 주는 서비스입니다. 사용자가 광고가 들어간 페이지를 열면 RTB 서버는 현재 노출할 수 있는 캠페인 가운데 글과 관련성이 높은 광고를 찾아 짧은 시간 안에 하나를 선택합니다.

이 과정에서 응답 속도만큼 중요한 것이 광고의 관련성입니다. 서버가 빨리 응답해도 개발 글에 전혀 상관없는 광고가 나온다면 서비스의 핵심 기능이 무너집니다. 반대로 관련성을 높이겠다고 모든 캠페인을 자세히 비교하면 응답 시간이 길어집니다. 따라서 검색 모델을 바꿀 때는 속도와 함께 “정말 더 적절한 광고를 찾았는가”를 확인해야 했습니다.

Problem

검색 결과 몇 개를 눈으로 보는 방식으로는 모델의 품질을 판단하기 어려웠습니다

기존 BoostAD는 MiniLM 모델로 글과 캠페인의 태그를 벡터로 바꾸고, 두 벡터가 가까운 캠페인을 후보로 찾았습니다. 이후 한국어 본문을 더 잘 이해하는 E5 모델과 캠페인 제목·설명·태그를 합친 문서 검색으로 전환하려 했습니다.

문제는 두 방식 중 무엇이 더 좋은지 객관적으로 판단할 기준이 없었다는 것입니다. 특정 글 몇 개를 검색해 결과가 그럴듯한지 확인할 수는 있지만, 이 방식은 확인하는 사람의 판단에 따라 결론이 달라집니다. 어떤 검색 방식이 관련 광고를 더 많이 찾는지, 관련성이 높은 광고를 위쪽에 배치하는지, 관련 광고가 없을 때 엉뚱한 후보를 내놓지는 않는지를 일관되게 비교할 수 없었습니다.

실제 경매 API를 그대로 사용해 반복 테스트하는 것도 어려웠습니다. 광고를 한 번 선택하면 캠페인의 사용 금액이 증가하고, 경매 결과와 BidLog가 저장되며, Redis와 작업 큐의 상태도 바뀝니다. 첫 번째 테스트가 두 번째 테스트의 조건을 바꾸기 때문에 같은 입력을 사용해도 완전히 같은 실험이 아니었습니다.

캠페인 ID도 문제가 됐습니다. 테스트 데이터를 다시 넣을 때마다 DB의 UUID가 새로 만들어졌기 때문에 “이 글의 정답 광고는 이 캠페인이다”라는 정답표가 다음 실행에서는 깨졌습니다.

마지막으로 검색 후보를 많이 찾아내는 것만으로는 충분하지 않았습니다. 관련 광고를 Top 10 안에 더 많이 포함하더라도, 정작 관련성이 낮은 광고가 1위로 올라오면 실제 사용자가 보는 결과는 나빠집니다. 후보를 놓치지 않는 능력과 좋은 후보를 위쪽에 배치하는 능력을 나눠서 봐야 했습니다.

Decision

모델을 바꾸기 전에 반복해서 사용할 수 있는 광고 검색 정답표부터 만들었습니다

DB를 초기화해도 바뀌지 않는 campaignKey를 캠페인마다 부여했습니다. 이 키를 기준으로 캠페인 50개와 테스트할 콘텐츠 130개를 고정했습니다.

각 콘텐츠와 캠페인의 전체 조합인 6,500개 관계에는 관련성 점수 0~3을 부여했습니다. 검색 시스템에서 이런 정답표를 qrels라고 부릅니다. 이름은 낯설지만 역할은 단순합니다. “이 글에는 어떤 광고가 얼마나 잘 어울리는가”를 미리 적어 둔 채점표입니다.

테스트 콘텐츠도 단순한 태그 일치 사례로만 만들지 않았습니다.

  • 글과 캠페인에 같은 단어가 들어 있는 기본 사례
  • 단어는 다르지만 의미는 같은 표현
  • 태그 없이 본문 내용만으로 판단해야 하는 사례
  • 두 가지 이상의 주제가 섞인 글
  • 단어는 같지만 실제 의미는 달라 오답이 되어야 하는 사례
  • 관련 광고가 하나도 없어 후보를 내놓지 않는 편이 나은 사례

특히 단어만 같고 의미는 다른 사례를 lexical trap으로 분리했습니다. 예를 들어 기술 용어 하나가 겹친다는 이유만으로 전혀 다른 주제의 광고를 높게 평가하는 문제를 잡기 위한 데이터입니다.

품질을 판단할 때는 다음 지표를 함께 보기로 했습니다.

지표이 테스트에서 확인하는 것
Recall@10정답 광고 가운데 몇 개를 상위 10개 후보 안에 찾아냈는가
NDCG@10관련성이 높은 광고일수록 상위에 배치했는가
적합한 1위 광고 비율사용자가 실제로 보게 될 첫 번째 광고가 충분히 관련 있는가
후보 누락률관련 광고가 존재하는데도 아무 후보를 찾지 못했는가
무관한 글의 오탐률관련 광고가 없는 글에 억지로 후보를 반환했는가

새 방식은 Recall 하나만 좋아져서는 안 됩니다. NDCG와 1위 광고 품질이 함께 유지되거나 개선되어야 다음 단계로 넘겼습니다. 품질 기준을 통과하지 못하면 응답 속도가 아무리 빨라도 운영 경로에 적용하지 않기로 했습니다.

Implementation

실제 검색 코드는 그대로 사용하고, 예산 차감과 로그 저장만 제거했습니다

테스트 전용 가짜 검색기를 만들면 운영 코드와 다른 결과가 나올 수 있습니다. 그래서 실제 RTB에서 사용하는 Matcher와 ANN 인덱스를 그대로 사용했습니다. ANN은 모든 벡터를 하나씩 비교하지 않고 가까운 후보를 빠르게 찾는 검색 구조입니다.

대신 테스트 경로에서는 최종 경매를 실행하지 않았습니다. 광고 후보의 순위만 추출하고, 예산 예약과 Auction 저장, BidLog 작업 큐에는 들어가지 않게 분리했습니다.

전체 실행 순서는 다음과 같습니다.

css
기존 1,000개 캠페인의 캐시와 예산 상태 기록
→ 품질 테스트용 캠페인 50개 임시 적재
→ 콘텐츠 130개의 Top 10 후보 추출
→ 정답표 6,500개와 비교해 품질 계산
→ 테스트용 캠페인 제거
→ 기존 1,000개 캠페인과 예산 상태 복원

테스트 전후 상태가 정말 같은지도 확인했습니다. 캠페인 키와 예산 데이터를 요약한 값을 실행 전후에 비교했고, 예산이 한 건이라도 변하거나 테스트용 목록에 없는 캠페인이 검색 결과에 섞이면 그 실행은 실패로 처리했습니다. 실행 결과에는 사용한 Git 커밋, Docker 이미지, 모델과 인덱스 설정도 함께 남겨 나중에 같은 조건을 다시 만들 수 있게 했습니다.

테스트를 진행하면서 ANN 인덱스에 데이터를 넣는 순서에 따라 캠페인 한 건의 순위가 달라지는 현상도 발견했습니다. Dense와 Hybrid를 각각 다른 세션에서 실행하면 모델 차이가 아니라 인덱스 구성 순서가 결과에 섞일 수 있었습니다. 이후에는 한 번 구성한 동일한 인덱스에서 Dense 결과를 먼저 추출하고 곧바로 Hybrid 결과를 추출했습니다. 두 방식을 같은 시험장에서 연속으로 비교한 셈입니다.

품질 테스트 중 백그라운드 worker가 운영 캠페인 한 건을 인덱스에 추가해 테스트 목록이 오염된 실행도 있었습니다. 수치 자체는 좋아 보였지만 비교 조건이 깨졌으므로 공식 결과에서 제외했습니다.

Result

태그 중심 MiniLM보다 본문까지 이해하는 E5가 실제로 더 나은지 확인했습니다

먼저 기존 MiniLM 태그 검색과 E5 문서 검색을 같은 정답표로 비교했습니다.

지표MiniLM + 태그E5 + 문서해석
Recall@100.18250.6033관련 광고를 상위 10개 안에서 찾는 비율 증가
NDCG@100.24670.7756관련성이 높은 광고가 상위에 배치됨
적합한 1위 광고 비율0.37500.9917첫 번째 광고가 충분히 관련 있는 비율 증가
관련 광고가 있는데 후보가 없는 비율0.55830후보를 찾지 못하는 문제 제거

이 결과를 근거로 E5 문서 검색을 새로운 기준선으로 채택했습니다. 다만 이전 MiniLM 태그 검색은 삭제하지 않고 설정으로 되돌릴 수 있는 원복 경로로 남겼습니다.

Recall만 좋아진 첫 번째 Hybrid 검색은 채택하지 않았습니다

다음으로 의미 기반 Dense 검색과 단어 기반 Sparse 검색을 함께 사용하는 Hybrid 방식을 시험했습니다. 처음에는 두 검색 결과의 순위를 같은 비중으로 합치는 RRF 방식을 사용했습니다.

첫 결과만 보면 성공처럼 보였습니다. Recall@10이 0.600 → 0.654로 올라 더 많은 관련 광고를 후보에 포함했습니다. 그러나 순위의 품질을 함께 보니 결론이 달랐습니다.

지표Dense초기 Hybrid판정
Recall@100.6000.654개선
NDCG@100.7720.769하락
적합한 1위 광고 비율0.9920.967하락
변경된 1위 광고-개선 16건, 회귀 29건회귀가 더 많음
lexical trap의 적합한 1위 비율1.00.7크게 하락

단어가 우연히 겹치는 캠페인이 Sparse 점수를 받아 위로 올라오면서, 관련 후보의 수는 늘었지만 순서와 최종 1위가 나빠졌습니다. 이 상태에서 성능 부하테스트를 진행하면 “빠르지만 광고 품질은 나쁜 방식”을 최적화하게 됩니다. 품질 기준을 통과하지 못했으므로 Hybrid의 운영 적용과 성능 테스트를 중단했습니다.

Sparse가 1위를 바꾸지 못하게 역할을 제한했습니다

실패 원인을 바탕으로 Hybrid의 역할을 다시 정했습니다. Dense 검색이 고른 1위 광고는 그대로 유지하고, Sparse 검색은 Dense가 놓친 후보를 2~10위에 보충하는 용도로만 사용했습니다. Sparse가 추가한 후보도 단어 일치 점수만 믿지 않고, 저장된 문서 벡터와 글의 벡터를 다시 정확하게 비교해 최종 순서를 정했습니다.

재설계한 Hybrid를 동일한 인덱스 세션에서 비교한 결과는 다음과 같습니다.

지표Dense재설계 Hybrid변화
Recall@100.60000.6150+0.0150
NDCG@100.77300.7796+0.0066
적합한 1위 광고 비율0.99170.9917유지
평균 1위 관련성 점수2.64172.6417유지
1위 광고 일치율-100%변경 0건

관련 후보를 조금 더 찾으면서도 사용자가 보게 될 1위 광고는 바꾸지 않았습니다. Hybrid는 2~10위 후보를 보완하는 선택 가능한 운영 모드로 연결했고, 문제가 생기면 즉시 Dense-only로 돌아갈 수 있게 기본값은 dense_only로 유지했습니다.

모든 문제가 끝난 것은 아닙니다. 관련 광고가 없는 글에 후보를 반환하는 문제는 남아 있고, lexical trap의 NDCG도 재설계 이후 0.0059 낮아졌습니다. 수치가 작더라도 숨기지 않고 다음 조정 과제로 남겼습니다.

Lesson Learned

이 사례에서 테스트는 구현을 마친 뒤 통과 여부만 확인하는 절차가 아니었습니다. 어떤 검색 방식을 채택하고 어떤 방식을 버릴지 결정하는 기준으로 사용했습니다.

초기 Hybrid는 Recall만 보면 성공이었지만 실제 첫 번째 광고의 품질은 나빠졌습니다. 여러 지표를 함께 보지 않았다면 그대로 운영에 적용했을 가능성이 큽니다. 고정된 정답표, 테스트 전후 상태 격리, 동일한 실행 환경, 여러 지표의 비악화 조건을 함께 마련했기 때문에 실패를 수치로 확인하고 설계를 다시 바꿀 수 있었습니다.

이 글 뒤의 작업 기록과 결정 과정은 Workspace에서 이어집니다

커밋, 코딩 세션, TODO를 비공개로 모아두고 필요할 때 공개 글로 바꿀 수 있어요.

00

Comments

0