엘라스틱서치 검색엔진 만들기 - ellaseutigseochi geomsaeg-enjin mandeulgi

검색 엔진(search engine)은 웹에서 정보를 수집해 검색 결과를 제공하는 프로그램이다. 검색엔진은 검색 결과로 제공되는 데이터의 특성에 따라 구현 형태가 각각 달라진다. 

검색 시스템(search system)은 대용량 데이터를 기반으로 신뢰성 있는 검색 결과를 제공하기 위해 검색엔진을 기반으로 구축된 시스템을 통칭하는 용어다. 수집기를 이용해 방대한 데이터를 수집하고 이를 다수의 검색엔진을 이용해 색인하고 검색 결과를 UI로 제공한다. 시스템 내부의 정책에 따라 관련도가 높은 문서를 검색 결과의 상위에 배치할 수 있을뿐더러 특정 필드나 문서에 가중치를 둬서 검색의 정확도를 높일 수 있다.

검색 서비스(search service)는 검색엔진을 기반으로 구축한 검색 시스템을 활용해 검색 결과를 서비스로 제공한다.

엘라스틱서치는 엄밀히 말하자면 검색엔진이며, 검색 서비스를 제공하기 위해 엘라스틱서치를 이용해 검색시스템을 구축할수 있다.


검색 시스템의 구성요소

검색 시스템의 기본 구조는 일반적으로 정보를 수집하는 수집기, 수집한 데이터를 저장하는 스토리지, 수집한 데이터를 검색에 적절한 형태로 변환하는 색인기, 색인된 데이터에서 일치하는 문서를 찾는 검색기로 구성된다.

수집기

수집기는 웹사이트, 블로그, 카페 등 웹에서 필요한 정보를 수집하는 프로그램이다. 크롤러(Crawler), 스파이더(Spider), 웜(Worms), 웹로봇(Web Robot) 등으로 불린다. 파일, 데이터베이스, 웹페이지 등 웹상의 대부분의 정보가 수집 대상이다.

스토리지

데이터베이스에서 데이터를 저장하는 물리적인 저장소다. 검색엔진은 색인한 데이터를 스토리지에 보관한다.

색인기

검색엔진이 수집한 정보에서 사용자 질의와 일치하는 정보를 찾으려면 수집된 데이터를 검색 가능한 구조로 가공하고 저장해야 한다. 그 역할을 하는 것이 색인기다. 색인기는 다양한 형태소 분석기를 조합해 정보에서 의미가 있는 용어를 추출하고 검색에 유리한 역색인 구조로 데이터를 저장한다.

검색기

검색기는 사용자 질의를 입력받아 색인기에서 저장한 역색인 구조에서 일치하는 문서를 찾아 결과로 반환한다. 질의와 문서가 일치하는지는 유사도 기반의 검색 순위 알고리즘으로 판단한다. 검색기 또한 색인기와 마찬가지로 형태소 분석기를 이용해 사용자 질의에서 유의미한 용어를 추출해 검색한다. 따라서 사용하는 형태소 분석기에 따라 검색 품질이 달라진다.


관계형 데이터베이스는 SQL 문을 이용해 원하는 정보의 검색이 가능한데 텍스트 매칭을 통한 단순한 검색만 가능하다. 텍스트를 여러 단어로 변형하거나 여러 개의 동의어나 유의어를 활용한 검색은 불가능하다.

반면 검색엔진은 데이터베이스에서는 불가능한 비정형 데이터를 색인하고 검색할 수 있다. 형태소 분석을 통해 사람이 구사하는 자연어의 처리가 가능해지고 역색인 구조를 바탕으로 빠른 검색 속도를 보장한다.

엘라스틱서치 관계형 데이터베이스
인덱스 (Index) 데이터베이스 (Database)
샤드 (Shard) 파티션 (Partition)
타입 (Type) 테이블 (Table)
문서 (Document) 행 (Row)
필드 (Field) 열 (Column)
매핑 (Mapping) 스키마 (Schema)
Query DSL SQL

엘라스틱서치는 기본적으로 HTTP를 통해 JSON 형식의 RESTful API를 이용한다. 엘라스틱서치는 자바로 개발됐지만 여러 가지 프로그래밍 언어를 통해 활용할 수도 있다.

엘라스틱서치에서 사용하는 HTTP 메서드 기능 데이터베이스 질의 문법
GET 데이터 조회 SELECT
PUT 데이터 생성 INSERT
POST 인덱스 업데이트, 조회 UPDATE, SELECT
DELETE 데이터 삭제 DELETE
HEAD 인덱스의 정보 확인 -

다음은 엘라스틱서치를 사용하기 위한 간단한 API 요청 구조다.

