본문 바로가기

데이터베이스(DA, AA, TA)/NoSQL

[엘라스틱서치] Elasticsearch in action 정리(1)

엘라스틱 서치는 실시간 검색을 제공하면서 집계 기능을 제공하고, 시스템 확장에도 용이하여 로그 시스템 개발에 활용하기 적합하다. 결과에 대한 통계를 제공해서 사용자가 흥미를 느끼는 것에 대해 범위를 좁혀 갈 수 있어야 한다. 데이터 검색을 위해서는 몇가지 이슈를 다루어야 한다. 관련 검색 결과를 내놓고, 통계를 제공하고, 빠른 처리 속도가 필요하다.

 

이러한 경우 엘라스틱서치와 같은 검색엔진을 사용하는 것이 적합하다. 관계형 데이터베이스 위에 검색엔진을 배포해서 색인을 생성하고 SQL 질의 속도를 올릴 수 있다. 검색 기능을 추가하기 위해서 NoSQL 저장소의 데이터 색인을 만들 수 있는데, 엘라스틱서치에서 모두 사용 가능한 기능이다. 앨라스틱서치 클러스터를 색인/검색/관리 하기 위해 데이터를 JSON 형태로 HTTP를 통해 전달할 수 있다.

 

엘라스틱서치는 Apache Lucene(이하 루씬)을 기반으로 만들었다. 루씬은 역색인(각각의 단어가 어느 문서에 속해있는지 목록을 유지하는 자료구조 생성)을 제공한다. 역색인은 관련성에 있어서도 검색엔진에 적합하다. 어떤 단어가 문서 대부분에 있다면 관련성이 적기 때문에 중요하다. (불용어?)

 

검색 성능을 개선하는 것과 관련성 사이에는 균형이 있다. 색인은 디스크 공간을 차지하고 새로운 문서를 추가하는 것이 느려질수 있다. 왜냐하면 데이터를 추가한 후에 색인을 갱신해야 하기 때문이다. 튜닝을 통해서 색인과 검색시 엘라스틱서치를 빠르게 할 수 있다.

 

엘라스틱서치는 기본적으로 결과를 정렬하기 위해 사용하는 relevancy score를 계산하기 위한 몇가지 알고리즘을 사용할 수 있다. 관련성 점수는 검색조건과 일치하는 각각의 문서에 할당된 점수이고 문서가 얼마나 조건과 관련성이 있는지를 나타낸다. 기본적으로 문서의 관련성 점수를 평가하는데 사용하는 알고리즘은 tf-idf(term frequency - inverse document frequency)다.

term frequency - 문서에서 찾고자 하는 단어가 많이 나올수록 높은 점수를 준다.
inverse document frequency - 단어가 다른 문서들 간에 흔치 않으면 각 단어의 가중치가 더 높다.

 

엘라스틱서치를 사용하면 검색을 직관적으로 만들고 exact match 수준을 넘어설 수 있다. (오타 처리 / 파생어 지원 / 통계 사용 / 제안 제공)

 


엘라스틱 서치의 사용 사례

 

 

1. 엘라스틱서치를 기본 백엔드로 사용하기 - 사람들이 블로그 글을 남기도록 하는 웹사이트를 가지고 있지만, 글을 검색하는 기능도 원한다고 하자. 엘라스틱서치를 사용하면 모든 글을 저장하고 질의도 제공할 수 있다.

 

전통적으로 검색엔진은 빠른 연관 검색 기능을 제공하기 위해서 안정된 데이터 저장소 위에 배포한다. 과거에는 검색엔진이 durable storage나 통계 같은 필요한 기능들을 제공하지 않았기 때문이다.

 

다른 NoSQL 저장소와 같이, 엘라스틱 서치는 트랜잭션을 지원하지 않는다. 그러나 트랜잭션이 필요하다면, "source of truth"로서 다른 데이터베이스를 사용하는 것도 고려할 수 있다. 정기적인 백업은 하나의 데이터 저장소를 사용할때 좋은 습관이다.

 

엘라스틱서치가 만능일 수는 없지만 전체 설계에 다른 저장소를 넣어서 복잡도를 증가시킬 가치가 있는지 저울질해봐야 한다. (천평?)

 

 

2. 기존 시스템에 엘라스틱서치 추가하기 - 대량의 데이터를 고속으로 처리하는 시스템을 이미 가지고 있거나 검색을 추가하고 싶을 경우, 전반적인 설계안이 존재한다.

 

엘라스틱서치 자체가 데이터 저장소로서 요구하는 모든 기능을 항상 제공하지 않을지도 모른다. 어떤 경우는 다른 저장소에 부가적으로 사용하길 요구할 수도 있다.

 

예를 들어, 트랜잭션 지원과 복잡한 관계는 엘라스틱서치가 버전1에서는 지원하지 않는다. 그런 기능이 필요하다면 다른 저장소와 함께 엘라스틱서치 사용을 고려할만 하다.

 

또는 이미 동작하는 복잡한 시스템을 갖고 있지만, 검색을 추가하길 원할지 모른다. 엘라스틱서치만 사용할 목적으로 전체 시스템을 다시 설계하는 것은 위험하다. 좀더 안전한 방법은 기존 시스템에 엘라스틱 서치를 추가해서 기존 컴포넌트와 함께 동작하도록 만드는 것이다.

 

