Elasticsearch 완전정복 - 엔터프라이즈 검색 엔진 구축 실전 가이드

December 21, 2025

Elasticsearch 완전정복 - 엔터프라이즈 검색 엔진 구축 실전 가이드

Elasticsearch는 Shay Banon이 2010년에 개발한 분산 검색 및 분석 엔진이다. Apache Lucene을 기반으로 하며, RESTful API를 통해 JSON 문서를 저장, 검색, 분석할 수 있다. 단순한 검색 엔진을 넘어서 로그 분석, 실시간 모니터링, 비즈니스 인텔리전스 등 다양한 용도로 활용되며, Elastic Stack(ELK Stack)의 핵심 컴포넌트로 자리잡았다. 현재는 Elastic N.V.가 주도하는 오픈소스 프로젝트로 발전했으며, 전 세계 수많은 기업들이 검색 기능, 로그 분석, 보안 모니터링 등에 Elasticsearch를 사용하고 있다. 이 글은 Elasticsearch의 핵심 개념부터 실전 활용까지, 엔터프라이즈급 검색 시스템을 구축하는 방법을 종합적으로 다룬다.

Elasticsearch가 해결하는 문제들

전통적인 관계형 데이터베이스의 검색 기능은 여러 한계가 있다. 첫 번째는 풀텍스트 검색의 제약이다. LIKE 쿼리를 사용한 검색은 성능이 떨어지고, 부분 일치나 유사도 검색 같은 고급 기능을 제공하기 어렵다. 특히 한글 같은 언어의 형태소 분석이나 동의어 처리 같은 기능은 관계형 데이터베이스만으로는 구현하기 어렵다.

두 번째 문제는 대용량 데이터의 검색 성능이다. 수백만, 수천만 건의 데이터에서 검색을 수행할 때, 관계형 데이터베이스는 인덱스를 활용하더라도 복잡한 쿼리에서는 성능 저하가 발생한다. 특히 여러 필드를 조합한 검색이나 집계 연산을 수행할 때는 더욱 느려진다.

세 번째 문제는 실시간 검색의 어려움이다. 새로운 데이터가 추가되거나 변경될 때, 인덱스를 즉시 갱신하기 어려워서 검색 결과에 반영되는 데 시간이 걸린다. 또한 분산 환경에서 여러 서버에 데이터가 분산되어 있을 때, 통합 검색을 수행하기 어렵다.

네 번째 문제는 로그 분석과 모니터링의 복잡성이다. 대량의 로그 데이터를 실시간으로 수집, 저장, 분석하는 것은 전통적인 데이터베이스로는 부담이 크다. 특히 구조화되지 않은 로그 데이터를 파싱하고 검색하는 것은 더욱 어렵다.

Elasticsearch는 이러한 문제들을 근본적으로 해결한다. Apache Lucene의 강력한 검색 엔진을 기반으로 하여, 풀텍스트 검색, 유사도 검색, 형태소 분석 등 고급 검색 기능을 제공한다. 분산 아키텍처를 통해 수평 확장이 가능하며, 수십억 건의 문서도 빠르게 검색할 수 있다. 실시간 인덱싱을 지원하여 데이터 변경이 즉시 검색 결과에 반영되며, RESTful API를 통해 간편하게 사용할 수 있다.

Elasticsearch의 핵심 개념 이해하기

Elasticsearch의 가장 중요한 개념은 인덱스(Index)다. 인덱스는 유사한 특성을 가진 문서들의 모음으로, 관계형 데이터베이스의 데이터베이스에 해당한다. 인덱스는 여러 샤드(Shard)로 분할되어 여러 노드에 분산 저장되며, 이를 통해 수평 확장과 고가용성을 달성한다.

문서(Document)는 인덱스에 저장되는 기본 정보 단위다. JSON 형식으로 저장되며, 관계형 데이터베이스의 행에 해당한다. 각 문서는 고유한 ID를 가지며, 여러 필드로 구성된다. 필드는 키-값 쌍으로, 문자열, 숫자, 날짜, 객체, 배열 등 다양한 타입을 가질 수 있다.

타입(Type)은 Elasticsearch 7.0 이전에 사용되던 개념으로, 인덱스 내에서 문서를 논리적으로 구분하는 데 사용되었다. 하지만 현재는 하나의 인덱스에 하나의 타입만 사용하는 것을 권장하며, 8.0부터는 완전히 제거되었다.

매핑(Mapping)은 문서의 구조를 정의한다. 각 필드의 데이터 타입, 분석기, 인덱싱 여부 등을 정의하며, 관계형 데이터베이스의 스키마에 해당한다. 동적 매핑을 사용하면 문서를 저장할 때 자동으로 매핑이 생성되지만, 프로덕션 환경에서는 명시적으로 매핑을 정의하는 것이 좋다.