curl -X(메서드) http://host:port/(인덱스)/(타입)/(문서 id) -d '{json 데이터}'

엘라스틱서치는 관계형데이터베이스 검색보다 훨씬 유연한다. 역색인되는 문자열 전체를 정책에 따라 소문자 혹은 대문자로 생성하고 쿼리가 들어오는 필터를 색인 시간과 검색 시간에 동일하게 지정한다면 해당 쿼리는 어떠한 문자열(대소문자 구분 없음)이 들어와도 검색이 가능해진다. 또한 엘라스틱서치는 구조화되지 않은 비정형 데이터도 검색이 가능하다. 데이터베이스는 스키마를 미리 정의해야만 데이터 저장과 조회가 가능한 반면 엘라스틱서치는 구조화되지 않은 데이터까지 스스로 분석해 자동으로 필드를 생성하고 저장한다.

엘라스틱서치는 NoSQL의 일종으로서 분류가 가능하고 분산 처리를 통해 실시간에 준하는 빠른 검색이 가능하다. 기존 데이터베이스로는 처리하기 어려운 대량의 비정형 데이터도 검색할 수 있으며, 전문 검색(Full Text)과 구조 검색 모두를 지원한다. 


엘라스틱서치의 장점

오픈소스 검색엔진

엘라스틱서치는 아파치 루씬(Lucene)을 기반으로 개발된 오픈소스 검색엔진이다. 전 세계에서 수많은 사람들이 사용하고 있으며, 버그가 발생할 경우에도 대부분 빠르게 해결된다.

전문 검색

대부분의 데이터베이스는 기본 쿼리 및 색인 구조의 한계로 인해 기본적인 텍스트 검색 기능만 제공한다. 하지만 엘라스틱서치는 좀더 고차원적인 전문 검색(Full Text)이 가능하다. 전문 검색이란 내용 전체를 색인해서 특정 단어가 포함된 문서를 검색하는 것을 말한다. 기존 관계형 데이터베이스는 전문 검색에 적합하지 않지만 엘라스틱서치는 다양한 기능별, 언어별 플러그인을 조합해 빠르게 검색할 수 있다.

통계 분석

비정형 로그 데이터를 수집하고 한곳에 모아 통계 분석을 할 수 있다. 엘라스틱서치와 키바나(Kibana)를 연결하면 실시간으로 쌓이는 로그를 시각화하고 분석할 수 있다.

스키마리스(Schemaless)

데이터베이스는 스키마라는 구조에 따라 데이터를 적합한 형태로 변경하고 저장하고 관리한다. 반면 엘라스틱서치는 정형화되지 않은 다양한 형태의 문서도 자동으로 색인하고 검색할 수 있다.

RESTful API

엘라스틱서치는 HTTP 기반의 RESTful API를 지원하고 요청뿐 아니라 응답에도 JSON 형식을 사용해 개발 언어, 운영체제, 시스템에 관계없이 이기종 플랫폼에서도 이용 가능하다.

멀티테넌시(Multi-tenancy)

서로 상이한 인덱스일지라도 검색할 필드명만 같으면 여러개의 인덱스를 한번에 조회할 수 있다. 이를 이용해 멀티테넌시 기능을 제공할 수 있다.

Document-Oriented

여러 계층의 데이터를 JSON 형식의 구조화된 문서로 인덱스에 저장할 수 있다. 계층 구조로 문서도 한 번의 쿼리로 쉽게 조회할 수 있다.

역색인(Inverted Index)

엘라스틱서치는 루씬 기반의 검색엔진으로 역색인을 지원한다.

확장성과 가용성

엘라스틱서치는 분산 구성으로 대량의 문서를 좀더 효율적으로 처리할 수 있다. 분산 환경에서 데이터는 샤드(Shard)라는 작은 단위로 나뉘어 제공되며, 인덱스를 만들때마다 샤드의 수를 조절할 수 있다. 이를 통해 데이터의 종류와 성격에 따라 데이터를 분산해서 빠르게 처리할 수 있다.

엘라스틱서치의 단점

첫째, '실시간'이 아니다. 일반적으로 색인된 데이터는 통상적으로 1초 뒤에나 검색이 가능해진다. 색인된 데이터는 내부적 커밋(Commit)과 플러시(Flush) 같은 복잡한 과정을 거치기 때문에 실시간이 아니다. 엄밀히 따지자면 준 실시간(Near Realtime)이라고 할 수 있다.

