← 목차 · 케이스 7
호텔 검색 자동완성 완화 — 조금 다르게 친 이름도 찾게
2026.07 ~ 2026.09 · Elasticsearch · Python · Jenkins · Datadog RUM · ClickHouse · Athena
팀 구성 — 개발 1(본인)
호텔 이름을 조금만 다르게 쳐도 0건이던 자동완성을, 정확히 맞는 결과는 그대로 두고 그 아래에만 비슷한 이름을 붙이게 고쳐 운영에 올렸습니다. 결과가 0건인 요청이 프리비아 15.5% → 0.26%, 투어비스 18.6% → 0.84%로 줄었습니다(글자 입력마다 한 번씩 센 요청 기준).
전면 배포라 A/B 대신 배포 전 색인과 쿼리를 보존해 같은 검색어를 재생해 효과와 회귀를 갈랐고, 대가로 늘어난 응답 시간은 아직 풀지 못했습니다.
이 문서는 배경 → 문제정의 → 해결방안 → 결과 → 정직하게 순서입니다. 같은 검색창의 다른 문제 — "에펠탑 근처"처럼 장소를 찾는 검색 — 는 케이스 2이고, 여기서는 호텔 이름 매칭만 다룹니다.
배경 — 이름을 조금만 다르게 치면 0건
프리비아·투어비스 호텔 검색창은 한 글자 칠 때마다 자동완성 후보를 띄웁니다. 그런데 이름을 조금만 다르게 치면 후보가 하나도 뜨지 않았습니다.
"소노벨 단양 호텔" → 0건 실제 이름에 "호텔"이 없다
"경주라한셀렉트" → 0건 실제 이름은 "라한셀렉트 경주"
"더블유 방콕" → 0건 실제 이름은 "W 방콕"
0건을 본 세션의 28%는 거기서 검색을 그만뒀습니다(2026-07 프리비아 브라우저 로그 보름, 세션 단위). 2026년 7월 조직 미션이 호텔 검색 품질이었고, 이 0건부터 잡았습니다.
전제가 하나 있었습니다. 자동완성 색인을 만드는 작업이 다른 조직 저장소에 있어 색인을 직접 바꿀 수 없었습니다. 먼저 그 작업을 우리 저장소로 옮기고, 기존 산출물 592,955건과 결과가 같은지 대조한 뒤 2026년 8월에 전환했습니다. 그 뒤에야 색인을 손댈 수 있었습니다.
문제정의 — 왜 어려운가
호텔 이름을 조금만 다르게 쳐도 자동완성이 0건이 되는데, 매칭 규칙을 느슨하게 풀면 정확히 친 검색어의 결과가 흔들립니다. 게다가 전 채널에 한 번에 나가는 변경이라, 좋아진 것과 망가진 것을 떼어 볼 비교군이 없습니다. 셋으로 나눴습니다.
① 0건은 왜 나고 어디서 나나
- 제보 한 건을 재현하면 될 것 같지만 조건이 잡히지 않습니다.
라한셀렉트경주는 나오는데경주라한셀렉트는 안 나옵니다. 어순 탓으로 보기 쉽지만, 어순만 바꾼 검색어는 거의 다 나옵니다. - 결과가 0건인 요청의 비율은 지표로 잡혀 있지 않았습니다. 얼마나 흔한지, 어떤 모양의 검색어에서 나는지부터 몰랐습니다.
② 결과를 늘리면서 정확히 맞는 결과를 어떻게 지킬까
- 자동완성은 1위가 사실상 전부입니다. 정확히 친 검색어의 순서가 흔들리면 고치기 전보다 나빠집니다.
- 규칙을 느슨하게 푸는 흔한 방법은 이 조건을 못 지킵니다. 단어 일부만 맞아도 남기면
서울하얏트호텔의 1위가 서울신라호텔이 되고, 군더더기 단어를 떼면대마도가대마가 되어 결과가 3건에서 9,083건으로 늘어납니다.
③ 전면 배포에서 효과와 회귀를 어떻게 가를까
- A/B를 할 수 없습니다. 쿼리는 채널 전체에서 한 번에 바뀝니다.
- 색인을 새로 만들기만 해도 점수가 같은 결과끼리 순서가 바뀝니다. 그대로 비교하면 쿼리와 무관한 순서 변화가 회귀처럼 보입니다.
- 브라우저 로그는 2주면 사라져 배포 전 기준을 나중에 다시 뽑을 수 없고, 채널마다 반영 시각이 달라 배포 직후 표본에는 옛 쿼리 구간이 섞입니다.
해결방안 — 무엇을 했나
전체 그림부터 봅니다. 아래는 지금 운영에서 도는 모습입니다. 어떻게 이 모양이 됐는지는 ①②③에서 다룹니다.
- 정확히 맞는 결과의 규칙은 건드리지 않았습니다. 모든 단어가 맞아야 남는 규칙을 푼 것이 아니라, 그 아래에 격리된 단계를 하나 붙였습니다(→ ②).
- 두 단계는 매 요청마다 함께 계산됩니다. 비슷한 이름이 필요한 요청은 일부인데 비용은 전부가 냅니다. 응답 시간이 늘어난 이유이고, 아직 풀지 못했습니다(→ 정직하게).
- 색인이 먼저, 쿼리가 나중에 나가야 합니다. 순서가 뒤집히면 오류 없이 결과만 조용히 비어 있습니다.
① 0건 제보를 하나씩 재현하는 대신 실사용 검색어를 재생하고 전 호텔 이름을 변형해 던져, 0건이 되는 조건을 세어 원인을 매칭 규칙으로 좁혔습니다
제보를 하나씩 보지 않고 분포로 셌습니다. 서버 액세스 로그 8일치의 실사용 검색어를 운영과 같은 설정의 로컬 색인에 재생해 0건 검색어를 모았고(고유 검색어 26,494개 — 타이핑 중간 상태 포함), 호텔 55만 곳의 이름을 단어 추가·조사·붙여쓰기·어순 교체 같은 방식으로 변형한 539만 개 검색어를 던져 어떤 차이에서 0건이 되는지 셌습니다.
원인은 규칙이었습니다. 검색어의 모든 단어가 어딘가에 있어야만 결과로 남는 규칙이라, 이름 뒤에 단어 하나만 붙여도 0건이 됩니다(단어 추가 변형 100%). 어순만 바꾼 검색어는 49만여 개 중 2개만 실패했습니다 — 깨지는 것은 붙여쓰기와 어순 교체가 겹칠 때였습니다. 저도 처음엔 어순을 원인으로 적었다가 이 측정으로 고쳤습니다. 같은 스윕에서, 표기 이형을 잡으려고 등록돼 있던 동의어 36세트가 설정 오류로 한 번도 동작하지 않고 있었다는 것도 나왔습니다.
다만 구조의 취약성과 사용자가 실제로 겪는 0건은 달랐습니다. 스윕에서 100% 깨지는 단어 추가는 실사용 0건에서 작은 몫이고, 실사용 0건은 오타와 치다 만 글자 조각이 대부분이었습니다. 단어를 떼는 처리만으로는 이쪽이 풀리지 않습니다(→ ②).
대신 포기한 것 — 착수 속도입니다. 미션을 받고 개발 환경에 첫 변경을 올리기까지 한 달을 측정과 비교에 썼습니다.
② 정확히 일치하는 결과는 기존 규칙 그대로 두고 그 아래에만 자모 조각이 절반 이상 겹치는 이름을 붙이되, 점수 구간을 나눠 비슷한 결과가 정확한 결과를 넘지 못하게 했습니다
점수 구간을 나눴습니다. 정확 일치 단계는 기존 쿼리를 그대로 두고 결과마다 점수를 크게 더했습니다. 실측에서 두 구간 사이 간격이 가장 좁을 때도 8,500점 넘게 벌어져, 인기도 가산이 아무리 붙어도 비슷한 이름이 정확한 결과 위로 오르지 않습니다.
비슷한 이름 단계는 격리했습니다. 이름을 자모 2~3글자 조각으로 풀어 색인하고, 검색어 조각의 절반 이상이 겹치면 후보로 올립니다. 정확 일치로 이미 나온 호텔은 이 단계에서 빼고, 판매 채널 조건도 다시 겁니다 — 처음 만든 원형에는 이 조건이 빠져 있어 다른 채널 상품이 섞일 수 있었습니다.
운영자가 모아 온 별칭을 정확 일치 쪽에 연결했습니다. 운영자가 호텔 463곳에 등록해 둔 별칭 2,870개를 자동완성은 읽지 않고 있었습니다. 더블유 방콕처럼 글자를 하나도 공유하지 않는 별칭은 조각으로도 못 찾기 때문에, 별칭을 색인 필드로 넣어 정확 일치로 걸리게 했습니다. 사내 테스트 담당이 제보한 3건이 두 채널에서 모두 1위로 나왔습니다.
앱 코드는 바꾸지 않았습니다. 색인과 쿼리 템플릿만 바꿨습니다.
대신 포기한 것 — "없는 것은 없다고 말하기"와 응답 시간입니다. 판매하지 않는 호텔을 쳐도 비슷한 이름이 뜹니다(→ 정직하게).
③ A/B를 할 수 없어 배포 전 색인과 쿼리를 보존해 두고, 같은 검색어를 옛 조합과 새 조합에 던져 색인을 새로 만든 효과를 떼어 낸 뒤 쿼리 변경의 효과만 셌습니다
배포 전 색인을 남겼습니다. 배포 전 색인은 보관 정책으로 며칠 뒤 지워질 예정이라, 배포 당일 복제해 비교 기준으로 보존했습니다.
같은 검색어를 세 조합에 던졌습니다.
| 조합 | 쿼리 | 색인 | 둘을 비교하면 |
|---|---|---|---|
| 가 | 옛 | 옛 | — |
| 나 | 옛 | 새 | 가 ↔ 나 = 색인을 새로 만든 효과 |
| 다 | 새 | 새 | 나 ↔ 다 = 쿼리 변경의 효과 |
자주 쓰는 검색어 150개(채널별)에서 1위가 바뀐 3건·6건은 전부 색인을 새로 만든 쪽이었고, 쿼리 변경으로 바뀐 1위는 0건이었습니다. 배포 전 0건이던 실사용 검색어 300개(채널별)는 배포 후 4개·3개만 여전히 0건이었습니다.
라이브 수치는 원천 두 개로 교차했습니다. 브라우저 실측과 서버 액세스 로그를 따로 재 같은 방향인지 봤습니다. 배포 직후 3시간 표본은 채널별 반영 시차가 섞여 한 채널 값이 틀렸고, 27시간으로 늘려 다시 쟀습니다.
대신 포기한 것 — 사용자 행동으로 본 효과입니다. 재생이 보여 주는 것은 "결과가 생겼다"까지이고, 사용자가 원하는 호텔을 골랐는지는 아닙니다.
무엇을 버리고 무엇을 샀나
| 어디 | 버린 것 | 산 것 | 왜 |
|---|---|---|---|
| ① | 착수 속도 — 한 달을 측정에 | 원인을 규칙으로 좁힘 · 어순 가설 기각 | 스윕에서 이름 뒤에 단어 하나만 붙여도 전부 0건이었다 — 제보된 단어만 막아서는 규칙이 그대로 남는다 |
| ② | 단어 일부만 맞아도 남기기 | 정확 일치 규칙 유지 | 오답이 정답 위로 — 서울하얏트호텔의 1위가 서울신라호텔 |
| ② | 오타 허용 검색 | 자모 조각 단계 | 한글 두 글자 이름에서 동작하지 않고, 한 글자 차이면 다낭과 다롄을 같은 거리로 본다 |
| ② | 부분 문자열 전부 매칭 분석기 설정(기존에 검토된 안) | 자모 조각 + 별칭 | 같은 검색어로 비교하니 우연히 겹친 조각으로 엉뚱한 호텔이 올랐고, 긴 입력에서 쿼리가 폭발했다 |
| ② | 군더더기 단어 떼기 — 배포 후 보류 | — | 가드 없이는 대마도 → 대마. 가드를 달고 배포 후 로그로 재니 정상 검색에 끼어드는 것이 고치는 것보다 훨씬 많았다 |
| ② | 없는 것을 없다고 말하기 · 응답 시간 | 0건 해소 | 0건을 본 세션의 28%가 검색을 그만뒀다 |
| ③ | 빠른 판정 — 배포 직후 표본 · 두 조합 비교 | 인과 분리 | 색인 재생성만으로 1위가 3·6건 바뀌었다. 그대로 봤으면 회귀로 적었다 |
결과 — 측정과 지금 상태
| 무엇 | 전 | 후 | 어떻게 쟀나 |
|---|---|---|---|
| 결과가 0건인 자동완성 요청 — 프리비아 | 15.5% | 0.26% | 브라우저 실측, 글자 입력마다 한 번씩 센 요청. 배포 전 14일 vs 배포 후 27시간 |
| 〃 — 투어비스 | 18.6% | 0.84% | 같음 |
| 같은 비율을 서버 로그로 | 17.05% · 18.80% | 0.25% · 0.73% | 서버 액세스 로그 전수, 배포 후 5일 |
| 배포 전 0건이던 실사용 검색어 300개 중 여전히 0건 | 300 · 299 | 4 · 3 | 배포 전 색인·쿼리를 보존해 재생 |
| 자주 쓰는 검색어 150개 중 쿼리 변경으로 1위가 바뀐 것 | — | 0 · 0 | 세 조합 재생 |
| 대가 — 응답 시간 중앙값 | 134 · 121ms | 239 · 234ms | 브라우저 실측 |
| 대가 — 검색엔진 처리 중앙값 | 11ms | 109ms | 검색어 8개 × 3회 최소값(소표본) |
- 운영 중입니다(2026-09-01부터, 두 채널). 색인과 쿼리 모두 지금도 제가 고칩니다.
- 반영 여부는 점수 한 줄로 판별합니다. 정확 일치에 더한 점수 덕에 응답의 첫 점수만 보면 새 쿼리가 도는지 알 수 있습니다.
- 사용자 행동은 판정 전입니다. 자동완성으로 목적지를 고른 비율을 배포 전 14일 기준선 대비 4주 관찰 중입니다.
배포 뒤에도 사용자를 계속 봅니다
0건 비율은 이 프로젝트 전까지 아무 지표에도 없었습니다(→ 문제정의 ①). 배포 뒤에는 "0건이 줄었나"만이 아니라 사용자가 무엇을 치고, 무엇을 고르고, 실패하면 어떻게 하는지를 매일 봅니다. 서버 액세스 로그의 글자 입력과 클릭 로그의 선택을 같은 방문자로 이어, 타이핑을 시작해 선택·검색·포기로 끝나기까지를 한 건("에피소드")으로 묶고 하루 한 번 집계합니다. 제보를 기다리지 않고 제가 먼저 보는 장치입니다.
| 무엇을 보나 | 지표 | 이어지는 일 |
|---|---|---|
| 입력 | 질의 유형 분포 · 급상승 검색어 · 0건인 최종 검색어(지금도 0건인지 재생) | 별칭·사전 등록 백로그 |
| 선택 | 1위 선택률 · 선택 순위 분포 · 뜨는데 안 고르는 검색어 · 검색어 → 선택 후보 쌍 | 순위 눈검수 재료 · 별칭 후보 |
| 실패 뒤 | 0건 뒤 다시 치기 · 도시명으로 후퇴 · 포기 | 후퇴 안내 후보 |
| 계측 자체 | 방문자 조인 회수율(90% 미만이면 그날은 집계에서 뺌) | 숫자를 믿어도 되는지 화면이 말함 |
- 첫 주(9/2~9/8, 프리비아) — 에피소드 23,691건 · 선택 75.1% · 1위 선택 77.3% · 포기 8.2% · 0건 0.1%. 투어비스는 선택 57.4% · 포기 12.3%로 채널 차이가 큽니다.
- 여기서 나온 첫 발견이 지역어입니다.
광안리·영종도·강화도를 친 사람의 절반 가까이가 고르지 않고 떠났습니다(이탈 53%·48%·44%, 9~38건). 원인은 완화가 아니라 원래 도시 문서에 걸리지 않던 지역어였고, 도시 별칭을 색인에 넣는 안을 다시 평가하는 계기가 됐습니다(→ 정직하게). - 자유 입력 검색어는 3건 이상 등장한 것만 화면에 냅니다 — 0건 검색어에 이메일이 실제로 섞여 있었습니다.
정직하게 — 한계와 다음 문제
- 응답이 느려졌고 아직 풀지 못했습니다. 중앙값이 프리비아 +78%·투어비스 +93%로, 정해 둔 기준(+50%)을 넘었습니다. 두 단계를 매 요청마다 계산하기 때문이고, 해법(정확 일치가 0건일 때만 비슷한 이름 단계를 돌리기)은 정했지만 착수 전입니다.
- 없는 것을 없다고 말하지 못합니다. 배포 전 로컬에서 요청이 많은 60건을 판정했을 때 9건(15%)이 오답이었고, 전부 색인에 없는 대상을 친 검색어였습니다. 판정은 에이전트가 했고, 운영 반영본으로는 다시 재지 않았습니다. 이 수준을 제품이 받아들일지는 결정을 받지 못했습니다.
- 0건이 줄어든 것과 원하는 호텔을 찾은 것은 다릅니다. 배포 전 0건이던 실사용 검색어 592개를 배포 후 색인에 재생해 판정하니(판정은 에이전트가 했고, 층화 표본 60건을 제가 눈으로 확인해 동의했습니다 — 불일치율은 내지 않았습니다), 판정할 수 있는 486개 중 찾던 호텔이 1위인 것은 252개(51.9%)였고 120개(24.7%)는 2~5위에 밀려 있었습니다. 밀린 원인의 절반 가까이는 인기도 가산이었습니다. 처방 세 계열(길이 보정 끄기·단어 단위 채점·자모 관용 넓히기)을 같은 판정 세트로 재니 전부 고친 것보다 망가뜨린 것이 많았고(길이 보정 끄기는 28건 해결·87건 회귀), 완화 구간에서만 인기도를 줄이는 것만 +5로 겨우 양수라 운영에 넣지 않았습니다.
- 설계 권고와 결정이 달랐습니다. 제 비교 보고서는 군더더기 단어 떼기를 먼저 하자고 권고했지만 두 가지를 함께 가기로 했고, 배포 후 로그로 재 보니 단어 떼기는 고치는 것이 없어 보류했습니다. 결과적으로 자모 조각 쪽만 나갔습니다.
- 별칭의 효과 측정은 순환적입니다. 넣은 별칭으로 검색해 회수가 71%에서 98%로 오른 것은 당연한 결과입니다. 의미 있는 증거는 따로 들어온 제보 3건입니다.
- 예약 전환은 재지 않았습니다. 사용자 행동 지표도 판정 전입니다.
- 이 방식으로 안 풀리는 것이 있습니다.
광안리·강화도처럼 행정 도시명에 없는 지역어는 조각으로도 도시에 걸리지 않습니다 — 행동 관찰에서 잡힌 것입니다. 도시 별칭을 색인에 넣는 안을 로컬에서 다시 평가했고(8월에 "넣지 않는다"고 판정했던 것을 지역어 평가셋으로 뒤집었습니다), 운영 반영 전입니다. - 행동 관찰은 아직 사내망 PC 하나에서 돕니다. 하루 한 번 배치이고 팀 표준 도구는 아닙니다. 선택 → 상세 → 예약까지 잇는 전환 조인은 넣지 않았습니다. 장소를 찾는 검색은 케이스 2의 문제입니다.
- 기존 위에 올린 작업입니다. 원래 쿼리는 이전 검색엔진팀의 설계로, 정확도만 재던 기준에서는 합리적인 선택이었습니다. 별칭은 운영자가 모은 데이터이고, 색인 작업의 원래 코드는 데이터 조직의 것을 옮겨 왔습니다.