샤드(Shard)는 인덱스를 분할한 단위다. 프라이머리 샤드는 문서를 저장하고, 레플리카 샤드는 프라이머리 샤드의 복사본으로 고가용성과 검색 성능 향상을 제공한다. 샤드 수는 인덱스 생성 시 결정되며, 이후에는 변경할 수 없다.

노드(Node)는 Elasticsearch 클러스터를 구성하는 서버 인스턴스다. 각 노드는 클러스터의 일부로 작동하며, 데이터를 저장하고 검색 요청을 처리한다. 마스터 노드는 클러스터 관리 작업을 수행하고, 데이터 노드는 데이터를 저장한다.

인덱싱 전략과 매핑 설계

효과적인 Elasticsearch 시스템을 구축하려면 인덱싱 전략을 신중하게 설계해야 한다. 첫 번째는 인덱스 템플릿을 활용하는 것이다. 인덱스 템플릿은 특정 패턴을 가진 인덱스에 자동으로 적용되는 설정과 매핑을 정의한다. 로그 데이터를 날짜별로 인덱스를 나누는 경우, 인덱스 템플릿을 사용하면 새로운 인덱스가 생성될 때마다 일관된 설정이 적용된다.

두 번째는 적절한 샤드 수를 결정하는 것이다. 샤드 수가 너무 많으면 오버헤드가 증가하고, 너무 적으면 확장성이 제한된다. 일반적으로 노드당 샤드 수를 20개 이하로 유지하는 것이 좋으며, 각 샤드의 크기는 20GB에서 50GB 사이가 적절하다.

세 번째는 매핑을 명시적으로 정의하는 것이다. 동적 매핑은 편리하지만, 필드 타입이 예상과 다르게 생성될 수 있다. 특히 숫자 필드가 문자열로 인식되면 검색이나 정렬에 문제가 발생할 수 있다. 프로덕션 환경에서는 매핑을 명시적으로 정의하여 이러한 문제를 방지해야 한다.

네 번째는 적절한 분석기를 선택하는 것이다. 분석기는 텍스트를 토큰으로 분리하고 처리하는 역할을 한다. 한국어 검색을 위해서는 한글 형태소 분석기를 사용해야 하며, 영어 검색을 위해서는 표준 분석기를 사용할 수 있다. 커스텀 분석기를 정의하여 특정 요구사항에 맞게 텍스트를 처리할 수도 있다.

다섯 번째는 인덱싱 여부를 신중하게 결정하는 것이다. 모든 필드를 인덱싱하면 검색은 빠르지만 저장 공간이 증가한다. 검색이 필요하지 않은 필드는 인덱싱을 비활성화하여 저장 공간을 절약할 수 있다. stored 속성을 false로 설정하면 원본 데이터를 저장하지 않고 인덱스만 저장할 수 있다.

쿼리 DSL과 검색 전략

Elasticsearch는 강력한 쿼리 DSL(Domain Specific Language)을 제공한다. match 쿼리는 풀텍스트 검색에 사용되며, 분석기를 통해 텍스트를 분석한 후 검색한다. matchphrase 쿼리는 정확한 구문을 검색할 때 사용하며, multimatch 쿼리는 여러 필드에서 동시에 검색할 수 있다.

term 쿼리는 정확한 값을 검색할 때 사용한다. 분석되지 않은 필드에서 정확히 일치하는 문서를 찾을 때 유용하며, 키워드 필드나 숫자 필드에 주로 사용된다. terms 쿼리는 여러 값을 동시에 검색할 수 있다.

bool 쿼리는 여러 쿼리를 조합할 때 사용한다. must, should, must_not, filter 절을 사용하여 AND, OR, NOT 연산을 구현할 수 있다. filter 절은 점수 계산 없이 필터링만 수행하므로, 성능이 중요한 경우에 유용하다.

range 쿼리는 범위 검색에 사용한다. 숫자나 날짜 필드에서 특정 범위의 값을 검색할 때 사용하며, gt, gte, lt, lte 연산자를 사용할 수 있다.

집계(Aggregation)는 데이터를 그룹화하고 통계를 계산하는 기능이다. terms 집계는 특정 필드의 값별로 문서를 그룹화하고, date_histogram 집계는 시간별로 문서를 그룹화한다. metrics 집계는 평균, 합계, 최대값, 최소값 등을 계산할 수 있다.

성능 최적화 기법

Elasticsearch의 성능을 최적화하는 방법은 여러 가지가 있다. 첫 번째는 적절한 샤드 수를 설정하는 것이다. 샤드 수가 너무 많으면 오버헤드가 증가하고, 너무 적으면 확장성이 제한된다. 인덱스당 샤드 수는 데이터 크기와 노드 수를 고려하여 결정해야 한다.

두 번째는 레플리카 수를 조정하는 것이다. 레플리카는 고가용성과 검색 성능 향상을 제공하지만, 저장 공간과 인덱싱 성능에 영향을 준다. 읽기 작업이 많은 경우 레플리카 수를 늘리면 검색 성능이 향상된다.

