
도큐먼트와 인덱스
엘라스틱 서치는 인덱스(index)와 도큐먼트(document)로 이루어져 있습니다. 엘라스틱서치를 이해하기 위해서는 이 두가지 개념이 무척이나 중요한데 인덱스는 데이터를 저장하는 논리적 구분자로, 도큐먼트가 실제 데이터를 JSON 형식으로 저장하게 됩니다.
보통 운영상에서 프로젝트를 구현할 때 하나의 클러스터를 생성하게 될것입니다. 그리고 그 클러스터 내부에는 데이터의 성격에 따라 여러 개의 인덱스가 들어있을 것입니다. 하나의 인덱스는 여러 개의 JSON 형식의 데이터를 가진 도큐먼트로 이루어져 있고, 도큐먼트(Document)는 복수의 필드를 가집니다.
도큐먼트(Document)
도큐먼트는 데이터가 저장되는 기본 단위로 JSON으로 저장됩니다. 다음과 같이 하나의 도큐먼트는 여러 필드(field)와 값(value)을 가질 수 있습니다.
{
"_index" : "index",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"foo" : "baz"
}
}
위의 `_source` 내부의 foo는 필드이며 "baz"는 값입니다. 각 필드는 매핑으로 데이터 타입을 지정할 수 있는데 String의 경우에는 텍스트(text) 타입이나 키워드(Keyword) 타입을 가질 수 있으며 숫자의 경우에는 정수 타입을 가질 수 있습니다.
엘라스틱 서치 7.x 미만 버전에서는 다음과 같은 그림으로 관계형 데이터 베이스와 엘라스틱 서치를 대조하며 설명하는 것이 유효했지만, 7버전 이상부터는 불가능합니다. 그 이유는 Type이 사라졌기 때문입니다.

