검색 속도를 높이는 Elasticsearch 인덱스 설계와 쿼리 기술

1. Elasticsearch 인덱싱의 기본 구조를 이해하는 것이 첫걸음입니다

Elasticsearch를 제대로 다루려면 가장 먼저 인덱싱 구조부터 짚고 넘어가야 합니다. 마치 도서관에서 책을 찾으려면 어떤 방식으로 책이 정리되어 있는지를 알아야 하듯이, Elasticsearch도 데이터가 어떤 구조로 저장되고 검색되는지를 아는 것이 핵심입니다. Elasticsearch에서 인덱스(index)는 관계형 데이터베이스의 ‘테이블(table)’에 해당합니다. 이 인덱스 안에는 여러 개의 도큐먼트(document)가 있고, 각각의 도큐먼트는 JSON 형식으로 데이터를 담고 있으며, 이들은 다시 필드(field)로 구성됩니다. 이 구조를 이해하면, 나중에 검색 쿼리를 구성할 때 어떤 필드에 어떤 데이터를 저장해야 효율적인 검색이 가능한지 감이 잡히게 됩니다. 특히, 데이터를 저장하면서 동시에 inverted index를 생성해두는 Elasticsearch의 특성 덕분에 매우 빠른 검색이 가능하죠. 그렇기 때문에 어떤 데이터를 어떤 필드에 담아야 하는지, 또 그 필드를 어떤 데이터 타입으로 정의할지(mapping)가 검색 속도에 큰 영향을 끼친다는 점을 절대 간과하셔선 안 됩니다.

2. 매핑(Mapping)은 검색 성능의 설계도입니다

Elasticsearch에서는 매핑이 곧 성능입니다. 왜냐하면 매핑은 각 필드가 어떤 데이터 타입인지, 어떤 분석기(analyzer)를 사용할지, 검색 가능하게 할지 여부 등을 결정하는 설정이기 때문인데요. 예를 들어, keyword 타입은 전체 문자열을 하나의 값으로 저장하고 검색할 때는 완전히 일치해야만 결과가 나옵니다. 반면, text 타입은 분석기를 사용해 여러 토큰으로 나눠서 저장하기 때문에 자연어 검색에는 적합하지만 정렬이나 집계에는 부적합하죠. 그래서 상품 이름이나 주소처럼 정렬과 필터링이 필요한 데이터는 keyword 타입을, 블로그 본문처럼 검색 위주의 데이터는 text 타입으로 나누는 것이 중요합니다. 잘못된 매핑은 검색 시 과도한 리소스를 사용하게 만들거나, 아예 원하는 결과를 못 찾게 만들 수 있습니다. 즉, 처음부터 “이 데이터는 어떤 방식으로 검색할 것인가?”를 고려해서 매핑을 설계하는 것이 쿼리 최적화의 첫걸음입니다.

3. 적절한 샤드와 복제 수 설정은 성능과 안정성의 균형입니다

Elasticsearch의 인덱스는 내부적으로 여러 개의 샤드(shard)로 나뉘어져 분산 저장됩니다. 이 샤드는 다시 프라이머리 샤드(primary shard)와 복제 샤드(replica shard)로 나뉘죠. 그런데 샤드를 너무 많이 설정하면 오히려 오버헤드가 커지고, 반대로 너무 적게 설정하면 확장성과 병렬처리가 제한됩니다. 예를 들어 100만 건 이상의 데이터를 처리해야 하는 상황에서 샤드를 1개로만 설정해버리면 하나의 노드에 부하가 집중되겠죠? 반면 100건 정도의 데이터를 처리하는데 샤드를 10개로 설정하면 메모리 낭비가 심해집니다. 또한 복제 샤드는 장애 대응을 위한 보험 같은 존재이므로, 노드 수가 3개 이상일 때는 복제 샤드를 최소 1개 이상 설정하는 게 좋습니다. 즉, 데이터를 얼마나 자주 조회하고, 얼마나 빠르게 처리해야 하는지를 기준으로 샤드와 복제 수를 유연하게 조절하는 게 핵심입니다.

4. Analyzer 선택은 검색 품질을 좌우합니다

분석기(analyzer)는 Elasticsearch가 텍스트 데이터를 저장하고 검색할 때 어떤 방식으로 분해할지를 결정하는 중요한 요소입니다. 마치 텍스트를 쪼개는 칼이라고 생각하시면 되는데요. 한글에서는 nori 분석기나 arirang, openkoreantext 등의 한글 특화 분석기가 자주 쓰입니다. 예를 들어 ‘자율 주행 자동차’라는 문장을 분석한다고 했을 때, 어떤 분석기를 쓰느냐에 따라 토큰이 ‘자율’, ‘주행’, ‘자동차’로 나뉠 수도 있고, ‘자율주행’, ‘자동차’로 나뉠 수도 있습니다. 이 차이가 검색 결과에 엄청난 영향을 줍니다. 사용자가 ‘자율주행’이라고 검색했을 때 결과에 ‘자율 주행 자동차’가 포함되려면 분석기 설정이 일치해야 하죠. 그래서 검색 품질을 높이고 싶다면, 자주 사용되는 검색어와 도큐먼트를 기반으로 테스트하면서 분석기를 튜닝하는 과정이 꼭 필요합니다. 사용자 검색 의도와 분석기 결과가 얼마나 일치하느냐가 곧 검색 성공률입니다.