어느쪽을 선택하여도 두개의 데이터 저장소를 가지고 있다면, 그것들을 지속적으로 동기화하는 방법을 찾아야 한다. 기본 저장소가 무엇이고 데이터가 어떻게 위치하느냐에 따라서 엘라스틱서치 플러그인을 배포해서 동기화할 수 있다.

 

예를들어, SQL 데이터베이스 기반의 쇼핑몰이 있다고 가정하자. 빠른 관련 검색이 필요해서 엘라스틱서치를 설치한다. 데이터 색인을 위해 동기화 방법을 배포할 필요가 있고, 그것은 플러그인/서비스에 포함된 기능일 수도 있다. 이 동기화 방법은 각각의 상품과 일치하는 모든 데이터를 가져와서 엘라스틱서치에 색인할 수 있고, 각각의 상품은 하나의 Document로 저장한다.

 

한 사용자가 웹페이지에서 검색 조건을 입력하면, 상점 웹페이지 애플리케이션은 그 조건에 대해 엘라스틱서치에 요청한다. 엘라스틱서치는 조건과 일치하고 선호하는 방법으로 정렬된 상품 문서들을 돌려준다. 정렬은 유사도 점수에 기반을 둘 수 있는데, 그것은 사람들이 검색한 단어가 얼마나 많이 상품이나 상품 문서에 저장된 어떤 것에 나타났는지 보여준다. (최근에 상품이 추가되었는지 / 평균평점 등등의 조합)

 

정보의 추가/갱신은 여전히 기본 SQL 데이터베이스에서 이루어지므로 엘라스틱서치를 오로지 검색을 다루기 위해 사용할 수 있다. 엘라스틱서치에 변경 내용을 최신으로 유지하는 것은 동기화 방법에 달렸다.

 

엘라스틱서치를 다른 컴포넌트들과 연동하길 원할때, 필요한 것을 하는 것이 이미 있는지 기존 도구들을 확인할 수 있다. 다음 섹션에서 다루겠지만 엘라스틱서치를 위해 커뮤니티를 만드는 강력한 도구들이 있어서, 자체 컴포넌트를 전혀 만들 필요가 없다.

 

 

3. 기존 시스템의 백엔드로 엘라스틱서치 사용하기 - 엘라스틱서치는 오픈소스이고 쉬운 HTTP 인터페이스를 제공하기 때문에 큰 ecosystem을 가지고 있다. 엘라스틱서치는 로그수집 용도로 대중적이다. 엘라스틱서치에 읽고 쓸수 있는 도구들이 이미 이용가능해서, 원하는 방식으로 동작하도록 도구를 설정하는 것 외에는 별도로 개발할 필요가 없다.

 

예를 들어, 많은 수의 이벤트를 저장하고, 검색하고 분석하는 대규모 로깅 프레임워크를 배포하길 원한다고 가정한다. 로그를 처리해서 결과를 엘라스틱서치에 넘기기 위해서 rsyslog, logstash, apache flume 같은 로깅 도구를 사용할 수 있다. 로그들을 검색하고 분석하기 위해서 키바나를 사용할 수 있다.

 

HTTP 상으로 JSON 객체를 보내 데이터를 보내고 질의를 수행하는 것은 엘라스틱서치와 상호작용하기 위해 무언가를 확장하기 쉽게 해준다. rsyslog같은 syslog 데몬에서부터 Apache ManifoldCF처럼 프레임워크를 연결하는 것까지 말이다. 새로운 애플리케이션을 시작부터 만들거나 기존 애플리케이션에서 검색을 추가하려고 하면, REST API는 엘라스틱서치에 관심이 가게 하는 특징 중 하나이다.

 


엘라스틱서치는 데이터를 색인하고 검색하는 루씬의 기능들에 쉽게 접근하도록 해준다. 색인 측면에서 어떻게 문서를 처리하고 저장할지 많은 선택이 있다. 검색 시에는 여러 질의와 필터들에 대한 선택권을 가진다. 엘라스틱서치는 REST API를 통해 기능을 제공하고 JSON으로 질의하며 대부분의 설정을 같은 API를 통해 조절할 수 있다.

 

루씬이 제공하는 것에 기반을 둬서 엘라스틱서치는 캐시부터 실시간 분석까지 자체적인 고수준 기능을 추가하였다. 다른 수준의 추상화는 Document를 구조화하는 방법이다. 여러 색인을 분리하거나 함께 검색할 수 있고, 하나의 색인에 다른 type의 document를 넣을 수도 있다.

 

기본값으로 클러스터링할 수 있어서 하나의 서버에서 실행할 때도 클러스터라고 부른다. 용량이나 내고장성을 증가시키기 위해 항상 서버를 더 추가할 수 있다. 유사하게 부하가 줄어들면 클러스터에서 쉽게 서버를 뻬서 비용을 절감할 수 있다.

 

 

문서를 색인하는 방식에 있어 중요한 측면이 분석(analysis)이다. 분석을 통해 색인하는 문서에서 추출된 words는 엘라스틱서치에서 terms가 된다. 어떤 텀을 검색할때, 일치하는 문서가 결과에 포함된다. 사용자 입장에서 아마도 정확히 일치하는 것만 검색하길 원하지는 않을 것이다. 아마도 두 단어를 어딘가 포함하는 문서들을 검색하기를 원할 것이다.

 

기본 분석기는 우선 공백이나 쉼표같은 공통 단어 분리기(common word separator)에 의해 글을 단어들로 나눈다. 그 다음에 단어들을 소문자로 바꾸어 텀을 만들어낸다. 많은 분석기가 존재하며 직접 만들 수도 있다.

 