세 번째는 refresh 간격을 조정하는 것이다. refresh는 인덱싱된 문서를 검색 가능하게 만드는 작업이다. 기본값은 1초이지만, 실시간 검색이 중요하지 않은 경우 간격을 늘리면 인덱싱 성능이 향상된다.

네 번째는 bulk API를 활용하는 것이다. 여러 문서를 한 번에 인덱싱하면 네트워크 오버헤드를 줄이고 성능을 향상시킬 수 있다. 적절한 bulk 크기는 문서 크기와 네트워크 대역폭에 따라 달라지지만, 일반적으로 5MB에서 15MB 사이가 적절하다.

다섯 번째는 쿼리 최적화다. filter 절을 적절히 사용하여 점수 계산을 피하고, 필요한 필드만 조회하여 네트워크 전송량을 줄인다. 또한 쿼리 캐싱을 활용하여 반복적인 쿼리의 성능을 향상시킬 수 있다.

Kibana를 활용한 시각화

Kibana는 Elasticsearch의 데이터를 시각화하고 탐색하는 도구다. 대시보드를 생성하여 여러 시각화를 한 화면에 표시할 수 있으며, Discover 기능을 통해 원시 데이터를 탐색할 수 있다.

시각화는 다양한 차트 타입을 지원한다. 막대 그래프, 선 그래프, 파이 차트, 히트맵 등 다양한 형태로 데이터를 표현할 수 있으며, 필터와 집계를 조합하여 원하는 데이터를 표시할 수 있다.

대시보드는 여러 시각화를 조합하여 종합적인 뷰를 제공한다. 실시간 모니터링, 비즈니스 인텔리전스, 로그 분석 등 다양한 용도로 활용되며, 공유 링크를 통해 다른 사용자와 공유할 수 있다.

Kibana의 Dev Tools는 Elasticsearch 쿼리를 테스트하고 디버깅하는 데 유용하다. 쿼리를 작성하고 실행하여 결과를 즉시 확인할 수 있으며, 인덱스 설정과 매핑도 확인할 수 있다.

Logstash와의 통합

Logstash는 데이터를 수집, 변환, 로드하는 파이프라인 도구다. 다양한 입력 소스에서 데이터를 수집하고, 필터를 통해 데이터를 변환한 후, Elasticsearch에 출력할 수 있다. Logstash는 Ruby로 작성되었으며, JVM에서 실행되므로 메모리를 많이 사용한다.

입력 플러그인은 파일, 데이터베이스, 메시지 큐 등 다양한 소스에서 데이터를 수집한다. 파일 입력은 로그 파일을 모니터링하고, sincedb 파일을 사용하여 읽은 위치를 추적한다. JDBC 입력은 데이터베이스에서 데이터를 가져오며, 주기적으로 쿼리를 실행하여 변경된 데이터를 수집한다. Kafka, RabbitMQ 같은 메시지 큐에서도 데이터를 수집할 수 있다.

필터 플러그인은 데이터를 파싱하고 변환한다. grok 필터는 정규식을 사용하여 로그를 파싱하고, 구조화된 데이터로 변환한다. grok 패턴을 조합하여 복잡한 로그 형식도 파싱할 수 있다. date 필터는 날짜 형식을 변환하고, 타임존을 처리한다. mutate 필터는 필드를 추가, 삭제, 변환할 수 있으며, 데이터 타입을 변경할 수도 있다. geoip 필터는 IP 주소를 지리적 위치 정보로 변환하고, useragent 필터는 User-Agent 문자열을 파싱한다.

출력 플러그인은 처리된 데이터를 다양한 대상에 전송한다. Elasticsearch 출력은 데이터를 Elasticsearch에 인덱싱하고, bulk API를 사용하여 효율적으로 전송한다. 파일 출력은 파일에 저장하고, HTTP 출력은 다른 API로 전송할 수 있다.

Beats는 경량 데이터 수집기로, Logstash보다 가볍고 단순하다. Go로 작성되어 메모리 사용량이 적고, 다양한 플랫폼에서 실행할 수 있다. Filebeat는 로그 파일을 수집하고, Metricbeat는 시스템 메트릭을 수집한다. Packetbeat는 네트워크 트래픽을 분석하고, Heartbeat는 서비스 가용성을 모니터링한다. Beats는 Logstash나 Elasticsearch에 직접 데이터를 전송할 수 있으며, 중간에 Logstash를 거치지 않아도 된다.

Elasticsearch와 Spring Boot 통합

Spring Boot에서 Elasticsearch를 사용할 때는 Spring Data Elasticsearch를 활용하는 것이 일반적이다. Spring Data Elasticsearch는 Repository 인터페이스를 제공하여, Elasticsearch 작업을 간편하게 수행할 수 있게 해준다. @Document 어노테이션으로 엔티티를 정의하고, @Id 어노테이션으로 ID를 지정한다.