5. 불필요한 필드는 인덱싱하지 마세요

Elasticsearch에서는 모든 필드를 자동으로 인덱싱하려는 기본 설정이 있지만, 꼭 모든 필드를 인덱싱할 필요는 없습니다. 인덱싱은 곧 디스크 공간과 리소스를 의미합니다. 예를 들어, 단순한 로그 데이터에서 ‘생성 시간’이나 ‘요청 ID’ 같은 필드는 검색이 필요 없을 수도 있죠? 이럴 경우 index: false로 설정해서 인덱싱을 생략하면 디스크 공간도 줄고, 전체적인 인덱스 최적화에도 도움이 됩니다. 또, 대용량 데이터를 다룰 때는 store: false로 설정해 디스크에 저장하되 검색은 하지 않게 할 수도 있습니다. 검색하지 않는 필드에까지 불필요하게 인덱싱 자원을 쓰는 것은 마치 사용하지 않는 방의 불을 켜놓는 것과 같습니다. 에너지 낭비를 줄이고 필요한 곳에만 집중하시는 것이 중요합니다.

6. 검색 시에는 꼭 필터와 쿼리를 구분하세요

Elasticsearch에서 쿼리(query)와 필터(filter)의 개념을 정확히 이해하는 것은 매우 중요합니다. 둘 다 데이터를 제한하는 역할을 하지만, 내부 처리 방식이 다릅니다. 쿼리는 검색 점수를 계산해서 결과의 순위를 매기지만, 필터는 점수 계산 없이 일치 여부만 판단합니다. 그래서 자주 반복되는 조건이나 캐싱이 가능한 조건(예: 날짜 필터링, 카테고리 선택 등)은 필터로 처리하는 것이 성능적으로 유리합니다. 예를 들어, 쇼핑몰에서 ‘전자제품’ 카테고리에서 ‘삼성’ 브랜드 제품을 검색한다고 할 때, ‘전자제품’과 ‘삼성’은 필터로 처리하고, ‘가성비 좋은’ 같은 키워드는 쿼리로 처리하는 것이 이상적입니다. 필터는 캐시가 가능하기 때문에 동일 조건의 요청이 많을수록 효과가 커지고, 시스템 자원 소모도 줄어듭니다. 즉, 어떤 조건이 자주 반복되거나 정해진 값이라면 필터를, 사용자의 자유로운 검색은 쿼리로 설정하는 습관이 쿼리 최적화의 핵심입니다.

7. Bool 쿼리를 잘 활용하면 성능이 달라집니다

Bool 쿼리는 Elasticsearch에서 가장 많이 사용되는 쿼리 형식 중 하나로, 여러 조건을 조합해 복잡한 검색을 구현할 수 있습니다. must, should, must_not, filter 같은 조건들을 논리적으로 엮을 수 있는데요. 예를 들어, ‘카테고리는 전자제품이면서, 가격은 10만 원 이하이고, 브랜드는 삼성 혹은 LG여야 하며, 품절된 상품은 제외’라는 복잡한 조건도 Bool 쿼리 하나로 표현할 수 있습니다. 그런데 여기서 중요한 건 조건의 순서와 성격입니다. must는 점수 계산이 들어가지만, filter는 캐시를 활용할 수 있어서 반복성 높은 조건은 filter에 넣는 것이 좋습니다. 또 should는 점수에 가중치를 줄 때 유용하며, must_not은 말 그대로 제외 조건이죠. 쿼리 구조를 어떻게 짜느냐에 따라 응답 속도에 수 초 차이가 날 수도 있으니, 단순히 동작만 되도록 만드는 게 아니라, 성능까지 고려해서 조합하셔야 합니다.

8. Pagination 대신 Scroll API를 적절히 사용하세요

Elasticsearch에서는 기본적으로 from과 size를 사용해 페이징 처리합니다. 하지만 이 방식은 페이지 수가 많아질수록 성능 저하가 발생합니다. 왜냐하면 내부적으로는 앞 페이지의 모든 데이터를 건너뛰고 나서 원하는 페이지 데이터를 가져오기 때문이죠. 수천 건 이상 되는 데이터를 순차적으로 조회해야 하는 경우에는 Scroll API를 사용하는 것이 훨씬 효율적입니다. Scroll은 데이터 스냅샷을 생성해 일정 시간 동안 고정된 결과를 페이징 방식으로 받아올 수 있기 때문에, 대량 데이터 일괄 처리에 최적화되어 있습니다. 예를 들어 로그 데이터나 백업 작업, 머신러닝 전처리 등에 유용하죠. 다만 Scroll은 실시간성이 떨어지기 때문에 사용자 인터페이스용 검색에는 부적합합니다. 목적에 따라 어떤 방식을 사용할지 명확하게 정하고, 올바른 API를 선택하는 것이 쿼리 최적화의 핵심입니다.