데이터는 문서로 이루어져 있다. 기본 설정으로 엘라스틱서치는 문서를 있는 그대로 저장하고 분석을 통해 파생된 텀들을 역색인에 넣어서 모든 중요한 것들을 빠르게 관련된 것들을 검색할 수 있게 한다. 왜 엘라스틱서치가 문서 기반이고 어떻게 타입/색인으로 문서를 그룹으로 나누는지도 알아본다. 분석은 색인과 검색 시점에 텍스트를 단어로 쪼갠다.

 

분석은 색인과 검색 시점에 텍스트를 단어로 쪼갠다.

 

데이터를 레코드나 행으로 저장하는 관계형 데이터베이스와는 다르게 엘라스틱서치는 데이터를 문서단위로 저장한다. 그러나 두가지 개념은 어느정도 유사하다. 테이블의 행에는 column이 있고, 각 column은 행마다 값을 가진다. 같은 방식으로 문서마다 키와 값을 가진다. 단, 엘라스틱서치에서는 문서가 계층적일 수 있어서 데이터베이스의 행보다 유연하다는 것이다. 이러한 유연성은 논리적으로 하나의 entity에 속한 데이터를 다른 테이블의 다른 행에 유지하는 것과는 반대로 모두 같은 문서에 유지하도록 하므로 중요하다. 예를 들어, 블로그 기사를 저장하는 가장 쉽고 빠른 방법은 하나의 글에 속한 모든 데이터를 하나의 문서에 유지하는 것이다.

 


엘라스틱서치를 실행할때, 로그들을 보면서 무슨의미인지 파악해보자. 

[INFO ][o.e.n.Node               ] [ZmOnXiV] version[6.8.1], pid[89531], build[oss/tar/1fad4e1/2019-06-18T13:16:52.517138Z], OS[Mac OS X/10.14.5/x86_64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_171/25.171-b11]

[INFO ][o.e.t.TransportService   ] [ZmOnXiV] publish_address {127.0.0.1:9300}, bound_addresses {[::1]:9300}, {127.0.0.1:9300}

[INFO ][o.e.c.s.MasterService    ] [ZmOnXiV] zen-disco-elected-as-master ([0] nodes joined), reason: new_master {ZmOnXiV}{ZmOnXiVvRde-cAYixqQW0w}{qbJ6S72KQ6a0vUaE7TkLWA}{127.0.0.1}{127.0.0.1:9300}

 

[INFO ][o.e.c.s.ClusterApplierService] [ZmOnXiV] new_master {ZmOnXiV}{ZmOnXiVvRde-cAYixqQW0w}{qbJ6S72KQ6a0vUaE7TkLWA}{127.0.0.1}{127.0.0.1:9300}, reason: apply cluster state (from master [master {ZmOnXiV}{ZmOnXiVvRde-cAYixqQW0w}{qbJ6S72KQ6a0vUaE7TkLWA}{127.0.0.1}{127.0.0.1:9300} committed version [1] source [zen-disco-elected-as-master ([0] nodes joined)]])

 

[INFO ][o.e.h.n.Netty4HttpServerTransport] [ZmOnXiV] publish_address {127.0.0.1:9200}, bound_addresses {[::1]:9200}, {127.0.0.1:9200}

9300 포트는 transport라 부르는 노드간 통신을 위해 기본으로 사용된다. REST API 대신 네이티브 자바 API를 사용하면, 이 포트에 연결해야 한다.

 

그 다음라인에서 마스터노드가 선출되고 위 샘플에서의 마스터노드는 ZmOnXiV 노드이다. 기본 방안은 각 클러스터가 어떤 노드가 클러스터에 있고 어디에 모든 샤드들이 있는지 알고 있는 마스터 노드를 가지는 것이다. 마스터가 유효하지 않으면 새로운 노드가 선출된다.

 

9200 포트는 기본으로 HTTP 통신을 위해 사용한다. 이 포트를 통해 REST API를 사용하는 애플리케이션이 연결한다. 

 

[INFO ][o.e.n.Node               ] [ZmOnXiV] started
[INFO ][o.e.g.GatewayService     ] [ZmOnXiV] recovered [0] indices into cluster_state

게이트웨이는 데이터를 디스크에 기록해서 노드가 내려가도 데이터를 잃지 않도록 하는 엘라스틱서치 컴포넌트이다. 노드를 시작했을때 게이트웨이는 데이터가 저장돼서 복구할 수 있는지 디스크를 살펴본다. 노드 이름에서 게이트웨이 세팅까지 로그에서 살펴본 대부분 정보는 설정 가능하다.

 

 

 - 엘라스틱서치는 아파치 루씬 위에 만들어진 오픈소스 분산 검색엔진이다.

 - 엘라스틱서치의 일반적인 쓰임은 많은 양의 데이터를 색인해서 전문검색과 실시간 통계를 실행하는 것이다.

 - 엘라스틱서치는 전문검색을 뛰어넘는 특성들을 가지고 있다. (검색의 유사성을 조정해서 검색 제안)

 - 엘라스틱서치는 자동으로 데이터를 샤드에 나눠서 클러스터의 이용 가능한 서버들 간에 균형을 유지한다.

 


엘라스틱서치에서 데이터가 어떻게 조직되는지 이해하기 위해 논리/물리적 데이터 배치에 대해 알아본다.

 

 