둘째, 트랜잭션과 롤백 기능을 제공하지 않는다. 엘라스틱서치는 기본적으로 분산 시스템으로 구성된다. 전체적인 클러스터의 성능 향상을 위해 시스템적으로 비용 소모가 큰 롤백(Rollback)과 트랜잭션(Transaction)을 지원하지 않기 때문에 최악의 경우 데이터 손실의 위험이 있다.

셋째, 데이터의 업데이트를 제공하지 않는다. 엘라스틱서치는 업데이트 명령이 요청될 경우 기존 문서를 삭제하고 변경된 내용으로 새로운 문서를 생성하는 방식을 사용한다. 이러한 이유로 단순 업데이트에 비해서는 상대적으로 많은 비용이 발생한다. 이를 통해 불변적(Immutable)이라는 이점을 취할 수도 있다.


엘라스틱서치는 기본적으로 분산 시스템을 지향한다. 시스템을 분석하기 위해서는 내부에서 사용되는 용어가 무엇인지부터 파악하는 것이 중요하다. 이를 통해 전체적인 아키텍처를 이해하는데 많은 도움이 된다.

기본 용어

엘라스틱서치의 데이터는 다음과 같이 인덱스, 타입, 문서, 필드 구조로 구성된다.

엘라스틱서치 검색엔진 만들기 - ellaseutigseochi geomsaeg-enjin mandeulgi

인덱스

인덱스(Index)는 데이터 저장 공간이다. 하나의 인덱스는 하나의 타입만 가지며 하나의 물리적인 노드에 여러 개의 논리적인 인덱스를 생성할 수 있다. 검색 시 인덱스 이름으로 문서 데이터를 검색하며, 여러 개의 인덱스를 동시에 검색하는 것도 가능하다. 

엘라스틱서치를 분산환경으로 구성하면 하나의 인덱스가 여러 노드에 분산 저장되어 관리된다. 따라서 분산 처리에 따른 여러 이점을 누릴 수 있다. 엘라스틱서치는 인덱스 생성시 기본적으로 5개의 프라이머리(Primary) 샤드와 1개의 레플리카(Replica) 샤드 세트를 생성한다. 각각의 샤드 수는 인덱스를 생성할때 옵션을 이용해 변경할 수 있다.

인덱스의 이름은 모두 소문자여야 하며 추가, 수정, 삭제, 검색은 RESTful API로 수행할 수 있다. 만약 인덱스가 없는 상태에서 데이터가 추가된다면 데이터를 이용해 인덱스가 자동으로 생성된다.

샤드

색인된 문서는 하나의 인덱스에 담긴다. 인덱스 내부에 색인된 데이터는 물리적인 공간에 여러 개의 파티션으로 나뉘어 구성되는데, 이 파티션을 엘라스틱서치에서는 샤드(Shard)라고 부른다. 엘라스틱서치는 다수의 샤드로 문서를 분산 저장하고 있어 데이터 손실 위험을 최소화할 수 있다.

타입

타입(Type)은 인덱스의 논리적 구조를 의미하며, 인덱스 속성에 따라 분류하기도 한다. 엘라스틱서치 6.0 버전 이하에서는 하나의 인덱스에 여러 타입 설정이 가능했지만 6.1 버전부터는 인덱스당 하나의 타입만 사용할 수 있다.

엘라스틱서치 6.0 이하 버전에서는 특정 카테고리를 분류하는 목적으로 타입이 많이 사용됐다. 예를 들어 music이라는 인덱스가 존재한다면 장르별(Rock, K-pop, Classic)로 분리해 사용했다. 하지만 현재는 타입을 사용하는 것을 권장하지 않기 때문에 장르별로 별도의 인덱스를 각각 생성해서 사용해야 한다.

문서

문서(Document)는 엘라스틱서치에서 데이터가 저장되는 최소 단위다. 기본적으로 JSON 포맷으로 데이터가 저장된다. 데이터베이스와 비교하자면 테이블의 행이 엘라스틱서치의 문서에 해당한다고 볼수 있다. 하나의 문서는 다수의 필드로 구성돼 있는데 각 필드는 데이터의 형태에 따라 용도에 맞는 데이터 타입(Data Type)을 정의해야 한다. 또한 문서는 중첩 구조를 지원하지 하기 때문에 이를 이용해 문서 안에 문서를 지정하는 것도 가능하다.

필드

필드(Field)는 문서를 구성하기 위한 속성이라고 할 수 있다. 일반적으로 데이터베이스의 칼럼(Column)과 비교할 수 있으나 컬럼이 정적(static)인 데이터 타입인 데 반해 필드는 좀 더 동적(dynamic)인 데이터 타입이라고 할 수 있다.