9. 집계(Aggregation)는 필요한 만큼만 사용하세요

Elasticsearch의 강력한 기능 중 하나가 바로 집계입니다. 집계는 데이터를 통계적으로 요약해주는 기능인데, 너무 많이 사용하면 검색 속도에 큰 영향을 줄 수 있습니다. 특히 중첩 집계(nested aggregation)나 범위 집계(range aggregation)를 남발하면 시스템에 큰 부하가 걸립니다. 예를 들어 쇼핑몰에서 ‘브랜드별 평균 가격’, ‘카테고리별 최대 할인율’ 등을 실시간으로 보여주고 싶다면 집계가 필요하지만, 모든 검색 결과에 무조건 집계를 넣는 건 자제하시는 게 좋습니다. 집계는 사용자의 의도와 비즈니스 로직에 따라 선별적으로 활용해야 하며, 가능하다면 자주 사용하는 집계 결과는 Elasticsearch 대신 캐시나 별도 인프라에 저장해서 처리하는 방식도 고려해볼 수 있습니다.

10. 쿼리 프로파일링으로 병목 지점을 찾아 최적화하세요

Elasticsearch에서는 쿼리의 실행 계획과 병목 구간을 시각적으로 확인할 수 있는 profile API를 제공합니다. 이 기능을 활용하면 어떤 쿼리 조건이 오래 걸리는지, 어떤 필드에서 병목이 생기는지 파악할 수 있죠. 마치 병원에서 CT를 찍듯이, 쿼리를 해부해서 어떤 부분이 시간을 가장 많이 잡아먹고 있는지 분석하는 도구입니다. 단순히 쿼리 실행 시간을 보는 것과는 차원이 다릅니다. 예를 들어 하나의 must 조건이 다른 필드보다 10배 이상 처리 시간이 길다면, 그 조건을 filter로 바꾸거나, 해당 필드의 매핑을 변경해볼 수도 있습니다. Elasticsearch를 튜닝할 때는 직관이 아니라 데이터로 판단하셔야 하며, profile API는 이를 위한 최적의 도구입니다.

마무리하며: Elasticsearch는 설계부터 전략입니다

Elasticsearch는 단순한 검색 엔진이 아닙니다. 정교한 설계와 전략이 필요한 고성능 데이터 플랫폼입니다. 인덱싱 구조 하나, 쿼리 조건 하나가 전체 시스템 성능에 직결되기 때문에, 처음부터 끝까지 ‘어떻게 저장할 것인가’와 ‘어떻게 검색할 것인가’를 함께 고민하셔야 합니다. 이 글에서 소개해드린 10가지 요소는 Elasticsearch 성능 최적화의 핵심이자, 실무에서 꼭 마주치게 될 중요한 개념들입니다. 데이터를 이해하고, 검색 의도를 파악하고, 쿼리 전략을 세워나가는 이 모든 과정이 결국 사용자의 검색 경험을 완성하게 됩니다. 오늘 배운 내용을 바탕으로, 더 똑똑하고 빠른 Elasticsearch 인프라를 설계해보시길 바랍니다.

자주 묻는 질문 (FAQ)
Q1. Elasticsearch의 인덱스 개수는 많을수록 좋은가요?
아닙니다. 너무 많은 인덱스는 클러스터 상태를 불안정하게 만들고, 자원 낭비를 초래할 수 있습니다. 데이터 특성과 사용 목적에 맞춰 인덱스를 설계하는 것이 중요합니다.

Q2. analyzer는 여러 개 조합해서 사용할 수 있나요?
네, custom analyzer를 만들어 여러 tokenizer와 filter를 조합해 사용할 수 있습니다. 다만 성능 테스트를 병행해야 합니다.

Q3. 검색 속도를 높이기 위해 어떤 캐싱 기법이 있나요?
filter 조건은 자동으로 캐시됩니다. 또한, 검색 결과 자체를 애플리케이션 레벨에서 Redis 등의 캐시로 저장할 수도 있습니다.

Q4. Elasticsearch 쿼리 결과 정렬은 어떻게 설정하나요?
sort 파라미터를 사용해 keyword 타입이나 숫자, 날짜 필드를 기준으로 정렬이 가능합니다. text 타입은 정렬이 되지 않습니다.

Q5. 로그 데이터에 Elasticsearch를 쓰면 괜찮을까요?
네, ELK 스택(Logstash, Elasticsearch, Kibana)은 로그 분석에 특화된 구조입니다. 단, 로그량이 많을 경우 rollover index나 ILM 정책을 적용하시는 것을 추천드립니다.

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다