논리 배치 - 검색 애플리케이션이 무엇을 알아야 하는가.

색인과 검색을 위해 사용하는 구성 단위는 문서이고, 관계형 데이터베이스의 행과 같이 생각할 수 있다. 문서는 타입으로 나누고, 타입은 테이블이 행을 포함하는 것과 유사하게 문서를 포함한다. 하나 혹은 그 이상의 타입이 하나의 색인에 존재한다. 색인은 가장 큰 컨테이너이며 SQL계에서의 데이터베이스와 유사하다.

 

물리적 배치 - 엘라스틱서치가 뒷단에서 어떻게 데이터를 다루는가.

엘라스틱서치는 각각의 색인을 샤드로 나눈다. 샤드는 클러스터를 구성하는 서버 간에 이동할 수 있다. 보통 애플리케이션은 서버가 하나거나 그 이상이거나 같은 방식으로 엘라스틱서치와 동작하기 때문에 이것에 대해 상관하지 않는다. 그러나 클러스터를 관리할 때는 물리적으로 배치하는 방식이 성능/확장성/가용성을 결정하기 때문에 관심을 갖아야 한다.

 


엘라스틱서치에서 문서의 색인을 만들때 색인 안의 타입에 문서를 넣는다. get-together 색인은 두가지 타입을 포함한다. 이벤트/그룹/타입은 문서들을 포함하게 된다. 색인-타입-ID 조합은 엘라스틱서치에서 하나의 문서를 유일하게 식별한다. 검색할때 특정 색인의 특정 타입에서 문서를 찾거나 여러 타입 혹은 여러 색인에 걸쳐 검색할 수 있다.

 

문서

엘라스틱서치가 문서기반이라고 했는데, 이것은 색인과 검색하는 데이터의 가장 작은 단위가 문서라는 것을 의미한다. 엘라스틱서치에서 문서는 몇가지 중요한 특징을 가지고 있다.

독립적이다. 문서는 필드(name)와 값(Elasticsearch Denver)을 가지고 있다.
계층을 가질 수 있다. 문서 안의 문서로 생각하자. 필드의 값은 위치 필드의 값이 문자열인 것과 같이 단순형일 수 있다. 다른 필드와 값들을 포함할 수 있다. 예를 들어, 위치 필드는 도시/거리주소 등을 모두 포함할 수 있다.
유연한 구조로 되어있다. 문서는 미리 정의된 스키마에 의존하지 않는다.

엘라스틱서치에서 문서는 스키마가 없다고 말한다. 모든 문서가 같은 필드를 가질 필요가 없으므로, 같은 스키마일 필요가 없다는 뜻이다. 필드를 추가하거나 생략할 수 있긴 하지만, 각 필드의 타입은 중요하다. 그 때문에 엘라스틱서치는 모든 필드/타입 그리고 다른 설정에 대한 매핑을 보관하고 있다. 이 매핑은 색인의 타입마다 다르다. 그래서 때로는 엘라스틱서치 용어에서 타입이 매핑 타입으로 불리기도 한다.

 

타입

타입은 테이블이 행에 대한 컨테이너인 것과 같이 문서에 대한 논리적인 컨테이너다. 문서를 다른 타입의 다른 구조에 넣게 된다. 각 타입에서 필드의 정의는 매핑이라고 부른다. 각각의 필드는 서로 다르게 다룬다. 엘라스틱서치가 스키마가 없다면 왜 문서는 타입에 속해 있으며 각 타입은 스키마와 같은 매핑을 포함할까?

 

문서가 꼭 스키마가 필요하지 않기 때문에 Schema-free하다고 말한다. 매핑에 정의한 모든 필드를 포함할 필요는 없고, 새로운 필드를 생성할지도 모른다. 우선, 매핑은 타입에서 지금까지 색인한 모든 문서의 모든 필드를 포함한다. 하지만 모든 문서가 모든 필드를 가질 필요는 없다. 또한 새로운 문서가 매핑한 존재하지 않는 필드와 함께 색인하면, 엘라스틱서치는 자동으로 새로운 필드를 매핑에 추가한다. 필드를 추가하기 위해 타입이 무엇인지 추측한다.

 

매핑 타입은 문서를 논리적으로만 나눈다. 물리적으로 같은 색인의 문서는 속해 있는 매핑 타입에 관련 없이 디스크에 쓰인다.

 

색인(Index)

색인은 매핑 타입의 컨테이너다. 엘라스틱서치 인덱스는 관계형 데이터베이스와 같이 독립적인 문서 덩어리이다. 각각의 색인은 디스크에 같은 파일 집합으로 저장한다. 모든 매핑 타입의 모든 필드를 저장하고, 고유의 설정을 한다. 각 색인은 refresh_interval이라는 설정으로 새로 색인한 문서를 검색할수 있도록 하는 간격을 정의한다.

 

엘라스틱서치를 여러대의 서버에서 실행하고, 모든 서버에서 구동하는 같은 색인의 샤드들을 가질 수 있다.

 

 


데이터가 물리적으로 배치되는지 이해하는 것은 결국 어떻게 엘라스틱서치가 확장하는지 이해하는 것이다. 여러개의 노드가 클러스텅서 함께 동작하는 방법에서 확장성이 어떻게 동작하는지, 데이터가 샤드와 복제로 어떻게 나누어지는지, 어떻게 색인과 검색이 여러 샤드, 복제와 함께 동작하는지 살펴본다.

 