하나의 필드는 목적에 따라 다수의 데이터 타입을 가질 수 있다. 영화 정보를 담아둔 문서에 제목 필드가 있다고 가정한다. 영화 제목을 검색할때 매칭 검색을 하거나 초성을 이용한 검색이 모두 지원되도록 제목 필드는 2개의 데이터 타입을 가져야 한다.

매핑

매핑(Mapping)은 문서의 필드와 필드의 속성을 정의하고 그에 따른 색인 방법을 정의하는 프로세스다. 인덱스의 매핑 정보에는 여러 가지 데이터 타입을 지정할 수 있지만 필드명은 중복해서 사용할 수 없다.


클러스터는 물리적인 노드 인스턴스들의 모임이다. 클러스터는 모든 노드의 검색과 색인 작업을 관장하는 논리적인 개념이라 할 수 있다. 관계형 데이터베이스의 경우 모든 요청을 서버 하나에서 처리해서 결과를 제공하지만 엘라스틱서치의 경우에는 다수의 서버로 분산해서 처리하는 것이 가능하기 때문에 대용량 데이터를 처리할 수 있다.

분산 처리를 위해서는 다양한 형태의 노드들을 조합해서 클러스터를 구성해야 한다. 기본적으로 마스터 노드가 전체적인 클러스터를 관리하고 데이터 노드가 실제 데이터를 관리한다. 엘라스틱서치는 각 설정에 따라 4가지 유형의 노드를 제공한다. 설정에 따라 각 노드는 한가지 유형으로 동작할 수도 있고 여러 개의 유형을 겸해서 동작할 수도 있다.

마스터 노드(Master Node) - 클러스터를 관리한다.
- 노드 추가와 제거 같은 클러스터의 전반적인 관리를 담당한다.
데이터 노드(Data Node) - 실질적인 데이터를 저장한다.
- 검색과 통계 같은 데이터 관련 작업을 수행한다.
코디네이팅 노드(Coordinating Node) - 사용자의 요청만 받아서 처리한다.
- 클러스터 관련 요청은 마스터 노드에 전달하고 데이터 관련 요청은 데이터 노드에 전달한다.
인제스트 노드(Ingest Node) - 문서의 전처리 작업을 담당한다.
- 인덱스 생성 전 문서의 형식을 다양하게 변경할 수 있다.

마스터 노드 (Master Node)

마스터 노드는 인덱스를 생성, 삭제하는 등 클러스터와 관련된 전반적인 작업을 담당한다. 따라서 네트워크 속도가 빠르고 지연이 없는 노드를 마스터 노드로 선정해야 한다. 다수의 노드를 마스터 노드로 설정할 수 있지만 결과적으로 하나의 노드만이 마스터 노드로 선출되어 동작한다.

만약 노드를 마스터 노드 전용으로 설정하고자 한다면 엘라스틱서치 서버의 conf 폴더 안의 elasticsearch.yml 파일을 열고 다음과 같이 설정하면 된다.

node.master: true
node.data: false
node.ingest: false
search.remote.connect: false

데이터 노드 (Data Node)

데이터 노드는 문서가 실제로 저장되는 노드다. 데이터가 실제로 분산 저장되는 물리적 공간인 샤드가 배치되는 노드이기도 하다. 색인 작업은 CPU, 메모리, 스토리지 같은 컴퓨팅 리소스를 많이 소모하기 때문에 리소스 모니터링이 필요하다.

데이터 노드는 가능한 한 마스터 노드와 분리해서 구성하는게 좋다. 단, 색인할 문서의 수가 적으면 함께 구성해도 상관은 없다. elasticsearch.yml 파일을 다음과 같이 수정하면 데이터 노드 전용으로 설정할 수 있다.

node.mster: false
node.data: true
node.ingest: false
search.remote.connect: false

코디네이팅 노드 (Coordinating Node)

데이터 노드, 마스터 노드, 인제스트 노드의 역할을 하지않고, 들어온 요청을 단순히 라운드로빈 방식으로 분산시켜주는 노드다. 구성 방법은 아래와 같다.

node.master: false
node.data: false
node.ingest: false
search.remote.connect: false

인제스트 노드 (ingest node)

색인에 앞서 데이터를 전처리하기 위한 노드다. 데이터의 포맷을 변경하기 위해 스크립트로 전처리 파이프라인을 구성하고 실행할 수 있다. 구성 방법은 다음과 같다.

node.master: false
node.data: false
node.ingest: true
search.remote.connect: false

클러스터, 노드, 샤드

엘라스틱서치 검색엔진 만들기 - ellaseutigseochi geomsaeg-enjin mandeulgi

위 그림을 기준으로 엘라스틱서치 클러스터는 인덱스의 문서를 조회할 때 마스터 노드를 통해 2개의 노드를 모두 조회해서 각 데이터를 취합한 후 결과를 하나로 합쳐서 제공한다.