ElasticsearchOperations를 사용하면 더 세밀한 제어가 가능하다. 쿼리를 직접 작성하거나, 집계를 수행할 수 있다. 또한 인덱스 생성, 매핑 설정, 문서 인덱싱 등 다양한 작업을 수행할 수 있다.

@Query 어노테이션을 사용하면 메서드 이름으로 쿼리를 자동 생성할 수 있다. 예를 들어, findByTitle 메서드는 title 필드로 검색하는 쿼리를 자동으로 생성한다. 더 복잡한 쿼리는 @Query 어노테이션에 쿼리 문자열을 직접 작성할 수 있다.

Elasticsearch의 REST Client를 직접 사용할 수도 있다. Low Level REST Client나 High Level REST Client를 사용하면, Spring Data의 추상화 없이 Elasticsearch API를 직접 사용할 수 있다.

검색 결과 하이라이팅

검색 결과에서 검색어를 하이라이팅하면 사용자가 관련 부분을 쉽게 찾을 수 있다. highlight 옵션을 사용하면 검색어가 포함된 부분을 강조 표시할 수 있다. 하이라이팅은 HTML 태그나 다른 마커를 사용하여 검색어를 감싸는 방식으로 작동한다.

하이라이팅을 설정할 때는 어떤 필드를 하이라이팅할지, 어떤 태그를 사용할지, 최대 조각 수 등을 지정할 수 있다. 여러 필드를 하이라이팅할 수 있으며, 각 필드마다 다른 설정을 적용할 수 있다.

하이라이팅은 메모리를 사용하므로, 필요한 필드에만 적용하는 것이 좋다. 또한 하이라이팅된 텍스트의 길이를 제한하여 성능을 최적화할 수 있다.

동의어와 검색 품질 향상

동의어 처리는 검색 품질을 향상시키는 중요한 기능이다. “노트북”과 “랩톱”을 같은 것으로 처리하거나, “자동차”와 “차”를 같은 것으로 처리할 수 있다. 동의어 필터를 사용하면 이러한 동의어를 자동으로 처리할 수 있다.

동의어는 파일로 정의하거나 인라인으로 정의할 수 있다. 동의어 파일을 사용하면 관리가 쉽고, 동의어를 추가하거나 수정할 때 인덱스를 재생성하지 않아도 된다. 하지만 동의어 파일을 변경하면 인덱스를 재오픈해야 한다.

동의어를 정의할 때는 방향을 고려해야 한다. 단방향 동의어는 한 방향으로만 매핑되고, 양방향 동의어는 양방향으로 매핑된다. 예를 들어, “노트북”을 “랩톱”으로 매핑하면, “노트북”을 검색할 때 “랩톱”도 찾을 수 있지만, “랩톱”을 검색할 때 “노트북”은 찾을 수 없다.

실시간 검색과 자동완성

실시간 검색을 구현하려면 사용자가 입력하는 동안 검색 결과를 업데이트해야 한다. Elasticsearch의 searchasyou_type 필드를 사용하면 부분 문자열 검색을 효율적으로 수행할 수 있다. 이 필드는 입력된 텍스트의 접두사를 인덱싱하여 빠른 검색을 제공한다.

자동완성 기능을 구현하려면 completion suggester를 사용할 수 있다. completion 필드는 입력된 텍스트의 접두사를 인덱싱하여, 사용자가 입력하는 동안 관련 검색어를 제안할 수 있다. 하지만 completion suggester는 메모리를 많이 사용하므로, 제안할 항목의 수를 제한해야 한다.

n-gram 토크나이저를 사용하면 더 유연한 자동완성을 구현할 수 있다. 텍스트를 n개의 연속된 문자로 분리하여 인덱싱하면, 중간 부분에서도 검색할 수 있다. 하지만 인덱스 크기가 크게 증가하므로 신중하게 사용해야 한다.

집계와 분석 고급 기법

집계는 Elasticsearch의 강력한 분석 기능이다. terms 집계는 가장 일반적인 집계로, 특정 필드의 값별로 문서를 그룹화한다. 하지만 정확한 집계는 메모리를 많이 사용하므로, 큰 데이터셋에서는 근사 집계를 사용하는 것이 좋다.

cardinality 집계는 고유 값의 수를 근사적으로 계산한다. 정확한 값이 필요하지 않은 경우, cardinality 집계를 사용하면 메모리를 크게 절약할 수 있다. 하이퍼로그로그 알고리즘을 사용하여 근사값을 계산하며, 정확도는 precision_threshold로 조정할 수 있다.

percentiles 집계는 백분위수를 계산한다. 응답 시간이나 대기 시간 같은 메트릭을 분석할 때 유용하며, p50, p95, p99 같은 백분위수를 계산할 수 있다. T-Digest 알고리즘을 사용하여 근사값을 계산하며, 메모리 사용량을 조정할 수 있다.