기본값으로 각 인덱스는 5개의 주 샤드로 구성되고, 각각은 하나의 복제를 가지고 있어서 모두 10개의 샤드를 가진다. 복제는 신뢰성과 검색 성능에 기여한다. 기술적으로 샤드는 루씬이 색인에 대한 데이터를 저장하는 파일들의 디렉토리다. 샤드는 또한 엘라스틱서치가 노드에서 노드로 옮기는 가장 작은 단위이다.

 

엘라스틱 다수의 노드는 같은 클러스터에 join할 수 있다. 같은 클러스터 이름을 가진 노드를 시작하고 다른 것들은 기본설정을 사용하는 것만으로도 클러스터를 구성하는데 충분하다. 다수 노드의 클러스터와 함께 같은 데이터는 여러 서버에 분산될 수 있다. 샤드당 최소 하나의 복제를 가지고 있으면, 어느 노드가 사라져도 엘라스틱서치는 여전히 모든 데이터를 제공할 것이다. 엘라스틱서치를 사용하는 애플리케이션이 클러스터에서 하나 이상의 노드를 갖는 것은 투명하다. 기본 설정으로 클러스터의 어떤 노드에든 접속해서 마치 하나의 노드를 가지고 있는 것처럼 전체 데이터와 작업할 수 있다.

 

클러스터링이 성능과 가용성에는 좋긴하지만 단점도 있다. 노드가 충분히 빨라 서로 간에 통신을 할수 있도록 보장해서, split brain이 발생하지 않도록 해야한다.

 

 

문서의 색인을 만들때 무슨일이 일어나는가?

문서는 랜덤한 주샤드와 레플리카에 색인을 만든다. 검색은 주 샤드나 레플리카의 상태에 상관없이 전체 샤드들에서 실행한다.

기본 설정으로 문서의 색인을 만들때, 우선 문서 ID의 해시값에 기반을 둬서 선택한 주 샤드 중 하나에 보낸다. 그 다음 문서는 주 샤드의 모든 복제에 색인하도록 보내진다. 이것은 복제가 주 샤드로부터 데이터를 동기화하도록 유지한다. 동기된 복제는 검색을 제공하고, 주 샤드가 이용 불가한 경우 주 샤드로 자동 승격될 수 있다.

 

 

색인을 검색할 때 무슨일이 일어나는가?

색인을 검색할때, 엘라스틱서치는 해당 색인의 전체 샤드를 찾아야 한다. 샤드가 primary 혹은 replica 일수 있는데, 보통은 주와 레플리카 샤드가 같은 문서를 포함하기 대문이다. 엘라스틱서치는 검색하는 색인의 주 샤드와 레플리카 샤드 간에 검색 로드를 분배해서, 검색 성능과 fault tolerance에 유용하게 replica를 사용한다.

 


엘라스틱서치가 다루는 가장 작은 단위는 샤드다. 하나의 샤드는 하나의 루씬 색인이다. 루씬 색인은 역색인을 포함하는 파일들의 모음(a directory of files)이다. 역색인은 엘라스틱서치가 전체 문서를 찾아보지 않고도 하나의 텀(혹은 단어)를 포함하는 문서를 찾도록 해주는 구조다.

엘라스틱서치 색인은 샤드라는 청크로 나뉜다. 하나의 샤드는 하나의 루씬 색인이기에 엘라스틱서치 색인은 여러개의 루씬 색인으로 구성된다. 이것은 엘라스틱서치가 데이터를 색인하고 검색하기 위한 핵심 라이브러리로 아파치 루씬을 사용하기 때문에 타당하다.

 

샤드는 주 또는 레플리카 샤드일 수 있고, 레플리카는 주 샤드의 정확한 복사본이다. 레플리카는 검색을 위해 사용하거나 본래의 주 샤드를 잃어버렸을때 새로운 주샤드가 될 수 있다. 엘라스틱서치 색인은 하나 이상의 주 샤드와 0개 이상의 레플리카 샤드로 구성된다. 

레플리카는 실행 시간에 추가/삭제 할수 있지만 주 샤드는 그렇지 않다.
레플리카는 항상 생성하거나 제거할 수 있으므로 언제든 샤드당 레플리카의 수를 변경할 수 있다. 이것은 색인이 나누어진 주 샤드의 수에는 적용할 수 없다. 색인을 생성하기 전에 샤드의 수를 결정해야 한다.
너무 적은 샤드는 확장에 제한하고 너무 많은 샤드는 성능에 영향을 준다. 기본 설정인 다섯개는 가장 일반적이다. 동적으로 레플리카 샤드를 추가하거나 삭제하는 것도 가능하다.

 

모든 샤드와 레플리카는 엘라스틱서치 클러스터 안에서 노드에 분산된다. 

 


클러스터에 샤드 분산하기

같은 클러스터에 더많은 노드를 추가할수록 기존 샤드는 모든 노드간에 균형을 이룬다. 결과적으로 해당 샤드들과 동작하는 색인과 검색 요청 모두 추가 노드들의 부가적인 성능의 이득을 얻는다. 클러스터에 노드를 추가하는 방법으로 확장하는 것을 수평적 확장(horizontal scaling)이라고 부른다. 노드를 더 추가하고 요청이 분산되어, 모든 노드가 일을 공유한다. 수평적 확장의 대안은 수직적으로 확장하는 것이다. 엘라스틱서치 노드에 더 많은 자원을 추가하는 것이다. 수직적인 확장이 거의 매번 성능에 도움이 되겠지만 항상 가능하거나 비용 효율적인것은 아니다. 샤드를 사용하는 것은 수평적으로 확장하도록 해준다.

 

 