여러개의 클러스터를 연결해서 구성할 수도 있으며, 이때는 클러스터의 이름으로 각각을 구분한다. 만약 클러스터의 이름이 명시적으로 설정되지 않았다면 엘라스틱서치는 클러스터의 이름을 임의의 문자열로 지정한다. 또한 클러스터에 있는 노드는 실시간으로 추가, 제거가 가능하기 때문에 가용성이나 확장성 측면에서 매우 유연하다.

엘라스틱서치는 장애시 레플리카 샤드를 이용해 샤드를 복구한다. 장애가 발생하면 마스터 노드는 데이터를 재분배하거나 레플리카 샤드를 프라이머리 샤드로 승격시켜 서비스 중단 없는 복구가 가능해진다. 따라서 장애극복(Failover) 상황을 염두에 두고 노드와 샤드의 수를 적절히 구성해야 한다.


엘라스틱서치에서 제공하는 주요 API

엘라스틱서치는 RESTful 방식의 API를 제공하며, 이를 통해 JSON 기반으로 통신한다. 엘라스틱서치는 기본적으로 HTTP 통신을 위해 9200번 포트를 사용한다.

  - 인덱스 관리 API (Indices API): 인덱스 관리

  - 문서 관리 API (Document API): 문서의 추가/수정/삭제

  - 검색 API (Search API): 문서 조회

  - 집계 API (Aggregation API): 문서 통계

문서를 색인하기 위해서는 기본적으로 인덱스를 생성해야 한다. 인덱스를 통해 입력되는 문서의 필드를 정의하고 각 필드에 알맞은 데이터 타입을 지정할 수 있다. 이러한 과정을 통해 좀더 효율적인 색인이 가능해진다.

Index vs. Indices

색인은 데이터가 토큰화되어 저장되는 자료구조를 의미하며, Index라는 단어를 번역한 것이다. 하지만 엘라스틱서치에서는 인덱스라는 용어를 색인과는 다른 의미로 사용한다. 엘라스틱서치에서는 용어에 따른 혼란을 방지하기 위해 색인을 의미할 경우 "Index"라는 단어를 사용하고, 매핑 정의 공간을 의미할 경우 "Indices"라는 단어로 표현한다.

  - Index: 색인 데이터

  - Indexing: 색인하는 과정

  - Indices: 매핑 정보를 저장하는 논리적인 데이터 공간

엘라스틱서치는 사용 편의성을 위해 스키마리스(Schemaless)라는 기능을 제공한다. 문서를 색인하기 위해서는 기본적으로 인덱스를 생성하는 과정이 필요한데 인덱스를 생성하는 과정없이 문서를 추가하더라도 문서가 색인되도록 지원하는 일종의 편의 기능이다. 엘라스틱서치는 최초 문서가 색인될때 인덱스의 존재 여부를 확인하고 만약 인덱스가 존재하지 않는다면 문서를 분석해서 문서가 색인될 수 있게 인덱스를 자동으로 생성한다.

스키마리스 기능은 가급적이면 사용하지 않는다

스키마리스 기능을 이용하면 다양한 형태의 비정형 데이터를 하나의 인덱스로 구성할 수 있다. 하지만 이는 성능과 밀접한 연관이 있기 때문에 특수한 상황에서만 사용해야 한다. 만약 스키마리스를 사용해야 한다면 데이터 구조 및 검색 방식을 확실히 이해해야 한다.

인덱스가 자동으로 생성되면 세부적인 필드 정보가 매핑되지 않는다. 이런식으로 인덱스를 자동 생성할 경우 특정 단어를 검색할 때 검색 결과에서 누락되는 등 문제가 발생할 가능성이 높아진다.

기본적으로 모든 필드가 text 타입과 keyword 타입을 동시에 제공하는 멀티필드 기능으로 구성된다. 하지만 특정 필드는 text 타입만 필요할 수도 있고 keyword 타입만 필요할 수도 있을 것이다. 이러한 경우 데이터 공간의 낭비를 초래한다.

엘라스틱서치는 스키마리스라는 특성에 따라 데이터에 대한 매핑을 자동으로 생성하는 편리한 기능을 제공하지만, 이 기능은 특수한 상황에서만 제한적으로 사용해야 한다. 사실상 실무에서는 대부분 사용하지 않는다. 실무에서는 데이터가 대부분 복잡한 구조를 갖기 때문에 자칫 검색 품질이 떨어지거나 성능상 문제가 발생할 가능성이 커지기 때문이다.

