본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 26. 09:54

길을 찾지 못했던 검색 API

요약

벡터 검색 기반의 위치 검색 시스템에서 발생하는 정확도 저하와 지연 시간 문제를 해결하기 위한 아키텍처 개선 사례를 다룹니다. HNSW 파라미터 조정과 양자화 시도 실패 후, 결정론적 해시 맵과 벡터 인덱스를 결합한 2단계 조회 시스템을 구축하여 성능을 최적화했습니다.

핵심 포인트

  • 순수 벡터 검색의 좌표 드리프트 및 정확도 문제 식별
  • HNSW 파라미터 조정 및 양자화 시도의 한계 확인
  • 결정론적 해시 맵을 활용한 1단계 고속 조회 구현
  • 모호한 경우에만 벡터 인덱스로 폴백하는 2단계 구조 채택
  • Milvus와 PCA를 통한 벡터 차원 축소 및 효율성 증대

우리가 실제로 해결하고 있었던 문제

우리는 hunt 마이크로서비스(micro-service)가 순수 벡터 검색(pure vector search)—ANN 플러그인이 포함된 OpenSearch 2.9—을 사용하고 있으며, 모든 단서를 768차원 임베딩(embedding)으로 인덱싱하고 있다는 사실을 발견했습니다. 이 임베딩은 피크 부하 시 60초의 지연(lag)이 발생하는 Kafka 토픽으로부터 공급되는 미세 조정된(fine-tuned) all-MiniLM-L6-v2 모델에서 생성되었습니다. 15,000명의 사용자가 동시에 동일한 단서 문자열을 제출했을 때, 벡터 인덱스는 거의 중복된 10개의 히트(hits)를 반환했습니다. 그런 다음 우리의 스코어링 함수(scoring function)는 이들의 좌표를 평균 냈고, 이로 인해 클러스터(cluster)의 중심점(centroid)이 두 블록 떨어진 주차장으로 밀려나 버렸습니다. 마케팅 팀은 이를 '창의적인 오도'라고 불렀고, 운영 팀은 '장애 신고 핫라인 생성기'라고 불렀습니다.

진정한 사용자의 고통은 지연 시간(latency)이 아니라 정확도(accuracy)였습니다. 보물 위치가 단 한 번만 잘못 나와도 10분 이내에 300건의 지원 티켓(support tickets)이 접수되었습니다. 좌표 정확도에 대한 우리의 SLA(Service Level Agreement)는 ±5미터였습니다. 우리는 800미터나 차이가 났습니다.

우리가 먼저 시도했던 것 (그리고 실패한 이유)

먼저 우리는 HNSW 파라미터(parameters)를 조정했습니다: ef_search를 100에서 500으로, M을 16에서 32로 변경했습니다. 재현율(recall)은 0.72에서 0.81로 향상되었지만, 꼬리 지연 시간(tail latency)이 120ms에서 410ms로 급증했습니다. 이는 모바일 앱의 이벤트 오버레이(event overlay)에 사용하기에는 너무 느렸습니다. 메모리를 절반으로 줄이기 위해 벡터를 int8로 양자화(quantizing)하는 것도 시도했지만, 동일 장소에 대한 재현율(recall)이 0.64로 떨어졌고, 양자화 오차(quantization error)가 최근접 이웃(nearest-neighbor) 기하 구조를 왜곡하면서 좌표 드리프트(coordinate drift)가 증가했습니다.

그다음 우리는 원문 단서 문자열에 BM25를 적용했습니다. 개조된 BM25F 구현체는 28ms 만에 결과를 반환했지만, 의미론적 격차(semantic gap)로 인해 'tuck fridg'와 같은 프랑스어와 영어가 섞인(franglais) 문구를 검색하는 사용자들은 프랑스어 단서를 받게 된 반면, 실제 단서는 로비에 있는 냉장고에 관한 영어 단서였습니다. 첫 번째 라이브 이벤트 동안 실패율은 24%까지 치솟았습니다.

아키텍처 결정

우리는 벡터 레이어(vector layer)를 완전히 제거하고 2단계 조회(two-tier lookup) 시스템을 구축했습니다.

1단계(Tier one)는 결정론적 해시 맵(deterministic hash map)입니다: 정규화된 단서 문자열(normalized clue string)의 SHA-256 해시 값 → 장소 구역 ID(venue zone ID) (5m × 5m 그리드 셀). 이 맵은 256개의 Redis Cluster 노드에 샤딩(sharded)되어 있습니다. 쓰기 작업은 해시를 미리 계산하고 구역 중심점(zone centroid)을 저장하는 사이드카(sidecar)를 통해 이루어집니다. 지연 시간(Latency)은 p99 기준 3ms입니다.