어떻게 색인과 검색이 여러 노드에 걸쳐 분산된 여러 샤드와 함께 동작할까?

색인 요청을 받은 엘라스틱서치 노드는 우선 문서를 색인할 샤드를 선택한다. 각 문서에 대해 샤드는 그 문서의 ID 문자열을 해싱해서 결정한다. 각 샤드는 동등하게 새로운 문서를 받을 기회와 함께 동등한 해시 범위를 갖는다. 대상 샤드가 결정되면 현재 노드는 샤드를 소유한 노드로 문서를 전달한다. 결과적으로 색인 작업은 샤드의 모든 복제 때문에 다시 수행된다. 색인 명령은 모든 가능한 레플리카가 문서를 색인한 후에 성공적으로 반환된다.

 

검색시에 요청을 받은 노드는 모든 데이터를 포함한 샤드에 요청을 전달한다. 라운드 로빈을 사용해서, 엘라스틱서치는 이용 가능한 주 혹은 레플리카 샤드를 선택하고 검색 요청을 전달한다. 엘라스틱서치는 샤드들로부터 결과를 모아서, 하나의 응답으로 집계 후에 클라이언트 애플리케이션에 응답을 전달한다.

 

클러스터의 모든 노드가 같은 하드웨어와 소프트웨어 설정을 가지고 같은 성능을 제공한다면, 기본값으로 주와 레플리카 샤드가 라운드 로빈으로 검색 요청을 받는다. 그러한 경우가 아니라면, 데이터를 조직하고 샤드를 설정해서 느린 노드가 병목이 되는 것을 방지할 수도 있다.

 

검색 요청은 전체 데이터를 포함한 주/레플리카 샤드에 전달된다. 그 후에 결과를 집계하여 클라이언트에 보낸다.

 

엘라스틱서치 문서 URI

 

Head, kopf, Marvel를 통해 브라우저로 엘라스틱서치 사용하기

엘라스틱서치 헤드(Head) - 이 도구를 엘라스틱서치 플러그인, 독립형 HTTP 서버, 파일 시스템에서 열 수 있는 웹페이지로 설치할 수 있다. HTTP 요청을 보낼 수 있지만, 헤드는 어떻게 샤드가 클러스터에 분산되는지 보여주는 모니터링 도구로써 가장 유용하다.

엘라스틱서치 코프(kopf) - 모니터링과 요청을 보내기 좋다는 점에서 헤드와 유사하다. 파일 시스템에서 웹페이지 혹은 엘라스틱서치 플러그인으로 실행한다. 헤드와 코프 둘다 빠르게 발전해서, 비교가 유의미하지 않다.

마블(Marvel) - 엘라스틱서치를 위한 모니터링 툴이다. 마블이 센스(Sense)라고 하는 엘라스틱서치에 요청을 보내는 그래픽 방법 또한 제공한다. 센스는 자동 완성을 제공해서 학습하는데 유용하다. (상용제품이다.)

 


색인과 매핑 타입 생성

엘라스틱서치를 설치하고 도큐먼트를 색인하기 위해 curl 명령을 실행할때 돌아오는 응답을 살펴보자.

색인은 이전에 존재하지 않았다. get-together라는 색인을 생성하는 어떤 명령을 준 적이 없다.
매핑을 이전에 정의하지 않았다. 문서의 필드를 정의하는 group이라는 매핑타입을 정의하지 않았다.

curl 명령은 엘라스틱서치가 자동으로 get-together 색인을 추가하고 group 타입을 위한 새로운 매핑도 생성하기 때문에 동작한다. 매핑은 필드 정의를 문자열로 포함한다. 엘라스틱서치는 기본값으로 이 모든 것을 처리해서 추가 설정 없이 색인을 시작하도록 해준다. 필요하다면 이러한 기본 동작을 변경할 수 있다.

 

 

매핑 이해하기

매핑은 새 문서와 함께 자동으로 생성되고, 엘라스틱서치는 자동으로 name과 organizer 필드를 문자열로 감지한다. 새로운 문서를 다른 새필드와 추가하면, 엘라스틱서치는 타입도 추측하고 새로운 필드를 매핑에 덧붙인다.

 

현재 매핑을 보기 위해 색인의 _mapping endpoint에 HTTP GET을 요청하면 된다. 이것은 해당 색인의 모든 타입에 대한 매핑을 보여주지만, _mapping 종단 아래 타입 이름을 명시해서 특정 매핑을 얻을 수 있다.

 


엘라스틱서치 설정하기

엘라스틱서치는 개발자 친화적인 초기설정을 가지고 있다. 어떤 설정 변경없이 테스트 서버에 색인과 검색을 할 수 있다. 엘라스틱서치는 자동으로 색인을 생성하고 문서에서 새로운 필드의 타입을 알아낸다.

 

엘라스틱서치는 또한 쉽고 효율적으로 확장한다. 이것은 대규모 데이터나 요청을 다루고 있을때 중요한 특징이다. 확장은 설정을 변경하지 않고도 가능하지만 아래 설정에 대해 알아두면 자연스러운 확장이 가능하다.