좀 더 직관적으로 이해해보기 위해 다음 표로 비교해보자면 다음과 같을 것입니다.
| MySQL | Elasticsearch |
| 테이블 | 인덱스 |
| 레코드 | 도큐먼트 |
| 컬럼 | 필드 |
| 스키마 | 매핑 |
엑셀로 따지자면 가로줄(row) 한 줄이 도큐먼트(JSON 파일)이며 각 셀의 값들이 필드와 값으로 묶이는 것일 겁니다.
인덱스(Index)
인덱스는 도큐먼트를 저장하는 논리적 단위로 위에서도 언급했듯 관계형 데이터베이스의 테이블과도 비슷한 역할을 합니다. 하나의 인덱스에는 여러 개의 도큐먼트가 포함되고, 이 때 모든 도큐먼트는 같은 스키마를 따라야 합니다. 인덱스 이름으로는 영어 소문자와, /, , *, ?, ", <, >, |, #, 공백, 쉼표 등을 제외한 특수문자를 사용할 수 있으며 255바이트를 넘을 수 없습니다.
인덱스는 주로 스키마와, 관리 목적에따라 그룹핑을 하게 됩니다.
스키마에 따른 그룹핑
인덱스는 관계형 데이터베이스의 테이블과 비슷한 역할을 하므로 서비스에서 사용하는 정보의 스키마에 따라 구분하게 됩니다. 예를 들어, 회원정보와 장바구니 도큐먼트는 다른 스키마를 사용하므로, 인덱스를 분리하여 도큐먼트들을 저장하게 될 것입니다.
관리 목적의 인덱스 그룹핑
기본적으로 인덱스는 용량이나 숫자 제한 없이 무한대의 도큐먼트를 포함할 수 있다고 합니다. 따라서, 이론적으로는 인덱스에 수억 개의 도큐먼트도 저장될 수 있습니다. 하지만 인덱스가 커지면 검색 시 많은 도큐먼트를 참조해야 하기 때문에 성능이 나빠질 것입니다. 그래서 엘라스틱 서치를 운용하면 인덱스의 용량 제한을 두게 됩니다.
이 때 여러 방식으로 인덱스를 분리할 수 있는데, 1)특정 도큐먼트 갯수에 도달하거나 2)특정 용량이 넘어서거나 3)일/주/월/년 단위같은 날짜/시간 단위로 인덱스를 분리할 수 있습니다.
날짜/시간 단위로 인덱스를 분리하면 특정 날짜의 데이터를 쉽게 처리할 수 있습니다. 예를 들어 특정 1개월치의 데이터를 삭제하고 싶다면 인덱스로 분리해놓았을 때는 쉽지만 그렇지 않다면 일일히 @Timestamp를 확인하여 삭제해야하는 비용이 들 것입니다. 특히나 많은 데이터를 관리하는 빅데이터 세계에서는 큰 비용이 들 것입니다.
이 정리 글에서는 Elasticsearch REST API 혹은 Kibana의 사용법에 대해서는 언급하지 않겠습니다. 관련 내용은 공식 문서 혹은 김종민님의 엘라스틱서치 가이드북에 잘 나와있기 때문에 충분한 자료가 있다고 판단됩니다.
응답메세지
엘라스틱서치에서 REST API를 통해 돌아오는 응답 상태 값을 정리하려고 합니다.
| 코드 | 상태 | 해결 방법 |
| 200, 201 | 정상적으로 수행함 | |
| 4xx | 클라이언트 오류 | 클라이언트에서 문제점 수정 |
| 404 | 요청한 리소스가 없음 | 인덱스나 도큐먼트가 존재하는지 체크 |
| 405 | 요청 메소드(GET, POST 등)를 지원하지 않음 | API 사용법 다시 확인 |
| 429 | 요청 과부화(busy) | 재전송, 노드 추가 같은 조치 |
| 5xx | 서버 오류 | 엘라스틱서치 로그 확인 후 조치 |
매핑(mapping)
관계형 데이터 베이스는 테이블을 만들 때 반드시 스키마 설계가 필요합니다. 여기서 말하는 스키마는 테이블을 구성하는 구성요소 간의 논리적인 관계와 정의를 의미합니다. 엘라스틱에서도 관계형 데이터베이스의 스키마와 비슷한 역할을 하는 것이 있는데 그것이 매핑(mapping)입니다. 쉽게 말하면 JSON 형태의 데이터를 루씬이 이해할 수 있도록 바꿔주는 작업입니다. 엘라스틱서치가 검색 엔진으로 전문 검색과 대용량 데이터를 빠르게 실시간 검색할 수 있는 이유는 매핑이 있기 때문인데 매핑을 엘라스틱 서치가 하면 다이나믹 매핑, 사용자가 직접 설정하게 되면 명시적 매핑입니다.
다이나믹 매핑 기준 데이터 변환 타입
| 원본 소스 데이터 타입 | 다이내믹 매핑으로 변환된 데이터 타입 |
| null | 필드를 추가하지 않음 |
| boolean | boolean |
| float | float |
| integer | long |
| object | object |
| string | string 데이터 형태에 따라 date, text / keyword 필드 |
명시적 매핑 형태
당신의 신문에서 사용했던 매핑 예시입니다. 다음과 같은 형태로 필드의 값을 명시할 수 있습니다.
{
"news-정치" : {
"settings": {
"number_of_shards" : 3,
"number_of_replicas" : 2,
"analysis": {
"analyzer": {
"korean": {
"tokenizer": "nori_tokenizer"
}
}
}
},
"mappings": {
"properties": {
"date": { "type": "date", "format" : "iso8601" },
"section" : { "type": "keyword" },
"press" : { "type": "keyword" },
"author" : { "type": "keyword" },
"title" : {
"type": "text",
"analyzer": "korean"
},
"contents" : {
"type": "text",
"analyzer": "korean"
},
"imageUrl" : { "type": "keyword" },
"url" : { "type": "keyword" }
}
}
}
}
매핑 타입
위처럼 명시적 매핑을 사용하기 위해서는 엘라스틱서치에서 사용하는 데이터 타입에 대한 이해가 필요합니다. 엘라스틱 서치에서 자주 사용하는 데이터 타입은 다음과 같습니다. 버전에 따라 타입의 종류는 달라지기 때문에 기본적인 타입만 적혀있습니다.
| 데이터 형태 | 데이터 타입 | 설명 |
| 텍스트 | text | 전문 검색이 필요한 데이터로 텍스트 분석기가 텍스트를 작은 단위로 분리한다. |
| keyword | 정렬이나 집계에 사용되는 텍스트 데이터로 분석을 하지 않고 원문을 통째로 인덱싱한다. | |
| 날짜 | date | 날짜 / 시간 데이터 |
| 정수 | byte, short, integer, long |
- byte : 부호 있는 8비트 데이터(-128 ~ 127) - short : 부호 있는 16비트 데이터(-32,768 ~ 32,767) - integer : 부호 있는 32비트 데이터(-2^31 ~ 2^31-1) - long : 부호 있는 64비트 데이터(-2^63 ~ 2^63-1) |
| 실수 | scaled_float, half_float, double, float |
- scaled_float : float 데이터에 특정 값을 곱해서 정수형으로 바꾼 데이터, 정확도는 떨어지나 필요에 따라 집계 등에서 효율적으로 사용 가능하다. - half_float : 16비트 부동소수점 실수 데이터 - double : 32비트 부동소수점 실수 데이터 - float : 64비트 부동소수점 실수 데이터 |
| 불린 | boolean | 참 / 거짓 데이터로 true / false만을 값으로 갖는다. |
| IP 주소 | ip | ipv4, ipv6 타입 IP주소를 입력할 수 있다. |
| 위치 정보 | geo-point, geo-shape |
- geo-point : 위도, 경도 값을 갖는다. - geo-shape : 하나의 위치 포인트가 아닌 임의의 지형 |
| 범위 값 | integer_range, longe_range, float_range, double_range, ip_range, date_range |
범위를 설정할 수 있는 데이터, integer_range, longe_range는 정수형 범위, float_range, double_range 실수형 범위, ip_range IP 주소 범위, date_range 날짜 / 시간 데이터 범위 값을 지정하고 검색할 수 있게 한다. 최솟값과 최댓값을 통해 범위를 입력한다. |
| 객체형 | object | 계층 구조를 갖는 형태로 필드안에 다른 필드들이 들어갈 수 있다. name : { "first" : "kim", "last" : "tony" }로 타입을 정의하면 name.first/name.last 형태로 접근할 수 있다. |
| 배열형 | nested | 배열형 객체를 저장한다. 객체를 따로 인덱싱하여 객체가 하나로 합쳐지는(flattend) 것을 막고, 배열 내부의 객체에 쿼리로 접근할 수 있다. |
| join | 부모(parent) / 자식(Child) 관계를 표현할 수 있다. |
인덱스 템플릿(Index Template)
인덱스 템플릿은 주로 설정이 동일한 복수의 인덱스를 만들 때 사용합니다. 관리 편의성, 성능 등을 위해 인덱스를 파티셔닝을 하게 되는데 이 때마다 인덱스를 따로 설정하고 만들고 수정하는 일은 번거롭고 실수를 유발할 수 있기 때문에 인덱스 템플릿을 작성하여 사용합니다.
실제로 `당신의 신문` 프로젝트에서도 `news-*`형태의 인덱스 템플릿을 이용하여 프로젝트를 진행했습니다.
인덱스 템플릿의 특징을 정하기 위한 파라미터로는 index_patterns, priority, template 등이 있습니다. 각각의 기능을 설명해보자면 "index_patterns"는 해당 인덱스 패턴에 알맞은 패턴을 가진 경우에 템플릿의 설정을 적용하고, "priority"은 이 템플릿의 우선도를, "template" 파라미터는 settings, mappings 와 같은 정보를 설정할 수 있습니다.
인덱스 템플릿도 다이나믹 템플릿을 사용하여 조건을 걸고 들어오는 도큐먼트 필드에 따라 적합한 매핑을 해줄 수 있습니다.
느낀점
인덱스에 패턴을 두어 관리하는 것을 보았을 때, 날짜나 카테고리 별 구분을 하기에 이렇게 관리하는 것인가 하는 심증적인 의문이 들었었는데 보통은 날짜/시간으로 분리할 수 있다는 것을 확인하였다. 신문 기사라는 우리 서비스의 데이터는 매일 날마다 나오는 것이니 적정한 크기의 인덱스 관리를 위해서 metadata 신문사 정보와 함께 날짜로 처리하면 어떨지 생각을 해보았다.
신문기사-중앙일보-문화-2022.09.08
어? 그렇다면 인덱스 패턴을 통해 쉽게 필터 검색이 가능할지도?
사용하면 할수록 강력한 엘라스틱서치 엔진의 힘을 느끼는 것 같다.
'Infra > Elasticsearch' 카테고리의 다른 글
| [Elasticsearch] 엘라스틱서치 운영 준비 (0) | 2022.10.13 |
|---|---|
| [Elasticsearch] 엘라스틱서치 설치 (0) | 2022.10.12 |
| [Elasticsearch] Ubuntu, Docker-compose를 이용한 Elasticsearch 환경 구성 (0) | 2022.10.12 |
| [Elasticsearch] 엘라스틱서치의 역사 (0) | 2022.10.10 |