본문 바로가기

언어/Java

ElasticSearch 스터디 정리 (1)

개요

이번에 우연히 elastic search를 회사에서 사용할 기회가 있어서 생겼다. 그래서 팀 안에서 스터디를 만들어서 같이 공부하고 그 내용을 정리하고 내 생각을 기록하려고 한다.

교재는 엘라스틱서치 실무 가이드 - 위키북스 출판 을 사용하지만, 최대한 공식 document를 참고하려고 한다.

ElasticSearch 란?

Elasticsearch는 루씬 기반의 검색 엔진이다. HTTP 웹 인터페이스와 스키마에서 자유로운 JSON 문서와 함께 분산 멀티테넌트 지원 전문 검색 엔진을 제공한다. 일래스틱서치는 자바로 개발되어 있으며 아파치 라이선스 조항에 의거하여 오픈 소스로 출시되어 있다. 공식 클라이언트들은 자바, 닷넷(C#), PHP, 파이썬, 그루비 등 수많은 언어로 이용이 가능하다. 일래스틱서치는 가장 대중적인 엔터프라이즈 검색 엔진으로 그 뒤를 루씬 기반의 Apache Solr가 잇는다.
Elasticsearch는 Logstash라는 이름의 데이터 수집 및 로그 파싱 엔진, 그리고 Kibana라는 이름의 분석 및 시각화 플랫폼과 함께 개발되어 있다. 이 3개의 제품들은 연동 솔루션으로 사용할 목적으로 설계되어 있으며 이를 Elastic Stack (과거 이름: ELK 스택)으로 부른다. - wikipedia

검색 시스템 이해하기

루씬은 자바로 개발된 오픈소스 검색 라이브러리이다. 우리는 분석코드와 텍스트를 루씬에 입력하면 루씬이 인덱스를 구성해서 쉽게 검색을 할 수 있다. Elasticsearch는 이를 이용해서 빠르게 검색할 수 있게 도와준다. 그래서 Elasticsearch는 엄밀히 말하자면 검색엔진이고 nosql이라고 할 수 있다.

VS RDBMS

관계형 데이터 베이스는 검색을 할때 많은 문제점이 있다. 일반적으로 매칭되지 않는 검색은 관계형 데이터 베이스에서 사용되지 않는다.

예로, mysql 에서 검색 기능을 사용하고 싶을 경우, full text index를 지정하면, LIKE 검색이나 MATCH의 성능을 향상할 수는 있다. 기본적으로 stop-word parser 가 작동하는데 기본은 공백으로 텍스트를 분리해서 저장한다.

오늘 쉬고 싶어요 => 오늘|쉬고|싶어요| => 그래서 SELECT * FROM aa WHERE b LIKE "%오늘%" 검색하면 빠르게 검색이 되지만, 싶어로 검색할 경우 full 검색을 하기 때문에 효율적이지 않다.

그리고 또 하나는 n-gram parser 인데, n 개씩 인덱싱 한다. n = 2 인 케이스:
오늘 쉬고 싶어요 => 오늘|늘 | | 쉬|쉬고|고 | 싶|싶어|어요| => 오늘|쉬고|싶어|어요| => 똑같이 싶어로 검색해도 인덱스를 탈수 있다. 하지만 더 많은 저장공간이 필요하다.

하지만 ElasticSearch에서는 analyzer를 통해서 더 유연한 검색이 가능하다. (유의어 검색, 자연어 처리 등..)

용어 정리

ElasticSearch RDBMS
Index Database
Shard Partition
Type Table
Document Row
Field Column
Mapping Schema
Query DSL SQL

데이터 추가, 검색, 삭제, 수정 기능

ElasticSearch는 데이터 처리를 HTTP를 통해 JSON 형식의 RESTful API 호출로 처리한다. 그래서 여러 가지 프로그래밍 언어로 쉽게 연결할 수 있다.

ElasticSearch에서 사용하는 HTTP 메소드 기능 SQL 문법
GET 데이터 조회 SELECT
PUT 데이터 생성 INSERT
POST 인덱스 업데이트, 데이터 조회 UPDATE, SELECT
DELETE 데이터 삭제 DELETE
HEAD 인덱스 정보 확인 (mysql 기준 SELECT information_schema.x ~)

여기를 보면서 PUT/POST가 REST 스럽지 않다고 생각됨. 보통 PUT는 업데이트 할 때 많이 사용된다고 생각했었음. POST에서 데이터 조회 경우 QueryDSL에서 쿼리가 길어질 수 있기 때문이라고 이해는 되지만, 차라리 GET, POST 만 개방하는 형태가 더 이해하기 쉽지 않나 생각함.

ElasticSearch 장점

  • 오픈소스
    • 사용자가 많고, 버그 패치가 빠르게 일어남
  • 전문검색
    • 일반 nosql에서 제공하지 않는 full text 검색 지원
  • 통계분석
    • kibana를 활용해서 로그 시각화 및 분석 가능
  • 비정형화
    • 미리 스키마를 지정하지 않고 다양한 형태 데이터 저장 가능
  • RESTful API
    • 유연하게 어떤 시스템에서도 쉽게 사용 가능
  • 계층 구조
    • 계층 데이터를 JSON 형식 구조로 저장 가능하고, 쉽게 조회도 가능
  • 역 색인
    • 큰 장점. 어떤 키워드가 주워졌을 때 어떤 Document에 포함되었는지 역으로 검색할 수 있다. (보통은 table을 지정하고 검색해야 한다. 그 테이블에 키워드가 있는지 검색하지만, ElasticSearch는 키워드가 주어지면 어떤 table에 이 키워드가 있는지도 확인 가능하다.)
  • 확장성 및 가용성
    • 분산 환경에서 데이터는 샤드라는 작은 단위로 나뉘어 제공되고, 이를 통해 데이터 종류와 성격에 따라서 데이터를 분산해서 빠르게 처리할 수 있다.

ElasticSearch 약점

  • 실시간이 아님
    • 내부 커밋과 플래시 같은 과정으로 인해서 1초 뒤에 검색이 가능하다. => RDBMS와 다르게 데이터 원자성을 보장하지 않는다. slave db를 바라보고 복제 지연이 1초 정도 일어나서 dirty read를 한다고 생각하면 좀 이해하기 쉽다.
  • 트랜잭션 및 롤백 기능 없음
    • 전체적인 클러스트 성능 향상을 위해 시스템적으로 비용이 큰 트랜잭션 및 롤백 기능을 제공하지 않는다. 그래서 항상 데이터 손실을 감안해야 한다.
  • 업데이트 이슈
    • 업데이트가 요청될 경우 기존 문서를 삭제하고 신규 내용으로 문서를 생성한다. 그래서 상대적 비용이 소모된다. 하지만 불변성이라는 이점을 취할 수 있기 때문에 완전히 단점이라고 단정할 수는 없다.

환경 구축

ElasticSearch는 하나의 노드만으로 실행할 수 있다. 이는 SingleMode 또는 TestMode라고 한다. (서비스 목적이라기보다는 테스트 목적이라고 판단됨.) 보통 서비스에서는 최소 3개 이상(홀수 개수, split brain 현상 방지 - 각자가 마스터 노드라고 판단하는데 과반으로 마스터 노드를 재설정하기 때문에 짝수인 경우 반반 투표로 인한 오류가 발생할 수 있음)의 물리적 노드로 클러스트를 구성한다.

공식 페이지 document의 tutorial에서 쉽게 실행 방법을 찾을 수 있다. (너무 쉽게 때문에 생략) 요약하자면,

  1. jdk 설치
  2. elastic search 설치
  3. 설정 후 실행

ElasticSearch 살펴보기

(Notice, 6.3부터 Elastic 라이선스를 가진 ElasticSearch 관련 제품을 설치할 경우 기본적으로 x-pack 포함되어 있음)

기본 개념

Index

  • Index는 데이터 저장 공간
  • RDBMS의 테이블과 비슷한 개념
  • 분산 환경으로 구성하면, 하나의 인덱스가 여러 개 샤드로 나눠져서 다른 노드에 분산 저장되어 관리. 기본값으로 5개 primary shard + 1개 replica shard로 생성
  • Index 이름은 모두 소문자
  • Index 가 없이 Index를 추가하면 자동으로 Index 가 생성(설정으로 막을 수 있음)

Shard

  • RDBMS의 파티션과 비슷한 개념
  • 각 다른 데이터 노드에 분산 저장되어서 손실 위험을 최소화

Document

  • 데이터가 저장되는 최소 단위.
  • RDBMS 의 로우와 비슷한 개념
  • 기본적으로 JSON 포맷 데이터가 저장

Field

  • Document를 구성하기 위한 속성
  • RDBMS의 칼럼과 비슷한 개념, 하지만 field는 좀 더 동적인 느낌
  • 목적에 따라 다수의 데이터 타입을 가질 수 있음

노드의 종류

Master Node

  • 노드 추가 / 제거 등 전반적인 관리
node.master: true
node.data: false
node.ingest: false
search.remote.connect: false

Data Node

  • 실질적인 데이터 저장
  • 검색과 통계 같은 데이터 관련 작업 수행
  • 리소스 이슈로 가능하면 따로 물리적으로 구성
node.master: false
node.data: true
node.ingest: false
search.remote.connect: false

Coordinating Node

  • 사용자 요청만 처리
  • 클러스터 관련 요청은 Master Node로, 데이터 관련 요청은 데이터 노드로 전달
  • gateway server 같은 느낌
  • 단순 라운드로빈 방식으로 요청 분산 => 요 부분은 로직을 수정하거나 할 수 없는 것 같음
  • 너무 많은 코디네이팅 노드를 클러스터에 추가할 경우 전체 클러스터 성능에 부하를 줄 수 있음
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

Machin Learning Node

  • ES 7.x 부터 머신러닝 노드가 추가됨
  • OSS-ONLY로 실행할 경우 node.ml = true 설정으로 실행 실패할 수 있음
xpack.ml = true
node.ml = true

클러스터, 노드, 샤드

책에서 잘 설명되어 있지만, 레플리카 샤드가 좀 이상하다. 프라이머리 샤드 3, 레플리카 샤드1 인경우, 프라이머리 샤드당 하나의 레플리카가 생성된다. (아래 [] 가 프라이머리 샤드 이고, () 가 레플리카 샤드이다)

[1] (0)
[2] (1)
[0] (2)

그러면 프라이머리 샤드3, 레플리카 샤드가 2인 경우는 아래와 같을까?

[1] (0) (2)
[2] (1) (0)
[0] (2) (1)

이렇게만 구성된다면, 2개의 노드가 죽어도 1개의 노드로 데이터 전체를 복구할 수도 있을 것이다. 다만 똑같은 데이터를 * 3 해서 가지고 있기 때문에 낭비가 심하고, 성능에도 이슈가 있을 수도 있겠다.

그렇다면 프라이머리 샤드 3, 레플리카 샤드가 3 이상인 경우는 어떻게 작동할까??

ElasticSearch API에서

  • ElasticSerach에서는 용어에 따른 혼란을 방지하기 위해 색인을 의미할 경우 index를 사용하고, 매핑 정의 공간을 의미할 경우 indicies라는 단어로 표현한다.
  • 스키마리스 기능은 가급적 사용 안 하는 게 좋다. 성능에 밀접한 연관이 있기 때문에 특수한 상황에서만 사용하고, 꼭 사용해야 하는 경우 데이터 구조 및 검색 방식을 확실히 이해하자.
    • 스키마 리스로 진행하는 경우 기본적으로 모든 필드가 text 타입과 keyword 타입을 동시에 제공하는 멀티 필드 기능으로 구성된다. 그래서 성능적 이슈가 있을 수도 있다. (불필요한 필드 생성)
    • keyword 타입 경우 정확히 일치 여부를 체크한다. 혹사 Java String.equals()와 같다.
    • text 타입 경우 분석기로 인해서 분석을 한다. 아버지가 방에 들어가신다 => 아버지가, 아버지 등등 이런 방식으로..
    • 그래서 보통 서비스에서는 auto_create_index를 turn off 처리한다.
  • 인덱스 삭제를 하면 복구할 수가 없다. 그래서 꼭 필요하다면 스냅샷 배치를 고려해야 한다.
  • id를 지정하지 않고 Document를 생성하는 경우, 자동으로 UUID를 통해서 무작위로 생성된다.
  • 검색 API 경우 HTTP URI 파리미터 형태 또는 QueryDSL로 하는데, 보통 QueryDSL을 사용한다. 이게 GraphQL과 비슷한 컨셉
    • QueryDSL에서는 아래와 같은 객체를 조합해서 검색할 수 있다.
      • size: 몇 개의 결과를 반환할지 결정
      • from: 어느 위치붜 반환할지 결정 (size, from 조합으로 mysql LIMIT, cubrid ROWNUM BETWEEN 효과와 비슷)
      • _sorce: 특정 필드만 결과로 반환하고 싶은 경우
      • sort: 정렬 방식
      • query: 검색 조건 정의
      • filter: 필터
  • 집계
    • 버킷 집계
      • 제일 많이 사용하는 집계
      • 어떤 text 나 keyword 또는 히스토그램이나 범위 집계
    • 메트릭 집계
      • 합 / 평균 / 최대 / 최소 같은 통계적 집계
      • 유니크한 결과에 대한 집계
    • 매트릭스 집계
      • 행렬 관련 집계
    • 파이프라인 집계
      • 복잡한 집계를 하기 위해서 파이프 라인을 생성하고 집계