파이프라인 집계는 다른 집계의 결과를 입력으로 받아 추가 처리를 수행한다. 예를 들어, terms 집계의 결과를 입력으로 받아 평균을 계산하거나, 이동 평균을 계산할 수 있다. 파이프라인 집계를 중첩하여 더 복잡한 분석을 수행할 수 있다.

인덱스 템플릿과 동적 인덱스 관리

로그 데이터나 시계열 데이터를 다룰 때는 시간별 또는 날짜별로 인덱스를 나누는 것이 일반적이다. 인덱스 템플릿을 사용하면 특정 패턴을 가진 인덱스에 자동으로 설정과 매핑을 적용할 수 있다. 예를 들어, “logs-2025-12-*” 패턴의 인덱스에 자동으로 로그 인덱스 템플릿이 적용된다.

인덱스 템플릿은 우선순위를 가질 수 있다. 여러 템플릿이 같은 인덱스에 적용될 수 있으며, 우선순위가 높은 템플릿의 설정이 우선 적용된다. 이를 통해 기본 템플릿을 정의하고, 특정 인덱스에만 추가 설정을 적용할 수 있다.

컴포넌트 템플릿은 재사용 가능한 설정 조각이다. 여러 인덱스 템플릿에서 공통으로 사용하는 설정을 컴포넌트 템플릿으로 정의하고, 인덱스 템플릿에서 참조할 수 있다. 이를 통해 설정 중복을 줄이고 유지보수를 쉽게 할 수 있다.

인덱스 별칭을 사용하면 인덱스 이름을 추상화할 수 있다. 여러 인덱스를 하나의 별칭으로 묶어서 검색하거나, 인덱스를 교체할 때 별칭만 변경하면 된다. 이를 통해 인덱스 재인덱싱이나 마이그레이션을 투명하게 수행할 수 있다.

트러블슈팅과 성능 튜닝

Elasticsearch를 운영하다 보면 여러 문제에 마주칠 수 있다. 첫 번째는 클러스터 상태 문제다. 노드가 떨어지거나 네트워크 문제가 발생하면 클러스터 상태가 빨간색이나 노란색으로 변경될 수 있다. 클러스터 상태를 정기적으로 확인하고, 문제가 발생하면 즉시 조치해야 한다. 빨간색 상태는 일부 샤드가 할당되지 않아서 검색이 실패할 수 있음을 의미하고, 노란색 상태는 일부 레플리카가 할당되지 않아서 고가용성이 저하될 수 있음을 의미한다.

두 번째는 샤드 할당 문제다. 샤드가 할당되지 않으면 검색이나 인덱싱이 실패할 수 있다. 디스크 공간 부족, 메모리 부족, 설정 오류 등이 원인일 수 있다. 클러스터 할당 설명 API를 사용하여 할당 실패 원인을 확인할 수 있다. 디스크 사용량이 85%를 초과하면 샤드 할당이 중단되므로, 디스크 공간을 정기적으로 모니터링해야 한다.

세 번째는 느린 쿼리 문제다. 복잡한 쿼리나 큰 데이터셋을 검색할 때는 시간이 오래 걸릴 수 있다. 쿼리를 최적화하거나, 페이징을 사용하여 결과를 나누어 가져와야 한다. 또한 timeout을 설정하여 무한 대기를 방지해야 한다. search_after를 사용하면 커서 기반 페이징을 구현할 수 있어, deep pagination 문제를 해결할 수 있다.

네 번째는 메모리 부족 문제다. 힙 메모리가 부족하면 성능이 저하되고 OutOfMemoryError가 발생할 수 있다. 힙 크기는 물리 메모리의 50% 정도로 설정하는 것이 일반적이며, 나머지는 파일 시스템 캐시에 사용된다. 필드 데이터 캐시나 쿼리 캐시를 모니터링하고, 필요시 캐시를 비워야 한다. 또한 segment를 병합하여 메모리 사용량을 줄일 수 있다.

Elasticsearch와 데이터베이스 동기화

Elasticsearch는 종종 관계형 데이터베이스와 함께 사용된다. 데이터베이스의 변경사항을 Elasticsearch에 동기화하는 방법은 여러 가지가 있다. 첫 번째는 애플리케이션 레벨에서 동기화하는 것이다. 데이터베이스에 쓰기 작업을 수행할 때 동시에 Elasticsearch에도 인덱싱한다. 이 방법은 구현이 간단하지만, 동기화 실패 시 불일치가 발생할 수 있다.

두 번째는 변경 데이터 캡처(CDC)를 사용하는 것이다. 데이터베이스의 변경 로그를 읽어서 Elasticsearch에 동기화한다. Debezium 같은 도구를 사용하면 MySQL, PostgreSQL 등의 변경 로그를 실시간으로 읽어서 Elasticsearch에 동기화할 수 있다. 이 방법은 애플리케이션 코드 변경 없이 동기화할 수 있지만, 인프라가 복잡해진다.