예를 들어, "아버지가 방에 들어 가신다"와 같은 문장을 색인한다고 가정한다. 스키마리스를 이용해 색인한다면 기본적으로 text 타입의 Standard Analyzer를 사용하는 데이터 타입이 정의될 것이다. 이러한 경우 해당 분석기는 "아버지가", "방에", "들어", "가신다"라는 토큰으로 분리되어 텀(Term)이 생성되고 검색시 "아버지"라는 키워드가 입력되더라도 해당 문서는 검색되지 않을 것이다. 검색을 위해서는 Standard Analyzer가 분리한 토큰 그대로 "아버지가"라는 키워드를 입력해야만한다.

Standard Analyzer로 분리된 토큰
"아버지가", "방에", "들어", "가신다"

원하는 결과를 얻기 위해서는 한글 형태소를 분석하는 분석기를 사용하도록 데이터 타입을 직접 정의해야 한다. 원하는 결과를 얻기 위해서라도 스키마리스 방식의 사용을 지양하고 반드시 인덱스를 직접 정의해서 사용하는 습관을 들이는 것이 좋다.

스키마리스 기능을 사용하고 싶지 않더라도 인덱스를 설정하지 않고 실수로 데이터를 색인하면 엘라스틱서치는 인덱스를 자동으로 생성한다. 이는 자칫 시스템의 안정성을 해칠 수도 있다. 이러한 실수를 방지하기 위해 스키마리스 기능을 명시적으로 사용하지 않도록 설정하는 것이 가능하다.

노드 설정 파일에서 action.auto_create_index를 false로 설정할 경우 자동으로 인덱스가 생성되지 않는다. 또는 인덱스별로 제공되는 index.maapper.dynamic 설정을 false로 설정하면 특정 칼럼의 자동 매핑 생성을 비활성화할 수 있다.

엘라스틱서치 검색엔진 만들기 - ellaseutigseochi geomsaeg-enjin mandeulgi

인덱스 관리 API

인덱스 관리 API는 인덱스를 관리하기 위한 API이다. 이를 이용해 인덱스를 추가하거나 삭제할 수 있다. 인덱스 관리 API는 HTTP 메서드를 지정하는 방식을 통해 사용할 수 있다.

인덱스를 생성할때는 매핑이라는 세부 설정을 이용할 수 있는데 매핑은 문서와 문서에 포함된 필드, 필드 타입 등을 세세하게 지정하는 것이 가능한 설정 방식이다. 인덱스 생성시 이러한 매핑 정보를 추가할 수 있다. 한가지 주의할 점은 한번 생성된 매핑 정보는 변경할 수 없다는 점이다. 만약 잘못 생성했거나 변경해야 하는 경우에는 데이터를 삭제하고 다시 색인하는 수밖에 없다.

엘라스틱서치에서는 다양한 형태의 데이터 타입을 제공한다. 단순히 문자열로 저장하고 싶을 경우 keyword 타입을 사용하면 되고 형태소 분석을 원할 경우 text 타입을 사용한다. 예를 들어, 매핑 설정으로 정의된 필드 중 한글 영화 이름(movieNm)과 영문 영화 이름(moviceNmEn)은 검색에 사용되기 때문에 형태소 분석이 가능하도록 text로 타입을 지정한다. 영화 코드(movieCd)와 제작연도(prdtYear)는 숫자 데이터 타입, 개봉일(openDt)은 날짜 타입으로 지정한다. 그 밖의 필드 타입은 모두 keyword 타입이다.

인덱스를 한번 삭제하면 다시는 복구할 수 없기 때문에 인덱스 삭제는 신중하게해야 한다.

문서 관리 API

문서 관리 API는 실제 문서를 색인, 조회, 수정, 삭제를 지원하는 API다. 엘라스틱서치는 기본적으로 검색엔진이기 때문에 검색을 위해 다양한 검색 패턴을 지원하는 Search API를 별도로 제공한다. 하지만 색인된 문서의 ID를 기준으로 한 건의 문서를 다뤄야 하는 경우 문서 관리 API를 사용한다.

문서 관리 API는 다음과 같은 세부 기능을 제공한다.

  - Index API: 한 건의 문서를 색인한다.

  - Get API: 한 건의 문서를 조회한다.

  - Delete API: 한 건의 문서를 삭제한다.

  - Update API: 한 건의 문서를 업데이트한다.

문서 관리 API는 기본적으로 한 건의 문서를 처리하기 위한 기능을 제공하며 Single document API라고도 부른다. 하지만 클러스터를 운영하다 보면 다수의 문서를 처리해야 하는 경우도 종종 발생할 것이다. 이러한 경우에 대비해 Multi-document API도 제공한다.

  - Multi Get API: 다수의 문서를 조회한다.

  - Bulk API: 대량의 문서를 색인한다.

  - Delete By Query API: 다수의 문서를 삭제한다.

  - Update By Query API: 다수의 문서를 업데이트한다.

  - Reindex API: 인덱스의 문서를 다시 색인한다.

