미리캔버스 백엔드팀이 AI 스타일 검색에 시맨틱 벡터를 더한 듀얼 벡터 검색을 설계하고, 그 부하를 견디기 위해 OpenSearch를 3.3으로 올린 과정을 담은 2편이다. 1편의 키워드 선필터 구조는 유저픽 키워드가 없으면 결과가 아예 비는 한계가 있었다. SigLIP 시맨틱 벡터를 더해 채택률을 9%p 끌어올렸지만 이번엔 IOPS가 터졌고, 원인은 쿼리 연산이 아니라 _source에 쌓인 벡터가 OS 페이지 캐시를 밀어낸 것이었다.
핵심 포인트- 기존 구조는 사용자 채택 이력인 유저픽으로 후보군을 만들어서, 등록되지 않은 롱테일 검색어는 후보군 자체가 비었다
- rescore 전략은 1차 후보군 품질에 종속되고 window_size 상한 10,000과 min_score의 radial search 전환 때문에 폐기했다
- 두 벡터를 한 쿼리에서 동시에 탐색해도 디스크 I/O는 rescore와 비슷했다. kNN 그래프 탐색이 off-heap 네이티브 메모리에서 일어나기 때문이다
- 두 모델의 임베딩 공간이 달라 스코어 분포 데이터를 받아 수동 boost로 스케일을 맞췄다
- IOPS와 레이턴시는 급증하는데 CPU는 그대로인 패턴이 쿼리 부하가 아니라 메모리 부족을 가리켰다
- OpenSearch 3.x의 derived source가 _source 비대화 문제를 정확히 겨냥해, 버전업 후 IOPS throttling이 완전히 사라졌다
상세 정리- 1편 성과: Efficient KNN Filtering 도입으로 채택률이 20%p 오르고 레이턴시가 81ms에서 14ms로 개선됐다
- 남은 한계: 유저픽에 없는 검색어는 결과가 없고, 등록된 키워드도 품질이 고르지 않아 노이즈가 남았다
- 모델 선택: 이미지와 텍스트를 하나의 임베딩 공간에 정렬하는 SigLIP으로 키워드 등록 없이 의미만으로 관련 요소를 찾게 했다
- rescore 폐기 이유: 1차 후보군에 어울리는 요소가 적었고, window_size를 키우면 레이턴시가 급증하면서 관련성 낮은 문서가 더 섞였고, min_score는 결과 수를 예측할 수 없었다
- 3단계 구조: 키워드 선필터로 4,000만 건의 탐색 범위를 좁히고 시맨틱 벡터로 후보군을 정제한 뒤 비주얼 벡터로 스타일 정렬한다
- A/B 결과: 유저픽 후보군을 SigLIP 시맨틱 후보군으로 대체한 실험군이 유료 사용자 채택률에서 약 9%p 높았다
- UX 결합: 템플릿 채택 시점에 플로팅 팝업으로 추천하니 기존 필터 UI 대비 사용자가 약 5배 늘고 직접 검색 채택률이 45.09%에서 62.47%로 올랐다. 전체 배포 후에는 77.39%까지, 현재는 79% 수준을 유지한다
- 진단 과정: kNN stats의 graph_memory_usage는 16%로 한도 이내였고 서킷 브레이커도 미발동이었는데, OS 지표를 보니 모든 노드 메모리 사용률이 99%에 swap까지 끌어 쓰고 있었다
- 스래싱 메커니즘: query phase가 읽는 inverted index와 fetch phase가 읽는 stored fields가 부족한 페이지 캐시를 서로 밀어내며 매 쿼리마다 디스크를 탔다
- 인스턴스 전략: CPU는 평균 30%대로 여유가 있어 m7g.4xlarge 8대를 r7g.2xlarge 12대로 바꿔 총 메모리를 512GB에서 768GB로 늘리면서 월 비용은 거의 그대로 뒀다
- 스펙 변경 효과: IOPS 최대치가 5,130에서 3,410으로 34%, 검색 레이턴시가 18.2ms에서 14.4ms로 21% 개선됐다
- 남은 문제: 새벽 일괄 업데이트 배치 직후 세그먼트 병합이 폭발했고 _forcemerge와 reindex, refresh_interval 조정 모두 효과가 미미했다
- 3.3 변경점: derived source, kNN concurrent segment search 기본 활성화, floor_segment 2MB에서 16MB로와 maxMergeAtOnce 10에서 30으로 상향, Lucene 10.3 엔진 개선이 겹친다
- 사전 검증: 프로덕션 서브셋으로 세그먼트 수를 152개로 정규화해 비교해도 CPU 40%, 검색 레이턴시 60% 개선이 확인됐다
- 마이그레이션: rolling upgrade는 분석기 호환 문제로 클러스터가 red가 된 경험이 있어 배제하고, 신규 클러스터를 세워 write 중단, 스냅샷 복원, 2.17에서 2.19를 거쳐 3.3까지 업그레이드, 리인덱싱, 엔드포인트 스위칭 순으로 하루 안에 마쳤다
- 버전업 결과: 평균 레이턴시 59%, 최대 레이턴시 85%가 개선돼 MAX가 175ms에서 26.7ms로 줄었고 새벽 배치 후 IOPS 급등도 크게 완화됐다
왜 읽나벡터 검색을 프로덕션에 얹을 때 쿼리 설계보다 메모리·세그먼트·스토리지가 먼저 무너진다는 것을 지표와 함께 확인할 수 있다.