세 번째는 주기적으로 전체 데이터를 동기화하는 것이다. ETL 도구를 사용하여 주기적으로 데이터베이스에서 데이터를 추출하고 Elasticsearch에 로드한다. 이 방법은 구현이 간단하지만, 실시간성이 떨어진다.

동기화 전략을 선택할 때는 실시간성, 일관성, 복잡도 등을 고려해야 한다. 실시간 검색이 중요하면 CDC를 사용하고, 주기적인 동기화로 충분하면 ETL을 사용할 수 있다.

실전 활용 사례와 성공 스토리

많은 기업들이 Elasticsearch를 프로덕션 환경에서 성공적으로 사용하고 있다. GitHub은 코드 검색에 Elasticsearch를 사용하여 수억 건의 코드를 빠르게 검색할 수 있게 했고, 특히 코드의 내용뿐만 아니라 파일 경로, 커밋 메시지, 이슈 등 다양한 메타데이터를 함께 검색할 수 있게 했다. Netflix는 콘텐츠 추천과 검색에 Elasticsearch를 활용하여, 사용자의 시청 이력과 선호도를 분석하고 개인화된 콘텐츠를 추천한다.

Uber는 실시간 위치 추적과 검색에 Elasticsearch를 사용하며, 수백만 명의 드라이버와 라이더의 위치를 실시간으로 인덱싱하고 검색한다. eBay는 제품 검색에 Elasticsearch를 활용하여, 수억 개의 제품을 빠르게 검색하고 필터링할 수 있게 했다. 특히 제품 카테고리, 가격 범위, 브랜드 등 다양한 필터를 조합하여 정확한 검색 결과를 제공한다.

검색 기능 외에도 Elasticsearch는 로그 분석, 보안 모니터링, 비즈니스 인텔리전스 등 다양한 용도로 활용된다. ELK Stack을 구성하여 로그를 수집, 저장, 분석하는 시스템을 구축하거나, APM(Application Performance Monitoring) 도구로 활용하기도 한다. 많은 기업들이 Elasticsearch를 사용하여 애플리케이션 로그를 실시간으로 분석하고, 문제를 조기에 발견하며, 트렌드를 파악한다.

보안 모니터링에서도 Elasticsearch가 널리 사용된다. Elastic Security를 사용하면 네트워크 트래픽, 시스템 로그, 애플리케이션 로그를 분석하여 보안 위협을 탐지할 수 있다. 이상 행위 패턴을 식별하거나, 알려진 공격 시그니처를 검색하여 보안 사고를 조기에 발견할 수 있다.

모니터링과 트러블슈팅

Elasticsearch를 운영할 때는 적절한 모니터링이 필수적이다. 클러스터 상태, 노드 상태, 인덱스 통계 등을 정기적으로 확인하여 문제를 조기에 발견해야 한다. Elasticsearch의 API를 통해 이러한 정보를 조회할 수 있으며, Kibana의 Monitoring 기능을 사용하면 시각적으로 확인할 수 있다.

메모리 사용량을 모니터링하는 것이 특히 중요하다. 힙 메모리가 부족하면 성능이 저하되고, OutOfMemoryError가 발생할 수 있다. JVM 힙 크기는 물리 메모리의 50% 정도로 설정하는 것이 일반적이며, 나머지는 파일 시스템 캐시에 사용된다.

디스크 공간도 정기적으로 확인해야 한다. 인덱스가 계속 증가하면 디스크 공간이 부족해질 수 있으므로, 오래된 인덱스를 삭제하거나 아카이빙하는 전략이 필요하다. 인덱스 라이프사이클 관리(ILM) 정책을 사용하면 자동으로 인덱스를 관리할 수 있다.

느린 쿼리를 식별하는 것도 중요하다. slow log를 활성화하여 느리게 실행된 쿼리를 기록하고, 이를 분석하여 최적화할 수 있다. Profile API를 사용하면 쿼리의 실행 계획을 상세히 분석할 수 있다.

고급 쿼리 패턴과 최적화

복잡한 검색 요구사항을 만족시키기 위해서는 여러 쿼리를 조합해야 한다. bool 쿼리의 must, should, mustnot, filter 절을 적절히 조합하면 복잡한 검색 조건을 구현할 수 있다. must 절은 반드시 일치해야 하는 조건이고, should 절은 일치하면 점수가 높아지는 조건이다. mustnot 절은 제외할 조건이고, filter 절은 점수 계산 없이 필터링만 수행한다.

