본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 29. · 25 Views
실전 검색 시스템 프로젝트 완벽 가이드
Elasticsearch를 활용한 실전 검색 시스템 구축 방법을 단계별로 알아봅니다. 검색 서비스 설계부터 자동완성, 하이라이팅, 로그 분석까지 실무에서 바로 적용할 수 있는 내용을 다룹니다.
목차
1. 검색 서비스 설계
김개발 씨는 이커머스 스타트업에 입사한 지 한 달이 되었습니다. 어느 날 팀장님이 다가와 말했습니다.
"우리 서비스에 검색 기능을 새로 만들어야 하는데, 김개발 씨가 한번 설계해볼래요?" 데이터베이스 쿼리는 좀 다뤄봤지만, 본격적인 검색 시스템은 처음이라 막막했습니다.
검색 서비스 설계란 사용자가 원하는 정보를 빠르고 정확하게 찾을 수 있도록 시스템의 전체 구조를 잡는 것입니다. 마치 대형 도서관을 설계할 때 서가 배치, 분류 체계, 사서 데스크 위치를 미리 정하는 것과 같습니다.
이 단계를 탄탄히 해두면 나중에 기능을 추가하거나 성능을 개선할 때 훨씬 수월해집니다.
다음 코드를 살펴봅시다.
// Elasticsearch 클라이언트 초기화
const { Client } = require('@elastic/elasticsearch');
const client = new Client({
node: 'http://localhost:9200',
// 운영 환경에서는 인증 정보 추가
auth: {
username: 'elastic',
password: process.env.ES_PASSWORD
}
});
// 클러스터 상태 확인
async function checkClusterHealth() {
const health = await client.cluster.health();
console.log('클러스터 상태:', health.status);
return health;
}
김개발 씨는 입사 한 달 차 주니어 개발자입니다. 팀장님의 갑작스러운 요청에 당황했지만, 일단 선배 개발자 박시니어 씨에게 조언을 구하기로 했습니다.
"검색 시스템이요? 생각보다 고려할 게 많아요." 박시니어 씨가 화이트보드 앞으로 김개발 씨를 이끌었습니다.
"먼저 전체 그림을 그려봐야 해요." 그렇다면 검색 서비스 설계란 정확히 무엇일까요? 쉽게 비유하자면, 검색 서비스 설계는 마치 대형 도서관을 처음부터 짓는 것과 같습니다.
어떤 책을 어느 구역에 배치할지, 사서 데스크는 몇 개나 둘지, 이용자들이 어떤 동선으로 움직일지 미리 계획해야 합니다. 이처럼 검색 시스템도 데이터를 어떻게 저장하고, 어떤 경로로 검색하며, 결과를 어떻게 보여줄지 설계해야 합니다.
검색 시스템이 없던 시절에는 어땠을까요? 개발자들은 SQL의 LIKE 연산자로 검색을 구현했습니다.
간단한 검색은 가능했지만, 데이터가 늘어날수록 속도가 급격히 느려졌습니다. 더 큰 문제는 "노트북"을 검색했을 때 "랩탑"이나 "laptop"은 찾지 못하는 것이었습니다.
사용자 경험이 나빠지고, 검색을 포기하는 고객이 늘어났습니다. 바로 이런 문제를 해결하기 위해 Elasticsearch가 등장했습니다.
Elasticsearch를 사용하면 수백만 건의 데이터에서도 밀리초 단위로 검색이 가능해집니다. 또한 동의어, 오타 교정, 유사어 검색도 지원합니다.
무엇보다 분산 시스템이라 데이터가 아무리 많아져도 서버를 추가하면 됩니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 Elasticsearch 공식 클라이언트를 불러옵니다. Node.js 환경에서 가장 많이 사용되는 방식입니다.
다음으로 Client 인스턴스를 생성할 때 node 옵션에 Elasticsearch 서버 주소를 넣습니다. 운영 환경에서는 반드시 인증 정보를 추가해야 보안 문제를 예방할 수 있습니다.
cluster.health() 메서드는 클러스터의 현재 상태를 반환합니다. green은 모든 샤드가 정상, yellow는 일부 복제본 미할당, red는 일부 데이터 접근 불가를 의미합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 쇼핑몰 서비스를 개발한다고 가정해봅시다.
상품 검색, 카테고리 필터링, 가격 정렬 등 다양한 기능이 필요합니다. Elasticsearch를 도입하면 이 모든 기능을 하나의 쿼리로 처리할 수 있습니다.
쿠팡, 네이버 쇼핑 같은 대형 서비스에서 이런 패턴을 적극적으로 사용하고 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 Elasticsearch를 주 데이터베이스로 사용하려는 것입니다. Elasticsearch는 검색에 최적화된 도구이지, 트랜잭션이 중요한 데이터 저장에는 적합하지 않습니다.
따라서 MySQL이나 PostgreSQL과 함께 사용하는 것이 올바른 방법입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 검색 전용 시스템이 따로 필요했군요!" 검색 서비스 설계를 제대로 이해하면 확장 가능하고 유지보수하기 쉬운 시스템을 구축할 수 있습니다.
여러분도 오늘 배운 내용을 바탕으로 자신만의 검색 아키텍처를 그려보세요.
실전 팁
💡 - 개발 환경에서는 Docker로 Elasticsearch를 간편하게 실행할 수 있습니다
- 클러스터 상태가 yellow여도 검색은 정상 작동하지만, 장애 대비를 위해 green으로 유지하세요
- 운영 환경에서는 최소 3개 노드로 클러스터를 구성하는 것을 권장합니다
2. 상품 검색 인덱스 구축
검색 시스템의 기본 설계를 마친 김개발 씨에게 다음 과제가 주어졌습니다. "이제 실제로 상품 데이터를 넣어볼까요?" 박시니어 씨가 물었습니다.
하지만 단순히 데이터를 넣는 게 아니었습니다. 검색이 제대로 동작하려면 인덱스를 어떻게 설계하느냐가 핵심이었습니다.
상품 검색 인덱스란 검색 대상 데이터를 Elasticsearch가 이해할 수 있는 형태로 구조화한 것입니다. 마치 도서관에서 책마다 분류 번호를 붙이고 색인 카드를 만드는 것과 같습니다.
인덱스를 잘 설계해야 원하는 검색 결과를 빠르고 정확하게 얻을 수 있습니다.
다음 코드를 살펴봅시다.
// 상품 인덱스 매핑 정의
const productMapping = {
mappings: {
properties: {
name: {
type: 'text',
analyzer: 'nori' // 한국어 형태소 분석기
},
description: { type: 'text', analyzer: 'nori' },
price: { type: 'integer' },
category: { type: 'keyword' }, // 정확히 일치해야 하는 필드
brand: { type: 'keyword' },
createdAt: { type: 'date' }
}
}
};
// 인덱스 생성
await client.indices.create({
index: 'products',
body: productMapping
});
김개발 씨는 Elasticsearch에 데이터를 넣으면 자동으로 검색이 될 거라고 생각했습니다. 그런데 박시니어 씨가 고개를 저었습니다.
"아뇨, 그렇게 간단하지 않아요. 인덱스 설계가 먼저예요." "인덱스요?
데이터베이스의 인덱스 같은 건가요?" 김개발 씨가 물었습니다. 박시니어 씨가 미소를 지었습니다.
"비슷하면서도 달라요. Elasticsearch의 인덱스는 데이터 자체를 담는 컨테이너에 가까워요." 그렇다면 상품 검색 인덱스란 정확히 무엇일까요?
쉽게 비유하자면, 인덱스는 마치 도서관의 서가 분류 체계와 같습니다. 소설은 소설 구역에, 과학책은 과학 구역에 배치되어 있어야 찾기 쉽습니다.
각 책에는 분류 번호가 붙어있고, 색인 카드에는 제목, 저자, 출판사 정보가 적혀있습니다. Elasticsearch 인덱스도 마찬가지로 데이터의 구조와 검색 방식을 미리 정의합니다.
인덱스 설계가 없던 시절에는 어땠을까요? Elasticsearch가 자동으로 타입을 추론해서 매핑을 만들어주긴 합니다.
하지만 이렇게 하면 문제가 생깁니다. 숫자인 줄 알았던 필드가 문자열로 인식되거나, 한국어 검색이 제대로 안 되는 경우가 발생합니다.
나중에 수정하려면 데이터를 전부 다시 색인해야 하는 대참사가 벌어집니다. 바로 이런 문제를 예방하기 위해 명시적 매핑을 정의합니다.
매핑을 직접 정의하면 각 필드의 타입과 분석 방식을 정확히 제어할 수 있습니다. text 타입은 전문 검색용으로, 문장을 단어 단위로 쪼개서 저장합니다.
keyword 타입은 정확히 일치하는 값을 찾을 때 사용합니다. 숫자형 필드는 범위 검색과 정렬에 최적화됩니다.
위의 코드를 자세히 살펴보겠습니다. name과 description 필드는 text 타입에 nori 분석기를 적용했습니다.
nori는 Elasticsearch 공식 한국어 형태소 분석기입니다. "삼성 갤럭시 스마트폰"을 "삼성", "갤럭시", "스마트폰"으로 분리해서 저장합니다.
덕분에 "갤럭시"만 검색해도 해당 상품이 나옵니다. category와 brand 필드는 keyword 타입입니다.
"전자제품"을 검색하면 정확히 "전자제품"만 찾습니다. "전자"만으로는 검색되지 않습니다.
필터링이나 집계에 주로 사용됩니다. price는 integer 타입으로, 가격 범위 검색과 정렬이 가능합니다.
"10000원 이상 50000원 이하" 같은 조건을 빠르게 처리합니다. 실제 현업에서는 어떻게 활용할까요?
대형 쇼핑몰에서는 상품 수가 수백만 개에 달합니다. 이때 인덱스 설계가 잘못되면 검색 속도가 급격히 느려집니다.
반면 잘 설계된 인덱스는 수천만 건에서도 수십 밀리초 안에 결과를 반환합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수는 모든 필드를 text 타입으로 설정하는 것입니다. 이렇게 하면 카테고리 필터링이 제대로 동작하지 않고, 저장 공간도 낭비됩니다.
정확한 값으로 필터링할 필드는 반드시 keyword 타입으로 설정해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
인덱스 매핑을 완성한 김개발 씨는 뿌듯한 표정을 지었습니다. "이제 데이터를 넣으면 한국어 검색도 제대로 되겠네요!" 인덱스 구축을 제대로 이해하면 검색 품질과 성능의 기반을 탄탄히 다질 수 있습니다.
서비스의 특성에 맞게 필드 타입과 분석기를 신중하게 선택하세요.
실전 팁
💡 - 인덱스를 생성하기 전에 검색 시나리오를 먼저 정리하세요
- nori 분석기는 Elasticsearch에 별도 플러그인 설치가 필요합니다
- 운영 중인 인덱스의 매핑은 변경이 어려우니 처음에 신중하게 설계하세요
3. 자동완성 기능 구현
인덱스 구축을 마친 김개발 씨에게 기획자가 찾아왔습니다. "검색창에 글자를 입력하면 추천 검색어가 뜨게 해주세요.
네이버처럼요!" 자동완성 기능이었습니다. 단순해 보이지만, 막상 구현하려니 생각보다 고려할 게 많았습니다.
자동완성이란 사용자가 검색어를 타이핑하는 동안 실시간으로 관련 검색어를 제안하는 기능입니다. 마치 친한 친구가 말을 끝맺기도 전에 "그거 말하려는 거지?"라고 알아채는 것과 같습니다.
사용자 경험을 크게 향상시키고, 검색 전환율도 높여줍니다.
다음 코드를 살펴봅시다.
// 자동완성용 인덱스 설정
const autocompleteSettings = {
settings: {
analysis: {
analyzer: {
autocomplete_analyzer: {
type: 'custom',
tokenizer: 'standard',
filter: ['lowercase', 'edge_ngram_filter']
}
},
filter: {
edge_ngram_filter: {
type: 'edge_ngram',
min_gram: 1,
max_gram: 20
}
}
}
},
mappings: {
properties: {
suggest: {
type: 'text',
analyzer: 'autocomplete_analyzer',
search_analyzer: 'standard'
}
}
}
};
// 자동완성 검색 쿼리
async function getAutocomplete(prefix) {
const result = await client.search({
index: 'autocomplete',
body: {
query: {
match: {
suggest: {
query: prefix,
operator: 'and'
}
}
},
size: 10
}
});
return result.hits.hits;
}
김개발 씨는 자동완성이 단순히 "포함된 문자열 찾기"일 줄 알았습니다. LIKE '%검색어%' 같은 쿼리로 구현하면 되지 않을까 생각했죠.
박시니어 씨가 고개를 저었습니다. "그렇게 하면 데이터가 조금만 많아져도 느려져요.
그리고 '갤럭'까지 쳤을 때 '갤럭시'가 나와야 하는데, LIKE로는 한계가 있어요." 그렇다면 자동완성은 어떻게 구현해야 할까요? 쉽게 비유하자면, 자동완성은 마치 전화번호부의 인덱스 탭과 같습니다.
"김"으로 시작하는 이름을 찾을 때, ㄱ 탭을 펼치면 바로 찾을 수 있습니다. 처음부터 끝까지 훑어볼 필요가 없습니다.
Elasticsearch의 edge_ngram도 비슷합니다. 단어의 앞부분을 미리 잘라서 색인해둡니다.
자동완성이 없던 시절에는 어땠을까요? 사용자는 검색어를 정확히 알아야만 검색할 수 있었습니다.
"스마트폰"을 검색하려면 다섯 글자를 모두 입력하고 엔터를 눌러야 했습니다. 오타가 나면 결과가 없다는 메시지만 보였습니다.
사용자들은 점점 검색을 포기하게 되었습니다. 바로 이런 문제를 해결하기 위해 자동완성이 등장했습니다.
자동완성을 사용하면 한두 글자만 입력해도 관련 검색어가 나타납니다. 사용자는 원하는 검색어를 클릭하기만 하면 됩니다.
오타를 줄이고, 검색 성공률을 높입니다. 무엇보다 "이 서비스는 내가 뭘 찾는지 알아"라는 느낌을 줍니다.
위의 코드에서 핵심은 edge_ngram 필터입니다. "스마트폰"이라는 단어가 들어오면 "스", "스마", "스마트", "스마트폰" 이렇게 앞에서부터 잘라서 모두 색인합니다.
사용자가 "스마"를 입력하면 이미 색인된 "스마"와 매칭되어 "스마트폰"이 결과로 나옵니다. analyzer와 search_analyzer를 다르게 설정한 것도 중요합니다.
색인할 때는 edge_ngram으로 잘게 쪼개지만, 검색할 때는 standard 분석기로 입력된 그대로 검색합니다. 이렇게 해야 "스마"로 검색했을 때 "스"나 "스마트"가 아닌 "스마"로 시작하는 결과만 정확히 나옵니다.
실제 현업에서는 어떻게 활용할까요? 네이버, 쿠팡 같은 서비스의 검색창을 떠올려보세요.
글자 하나를 입력할 때마다 순식간에 추천 검색어가 바뀝니다. 이 속도를 내려면 일반적인 데이터베이스 쿼리로는 불가능합니다.
미리 색인해둔 데이터에서 바로 찾아오는 방식이 필수입니다. 하지만 주의할 점도 있습니다.
edge_ngram의 min_gram을 너무 작게 설정하면 저장 공간이 급격히 늘어납니다. 또한 한 글자 검색 시 너무 많은 결과가 나와 오히려 느려질 수 있습니다.
보통 min_gram은 2 이상으로 설정하는 것을 권장합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
자동완성 기능을 완성하고 테스트해본 김개발 씨는 감탄했습니다. "와, 진짜 네이버처럼 되네요!" 자동완성을 제대로 이해하면 사용자 친화적인 검색 경험을 만들 수 있습니다.
edge_ngram의 원리를 익히고, 서비스에 맞게 min_gram과 max_gram 값을 조절해보세요.
실전 팁
💡 - 자동완성 결과는 인기 검색어 순으로 정렬하면 더 유용합니다
- 한글의 경우 초성 검색(ㅅㅁㅌㅍ → 스마트폰)도 고려해보세요
- 자동완성 인덱스는 상품 인덱스와 별도로 관리하는 것이 성능에 유리합니다
4. 검색 결과 하이라이팅
자동완성까지 구현한 김개발 씨에게 또 다른 요청이 들어왔습니다. "검색 결과에서 검색어가 어디에 있는지 한눈에 보이게 해주세요.
구글처럼 노란색으로 표시되면 좋겠어요." 하이라이팅 기능이었습니다. 사용자가 왜 이 결과가 나왔는지 직관적으로 알 수 있게 해주는 기능이죠.
하이라이팅이란 검색 결과에서 검색어와 매칭된 부분을 시각적으로 강조하는 기능입니다. 마치 책에서 중요한 부분에 형광펜으로 밑줄을 긋는 것과 같습니다.
사용자는 결과를 훑어보며 원하는 정보가 있는지 빠르게 판단할 수 있습니다.
다음 코드를 살펴봅시다.
// 하이라이팅이 적용된 검색 쿼리
async function searchWithHighlight(keyword) {
const result = await client.search({
index: 'products',
body: {
query: {
multi_match: {
query: keyword,
fields: ['name^2', 'description'] // name 필드 가중치 2배
}
},
highlight: {
pre_tags: ['<em class="highlight">'],
post_tags: ['</em>'],
fields: {
name: { number_of_fragments: 0 }, // 전체 텍스트 반환
description: {
fragment_size: 100, // 100자 단위로 조각
number_of_fragments: 3 // 최대 3개 조각
}
}
}
}
});
return result.hits.hits.map(hit => ({
...hit._source,
highlights: hit.highlight
}));
}
김개발 씨는 검색 결과 페이지를 보며 고민에 빠졌습니다. 결과는 잘 나오는데, 어떤 부분이 검색어와 매칭되었는지 알 수가 없었습니다.
사용자 입장에서 불편할 것 같았습니다. "하이라이팅을 적용해보는 건 어때요?" 박시니어 씨가 제안했습니다.
"Elasticsearch가 기본으로 지원하는 기능이에요." 그렇다면 하이라이팅이란 정확히 무엇일까요? 쉽게 비유하자면, 하이라이팅은 마치 시험 공부할 때 형광펜으로 중요한 부분을 표시하는 것과 같습니다.
두꺼운 교과서에서 핵심 내용만 눈에 띄게 만들면 나중에 빠르게 훑어볼 수 있습니다. 검색 결과도 마찬가지입니다.
수십 개의 결과 중에서 검색어가 포함된 부분만 강조해주면 사용자가 원하는 정보를 빨리 찾을 수 있습니다. 하이라이팅이 없던 시절에는 어땠을까요?
사용자는 검색 결과 목록에서 각 항목을 일일이 읽어봐야 했습니다. "내가 검색한 키워드가 대체 어디에 있는 거지?" 혼란스러웠습니다.
특히 긴 설명문에서는 검색어를 찾기가 더 어려웠습니다. 사용자는 점점 지쳐갔고, 검색 결과에 대한 신뢰도도 떨어졌습니다.
바로 이런 문제를 해결하기 위해 하이라이팅이 등장했습니다. 하이라이팅을 사용하면 매칭된 부분이 한눈에 들어옵니다.
사용자는 결과를 빠르게 스캔하며 원하는 정보인지 판단할 수 있습니다. 무엇보다 "아, 이래서 이 결과가 나왔구나"라는 이해를 돕습니다.
위의 코드에서 핵심은 highlight 옵션입니다. pre_tags와 post_tags는 매칭된 텍스트를 감싸는 HTML 태그를 지정합니다.
기본값은 <em></em>이지만, CSS 클래스를 추가해서 스타일링할 수 있습니다. number_of_fragments가 0이면 필드 전체를 반환합니다.
상품명처럼 짧은 텍스트에 적합합니다. 반면 긴 설명문에서는 fragment_size와 number_of_fragments를 조합해서 매칭된 부분 주변만 잘라서 보여줍니다.
multi_match 쿼리에서 name^2는 name 필드의 가중치를 2배로 높인다는 뜻입니다. 상품명에 검색어가 있으면 더 높은 점수를 받아 상위에 노출됩니다.
실제 현업에서는 어떻게 활용할까요? 구글, 네이버 검색 결과를 보면 검색어가 굵은 글씨로 표시됩니다.
이것이 바로 하이라이팅입니다. 사용자는 0.1초 만에 결과를 훑어보고 클릭 여부를 결정합니다.
하이라이팅이 없으면 이 판단 시간이 길어지고, 이탈률이 높아집니다. 하지만 주의할 점도 있습니다.
하이라이팅된 HTML을 프론트엔드에서 그대로 렌더링하면 XSS 공격에 취약해질 수 있습니다. 반드시 신뢰할 수 있는 태그만 허용하도록 처리해야 합니다.
또한 fragment_size가 너무 작으면 문맥을 이해하기 어렵고, 너무 크면 화면을 지저분하게 만듭니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
하이라이팅을 적용한 검색 결과 페이지를 본 기획자가 만족스러운 표정을 지었습니다. "오, 이제 진짜 검색 서비스 같네요!" 하이라이팅을 제대로 이해하면 사용자에게 명확한 피드백을 줄 수 있습니다.
fragment_size와 number_of_fragments를 조절해서 서비스에 맞는 최적의 설정을 찾아보세요.
실전 팁
💡 - 하이라이팅 태그에 CSS 클래스를 추가하면 디자인 커스터마이징이 쉬워집니다
- 긴 텍스트에서는 매칭된 부분 주변 문맥만 보여주는 것이 효과적입니다
- 프론트엔드에서 하이라이팅 HTML을 렌더링할 때는 보안에 주의하세요
5. 검색 로그 분석
검색 기능 출시 후 일주일이 지났습니다. 팀장님이 김개발 씨에게 물었습니다.
"사용자들이 뭘 많이 검색하는지 알 수 있어요? 그리고 검색했는데 결과가 없는 경우도 파악하고 싶은데요." 검색 로그 분석이 필요한 시점이었습니다.
검색 로그 분석이란 사용자의 검색 행동 데이터를 수집하고 분석하는 것입니다. 마치 쇼핑몰 매니저가 손님들이 어느 코너에서 오래 머무르고, 어디서 그냥 지나치는지 관찰하는 것과 같습니다.
이 데이터를 통해 검색 품질을 개선하고, 비즈니스 인사이트를 얻을 수 있습니다.
다음 코드를 살펴봅시다.
// 검색 로그 저장
async function logSearch(userId, keyword, resultCount, clickedItems) {
await client.index({
index: 'search_logs',
body: {
userId,
keyword,
resultCount,
clickedItems, // 클릭한 상품 ID 배열
hasResult: resultCount > 0,
timestamp: new Date().toISOString()
}
});
}
// 인기 검색어 집계
async function getPopularKeywords(days = 7) {
const result = await client.search({
index: 'search_logs',
body: {
query: {
range: {
timestamp: {
gte: `now-${days}d/d`
}
}
},
aggs: {
popular_keywords: {
terms: {
field: 'keyword.keyword',
size: 10
}
}
},
size: 0 // 문서는 필요 없고 집계 결과만
}
});
return result.aggregations.popular_keywords.buckets;
}
김개발 씨는 검색 기능이 잘 돌아가고 있다고 생각했습니다. 그런데 팀장님의 질문에 아무것도 대답할 수 없었습니다.
어떤 검색어가 인기 있는지, 검색 결과가 없어서 사용자가 이탈하는 경우가 얼마나 되는지 전혀 알 수 없었습니다. "로그가 없으면 장님과 같아요." 박시니어 씨가 말했습니다.
"사용자가 뭘 원하는지 모르면 개선할 수도 없어요." 그렇다면 검색 로그 분석이란 정확히 무엇일까요? 쉽게 비유하자면, 검색 로그 분석은 마치 병원의 진료 기록과 같습니다.
환자가 어떤 증상을 호소했고, 어떤 치료를 받았는지 기록해두면 다음 진료에 참고할 수 있습니다. 검색 로그도 마찬가지입니다.
사용자가 무엇을 검색했고, 결과를 클릭했는지 기록해두면 검색 시스템을 진단하고 개선할 수 있습니다. 검색 로그가 없던 시절에는 어땠을까요?
개발자들은 "검색 잘 되는 것 같은데요?"라고 막연하게 생각했습니다. 사용자 불만이 접수되어야 문제를 인지했고, 그때는 이미 많은 사용자가 이탈한 후였습니다.
어떤 검색어에서 문제가 발생하는지 추적하기도 어려웠습니다. 바로 이런 문제를 해결하기 위해 검색 로그 분석이 등장했습니다.
검색 로그를 수집하면 데이터 기반 의사결정이 가능해집니다. "에어팟"이라는 검색어가 많다면 관련 상품을 더 확보해야 합니다.
검색 결과가 0인 키워드가 많다면 동의어 사전을 추가해야 합니다. 클릭률이 낮은 검색어는 결과 품질에 문제가 있다는 신호입니다.
위의 코드에서 logSearch 함수는 검색할 때마다 로그를 저장합니다. userId는 개인화 추천에 활용할 수 있습니다.
resultCount가 0이면 검색 실패로 분류됩니다. clickedItems를 분석하면 어떤 결과가 실제로 유용했는지 알 수 있습니다.
getPopularKeywords 함수는 aggregations를 사용해서 인기 검색어를 집계합니다. terms 집계는 특정 필드의 값별로 문서 수를 세어줍니다.
size가 10이면 상위 10개만 반환합니다. 이런 집계는 Elasticsearch가 내부적으로 최적화되어 있어 매우 빠릅니다.
실제 현업에서는 어떻게 활용할까요? 대형 이커머스에서는 검색 로그로 마케팅 전략을 수립합니다.
"블랙프라이데이" 검색이 급증하면 관련 프로모션을 준비합니다. 검색은 됐지만 클릭이 없는 경우를 분석해서 상품 상세 페이지를 개선합니다.
데이터 기반 의사결정의 핵심 자료입니다. 하지만 주의할 점도 있습니다.
검색 로그에는 개인정보가 포함될 수 있습니다. 민감한 검색어(건강, 금융 등)는 별도로 관리하거나 익명화해야 합니다.
또한 로그가 너무 많이 쌓이면 저장 비용이 증가하니, 오래된 로그는 주기적으로 정리해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
일주일 치 검색 로그를 분석한 김개발 씨는 놀라운 사실을 발견했습니다. "아이폰 케이스"를 검색한 사용자가 많았는데, 정작 상품이 "iPhone 케이스"로 등록되어 있어서 검색 결과가 적게 나왔던 것입니다.
검색 로그 분석을 제대로 이해하면 데이터 기반으로 검색 품질을 개선할 수 있습니다. 로그를 쌓는 것에서 그치지 말고, 정기적으로 분석하는 습관을 들이세요.
실전 팁
💡 - 검색 로그 인덱스는 날짜별로 분리하면 관리가 쉬워집니다 (search_logs_2024_01)
- 결과 없음 비율이 10%를 넘으면 동의어 사전 확장을 고려하세요
- 클릭률(CTR)은 검색 품질의 핵심 지표입니다
6. 검색 품질 개선
검색 로그를 분석한 김개발 씨는 여러 문제점을 발견했습니다. "노트북"과 "랩탑"을 같은 의미로 인식하지 못하고, "삼성 갤럭시"를 검색하면 정작 인기 상품이 아래쪽에 묻히는 문제가 있었습니다.
이제 검색 품질을 본격적으로 개선할 때가 되었습니다.
검색 품질 개선이란 사용자가 원하는 결과를 더 빠르고 정확하게 찾을 수 있도록 검색 시스템을 튜닝하는 것입니다. 마치 요리사가 손님 피드백을 받아 레시피를 조금씩 수정하는 것과 같습니다.
동의어 처리, 가중치 조절, 개인화 등 다양한 기법을 활용합니다.
다음 코드를 살펴봅시다.
// 동의어 분석기 설정
const synonymSettings = {
settings: {
analysis: {
filter: {
synonym_filter: {
type: 'synonym',
synonyms: [
'노트북, 랩탑, laptop',
'핸드폰, 스마트폰, 휴대폰, 모바일',
'이어폰, 이어버드, 무선이어폰'
]
}
},
analyzer: {
korean_synonym: {
type: 'custom',
tokenizer: 'nori_tokenizer',
filter: ['lowercase', 'synonym_filter']
}
}
}
}
};
// 인기도와 연관성을 조합한 검색
async function improvedSearch(keyword) {
const result = await client.search({
index: 'products',
body: {
query: {
function_score: {
query: {
multi_match: {
query: keyword,
fields: ['name^3', 'description', 'brand^2']
}
},
functions: [
{
field_value_factor: {
field: 'salesCount', // 판매량 반영
factor: 1.2,
modifier: 'log1p'
}
},
{
filter: { term: { isPromoted: true } },
weight: 1.5 // 프로모션 상품 가중치
}
],
score_mode: 'multiply'
}
}
}
});
return result.hits.hits;
}
김개발 씨는 검색 로그 분석 결과를 들고 박시니어 씨를 찾아갔습니다. "노트북"을 검색한 사용자가 많았는데, 일부 상품이 "랩탑"으로 등록되어 있어서 검색되지 않았습니다.
또한 신상품이 베스트셀러보다 위에 노출되는 문제도 있었습니다. "검색 품질 개선이 필요하네요." 박시니어 씨가 말했습니다.
"크게 두 가지 작업을 해봐요. 동의어 처리와 랭킹 로직 개선이요." 그렇다면 검색 품질 개선이란 정확히 무엇일까요?
쉽게 비유하자면, 검색 품질 개선은 마치 레스토랑 메뉴 개선과 같습니다. 손님들이 "파스타" 대신 "스파게티"라고 많이 물어본다면 메뉴판에 둘 다 적어둡니다.
가장 인기 있는 요리를 메뉴 맨 위에 올립니다. 이처럼 검색 시스템도 사용자 언어에 맞추고, 인기 있는 결과를 상위에 노출합니다.
검색 품질 개선이 없던 시절에는 어땠을까요? 동일한 상품이 다른 이름으로 등록되면 검색에서 누락되었습니다.
신규 상품이 무조건 상위에 노출되어 베스트셀러가 묻혔습니다. 사용자는 원하는 상품을 찾지 못하고 이탈했고, 매출에도 직접적인 영향을 미쳤습니다.
바로 이런 문제를 해결하기 위해 다양한 검색 품질 개선 기법이 발전했습니다. 동의어 사전을 활용하면 "노트북"을 검색해도 "랩탑"으로 등록된 상품이 함께 나옵니다.
function_score를 사용하면 텍스트 매칭 점수에 비즈니스 로직을 결합할 수 있습니다. 판매량이 많거나 프로모션 중인 상품에 추가 점수를 줄 수 있습니다.
위의 코드에서 synonym_filter는 동의어를 정의합니다. 쉼표로 구분된 단어들은 같은 의미로 처리됩니다.
"노트북, 랩탑, laptop" 중 어떤 단어로 검색해도 모든 관련 상품이 나옵니다. 동의어 사전은 파일로 분리해서 관리하면 업데이트가 편리합니다.
function_score 쿼리가 핵심입니다. 기본 검색 점수에 추가 함수를 적용해서 최종 점수를 계산합니다.
field_value_factor는 특정 숫자 필드의 값을 점수에 반영합니다. salesCount가 높을수록 점수가 올라갑니다.
log1p modifier는 로그 함수를 적용해서 극단적인 차이를 완화합니다. 필터 조건을 만족하는 문서에만 가중치를 줄 수도 있습니다.
isPromoted가 true인 상품은 1.5배 점수를 받습니다. 광고 상품 우선 노출에 활용할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 대형 쇼핑몰에서는 수백 개의 동의어와 수십 개의 랭킹 요소를 조합합니다.
리뷰 점수, 배송 속도, 반품률, 계절성까지 고려합니다. A/B 테스트를 통해 어떤 조합이 클릭률과 구매 전환율을 높이는지 지속적으로 실험합니다.
하지만 주의할 점도 있습니다. 동의어를 너무 광범위하게 설정하면 엉뚱한 결과가 나올 수 있습니다.
"애플"을 "사과"와 동의어로 설정하면 아이폰 검색 시 과일이 나올 수 있습니다. 또한 프로모션 가중치가 너무 높으면 검색 품질보다 광고 노출이 우선되어 사용자 경험이 나빠질 수 있습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 동의어 사전을 추가하고 랭킹 로직을 개선한 후, 검색 결과 만족도가 눈에 띄게 올라갔습니다.
팀장님이 말했습니다. "오, 이제 정말 검색 잘 되네!" 검색 품질 개선을 제대로 이해하면 비즈니스 목표에 맞는 검색 경험을 설계할 수 있습니다.
동의어 사전과 랭킹 로직을 지속적으로 다듬어 나가세요. 검색은 한 번 만들고 끝나는 게 아니라, 계속 개선해야 하는 시스템입니다.
실전 팁
💡 - 동의어 사전은 검색 로그에서 발견된 패턴을 기반으로 추가하세요
- function_score의 가중치는 A/B 테스트로 최적값을 찾아야 합니다
- 검색 품질 지표(CTR, 전환율)를 정기적으로 모니터링하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
분산 추적 완벽 가이드
마이크로서비스 환경에서 요청의 전체 흐름을 추적하는 분산 추적 시스템의 핵심 개념을 배웁니다. Trace, Span, Trace ID 전파, 샘플링 전략까지 실무에 필요한 모든 것을 다룹니다.
CloudFront CDN 완벽 가이드
AWS CloudFront를 활용한 콘텐츠 배포 최적화 방법을 실무 관점에서 다룹니다. 배포 생성부터 캐시 설정, HTTPS 적용까지 단계별로 알아봅니다.