elasticsearch.yml에 클러스터 이름 명시 - 엘라스틱 특유의 옵션이 들어가는 주 설정 파일
logging.yml에 로깅 옵션 수정 - 로깅 설정 파일은 엘라스틱서치가 로깅을 위해 사용하는 log4j의 옵션에 관한것
환경 변수나 elasticsearch.in.sh에 메모리 설정 조정 - 이 파일은 엘라스틱서치를 작동시키는 JVM을 설정하기 위한것

 

elasticsearch.yml에 클러스터 이름 명시하기

엘라스틱서치의 주 설정 파일은 압축을 풀을 tar.gz이나 zip 아카이브의 config/ 디렉토리에서 찾을 수 있다. REST API와 같이 설정은 JSON이나 YAML이 될 수 있다. REST API와는 다르게 가장 대중적인 포맷은 YAML이다.

 

기본적으로 새로운 노드는 멀티캐스트로 기존 클러스터를 찾는다. 특정 멀티캐스트 주소를 듣고 있는 모든 호스트에 핑을 보내는 방식이다. 클러스터를 찾으면 새로운 노드는 클러스터 이름이 같은 경우 join하게 된다. 기본 설정을 가진 인스턴스가 클러스터에 join하지 않게 하기 위해서는 클러스터 명을 변경해두는 것이 좋다. 클러스터명을 바꾸려면 elasticsearch.yml에 cluster.name을 코멘트해제 후 변경하면 된다.

 

 

logging.yml을 통해 자세한 로깅 명시하기

엘라스틱서치의 로그를 봐야한다면, 기본 위치는 zip/tar.gz 아카이브를 푼 경로 아래의 logs/ 디렉토리다. 엘라스틱서치 로그 엔트리는 세가지 파일 형태로 구성된다.

cluster-name.log: 엘라스틱서치가 동작 중일때 무슨 일이 일어났는지에 관한 일반적인 정보를 알 수 있다. 쿼리가 실패했거나 새로운 노드가 클러스터에 합류했는지 알 수 있다.
cluster-name_index_search_slowlog.log: 쿼리가 너무 느리게 실행될때 엘라스틱서치가 로그를 남기는 곳이다. 기본으로 쿼리가 0.5초 넘게 걸리면 이곳에 로그를 남긴다.
cluster-name_index_indexing_slowlog.log: 느린 검색 로그와 유사하지만 기본으로 색인 작업이 0.5초 이상 걸리면 로그를 남긴다.

로깅 옵션을 변경하려면 elasticsearch.yml과 같은 위치에 있는 logging.yml 파일을 수정한다. 엘라스틱서치는 log4j를 사용하고, logging.yml의 설정 옵션은 이 로깅 유틸리티에 한정된다.

 

다른 설정처럼 기본값들은 합리적이지만, 좀더 자세한 로그를 남기고 싶다면 rootLogger를 변경할 수도 있을 것이다. 기본값으로 로깅 레벨은 INFO이고, INFO나 그 이상의 모든 이벤트를 남긴다.

 


실서비스에서는 얼마나 많은 ES에 메모리를 할당할까?

서버에 엘라스틱서치를 단독으로 실행한다면 전체 RAM의 절반을 ES_HEAP_SIZE에 할당하는 것으로 시작한다. 다른 애플리케이션이 큰 메모리가 필요하다면 더 적은 값을 시도한다. 나머지 절반은 운영체제의 캐시로 사용해서 저장한 데이터가 빠르게 접근되도록 한다. 이러한 경험 법칙을 넘어서 엘라스틱서치가 얼마나 많은 메모리가 있어야 하는지 보기 위해 클러스터를 모니터링하면서 테스트를 실행해야 한다.

 

curl -H 'Content-Type: application/json' -X GET 'http://localhost:9200/_cat/shards?v'

index        shard prirep state      docs store ip        node

get-together 1     p      STARTED       0  261b 127.0.0.1 ZmOnXiV

get-together 1     r      UNASSIGNED

get-together 4     p      STARTED       0  261b 127.0.0.1 ZmOnXiV

get-together 4     r      UNASSIGNED

get-together 3     p      STARTED       1 4.1kb 127.0.0.1 ZmOnXiV

get-together 3     r      UNASSIGNED

get-together 2     p      STARTED       0  261b 127.0.0.1 ZmOnXiV

get-together 2     r      UNASSIGNED

get-together 0     p      STARTED       0  261b 127.0.0.1 ZmOnXiV

get-together 0     r      UNASSIGNED

.kibana_1    0     p      STARTED       2 9.3kb 127.0.0.1 ZmOnXiV

 

  • 엘라스틱서치는 기본적으로 문서기반이고, 확장성이 있으며, 스키마는 자유롭다.
  • 기본 설정으로 클러스터를 형성할 수는 있지만, 다음 단계로 넘어가기 전에 그것 중 최소 몇가지는 조정해야 한다.
  • 색인 요청은 기본 샤드들에 분산되고 기본 샤드의 레플리카로 복제된다.
  • 검색은 전체 데이터 집합 간에 순차 순환 대기 방식으로 처리되고, 그것들이 샤드나 복제를 구성한다. 검색 요청을 받은 노드는 각 샤드들로부터 부분 결과를 집계하고, 그 결과들을 애플리케이션에 반환한다.
  • 클라이언트 애플리케이션은 각 색인이 조각났다거나 클러스터가 무엇을 찾는지 알지 못할 것이다. 그것은 오직 색인/타입/문서ID 만 신경쓴다. 문서들의 색인을 만들고 검색하기 위해 REST API를 사용한다.
  • 새로운 문서를 보내고 HTTP 요청의 JSON 페이로드로 파라미터를 검색할 수 있다. 그리고 JSON 응답을 결과와 함께 돌려받을 것이다.