function_score 쿼리는 검색 결과의 점수를 커스터마이징할 수 있게 해준다. 거리, 인기도, 시간 등 다양한 요소를 점수에 반영하여 더 관련성 높은 결과를 상위에 표시할 수 있다. 예를 들어, 최근에 작성된 문서에 더 높은 점수를 부여하거나, 특정 지역에 가까운 결과에 더 높은 점수를 부여할 수 있다.

nested 쿼리는 중첩된 객체를 검색할 때 사용한다. 일반적인 객체 필드는 평탄화되어 저장되므로, 중첩된 객체의 필드를 개별적으로 검색하기 어렵다. nested 타입을 사용하면 중첩된 객체를 독립적으로 인덱싱하고 검색할 수 있다.

parent-child 관계는 문서 간의 관계를 표현한다. 부모 문서와 자식 문서가 서로 다른 샤드에 저장될 수 있으므로, 관계 쿼리는 성능에 주의해야 한다. 가능하면 nested 타입을 사용하는 것이 더 효율적이다.

분석기와 토크나이저 이해하기

분석기는 텍스트를 검색 가능한 토큰으로 변환하는 과정을 담당한다. 분석기는 문자 필터, 토크나이저, 토큰 필터 세 단계로 구성된다. 문자 필터는 텍스트를 정제하고, 토크나이저는 텍스트를 토큰으로 분리하며, 토큰 필터는 토큰을 변환하거나 필터링한다.

표준 분석기는 대부분의 언어에 적합한 범용 분석기다. 텍스트를 단어 단위로 분리하고, 소문자로 변환하며, 불용어를 제거한다. 하지만 한국어나 일본어 같은 언어에는 적합하지 않다.

한국어 검색을 위해서는 한글 형태소 분석기를 사용해야 한다. Nori, 은전한닢 같은 한글 분석기를 사용하면, “안녕하세요”를 “안녕”, “하세요”로 분리하여 검색할 수 있다. 동의어 필터를 추가하면 “안녕”과 “인사”를 같은 것으로 처리할 수 있다.

커스텀 분석기를 정의하면 특정 요구사항에 맞게 텍스트를 처리할 수 있다. 예를 들어, 이메일 주소를 도메인과 사용자명으로 분리하거나, 특정 패턴의 텍스트를 특별하게 처리할 수 있다.

인덱스 라이프사이클 관리

인덱스는 시간이 지나면서 계속 증가한다. 로그 데이터의 경우 날짜별로 인덱스를 나누면, 오래된 인덱스를 삭제하거나 아카이빙하기 쉽다. 인덱스 라이프사이클 관리(ILM) 정책을 사용하면 인덱스를 자동으로 관리할 수 있다.

ILM 정책은 hot, warm, cold, delete 단계로 구성된다. hot 단계는 활발하게 읽고 쓰는 단계이고, warm 단계는 읽기만 하는 단계다. cold 단계는 거의 접근하지 않는 단계이고, delete 단계는 인덱스를 삭제하는 단계다.

각 단계에서 인덱스 설정을 변경할 수 있다. warm 단계에서는 레플리카 수를 줄이거나, cold 단계에서는 압축하거나 읽기 전용으로 만들 수 있다. 이를 통해 저장 공간을 절약하고 성능을 최적화할 수 있다.

보안 고려사항

Elasticsearch는 기본적으로 보안 기능이 비활성화되어 있다. 프로덕션 환경에서는 반드시 보안을 활성화해야 한다. X-Pack Security를 사용하면 인증, 권한 부여, 암호화 등의 보안 기능을 사용할 수 있다.

인증은 사용자 이름과 비밀번호를 사용하거나, LDAP, Active Directory 같은 외부 인증 시스템과 통합할 수 있다. API 키를 사용하여 프로그램적으로 접근할 수도 있다. OAuth나 SAML 같은 표준 프로토콜도 지원한다.

권한 부여는 역할 기반 접근 제어(RBAC)를 통해 구현한다. 역할을 정의하고 사용자에게 역할을 할당하여, 특정 인덱스나 작업에 대한 접근을 제한할 수 있다. 필드 레벨 보안을 사용하면 특정 필드에 대한 접근을 제한할 수 있다.

네트워크 보안도 중요하다. Elasticsearch는 기본적으로 모든 네트워크 인터페이스에서 접근 가능하므로, 방화벽을 설정하여 필요한 IP만 접근할 수 있도록 해야 한다. 또한 TLS/SSL을 사용하여 네트워크 통신을 암호화해야 한다.

데이터 암호화도 고려해야 한다. 저장된 데이터를 암호화하거나, 특정 필드를 마스킹하여 민감한 정보를 보호할 수 있다. 예를 들어, 신용카드 번호나 주민등록번호 같은 정보는 마스킹하여 저장할 수 있다.

실시간 검색과 자동완성

실시간 검색을 구현하려면 사용자가 입력하는 동안 검색 결과를 업데이트해야 한다. Elasticsearch의 searchasyou_type 필드를 사용하면 부분 문자열 검색을 효율적으로 수행할 수 있다. 이 필드는 입력된 텍스트의 접두사를 인덱싱하여 빠른 검색을 제공한다.