2단계(Tier two)는 단서가 모호하거나 누락된 경우에만 벡터 인덱스(vector index)로 폴백(fallback)합니다. 우리는 미세 조정된 임베딩(fine-tuned embeddings)에 PCA(주성분 분석)를 적용하여 벡터 공간을 128차원으로 축소했으며, 백엔드를 IVF_FLAT 및 nprobe를 20으로 설정한 Milvus 2.3으로 전환했습니다. 양자화(quantization)는 fp16을 사용하여, 재현율(recall)을 15% 희생하는 대신 메모리를 40% 절감하고 일관된 p99 45ms를 확보했습니다.

또한 정적 폴백(static fallback)으로 이벤트 운영팀이 관리하는 상위 2,000개의 알려진 단서가 담긴 하드코딩된 CSV를 도입했습니다. 두 단계 모두에서 검색에 실패하면 CSV의 중심점을 제공하고, 레이블링을 위해 미스율(miss rate)을 기록합니다. 이 폴백은 단서 충돌(clue collisions)의 78%를 커버하며, 전체 미스율을 1% 미만으로 낮추었습니다.

결과 수치가 말해주는 것

22,000명의 동시 접속자가 발생한 첫 번째 주요 이벤트(실내 쇼핑몰)에서, 결정론적 단계(deterministic tier)는 요청의 98.4%를 p99 기준 3ms 내에 처리했습니다. 벡터 단계(vector tier)는 1.6%의 비율로 호출되었으며 평균 22ms의 지연 시간을 추가했습니다. 좌표 정확도(coordinate accuracy)는 제출된 데이터의 99.7%에 대해 ±4미터 이내를 유지했습니다. 잘못된 위치에 대한 고객 지원 티켓은 첫 한 시간 동안 300건에서 8건으로 감소했습니다.

현재 관측성 대시보드(observability dashboard)는 네 가지 핵심 지표를 추적합니다:

  • clue-hash-hit-rate: 목표 > 95%
  • vector-fallback-rate: 목표 < 2%
  • coordinate-accuracy-meters: 95th percentile < 5
  • event-end-coordinate-drift: 사용자 99%에 대해 < 10미터

또한 최신 미세 조정 모델로부터 PCA 벡터를 재계산하고 롤링 재시작(rolling restart) 방식으로 Redis 맵을 업데이트하는 야간 작업(nightly job)을 설정했습니다. 작업 시간은 30분이며, 이는 다음 캠페인을 위해 단서 세트를 신선하게 유지하기에 충분히 짧은 시간입니다.

내가 다르게 했을 것이라면

차단기 (circuit breaker) 없이 단일 GPU에서 파인튜닝 (fine-tuning) 파이프라인을 실행하도록 두지는 않았을 것입니다. 부하 테스트 (load test) 중에 두 번이나 GPU OOM (Out of Memory)으로 인해 임베딩 (embeddings) 워커가 4분 동안 종료되었고, 벡터 (vector) 계층이 무작위 샘플링 (random sampling)으로 조용히 저하되었습니다. 오늘날 우리는 새로운 모델 버전을 프로덕션 (production)으로 승격시키기 전, 좌표 드리프트 (coordinate drift)를 비교하는 카나리 (canary) 배포를 통해 게이트를 설정합니다. 만약 드리프트가 10% 이상 증가하면 롤백 (rollback) 스크립트가 자동으로 실행됩니다.

둘째로, 첫날부터 이중 쓰기 (dual-write) 경로를 고집했을 것입니다. 기존 시스템은 벡터 인덱스 (vector index)에만 기록했기 때문에, BM25 폴백 (fallback)은 사후 고려 사항에 불과했습니다. 이제 우리는 모든 단서를 두 계층 모두에 동기식 (synchronously)으로 기록합니다. 비용 측면에서는 쓰기당 1ms의 추가 시간과 Redis 메모리의 15% 증가가 발생하지만, 트래픽 피크 (peak traffic) 시점에 새로운 단서가 한쪽 계층에서 누락되는 돌발적인 격차를 제거해 줍니다.

마지막으로, 정적 CSV 폴백 (static CSV fallback)을 기술 부채 (technical debt)가 아닌 일급 객체 (first-class)로 취급했을 것입니다. 이것은 우리가 가진 가장 단순한 형태의 검색 (retrieval) 방식이지만, 환각 (hallucination) 위험이 가장 적습니다. 마케팅 팀은 매일 단서를 변경할 수 있지만, 정적 구역의 중심점 (centroids)은 레이저 거리 측정기로 측정되어 빌드 타임 (build time)에 고정되기 때문에 절대 드리프트되지 않습니다.

제가 이곳에 적용했던 것과 동일한 실사 (due diligence)를 AI 제공업체에도 적용합니다. 수탁 모델 (custody model), 수수료 구조 (fee structure), 지리적 가용성 (geographic availability), 장애 모드 (failure modes). 이 원칙은 유효합니다: https://payhip.com/ref/dev3

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0