엘라스틱서치 UPDATE 전략 : 버전 관리로 동시성 제어 구현

도잇에 다수 변경을 실행한다면 동시성 문제에 맞닥들일 수 있다. 하나의 변경이 원본 도큐먼트를 가져와서 변경 사항을 적용하는 동안 또 다른 하나의 변경이 도큐먼트를 다시 색인하게 할 수 있다. 동시성 제어 없이는 두번째 재색인은 첫번째 변경의 변경사항을 무효화 할 수 있게 된다.

 

다행히, 엘라스틱서치는 각 도큐먼트의 버전 번호를 이용해서 동시성 제어를 제공한다. 처음으로 색인한 도큐먼트 버전은 1이다. 변경으로 인해 다시 색인하면 버전 번호는 2가 된다. 동시에 다른 변경에 의한 버전 번호가 2가 되었다면 이는 충돌이 발생한 것이고 지금의 변경은 실패한다. 그렇게 하면 변경 작업을 다시 시도할 수 있고 버전 충돌이 없다면 버전 번호는 3이 된다.

 

  1. 도큐먼트를 색인하면 변경(update1)한다.
  2. 백그라운드에서 변경 작업을 시작한 update1은 대기 시간동안 기다린다.
  3. 대기하는 동안 그 도큐먼트를 변경하는 다른 변경 명령(update2)를 만든다. 이 변경은 update1의 원본 도큐먼트에서 가져오는 시점과 이 도큐먼트의 재색인 작업 사이에 발생한다.
  4. Update2 변경을 무효화하는 대신 도큐먼트 버전이 이미 2가 되었으므로 update1이 실패한다. 이 시점에 update1을 재시도할 기회를 갖게 되고, 버전 3에서 변경을 적용한다.

curl -XPOST 'localhost:9200/online-shop/shirts/1/_update' -d '{
  /* Update1은 10초를 대기하고 백그라운드로 진입한다. */
  "script": "Thread.sleep(10000); ctx._source.price = 2"
}' &
curl -XPOST 'localhost:9200/online-shop/shirts/1/_update' -d '{
  /* Update2가 10초 이내로 동작하면 버전 번호가 증가하므로 update1을 실패하게 만든다. */
  "script": "ctx._source.caption = \"Knowing Elasticsearch\""
}'

 

이러한 동시성 제어 방법은 병렬 작업을 허용하고, 드물게 나타는 충돌을 추정하기 때문에 Optimistic locking으로 부른다. 이와 반대로 Pessimistic locking이 있는데, 충돌을 야기하는 작업을 우선 막으며 충돌을 방지한다.

 

(1) 충돌시 자동으로 재시도하는 변경

버전 충돌이 발생해도 애플리케이션에서 이를 해소할 수 있다. 만일, 변경 작업이라면 이를 다시 적용하도록 다시 시도해볼 수 있다. 그런데 retry_on_conflict 파라미터를 이용하면, 애플리케이션 도움없이 자동으로 엘라스틱서치가 재시도하도록 할 수 있다.

SHIRTS="localhost:9200/online-shop/shirts"
curl -XPOST "$SHIRTS/1/_update?retry_on_confilct=3" -d '{
  "script": "ctx._source.price = 2"
}'

 

(2) 도큐먼트 색인할때 버전 사용하기

변경 API를 사용하지 않고 도큐먼트를 변경하는 방법은 새 데이터를 index, type 및 ID로 색인하는 것이다. 이는 기존 도큐먼트를 덮어쓰면서도 동시성 제어를 위한 버전 필드를 사용할 수 있다. 이를 위해 HTTP 요청에 버전 파라미터를 지정한다. 이 버전 값은 도큐먼트가 가질 것이라고 기대하는 버전이여야 한다. 예를 들어 버전이 3이라고 예상한다면, 다음과 같이 재색인을 요청할 수 있다.

curl -XPUT 'localhost:9200/online-shop/shirts/1?version=3' -d '{
  "caption": "I know about Elasticsearch Versioning",
  "price": 5
}'

현재 버전이 3과 다르다면 버전 충돌 예외로 실패하게 될것이다. 버전을 이요하면 도큐먼트를 안전하게 색인 똔느 변경할 수 있다.

 

 

외부 버전관리 이용하기

위 내용은 엘라스틱서치 내부 버전 관리 기능이다. 원본 데이터가 다른 데이터 저장소에 있다면, 아마도 버전 관리 시스템을 이미 자체적으로 갖고 있을 경우다. 이경우 도큐먼트도 함께 동기적으로 버전을 유지하고 싶을 수 있다. 외부 버전 관리에 의존하려면 매번 요청할 때 버전 번호에 더해서 version_type=external을 추가하면 된다. 이렇게 하면 엘라스틱서치는 버전 번호를 자동으로 증가시키지 않고, 현재 버전보다 요청으로 받은 버전 번호가 더 높기만 한다면 어떤 버전 번호라도 받아들인다.

 

DOC_URL="localhost:9200/online-shop/shirts/1"
curl -XPUT "$DOC_URL?version=101&version_type=external" -d '{
  "caption": "This time we use external versioning",
  "price": 100
}'