자동완성 기능을 구현하려면 completion suggester를 사용할 수 있다. completion 필드는 입력된 텍스트의 접두사를 인덱싱하여, 사용자가 입력하는 동안 관련 검색어를 제안할 수 있다. 하지만 completion suggester는 메모리를 많이 사용하므로, 제안할 항목의 수를 제한해야 한다.

n-gram 토크나이저를 사용하면 더 유연한 자동완성을 구현할 수 있다. 텍스트를 n개의 연속된 문자로 분리하여 인덱싱하면, 중간 부분에서도 검색할 수 있다. 하지만 인덱스 크기가 크게 증가하므로 신중하게 사용해야 한다.

집계와 분석 고급 기법

집계는 Elasticsearch의 강력한 분석 기능이다. terms 집계는 가장 일반적인 집계로, 특정 필드의 값별로 문서를 그룹화한다. 하지만 정확한 집계는 메모리를 많이 사용하므로, 큰 데이터셋에서는 근사 집계를 사용하는 것이 좋다.

cardinality 집계는 고유 값의 수를 근사적으로 계산한다. 정확한 값이 필요하지 않은 경우, cardinality 집계를 사용하면 메모리를 크게 절약할 수 있다. 하이퍼로그로그 알고리즘을 사용하여 근사값을 계산한다.

percentiles 집계는 백분위수를 계산한다. 응답 시간이나 대기 시간 같은 메트릭을 분석할 때 유용하며, p50, p95, p99 같은 백분위수를 계산할 수 있다. T-Digest 알고리즘을 사용하여 근사값을 계산한다.

파이프라인 집계는 다른 집계의 결과를 입력으로 받아 추가 처리를 수행한다. 예를 들어, terms 집계의 결과를 입력으로 받아 평균을 계산하거나, 이동 평균을 계산할 수 있다.

트러블슈팅과 성능 튜닝

Elasticsearch를 운영하다 보면 여러 문제에 마주칠 수 있다. 첫 번째는 클러스터 상태 문제다. 노드가 떨어지거나 네트워크 문제가 발생하면 클러스터 상태가 빨간색이나 노란색으로 변경될 수 있다. 클러스터 상태를 정기적으로 확인하고, 문제가 발생하면 즉시 조치해야 한다.

두 번째는 샤드 할당 문제다. 샤드가 할당되지 않으면 검색이나 인덱싱이 실패할 수 있다. 디스크 공간 부족, 메모리 부족, 설정 오류 등이 원인일 수 있다. 클러스터 할당 설명 API를 사용하여 할당 실패 원인을 확인할 수 있다.

세 번째는 느린 쿼리 문제다. 복잡한 쿼리나 큰 데이터셋을 검색할 때는 시간이 오래 걸릴 수 있다. 쿼리를 최적화하거나, 페이징을 사용하여 결과를 나누어 가져와야 한다. 또한 timeout을 설정하여 무한 대기를 방지해야 한다.

네 번째는 메모리 부족 문제다. 힙 메모리가 부족하면 성능이 저하되고 OutOfMemoryError가 발생할 수 있다. 힙 크기를 적절히 설정하고, 필드 데이터 캐시나 쿼리 캐시를 모니터링해야 한다.

결론

Elasticsearch는 현대 웹 애플리케이션에서 필수적인 검색 및 분석 엔진이다. 강력한 검색 기능과 분산 아키텍처를 통해 수평 확장이 가능하며, 수십억 건의 문서도 빠르게 검색할 수 있다. 풀텍스트 검색, 집계, 실시간 분석 등 다양한 기능을 제공하며, ELK Stack과 함께 사용하면 종합적인 데이터 분석 플랫폼을 구축할 수 있다.

하지만 Elasticsearch도 만능은 아니다. 메모리와 디스크 공간을 많이 사용하며, 설정과 튜닝이 복잡하다. 또한 실시간 쓰기 작업이 많은 경우 성능이 저하될 수 있다. 프로젝트의 특성과 요구사항을 고려하여 Elasticsearch를 적절히 활용하는 것이 중요하다.

Elasticsearch를 효과적으로 사용하려면 인덱싱 전략 수립, 쿼리 최적화, 성능 튜닝, 모니터링 등 다양한 측면을 고려해야 한다. 또한 분석기 선택, 인덱스 라이프사이클 관리, 보안 설정 같은 고급 주제도 중요하다. 이 글에서 다룬 내용들을 바탕으로, 자신의 프로젝트에 맞는 Elasticsearch 시스템을 구축할 수 있을 것이다.


Written by Jeon Byung Hun 개발을 즐기는 bottlehs - Engineer, MS, AI, FE, BE, OS, IOT, Blockchain, 설계, 테스트