ID를 지정하지 않고 문서를 생성

_id 값을 직접 지정하지 않고 URI에 인덱스명과 타입만 지정한다면 엘라스틱서치가 자동으로 _id 값을 생성하게 된다. 이때 _id 값은 UUID를 통해 무작위로 생성된다.

이러한 방식이 자칫 편리해 보일 수도 있다. 하지만 무작위로 생성된 Id 값 때문에 해당 문서를 업데이트할때 애로 사항이 생길 수 있다. 예를 들어 엘라스틱서치와 동기화된 데이터베이스의 데이터가 변경됐다고 가정한다. 검색엔진은 해당 데이터베이스와 주기적으로 동기화해야 하기 때문에 변경된 내용을 따라 동기화돼야 할 것이다. 이를 위해서는 엘라스틱서치에 색인된 _id 값을 데이터베이스의 PK(Primary Key) 혹은 식별이 되는 키 값과 매칭한 정보가 어딘가에는 저장되어 관리돼야 한다. 하지만 1천만 건이나 1억건 정도 되는 데이터에 해당 식별 값을 어딘가에 별도로 저장한다는 것은 거의 불가능할 것이다. 그래서 색인된 문서의 _id 값은 업데이트를 고려해서 데이터베이스 테이블의 식별 값과 맞춰 주는 것이 중요하다.

검색 API

엘라스틱서치 검색 API의 사용 방식은 다음과 같이 크게 두가지로 나뉜다.

  1. HTTP URI(Uniform Resource Identifier) 형태의 파라미터를 URI에 추가해 검색하는 방법

  2. RESTful API 방식인 QueryDSL을 사용해 요청 본문(Request Body)에 질의 내용을 추가해 검색하는 방법

Request Body 방식은 URI 방식보다 제약사항이 적기 때문에 현업에서는 Request Body 방식을 선호한다. URI 방식은 간단한 쿼리 검색을 하거나 디버깅할때 간편하게 사용하는 경우에 종종 이용된다.

간단한 표현식이라면 두가지 형식을 섞어서 사용하는 것도 가능하다. 예를 들어, Query를 URI 방식으로 사용하고 나머지 기능을 JSON 형태로 사용해도 된다.

GET /movice/_doc/_search?q=prdtYear:2017&pretty=true
{
    "sort": {
        "movieCd": {
            "order" : "asc"
        }
    }
}

QueryDSL을 사용하면 가독성이 높고, JSON 형식으로 다양한 표현이 가능해진다. Query의 조건을 여러개 만들거나, 통계를 위한 집계(Aggregation) 쿼리 등 복잡한 쿼리를 작성하려면 QueryDSL을 사용하는 것이 좋다. URI로 여러 단계의 구조를 가지는 중첩된 형태 표현하는 것은 불가능하기 때문이다.

URI 방식의 검색 질의

먼저 살펴볼 URI 방식의 검색 질의는 문서 ID인 _id 값을 사용해 문서를 조회하는 방식이다. 결국 URL에 파라미터를 붙여 조회하는 식이다. 다음은 q 파라미터를 사용해 해당 용어와 일치하는 문서만 조회한다.

POST /movie/_search?q=장편

응답결과를 확인하면 JSON 포맷 헤더에는 쿼리가 실행된 총 시간(took)과 결과를 보여준다. _shards에서는 성공적으로 반환한 샤드의 수와 실패한 샤드의 수를 알 수 있다. hits에서는 일치하는 문서의 수와 함께 점수(_score)가 가장 높은 상위 10개의 문서를 보여준다. 검색에 실패한 샤드의 수는 검색시 설정된 time_out에 따라 결정된다. time_out 시간이 초과되면 그때까지 검색된 내용까지만 검색 결과로 반환된다. 따라서 실패한 샤드의 수가 지나치게 많다면 time_out 시간을 적절히 조정해야 한다.

q 파라미터를 사용할때 별도의 필드명을 지정하지 않으면 존재하는 모든 필드를 대상으로 검색을 수행한다. 특정 필드만 조회하고 싶다면 다음 코드와 같이 필드명을 포함해서 요청하면 된다. 다음은 typeNm 필드의 값이 "장편"인 문자열만 검색한 예다.

POST /movie/_search?q=typeNm:장편

Request Body 방식의 검색 질의

URI 검색 질의는 여러 필드를 각기 다른 검색어로 질의하는 것이 어렵다. 쿼리의 조건이 복잡하고 길어지기 때문이다. 이럴때는 JSON 방식으로 질의하는게 좋다. JSON 포맷을 이용해 RESTful 방식으로 질의하면 매우 복잡한 쿼리도 쉽게 표현할 수 있고 여러 조건을 한번에 처리할 수 있다.

POST /{index명}/_search
{
  JSON 쿼리 구문
}

movie 인덱스의 typeNm 필드를 검색하는 예는 다음과 같다.

POST movie/_search
{
  "query": {
    "term": {"typeNm": "장편"}
  }
}

쿼리 구문은 다음과 같이 여러 개의 키를 조합해 객체의 키 값으로 사용할 수 있다.

{
   size: # 몇개의 결과를 반환할지 결정한다(기본값은 10)
   
   from: # 어느 위치부터 반환할지를 결정한다
         # 0부터 시작하면 상위 0~10건의 데이터를 반환한다(기본값은 0)
   _source: # 특정 필드만 결과로 반환하고 싶을때 사용한다.
   
   sort: # 특정 필드를 기준으로 정렬한다.
         # asc, desc의 오름차순, 내림차순 정렬을 지정할 수도 있다.
   
   query: {
      # 검색될 조건을 정의한다. 
   }
   
   filter: {
      # 검색 결과 중 특정한 값을 다시 보여준다.
      # 결과 내에서 재검색할 때 사용하는 기능 중 하나이다.
      # 다만 필터를 사용하게 되면 자동으로 score 값이 정렬되지 않는다.
   }
}

집계 API

과거에는 통계 작업을 위해 루씬이 제공하는 패싯(Facets) 기능을 많이 활용했다. 패싯 기능은 기본적으로 디스크 기반으로 동작했고, 분산 환경에는 최적화되지 않았기 때문에 대용량 데이터의 통계 작업에는 적합하지 않다. 이로 인해 많은 장애가 발생했기 때문이다.

엘라스틱서치에서는 5.0 이후에 패싯 방식의 통계 기능을 제거하고 독자적인 집계(Aggregation) API를 내놓았다. 집계 API는 기본적으로 메모리 기반으로 동작하기 때문에 대용량의 데이터 통계 작업이 가능해졌다.

엘라스틱서치는 집계 API를 통해 기존의 패싯 API로는 하기 어려운 작업을 처리하는 것이 가능해졌다. 쿼리에 사용되는 집계에 따라 수치를 계산하고 동적으로 카운팅하거나 히스토그램 같은 작업 등도 할수 있다. 엘라스틱서치의 집계 API는 각종 통계 데이터를 실시간으로 제공할 수 있는 강력한 기능이다.

데이터 집계

movie 인덱스의 문서를 장르별로 집계해본다. _search API를 사용해 집계 쿼리를 만들고 terms 키워드를 이용해 genreAlt라는 필드의 데이터를 그룹화한다.

POST /movie/_search?size=0
{
  "aggs": {
    "genre": {
      "terms": {
        "field": "genreAlt"
      }
    }
  }
}

집계 결과를 살펴보면 버킷(Buckets)이라는 구조 안에 그룹화된 데이터가 포함돼 있다. 엘라스틱서치의 집계가 강력한 이유 중 하나는 버킷 안에 다른 버킷의 결과를 추가할 수 있다는 점이다. 이러한 특성을 이용해 다양한 집계 유형을 결합하거나 중첩, 조합하는 것이 가능해진다.

다음은 장르별 국가 형태를 중첩해서 보여주는 집계의 예이다.

POST movie/_search?size=0
{
   "aggs": {
      "genre": {"terms": {"field": "genreAlt"}},
      "aggs": {
         "nation": {"terms": {"field": "nationAlt"}}
      }
   }
}

데이터 집계 타입

집계 기능은 서로 조합해 사용할 수 있으며 이를 조합해서 매우 강력한 기능을 제공할 수 있다.

버킷 집계 (Bucket Aggregation) 집계 중 가장 많이 사용한다. 문서의 필드를 기준으로 버킷을 집계한다.
메트릭 집계 (Metric Aggregation) 문서에서 추출된 값을 가지고 Sum, Max, Min, Avg를 계산한다.
매트릭스 집계 (Matrix Aggregation) 행렬의 값을 합하거나 곱한다.
파이프라인 집계 (Pipeline Aggregation) 버킷에서 도출된 결과 문서를 다른 필드 값으로 재분류한다. 즉, 다른 집계에 의해 생성된 출력 결과를 다시 한번 집계한다. 집계가 패싯보다 강력한 이유가 여기에 있다.