Elasticsearch cluster

Elasticsearch의 Cluster는 물리적으로 나뉜 하나 이상의 노드를 논리적으로 하나의 서버 그룹으로 묶어 관리하는 것입니다. 하나의 노드로 모든 역할을 수행할 수 있도록 설정할 수 있지만 그렇게 설정할 경우 하나의 노드가 모든 부하를 받기 때문에 클러스터가 깨질 수 있는 위험이 있습니다. 물론 개인 테스트 환경 및 개발환경에서는 리소스를 아끼기 위해 이렇게 설정할 수 있지만 상용으로 사용할 경우 Cluster를 구성할 때는 각각의 노드가 역할을 분리하여 한 노드의 문제가 발생하더라도 클러스터를 유지하는데 문제가 없게 클러스터를 구성하는 것이 좋습니다.

각 노드들의 역할

위에서 설명한 것처럼 Elasticsearch에서는 모든 역할을 수행하는 하나의 노드로 클러스터를 구성할 수도 있지만 안정적인 클러스터를 운영하기 위해서 각각의 노드의 역할을 분리하여 관리할 수 있습니다. 뿐만 아니라 규모가 커질 경우 자신에 상황에 맞게 해당 노드에 리소스를 할당하여 리소스를 보다 효율적으로 사용할 수 있습니다. Elasticsearch 노드의 종류와 기능은 다음과 같습니다.

 

마스터 노드 (Master node)

마스터 노드는 Elasticsearch의 클러스터 전체를 관리하며 각 노드들의 연결 상태 및 인덱스 및 샤드의 상태 등을 관리하는 역할을 수행합니다. 마스터 노드의 개수를 여러 개 설정하여 현재 마스터는 하나만 수행하고 나머지 마스터 노드는 현재 마스터 노드에 문제가 발생하면 마스터로 승격될 수 있도록 스탠바이 형식으로 구성하여 안정적인 클러스터 운영을 할 수 있습니다. 마스터 노드의 개수는 보팅 알로리즘으로 인한 스플릿 브레인 현상이 발생하지 않도록 1개 이상의 홀수개로 생성하는 것을 권장합니다.

 

데이터 노드 (Data node)

데이터 노드는 이름 그대로 데이터를 처리하는 노드입니다. 데이터를 저장하고 색인처리를 수행하며 저장된 데이터를 검색하는 역할을 수행할 수 있습니다.  데이터 노드를 여러 개 생성하여 데이터를 분산 저장하여 하나의 노드에 부하가 몰리는 것을 방지할 수 있습니다. 데이터 노드는 hot, warm, cold 등으로 여러 개로 분리하여 리소스를 보다 효율적으로 사용할 수 있도록 설정할 수 있습니다.

 

코디네이팅 노드 (Coordinating Node)

코디네이팅 노드는 요청을 받아서 해당 요청을 처리하는 역할을 수행합니다. 코디네이팅 노드는 별도로 설정하는 것이 아닌 요청을 받은 노드를 코디네이팅 노드라고 합니다. 그렇기 때문에 일반적으로 데이터 노드에서 코디네이팅 역할을 같이 수행하도록 하지만 대규모 클러스터에서는 데이터를 저장하지 않고 요청만 받아서 처리하는 코디네이팅 노드를 별도로 구성하여 요청에 대한 부하를 분리하기도 합니다.

 

Elasticsearch cluster 구성도

 

저는 별도로 사용해 본 적은 없지만 그밖에 ingest, ml, client, transform 등 여러 형식의 노드 또한 존재합니다.

index, shard, document

도큐먼트 (Document)

도큐먼트는 Elasticsearch의 가장 작은 데이터 단위를 나타냅니다. 도큐먼트는 JSON 형식으로 표현되며 인덱스로 색인되어 저장됩니다. 데이터가 인덱싱 될 때 자동으로 고유한 도큐먼트 아이디가 생성되어 저장됩니다.

 

인덱스 (Index)

인덱스는 Elasticsearch의 핵심적인 기능으로 데이터를 그룹화하여 데이터를 저장하고 검색 및 통계를 할 수 있도록 하는 기능을 수행합니다. 인덱스는 고유한 이름을 가지며 하나 이상의 샤드로 구성되어 여러 노드에 분산되어 데이터가 저장됩니다. index는 template을 구성하여 데이터를 보다 효율적으로 관리할 수 있으며 ilm(Index Lifecycle Manager)를 통해 인덱스의 생명주기를 관리할 수 있습니다.

 

인덱스 구성 방법

  • Long term retention index : 인덱스의 크기가 크지 않고 변화가 많지 않아 지속적으로 데이터를 읽기 위해 사용하는 방식으로 단일 인덱스로 구성된 방식입니다.
  • Rolling update : 인덱스를 my-index-2023-09-29, my-ingdex-2023-09-30 등과 같이 날짜별로 구성하여 관리하는 방식입니다. 날짜별로 데이터가 분리되어 저장되었기 때문에 시계열 데이터 및 로그와 같은 데이터인 경우 날짜별로 인덱스를 관리하고 검색하여 보다 효율적으로 사용할 수 있는 방식입니다. 뿐만 아니라 날짜 별로 데이터를 스냅샷을 찍을 수 있으며 복구할 수 있어 데이터를 날짜별로 관리해야 되는 경우 매우 유용한 방식입니다.
  • Rollover : 인덱스의 크기를 예측하기 어려운 경우 사용하는 방식입니다. 하나의 인덱스가 너무 크면 해당 인덱스의 검색에 많은 부하를 받을 수 있기 때문에 인덱스가 일정 개수의 도큐먼트 또는 일정 크기에 도달했을 경우 인덱스를 분리하여 저장하는 방식입니다. 예시로 my-index-0001, my-index-0002 형식으로 데이터가 저장되며 검색할 때는 모든 같은 형식의 이름의 인덱스를 Alias를 통해 모두 검색합니다.

샤드 (Shard)

인덱스를 구성하는 단위로 인덱스는 하나 이상의 샤드로 구성됩니다. Elasticsearch에서는 각 노드에 부하를 분산하기 위해 샤드를 기반으로 한 인덱스의 데이터가 분산 저장하게 됩니다. 또한 데이터를 검색하는 데 있어 병렬로 데이터를 검색하여 빠른 검색 및 색인이 가능해집니다. 샤드의 수는 인덱스가 생성되고 나서 수정할 수 없어 새롭게 생성되는 인덱스에만 샤드를 설정할 수 있습니다. 또한 샤드는 Primary 샤드와 Replica 샤드로 구분되며 Primary 샤드는 원본 데이터를 저장하고 Replica 샤드에서는 Primary 샤드를 복제하여 고가용성(HA)을 보장해 주는 역할을 수행합니다.

샤드는 Elasticsearch 7 버전부터 기본값으로 인덱스당 1개가 생성되게 되어 있습니다. 그렇기 때문에 인덱스를 생성할 때 샤드의 개수를 지정해야 됩니다. 만약 너무 많은 양의 샤드를 구성할 경우 Master 노드에서 클러스터를 운영하는데 부하를 받기 때문에 샤드의 크기는 하나의 샤드당 30GB 이하의 크기를 가지도록 설정하여 적당한 개수를 가지게 하는 것을 권장합니다. 인덱스당 샤드를 최대 1024개까지 구성할 수 있습니다.

Lucene

Shard는 루씬 인덱스로 구성되며 루씬 인덱스는 여러개의 segment로 구성되어 있습니다. 다수의 세그먼트로 분산되어 있기 때문에 병렬로 데이터를 검색을 수행하며 역색인 형식으로 데이터를 저장하여 검색에 최적화되어 있습니다. IndexWriter를 통하여 새로운 세그먼트가 생성되고 세그먼트의 개수가 늘어나면 주기적으로 각각의 세그먼트를 병합(merge)하여 일정 크기의 세그먼트를 유지하게 됩니다.

세그먼트는 읽기 전용이기 때문에 한번 기록되면 변경이 불가능합니다. 데이터가 변경되면 새로운 세그먼트가 생성되고 변경된 내용이 반영되기 위해서는 Commit이라는 작업을 통해 이루어집니다. 세그먼트가 인덱스 샤드에 구성되는 모습은 다음과 같습니다.

segment

검색 동작 과정

Request를 요청하게 되면 Request를 수신한 노드(코디네이팅 노드)는 해당 인덱스의 샤드를 가지고 있는 데이터 노드에게 검색을 요청하게 됩니다. 그럼 해당 샤드에서 조건에 맞는 데이터를 검색하여 리턴하게 되고 사용자 요청을 수신한 노드에서 결과를 통합하여 사용자에게 전달합니다.

 

 

kubernetes 환경에서 elasticsearch를 설치하는 방법을 정리하였습니다. 비록 테스트 환경이지만 실제 운영환경처럼 구성하기 위해 3대의 마스터 노드와 2대의 데이터 노드로 구성하였습니다(현업에서 직접 사용할 경우 보다 더 여유 있는 구성을 추천드립니다) 3대의 마스터 노드 중 실제로 마스터의 역할을 수행하는 것은 1대이며 나머지 2대의 마스터 노드는 현재 마스터 노드의 문제가 발생할 경우 마스터로 승격할 수 있도록 Standby 형식으로 구성하였습니다.

 

1. Helm chart 다운로드

저는 kubernets 환경에서 elasticsearch를 설치하기 위해 helm을 사용하였습니다. 아래 링크의 helm으로 구성하였습니다.
https://artifacthub.io/packages/helm/elastic/elasticsearch

 

elasticsearch 8.5.1 · elastic/elastic

Official Elastic helm chart for Elasticsearch

artifacthub.io

 

helm 명령어를 통하여 repo를 추가하고 다운로드합니다.

# helm repo 추가
$ helm repo add elastic https://helm.elastic.co

# helm 파일 다운로드
$ helm pull elastic/elasticsearch
$ ls -al
drwxr-xr-x@  6 hsw  staff    192  6 25 09:45 .
drwxr-xr-x@  9 hsw  staff    288  6 25 09:44 ..
-rw-r--r--@  1 hsw  staff  28898  6 25 09:45 elasticsearch-8.5.1.tgz

# helm 압축 출기
$ tar -zxvf elasticsearch-8.5.1.tgz
$ ls -al
drwxr-xr-x@  6 hsw  staff    192  6 25 09:45 .
drwxr-xr-x@  9 hsw  staff    288  6 25 09:44 ..
drwxr-xr-x@ 12 hsw  staff    384  6 25 10:26 elasticsearch
-rw-r--r--@  1 hsw  staff  28898  6 25 09:45 elasticsearch-8.5.1.tgz

 

2. values.yaml 수정

elasticsearch 디렉토리로 이동하여 values 값을 설정합니다. 저의 경우 values.yaml 파일을 직접적으로 수정하는 것보다는 새로운 yaml을 구성하여 values.yaml 파일을 override 하여 사용하는 것을 선호하기 때문에 마스터 노드와 데이터 노드 별로 설정파일을 구성하여 작업하였습니다.

master.yaml 파일 구성

마스터 노드는 3대로 구성되어 있고 클러스터 유지를 위해 CPU는 4 코어, Memory는 16GB로 여유 있게 구성하였습니다. 그리고 Lucene의 메모리 사용을 위해 16GB의 절반인 8GB만 JVM옵션으로 설정을 하였습니다.

tolerations, nodeAffinity의 경우 사용자 환경에 맞게 설정하여 사용해 주세요 사용법은 링크를 달아놨습니다.
tolerations : https://kubernetes.io/ko/docs/concepts/scheduling-eviction/taint-and-toleration/
nodeAffinity : https://kubernetes.io/ko/docs/concepts/scheduling-eviction/assign-pod-node/

##################################
# cluster info
createCert: false
clusterName: "elasticsearch"
nodeGroup: "master"
replicas: 3
imageTag: "7.10.2"

roles:
- master

##################################
# nodeAffinity
tolerations:
- key: "develop/elasticsearch-master"
  operator: "Exists"

nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
  nodeSelectorTerms:
    - matchExpressions:
      - key: develop/group
        operator: In
        values:
        - elasticsearch-master

##################################
# cluster config
# 테스트 환경이라 보안 설정을 끔
esConfig:
  elasticsearch.yml: |
    xpack.security.enabled: false

# AWS S3 스냅샷 테스트를 위한 설정
esJvmOptions:
  jvm.options: |
    -Des.allow_insecure_settings=true

##################################
# resources
esJavaOpts: "-Xmx8g -Xms8g"

resources:
  requests:
    cpu: 4
    memory: "16Gi"
  limits:
    cpu: 4
    memory: "16Gi"

volumeClaimTemplate:
  resources:
    requests:
      storage: 10Gi
  storageClassName: openebs

##################################
# service
protocol: http
service:
  type: NodePort
  nodePort: "30090"

data.yaml 파일 구성

데이터 노드의 경우 마스터 노드보다 더 많은 요청을 처리하고 데이터 관리를 수행하기 때문에 마스터 노드보다 더 여유 있는 스펙으로 리소스를 구성하였습니다. 데이터 노드 또한 마스터 노드처럼 사용자의 환경에 맞는 값으로 구성해 주세요

##################################
# cluster info
createCert: false
clusterName: "elasticsearch"
nodeGroup: "data"
replicas: 2
imageTag: "7.10.2"

roles:
- data
- ingest

##################################
# nodeAffinity
tolerations:
- key: "develop/elasticsearch-data"
  operator: "Exists"

nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
  nodeSelectorTerms:
    - matchExpressions:
      - key: develop/group
        operator: In
        values:
        - elasticsearch-data

##################################
# cluster config
# 테스트 환경이라 보안 설정을 끔
esConfig:
  elasticsearch.yml: |
    xpack.security.enabled: false

# AWS S3 스냅샷 테스트를 위한 설정
esJvmOptions:
  jvm.options: |
    -Des.allow_insecure_settings=true

##################################
# resources
esJavaOpts: "-Xmx16g -Xms16g"

resources:
  requests:
    cpu: 8
    memory: "32Gi"
  limits:
    cpu: 8
    memory: "32Gi"

volumeClaimTemplate:
  resources:
    requests:
      storage: 200Gi
  storageClassName: openebs

##################################
# service
# UI 사용을 위해 NodePort로 설정
protocol: http
service:
  type: NodePort
  nodePort: "30091"

Makefile 구성

저는 지속적으로 지웠다 설치했다는 반복 하는데 편리하기 위해 Makefile을 구성하였습니다.

PREFIX := es
TIMEOUT := 1200s
NAMESPACE := elasticsearch

install:
helm upgrade --wait --timeout=$(TIMEOUT) --install -n $(NAMESPACE) --create-namespace --values override-master.yaml $(PREFIX)-master .
helm upgrade --wait --timeout=$(TIMEOUT) --install -n $(NAMESPACE) --create-namespace --values override-data.yaml $(PREFIX)-data .

purge:
helm del $(PREFIX)-master -n $(NAMESPACE)
helm del $(PREFIX)-data -n $(NAMESPACE)

 

3. 설치 진행

Makefile을 이용하여 설치를 진행합니다.

# elasticsearch 설치
$ make install

 

설치 작업이 완료 되면 Elasticsearch는 다음과 같이 구성됩니다.

pod/elasticsearch-data-0             1/1     Running     0       1d
pod/elasticsearch-data-1             1/1     Running     0       1d
pod/elasticsearch-master-0           1/1     Running     0       1d
pod/elasticsearch-master-1           1/1     Running     0       1d
pod/elasticsearch-master-2           1/1     Running     0       1d

service/elasticsearch-data               NodePort    10.233.47.200  <none>   9200:30091/TCP,9300:31095/TCP   1d
service/elasticsearch-data-headless      ClusterIP   None           <none>   9200/TCP,9300/TCP               1d
service/elasticsearch-master             NodePort    10.233.56.28   <none>   9200:30090/TCP,9300:32277/TCP   1d
service/elasticsearch-master-headless    ClusterIP   None           <none>   9200/TCP,9300/TCP               1d

statefulset.apps/elasticsearch-data         2/2     1d
statefulset.apps/elasticsearch-master       3/3     1d

 

4. Elasticsearch 삭제

테스트가 끝나고 elasticsearch를 지우기 위해서는 다음과 같이 진행하면 됩니다.

# Elasticsearch 제거
$ make purge

 

본 문서는 2023년 6월에 작성된 문서입니다.

Elasticsearch 라이센스 문제로 현재 7.10 버전을 사용하고 있습니다. 그러나 7.10 버전에서는 AWS S3에 저장하는 기능이 기본적으로 제공하고 있지 않습니다. 그래서 7.10 버전을 AWS S3에 스냅샷 저장하는 방법에 대하여 문서를 작성하였습니다. (참고로 8.0 버전 이상에서는 기본적으로 AWS S3 Snapshot을 제공하고 있습니다. 8.0 이상의 버전을 사용하시는 분들은 큰 제목 3번부터 진행하셔도 됩니다.)

1. 현재 나의 환경은?

저의 경우 Elasticsearch를 kubernetes에 설치하여 사용하고 있습니다. 그렇기 때문에 Kubernetes 환경 위주로 설명을 진행하지만 플러그인 설치하는 방법은 비슷하므로 일반적인 VM 방법과 Kubernetes 및 Docker에서 사용하는 방법에 대하여 정리하였습니다.

VM Elasticsearch

Elasticsearch는 공식적으로 제공하지 않는 부분에 있어 Elasticsearch Plugin을 통하여 확장 기능을 설치할 수 있도록 제공하고 있습니다. 이런 기능을 사용하여 Elasticsearch가 설치된 VM으로 접속하여 아래 명령어를 통하여 모든 노드에 AWS S3 Plugin을 설치하여 줍니다.

bin/elasticsearch-plugin install --batch repository-s3

모든 노드에 플러그인이 설치가 되었다면 모든 노드들을 하나씩 재시작하여 줍니다.

Kubernetes 및 Docker 환경

kubernetes, docker 환경에서는 플러그인을 설치하여 재시작을 하게 되면 해당 파드가 초기화가 되기 때문에 위 방법을 사용하지 않고 Elasticsearch 이미지를 다시 생성하여 사용해야 합니다. 

FROM docker.elastic.co/elasticsearch/elasticsearch:7.10.2
RUN bin/elasticsearch-plugin install --batch repository-s3

 

다음과 같이 Dockerfile을 생성하여 이미지를 빌드하고 빌드된 이미지를 사용합니다.

docker build -t (이미지) (도커파일경로)

2. Helm을 이용하여 Elasticsearch 설치 진행

저는 아래의 ArtifactHub를 통해 Elasticsearch 설치 작업을 진행하였습니다. 해당 문서는 AWS S3 스냅샷에 대한 내용이므로 Helm 사용법에 대해서는 추후 문서를 작성하여 링크를 붙이도록 하겠습니다. values.yaml 파일을 다음과 같이 수정하여 Elasticsearch 설치를 진행합니다. https://artifacthub.io/packages/helm/elastic/elasticsearch

image: "위에서 생성한 이미지"
imageTag: "이미지 태그"

esJvmOptions:
  jvm.options: |
    -Des.allow_insecure_settings=true

3. Elasticsearch Snapshot S3 Repo 등록

Elasticsearch가 정상적으로 설치가 되었다면 스냅샷을 저장할 Repo를 생성합니다. 한글로 작성된 부분을 사용자 환경에 맞는 값을 넣어주세요.

curl -XPUT 'localhost:9200/_snapshot/my_repository?pretty' -H 'Content-Type: application/json' -d '{
  "type": "s3",
  "settings": {
    "bucket": "버킷",
    "region": "리전",
    "base_path": "백업경로",
    "access_key": "엑세스키",
    "secret_key": "스크릿키",
    "compress": true
  }
}'

# 성공 결과
{
  "acknowledged" : true
}

4. 정상적으로 Repo가 생성되었는지 확인

curl -XGET localhost:9200/_snapshot/_all?pretty

5. Snapshot 생성

SLM(Snapshot Lifecycle Management) 를 진행하실 분은 해당 부분을 스킵하여도 됩니다.
해당 Request를 통하여 스냅샷 생성이 성공적으로 진행이 되었다면 스냅샷 정보와 스냅샷에 성공한 샤드와 실패한 샤드에 대한 개수를 알려줍니다. 종종 네트워크의 문제로 특정 노드의 샤드가 실패할 수 있는데 이런 경우 다시 시작하여 주시면 됩니다.

curl -XPUT "http://localhost:9200/_snapshot/my_repository/backup_20230613?wait_for_completion=true?pretty" -H "Content-Type: application/json" -d '
{
  "indices": "*",
  "ignore_unavailable": true,
  "include_global_state": false
}'

# 결과
생략 ...
{"total":50,"failed":0,"successful":50}

6. SLM 생성

SLM(Snapshot Lifecycle Management) 이란 Snapshot의 생명주기를 관리해 주는 기능입니다. 일정 시간에 자동으로 스냅샷을 생성하고 저장 기간을 관리하여 줍니다.

curl -X PUT 'http://localhost:9200/_slm/policy/my-snapshot' -H 'Content-Type: application/json' -d '
{
  "schedule": "0 30 1 * * ?", 
  "name": "<backup-{now/d{yyyy-MM-dd}}>",
  "repository": "my_repository", 
  "config": { 
    "indices": ["*"],
    "ignore_unavailable": true,
    "include_global_state": false
  },
  "retention": { 
    "expire_after": "30d", 
    "min_count": 5, 
    "max_count": 50 
  }
}
'

# 결과
{
  "acknowledged" : true
}
  • schedule : 스냅샷을 생성할 시간을 입력합니다.
  • name : 생성된 스냅샷을 이름 규칙을 입력합니다.
  • repository : 큰제목 3번에서 생성한 Repo를 입력합니다.
  • config
    • indices : 스냅샷 생성할 인덱스를 입력합니다. (* 이면 모든 인덱스를 스냅샷으로 저장합니다.)
    • ignore_unavailable : 유효하지 않는 값은 무시합니다.
    • include_global_state : 글로벌 설정 값을 저장할지 결정합니다.
  • retention
    • expire_after : 스냅샷 보존 기간을 설정합니다.
    • min_count : 스냅샷 최소 보존 개수를 설정합니다.
    • max_count : 스냅샷 최대 보존 개수를 설정합니다.

위 작업이 완료되었다면 매일 새벽 1시 30분마다 모든 인덱스의 스냅샷을 생성하고 30일 동안 보존되며 최대 50개 최소 5개의 스냅샷을 관리합니다.

7. SLM을 이용하여 바로 스냅샷 생성해 보기

SLM이 정상적으로 잘 작동하는지 우리는 새벽 1시 30분까지 기다릴 수 없습니다. 그렇기 때문에 다음 API를 통하여 방금 설정한 SLM을 통해 스냅샷을 지금 바로 생성하는 방법을 알아보겠습니다.

curl -X POST 'http://localhost:9200/_slm/policy/my-snapshot/_execute?pretty'

# 결과
{
  "snapshot_name" : "backup-2023-06-13-hci5ctcoq9wjfiwiuy2osg"
}

생성된 스냅샷 결과 확인

curl -XGET 'http://localhost:9200/_snapshot/my_repository/_all?pretty'

# 결과
    {
      "snapshot" : "backup-2023-06-13-hci5ctcoq9wjfiwiuy2osg",
      "uuid" : "wXXFF223FTt-rLTKIOFQk2A",
      "version_id" : 7100299,
      "version" : "7.10.2",
      "indices" : [
        "인덱스-0",
        "인덱스-1",
        "인덱스-2",
        "인덱스-3",
        "인덱스-4",
        "인덱스-5"
      ],
      (생략 ...)
      "state" : "SUCCESS",
      "start_time" : "2023-06-13T06:38:37.211Z",
      "start_time_in_millis" : 1686638317211,
      "end_time" : "2023-06-13T06:38:50.620Z",
      "end_time_in_millis" : 1686638330620,
      "duration_in_millis" : 13409,
      "failures" : [ ],
      "shards" : {
        "total" : 50,
        "failed" : 0,
        "successful" : 50
      }
    }

8. 스냅샷으로 복구하기

위에서 생성한 스냅샷으로 복구하는 방법은 다음과 같습니다. 그러나 여기서 명심해야 되는 것은 이미 같은 이름의 인덱스가 Elasticsearch에 존재하면 안 됩니다. 스냅샷은 특정 시점으로 돌아가는 것이기 때문에 같은 이름의 인덱스가 존재하면 해당 인덱스를 삭제해야 합니다.

curl -X POST "http://localhost:9200/_snapshot/my_repository/backup-2023-06-13-hci5ctcoq9wjfiwiuy2osg/_restore?pretty" -H "Content-Type: application/json" -d '
{
  "indices": "*"
}'

# 결과
{"accepted":true}
  • indices : 복구하려는 인덱스를 명시합니다.

 


Snapshot 삭제하기

curl -XDELETE 'http://localhost:9200/_snapshot/my_repository/backup-2023-06-13-hci5ctcoq9wjfiwiuy2osg?pretty'

Snapshot Repo 삭제하기

curl -XDELETE 'http://localhost:9200/_snapshot/my_repository?pretty'

 

elasticdump는 Elasticsearch 인덱스와 데이터를 내보내거나 가져오기 위한 도구입니다. elasticdump를 사용하면 Elasticsearch 클러스터의 index, document, template 등을 백업하거나 다른 클러스터로 마이그레이션하는 작업을 수행할 수 있습니다. 본 문서에서 elasticdump의 기본 적인 사용법에 대해서 정리하였습니다.

elasticdump install

$ sudo apt update
$ sudo apt install npm

$ npm install elasticdump -g

$ elasticdump --version
6.103.0
(node:16397) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.
(생략...)

Elasticsearch Index의 데이터를 파일로 가져오기

Index의 documents데이터를 디렉토리에 파일로 가져올 수 있습니다. 현재 Index에 저장된 데이터는 다음과 같습니다.

 

이제 elasticdump를 이용하여 파일로 가져오겠습니다.

$ elasticdump --input="http://localhost:9200/my-index" --output="./my-index.txt"

 

위 명령어를 통하여 현재 디렉토리에 my-index.txt 파일로 데이터를 가져왔습니다. 가져온 데이터는 다음과 같습니다.

{"_index":"my-index","_type":"_doc","_id":"RO_7BokBQKytg6noxeXW","_score":1,"_source":{"uid":"1","message":"aaaa","type":"APP","timestamp":1688038455}}
{"_index":"my-index","_type":"_doc","_id":"Ru_7BokBQKytg6noxeXW","_score":1,"_source":{"uid":"3","message":"cccc","type":"DB","timestamp":1688038457}}
{"_index":"my-index","_type":"_doc","_id":"Re_7BokBQKytg6noxeXW","_score":1,"_source":{"uid":"2","message":"bbbb","type":"APP","timestamp":1688038456}}
{"_index":"my-index","_type":"_doc","_id":"R-_7BokBQKytg6noxeXW","_score":1,"_source":{"uid":"4","message":"dddd","type":"APP","timestamp":1688038459}}
{"_index":"my-index","_type":"_doc","_id":"SO_7BokBQKytg6noxeXW","_score":1,"_source":{"uid":"5","message":"eeee","type":"DB","timestamp":1688038459}}
{"_index":"my-index","_type":"_doc","_id":"Se_7BokBQKytg6noxeXW","_score":1,"_source":{"uid":"6","message":"ffff","type":"DB","timestamp":1688038459}}

파일로 가져온 데이터 Elasticsearch로 보내기

앞에서는 elasticsearch에서 데이터를 가져오는 방법을 알아봤습니다. 파일에서 elasticsearch로 전송하는 방법은 input과 output를 반대로 설정해 주면 파일에서 elasticsearch로 데이터를 전송합니다.

elasticdump --input="./my-index.txt" --output="http://localhost:9200/my-index"

A Cluster에서 B Cluster로 데이터 전송하기

이번에는 파일로 가져오지 않고 다른 클러스터로 바로 데이터를 전송하겠습니다.

A Cluster가 "localhost:9200"이고 B Cluster는 "localhost:9400" 일 경우 A Cluster에서 B Cluster로 데이터를 전송해보겠습니다.

elasticdump --input="http://localhost:9200/my-index" --output="http://localhost:9400/my-index"

그밖에 elasticdump 옵션

  • --type=analyzer: settings를 내보낸다.
  • --type=mapping: mapping을 내보낸다.
  • --type=data: documents를 내보낸다 (Default).
  • --overwirte: export 파일이 이미 존재한다면 덮어쓰기 한다.
  • --limit: 지정한 limit 개수만큼씩 끊어 가져온다. (Default: 100)
  • --output-index=${index명}: 백업해 놓은 파일로부터 가져오기 할 때 원래 인덱스가 아닌 원하는 인덱스로 지정해 줄 수 있다.

마지막으로

elasticdump는 적은 데이터를 옮길 때는 편하게 사용할 수 있지만 속도가 매우 느리기 때문에 큰 인덱스의 데이터를 가져오는 것은 추천하지 않습니다. 큰 데이터를 마이그레이션 할 때는 snapshot을 이용하여 특정 인덱스만 가져오는 것을 추천드립니다.

 

더 자세한 사용방법은 elasticdump github에서 확인할 수 있습니다.

https://github.com/elasticsearch-dump/elasticsearch-dump

 

GitHub - elasticsearch-dump/elasticsearch-dump: Import and export tools for elasticsearch

Import and export tools for elasticsearch. Contribute to elasticsearch-dump/elasticsearch-dump development by creating an account on GitHub.

github.com

 

Elasticsearch의 성능을 위해 데이터를 한 건 한 건 처리하는 것이 아닌 bulk를 이용하여 한 번에 처리하는 경우가 많습니다.
bulk를 사용하여 많은 양의 데이터를 Elasticsearch에 저장할 경우 분명 HTTP 요청은 정상적으로 끝이 났는데 데이터가 저장이 안 되는 경우가 발생할 수 있습니다. 여기서 중요한 것은 Elasticsearch로 보낸 HTTP의 Response는 200이라는 정상적인 값을 리턴을 하기 때문에 문제를 찾기가 어려운데요. 이럴 때는 Response의 state code를 보는 것이 아라 Response의 body의 내용을 볼 필요가 있습니다.

문제 해결방법

해당 문제를 해결하는 방법은 2가지 방법이 있습니다.

  1. 한번에 요청하는 bulk의 사이즈를 줄인다 : 만약 자원의 부족으로 thread 설정이 불가능한 경우 한번에 처리하는 bulk의 개수를 줄여서 해당 문제를 해결할 수 있습니다.
  2. thread_pool의 wrtie queue size를 늘린다 : http request connect를 통한 부하를 줄이는 것이 더 중요한 경우 1번 방법을 사용할 수 없습니다. 그런 경우 thread가 처리할 수 있는 자원이 있을 때 2번 방법을 통하여 한번에 처리할 수 있는 bulk의 양을 늘려서 문제를 해결할 수 있습니다.

모든 상황에 만족하는 문제 해결은 없습니다. 본인의 환경에 맞는 문제 해결 방법을 찾고 해당 방법을 적용하는 것을 추천드립니다.

아래는 2번 방법을 통해 한번에 처리할 수 있는 bulk의 양을 늘리는 방법에 대해 정리하였습니다.

설정 변경하기

Elasticsearch 디렉토리 위치에 config/elasticsearch.yml파일에 다음과 같은 설정 값을 추가하고 Elasticsearch를 재시작합니다.

thread_pool.write.queue_size: 2000
thread_pool.write.size: 16

설정을 진행하자가 문제가 발생한 경우

만약 다음과 같은 메시지가 발생한 다면 thread_pool.write.size의 값을 13보다 작은 값으로 설정하세요.

java.lang.IllegalArgumentException: Failed to parse value [16] for setting [thread_pool.write.size] must be <= 13

설정값의 의미

  • thread_pool.write.size : write를 수행하는 스레드 풀의 스레드 수
  • thread_pool.write.queue_size : write를 할 때 사용되는 스레드 풀의 큐 크기
  • thread_pool.search.size : search를 수행하는 스레드 풀의 스레드 수
  • thread_pool.search.queue_size : earch를 할 때 사용되는 스레드 풀의 큐 크기

마지막으로

thread에 대한 설정은 노드의 리소스 스펙에 한계가 있기 때문에 어느 정도 설정 값 튜닝을 했음에도 불구하고 더 이상의 성능 향상이 없을 경우 노드의 Scale up을 고려하는 것을 추천드립니다.

 

Aggregations을 수행할 때 사용할 데이터

 

Sub Aggregations

aggregation을 통해 집계를 내고 그 결과에서 세부적인 집계를 위해 Sub Aggregation을 지원하고 있습니다.

 

level 필드별 duration의 평균 시간 구하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "match_all": {}
    },
    "aggs": {
        "byLevel": {
            "terms": {
                "field": "level"
            },
            "aggs": {
                "avgDuration": {
                    "avg": {
                        "field": "duration"
                    }   
                }
            }
        }
    }
}

결과 보기

level의 "info"는 9개가 있으며 9개의 duration 평균은 167.777777입니다.

level의 "warn"는 6개가 있으며 6개의 duration 평균은 333.33333입니다.

level의 "debug"는 5개가 있으며 5개의 duration 평균은 258입니다.

 

level별 action 종류 및 개수 측정하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "match_all": {}
    },
    "aggs": {
        "byLevel": {
            "terms": {
                "field": "level"
            },
            "aggs": {
                "byAction": {
                    "terms": {
                        "field": "action"
                    }   
                }
            }
        }
    }
}

결과 보기

{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 20,
            "relation": "eq"
        },
        "max_score": null,
        "hits": []
    },
    "aggregations": {
        "byLevel": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
                {
                    "key": "info",
                    "doc_count": 9,
                    "byAction": {
                        "doc_count_error_upper_bound": 0,
                        "sum_other_doc_count": 0,
                        "buckets": [
                            {
                                "key": "close_file",
                                "doc_count": 4
                            },
                            {
                                "key": "open_file",
                                "doc_count": 4
                            },
                            {
                                "key": "open_socket",
                                "doc_count": 1
                            }
                        ]
                    }
                },
                {
                    "key": "warn",
                    "doc_count": 6,
                    "byAction": {
                        "doc_count_error_upper_bound": 0,
                        "sum_other_doc_count": 0,
                        "buckets": [
                            {
                                "key": "close_file",
                                "doc_count": 2
                            },
                            {
                                "key": "open_file",
                                "doc_count": 2
                            },
                            {
                                "key": "close_socket",
                                "doc_count": 1
                            },
                            {
                                "key": "open_socket",
                                "doc_count": 1
                            }
                        ]
                    }
                },
                {
                    "key": "debug",
                    "doc_count": 5,
                    "byAction": {
                        "doc_count_error_upper_bound": 0,
                        "sum_other_doc_count": 0,
                        "buckets": [
                            {
                                "key": "close_file",
                                "doc_count": 2
                            },
                            {
                                "key": "open_file",
                                "doc_count": 2
                            },
                            {
                                "key": "close_socket",
                                "doc_count": 1
                            }
                        ]
                    }
                }
            ]
        }
    }
}

"info"는 9개이며 그중에 "close_file"는 4개, "open_file"은 4개, "open_socket"은 1개 입니다.

"warn"는 6개이며 그중에 "close_file"는 2개, "open_file"은 2개, "open_socket"은 1개, "close_socket"은 1개입니다.

"info"는 9개이며 그중에 "close_file"는 2개, "open_file"은 2개, "close_socket"은 1개입니다.

 

 

Aggregation을 수행할 때 사용할 데이터

terms

terms는 데이터의 keyword별 종류 및 개수를 집계하는데 사용합니다.

 

모든 도큐먼트의 level의 종류와 개수 구하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregation을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

level 필드의 데이터 종류와 각 종류별로 도큐먼트가 몇개 있는지 확인합니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "match_all": {}
    },
    "aggs": {
        "byLevel": {
            "terms": {
                "field": "level"
            }
        }
    }
}

결과 보기

"byLevel" 내부의 buckets를 보면 "key"에는 level필드의 값이 "doc_count"에는 개수를 표현하고 있습니다.

즉 "info"는 9개가 있고, "warn"은 6개, "debug"는 5개가 있는 것을 확인할 수 있습니다.

 

range

값의 범위 별로 도큐먼트의 개수를 측정하는데 사용할 수 있습니다.

 

모든 도큐먼트의 duration의 범위별 개수 측정하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

duration의 범위 100이하, 100부터 200까지, 200부터 300까지, 300부터 400까지, 400이상 으로 범위를 지정하여 검색하겠습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "match_all": {}
    },
    "aggs": {
        "byDuration": {
            "range": {
                "field": "duration",
                "ranges": [
                    {
                        "to": 100
                    },
                    {
                        "from": 100,
                        "to": 200
                    },
                    {
                        "from": 200,
                        "to": 300
                    },
                    {
                        "from": 300,
                        "to": 400
                    },
                    {
                        "from": 400
                    }
                ]
            }
        }
    }
}

결과 보기

 

histogram

range의 경우 from, to를 통하여 범위를 지정했다면 histogram은 range와 다르게 interval옵션을 통하여 interval단위 별로 필드의 개수를 측정하는 Aggregations입니다.

 

모든 도큐먼트의 duration을 100 단위로 개수 측정하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "match_all": {}
    },
    "aggs": {
        "byDuration": {
            "histogram": {
                "field": "duration",
                "interval": 100
            }
        }
    }
}

결과 보기

100 단위 별로 도큐먼트의 개수를 확인할 수 있습니다.

 

date_range

range처럼 date필드 또한 범위를 지정하여 집계를 할 수 있습니다.

 

모든 도큐먼트의 start_time 필드를 범위 별로 측정하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

"start_time"필드의 값을 범위 별로 측정하도록 하겠습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "match_all": {}
    },
    "aggs": {
        "date_range": {
            "date_range": {
                "field": "start_time",
                "ranges": [
                    {
                        "from": "2021-09-12 10:10:10",
                        "to": "2021-09-12 10:10:15"
                    },
                    {
                        "from": "2021-09-12 10:10:15",
                        "to": "2021-09-12 10:10:20"
                    },
                    {
                        "from": "2021-09-12 10:10:20"
                    }
                ]
            }
        }
    }
}

결과 보기

각 시간대별로 도큐먼트의 개수를 확인할 수 있습니다.

 

date_histogram

histogram과 같이 date_histogram도 interval 옵션을 넣어 각 interval별 집계를 측정할 수 있습니다.

interval옵션에 second, minute, hour, day, month, week 등을 넣을 수 있습니다.

 

모든 도큐먼트의 start_time 필드를 1분 별로 측정하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "match_all": {}
    },
    "aggs": {
        "date_his": {
            "date_histogram": {
                "field": "start_time",
                "interval": "minute"
            }
        }
    }
}

결과 보기

 

 

 

 

Aggregation을 수행할 때 사용할 데이터

 

cardinality

cardinality는 데이터의 종류가 몇 가지 있는지 확인하는 데 사용됩니다.

 

user_id가 1인 도큐먼트에 action이 몇가지 종류가 있는지 확인하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "cardinality_by_action": {
            "cardinality": {
                "field": "action"
            }
        }
    }
}

결과 보기

"cardinality_by_action"를 통해 user_id가 1인 도큐먼트에서는 action의 종류가 4개가 있다는 것을 확인할 수 있습니다.

실제로 "open_file", "close_file", "open_socket", "close_socket" 이렇게 4개입니다.

 

percentiles

각각의 값을 백분율로 보는 Aggregations입니다. 퍼센트 옵션을 설정하지 않으면 디폴트로 [1, 5, 25, 50, 75, 95, 99]로 설정됩니다.

 

user_id가 1인 도큐먼트에 duration의 백분율 확인하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "percentiles_by_duration": {
            "percentiles": {
                "field": "duration"
            }
        }
    }
}

결과 보기

하위 1%의 값은 100이며, 상위 99%의 값은 430인 것을 확인할 수 있습니다.

 

퍼센트를 설정하여 확인하기

이제는 디폴트 퍼센트값이 아닌 퍼센트를 사용자가 [25, 50, 75]로 지정하여 검색하겠습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "percentiles_by_duration": {
            "percentiles": {
                "field": "duration",
                "percents": [ 25, 50, 75 ]
            }
        }
    }
}

결과 보기

 

percentile_ranks

지정한 값이 백분율로 몇 퍼센트 안에 속하는지 확인할 수 있습니다.

values라는 옵션으로 값을 지정할 수 있습니다.

 

user_id가 1인 도큐먼트에 필드 duration에 100, 200, 300의 값이 있다면 백분율 몇 퍼센트인지 확인하기

우리는 데이터를 가져오는 것이 목적이 아닌 Aggregations을 수행하는 것이 목적이기 때문에 "size"를 0으로 설정하였습니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "rank_by_duration": {
            "percentile_ranks": {
                "field": "duration",
                "values": [ 100, 200, 300 ]
            }
        }
    }
}

결과 보기

 

Aggregation을 수행할 때 사용할 데이터

Aggregations이란

저장된 데이터를 수치화시키는 여러 가지의 연산을 수행하는 역할을 합니다. 이러한 기능을 통하여 데이터를 분석하여 사용자가 보기 좋게 시각화를 할 수 있습니다.

 

이번 페이지에서는 min, max, sum, avg, stats에 대하여 알아보겠습니다.

min

query의 검색 결과에 특정 필드의 값이 가장 작은 값을 나타냅니다.

 

user_id가 1인 도큐먼트의 duration 필드의 최솟값 구하기

"size"를 0으로 설정한 이유는 우리는 데이터를 가져오는 것이 목적이 아닌 user_id가 1인 도큐먼트에서 duration이 가장 작은 값만 도출하는 것이 목적이기 때문에 불필요하게 데이터를 가져오는 것을 생략하기 위함입니다.

"aggs"를 통하여 쿼리에 Aggregation을 선언하고 "minDuration"이라는 Aggregation의 이름을 사용자가 지정합니다. 그리고 "min"을 통하여 duration이라는 필드의 최솟값을 측정하도록 합니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "minDuration": {
            "min": {
                "field": "duration"
            }
        }
    }
}

결과 보기

"hits"는 검색 결과를 나타내지만 우리는 "size"를 0으로 설정하였기 때문에 결과값은 나오지 않습니다.

"aggregations"에 우리가 지정한 "minDuration"이라는 명칭으로 최소값이 100이라는 결과가 나왔습니다.

 

max

query의 검색 결과에 특정 필드의 값이 가장 큰 값을 나타냅니다.

 

user_id가 1인 도큐먼트의 duration 필드의 최대값 구하기

"maxDuration"이라는 명칭으로 duration의 값이 가장 큰 값을 구하는 쿼리입니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "maxDuration": {
            "max": {
                "field": "duration"
            }
        }
    }
}

결과 보기

maxDuration을 통하여 user_id가 1인 도큐먼트에서 duration이 가장 큰 값은 430이라는 결과를 알 수 있습니다.

 

sum

query의 검색 결과에 특정 필드의 값을 전부 더한 값을 나타냅니다.

 

user_id가 1인 도큐먼트의 duration 필드의 전부 더한 값 구하기

"sumDuration"이라는 명칭으로 user_id가 1인 duration의 값을 전부 더한 값을 구하는 쿼리입니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "sumDuration": {
            "sum": {
                "field": "duration"
            }
        }
    }
}

결과 보기

sumDuration을 통하여 user_id가 1인 도큐먼트에서 duration을 전부 더한 값이 2600이라는 것을 알 수 있습니다.

 

avg

query의 검색 결과에 특정 필드의 값의 평균 값을 나타냅니다.

 

user_id가 1인 도큐먼트의 duration 필드의 전부 더한 값 구하기

"avgDuration"이라는 명칭으로 user_id가 1인 duration의 값의 평균을 구하는 쿼리입니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "avgDuration": {
            "avg": {
                "field": "duration"
            }
        }
    }
}

결과 보기

avgDuration을 통하여 user_id가 1인 도큐먼트에서 duration의 평균값이 216.66666666이라는 것을 알 수 있습니다.

 

stats

stats를 통하여 위에서 본 count, min, max, sum, avg의 모든 결과를 가져올 수 있습니다.

 

user_id가 1인 도큐먼트의 duration 필드의 stats 결과 값 확인하기

"statsDuration"이라는 명칭으로 user_id가 1인 도큐먼트의 count, min, max, sum, avg의 값을 확인하는 쿼리입니다.

api POST http://localhost:9200/test-log-index-2021-09-12/_search
header content-type: application/json
body {
    "size": 0,
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "user_id": "1"
                    }
                }
            ]
        }
    },
    "aggs": {
        "statDuration": {
            "stats": {
                "field": "duration"
            }
        }
    }
}

결과 보기

"statDuration"을 통해 count, min, max, avg, sum의 결과 값을 확인할 수 있습니다.

 

그렇다면 min만 필요하더라도 stats하나로 모든 결과 값을 구하면 된다고 생각할 수 있습니다. 그러나 이러한 생각은 좋지 못한 생각입니다. min만 구하기 위해서 stats를 사용하는 것은 매우 안 좋은 선택입니다. 이 이유는 Elasticsearch에게 그만큼 많은 부하를 줄 수 있기 때문입니다. 그러나 반대로 min, max, sum을 구해야 될 경우에는 min, max, sum 각각 3번의 검색을 수행하는 것보다는 stats를 한 번을 사용하여 네트워크 I/O를 줄여 주는 것이 더 현명한 선택이라고 생각됩니다.

 

삽입된 데이터는 이전 페이지를 참조해주세요

https://stdhsw.tistory.com/entry/Elasticsearch-%EA%B2%80%EC%83%89%ED%95%98%EA%B8%B01

 

match 쿼리

match는 term과 비슷해 보이지만 미묘한 차이가 있습니다. match의 경우 기본적인 검색을 수행하는데 사용을 하지만 저 같은 경우 text 필드에서 보다 상세한 검색을 수행할 경우 주로 사용됩니다.  즉 문서의 내용에서 특정 단어가 들어 갔는지 검색할때 많이 사용됩니다.

 

text 필드 검색하기

message 필드에 "aaa"라는 문자열이 포함된 모든 도큐먼트를 검색합니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "message": "aaa"
                    }
                }
            ]
        }
    }
}

 

term 쿼리

term의 경우 match와 비슷한 모습을 취하지만 term은 완전히 같은 데이터를 검색하는데 많이 사용됩니다. 저 같은 경우 주로 keyword타입에서 특정 단어를 검색하는데 많이 사용하고 있습니다.

 

keyword 타입 검색하기

level 필드의 값이 "info"인 값을 가진 도큐먼트를 검색합니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "level": "info"
                    }
                }
            ]
        }
    }
}

 

range 쿼리

range 쿼리는 값의 범위를 검색하는데 사용됩니다. 

 

range쿼리의 파라미터

파라미터명 설명
gte 크거나 같다
gt 크다
lte 작거나 같다
lt 작다

 

범위를 이용하여 검색하기

user_id가 2보다 크거나 같으면서 7보다 작은 도큐먼트 검색하기

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "range": {
                        "user_id": {
                            "gte": "2",
                            "lt": "7"
                        }
                    }
                }
            ]
        }
    }
}

 

 

데이터는 이전 페이지를 참조해주세요

https://stdhsw.tistory.com/entry/Elasticsearch-%EA%B2%80%EC%83%89%ED%95%98%EA%B8%B01

bool 쿼리

Elasticsearch에서 보다 자세한 검색을 위해 복합 쿼리가 필요합니다. 이럴 때 bool을 사용하게 됩니다. bool 쿼리에는 must, must_not, should가 있고 이러한 쿼리를 이용하여 score에 점수를 매길 수 있습니다. score의 점수가 높을수록 보다 정확한 결괏값을 도출할 수 있습니다.

must

must는 검색 조건에 반듯이 포함한 도큐먼트를 가져옵니다.

 

keyword타입 검색하기

level필드에 값이 "info"값을 가지는 모든 도큐먼트를 검색합니다.

참고로 level필드의 타입은 keyword입니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": {
                "match": {
                    "level": "info"
                }
            }
        }
    }
}

 

복합 쿼리 검색하기

level필드의 값이 "info"이며, message에 "aaa"값을 포함하는 도큐먼트를 검색합니다.

level필드는 keyword이며, message는 text입니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "level": "info"
                    }
                },
                {
                    "match": {
                        "message": "aaa"
                    }
                }
            ]
        }
    }
}

 

text타입 OR 검색하기

message필드의 값이 "aaa" 또는 "open" 중 하나 이상의 값을 가지는 도큐먼트를 검색합니다.

만약 "aaa"와 "open"의 값 모두 가지고 있다면 score의 점수가 더 높습니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "message": "aaa open"
                    }
                }
            ]
        }
    }
}

 

text타입 AND 검색하기

message필드의 값이 "aaa"와 "open" 모든 값을 가지는 도큐먼트를 검색합니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "message": "aaa"
                    }
                },
                {
                    "match": {
                        "message": "open"
                    }
                }
            ]
        }
    }
}

must_not

must_not은 검색 조건에 반듯이 포함하지 않은 도큐먼트를 가져옵니다.

 

must_not 쿼리 검색하기

level필드에 값이 "info"가 아닌 모든 도큐먼트를 검색합니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must_not": [
                {
                    "match": {
                        "level": "info"
                    }
                }
            ]
        }
    }
}

 

must_not 복합 쿼리 검색하기

message 필드의 값이 "open"을 가지며, level 필드의 값이 "info"가 아닌 도큐먼트를 검색합니다.

level은 keyword타입이며, message는 text타입입니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "message": "open"
                    }
                }
            ],
            "must_not": [
                {
                    "match": {
                        "level": "info"
                    }
                }
            ]
        }
    }
}

should

should의 경우 반듯이 필요한 것은 아니지만 만약 있다면 score의 점수를 높여 줍니다.

keyword 타입의 경우 OR 검색을 지원하지 않습니다. 그러나 정상적인 방법은 아니지만 저 같은 경우 keyword 타입인 경우 should를 이용하여 OR 검색처럼 사용합니다.

 

should 쿼리 검색하기

level 필드의 값이 "debug"일 경우 보다 높은 점수를 부여하고 검색합니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "should": [
                {
                    "match": {
                        "level": "debug"
                    }
                }
            ]
        }
    }
}

 

should 복합 쿼리로 검색하기

message필드에는 "open"의 값을 가진 도큐먼트 중에 level필드에는 "info"의 값을 가지면 보다 높은 score 점수를 부여하여 검색합니다.

(level필드에는 info 이외의 값도 같이 검색됩니다. 그러나 info의 값을 가진 도큐먼트가 더 높은 score 점수를 가집니다.)

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "message": "open"
                    }
                }
            ],
            "should": [
                {
                    "match": {
                        "level": "info"
                    }
                }
            ]
        }
    }
}

 

 

참조

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html

 

Boolean query | Elasticsearch Guide [7.14] | Elastic

A query that matches documents matching boolean combinations of other queries. The bool query maps to Lucene BooleanQuery. It is built using one or more boolean clauses, each clause with a typed occurrence. The occurrence types are: Occur Description must

www.elastic.co

 

 

 

데이터 넣기

지금 넣는 데이터로 앞으로 검색하는 데 사용하겠습니다.

(http request client를 사용하신다면 body에 반듯이 enter 한번 더 입력하세요)

api PUT http://localhost:9200/_bulk
header Content-type: application/json
body {"index": {"_index": "my-log-index-2021-08-29", "_id": 1}}
{"user_id":1, "level":"info", "timestamp": 1630230119, "action": "open_file", "message": "user1 aaa file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 2}}
{"user_id":2, "level":"debug", "timestamp": 1630230120, "action": "open_file", "message": "user2 bbb file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 3}}
{"user_id":3, "level":"info", "timestamp": 1630230121, "action": "open_socket", "message": "user3 socket open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 4}}
{"user_id":4, "level":"debug", "timestamp": 1630230121, "action": "open_socket", "message": "user4 ddd socket open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 5}}
{"user_id":5, "level":"info", "timestamp": 1630230122, "action": "open_file", "message": "user5 ccc file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 6}}
{"user_id":6, "level":"info", "timestamp": 1630230123, "action": "close_file", "message": "user6 aaa close open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 7}}
{"user_id":7, "level":"info", "timestamp": 1630230124, "action": "close_socket", "message": "user7 ccc close socket"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 8}}
{"user_id":8, "level":"debug", "timestamp": 1630230125, "action": "open_file", "message": "user8 aaa file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 9}}
{"user_id":9, "level":"info", "timestamp": 1630230126, "action": "open_file", "message": "user9 bbb file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 10}}
{"user_id":10, "level":"info", "timestamp": 1630230127, "action": "open_file", "message": "user10 aaa file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 11}}
{"user_id":11, "level":"warn", "timestamp": 1630230128, "action": "open_file", "message": "user11 bbb file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 12}}
{"user_id":12, "level":"info", "timestamp": 1630230128, "action": "open_file", "message": "user12 aaa file open"}
{"index": {"_index": "my-log-index-2021-08-29", "_id": 13}}
{"user_id":13, "level":"info", "timestamp": 1630230129, "action": "open_file", "message": "user13 bbb file open"}


GET으로 데이터 가져오기

일상적으로 많이 사용하였던 방법입니다. GET으로는 도큐먼트 아이디를 통하여 해당 데이터를 가져올 수 있습니다.

 

도큐먼트 아이디 1 데이터 가져오기

api GET http://localhost:9200/my-log-index-2021-08-29/_doc/1

출력 결과

query를 이용하여 가져오기

이번에는 도큐먼트 아이디가 아닌 query를 통하여 값을 가져오는 방법을 알아보겠습니다.

 

인덱스의 모든 데이터 가져오기

size는 가져올 수 있는 최대 개수입니다. 이번 결과에서는 size가 없어도 무방합니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "size": 1000,
    "query": {
        "match_all": {}
    }
}

 

필드 값으로 검색하여 데이터 가져오기

active필드에 open_socket 값을 가지는 도큐먼트 가져오기

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "match": {
            "action": "open_socket"
        }
    }
}

출력 결과

출력 결과에서 각 태그의 의미는 다음과 같습니다.

took 쿼리 수행 시간
timed_out 쿼리 수행 시간이 넘었는지 확인
_shard 쿼리를 수행한 shard의 정보
_shard.total 쿼리를 수행한 shard의 수
_shard.successful 쿼리 수행을 성공한 shard의 수
_shard.skipped 쿼리를 건너뛴 shard의 수
_shard.failed 쿼리를 실패한 shard의 수
hits 쿼리 수행 결과
hits.total 쿼리 수행에 일치하는 도큐먼트 수
hits.max_score 쿼리 수행후 가장 높은 점수를 가진 값
hits.hits 수행 결과의 도큐먼트 정보들

 

단어가 아닌 문장으로 검색하기

match_phrase를 이용하여 message필드에 "aaa file"문장을 포함한 도큐먼트를 검색합니다.

api POST http://localhost:9200/my-log-index-2021-08-29/_search
header Content-type: application/json
body {
    "query": {
        "match_phrase": {
            "message": "aaa file"
        }
    }
}

 

 

 

Keyword 

Elasticsearch에는 문자열 필드 중 text와 keyword가 존재합니다. Keyword는 키워드라는 표현 그대로 데이터 자체를 분류하는데 사용하는 타입입니다. 즉 검색 필터로 사용되며, 데이터를 정렬하고 Aggregation으로 데이터를 집계하는데 사용되는 타입입니다. Keyword 타입으로 "hello my elasticsearch"라는 데이터가 색인되면 "hello my elasticsearch" 그대로 데이터가 색인되어 검색 시 "hello my elasticsearch" 그대로 데이터를 검색해야 합니다.

 

Text

Text 필드는 지정한 분석기를 이용하여 데이터를 분석하는데 사용합니다. 만약 Analyzer를 지정하지 않으면 Standard Analyzer로 설정되는데 "hello my elasticsearch"라는 데이터가 색인되면 "hello", "my", "elasticsearch"로 데이터가 색인되어 keyword와 다르게 "hello"라고 검색을 해도 검색이 됩니다. 즉 유사한 문자열을 검사하거나 Analyzer 설정을 통해 보다 더 전문적인 검색을 수행하는데 사용하는 타입입니다.

 

정리

간단하게 정리하면 다음과 같습니다.

  Keyword Text
사용 목적 통계, 정렬, 필터링 유사성 및 전문적인 분석
검색 방식 입력된 값과 정확히 일치하는 문자를 검색 설정한 Analyzer에 따라서 검색
색인 방식 입력한 문자열 그대로 색인 토큰으로 분리되어 색인
Aggregation  색인된 데이터로 집계 가능 집계 불가능

 

Keyword와 Text 타입을 둘다 적용

Text처럼 분석도 필요하지만 Keyword처럼 Aggregation 모두 필요할 때 필드의 타입을 Keyword와 Text 둘 다 적용할 수 있습니다. 사실 별 다른 타입을 지정하지 않으면 기본 값으로 Keyword와 Text 타입 둘 다 적용됩니다. 이런 경우 문득 다방면으로 사용할 수 있을 것 같아 좋아 보일 수 있지만 보다 더 많은 리소스를 사용하기 때문에 사용 목적에 맞게 필드 타입을 지정하여 사용하는 것을 권장합니다.

 

Array 형식으로 필드에 데이터 저장하기

하나의 필드에 같은 형식의 데이터를 여러 개 넣어야 되는 경우가 많이 발생합니다. 그럴 때 대표적으로 2가지 방식이 있습니다.

1번째 방식으로는 필드와 함께 값을 같이 저장하는 방식입니다.

2번째 방식은 값만 저장하는 방식입니다.

이번에는 이 2가지 방식 모두에 대하여 알아보겠습니다.

1. 필드에 Object형식으로 데이터 리스트 저장하기

하나의 필드와 같이 값을 저장하는 방식으로는 nested를 사용하는 것 입니다.

 

nested 맵핑 만들기

api PUT http://localhost:9200/object-list-index/
header Content-type: application/json
body {
    "mappings": {
        "properties": {
            "server": {
                "type": "keyword"
            },
            "users": {
                "type": "nested"
            }
        }
    }
}

 

데이터 넣기

api PUT http://localhost:9200/object-list-index/_doc/1
header Content-type: application/json
body {
    "server": "server1",
    "users": [{"id": "user1"}, {"id": "user2"}, {"id": "user3"}, {"id": "user4"}]
}

 

데이터 출력하기

api GET http://localhost:9200/object-list-index/_doc/1

 

2. 필드에 데이터만 리스트로 넣기

이번에는 위에 방식과 다른 그냥 값만 배열 형식으로 넣는 방식입니다.

 

맵핑만들기

api PUT http://localhost:9200/value-list-index/
header Content-type: application/json
body {
    "mappings": {
        "properties": {
            "server": {
                "type": "keyword"
            },
            "users": {
                "type": "keyword",
                "fields": {
                    "keyword": {
                        "type": "keyword"
                    }
                }
            }
        }
    }
}

 

데이터 넣기

api PUT http://localhost:9200/value-list-index/_doc/1
header Content-type: application/json
body {
    "server": "server1",
    "users": ["user1","user2", "user3","user4"]
}

 

데이터 출력하기

api GET http://localhost:9200/value-list-index/_doc/1

 

Object처럼 데이터 저장하기

Elasticsearch을 사용하다 보면 데이터를 일반적인 필드 형식으로 저장하는 것이 아닌 트리 구조로 데이터를 저장하고 싶은 상황이 있습니다. 즉 object형식으로 데이터를 저장해야 되는데 이럴 때 사용하는 것이 properties입니다. 이제부터 properties 사용법에 대하여 알아보겠습니다. (참고로 해당 방식처럼 Object형식으로 데이터를 저장하게 되면 인덱스의 구조가 복잡해져 검색하는 상황에 따라 검색의 성능이 저하될 수 있습니다. 참고하시고 본인의 데이터 검색 방식에 대하여 확인하시고 적용하는 것을 추천드립니다.)

properties 사용하기

데이터의 구조는 다음과 같습니다.

  • 학과
  • 학생
    • 이름
    • 나이
    • 이메일
api PUT http://localhost:9200/student-index
header Content-type: application/json
body {
    "mappings": {
        "properties": {
            "subject": {
                "type": "keyword"
            },
            "student": {
                "properties": {
                    "name": {
                        "type": "text"
                    },
                    "age": {
                        "type": "short"
                    },
                    "email": {
                        "type": "text"
                    }
                }
            }
        }
    }
}

 

출력 결과

추가적으로 데이터를 넣고 student-index를 출력하였습니다.

Object 형식의 필드를 추천하는 상황과 추천하지 않는 상황

상황에 따라 object형식으로 데이터를 저장할 때가 좋은 경우가 있는 반면 그렇지 않은 경우도 있습니다.

 

추천하는 상황

  • object의 필드 별로 데이터를 집계 및 통계를 내야 되는 경우

추천하지 않는 상황

  • 그냥 데이터를 저장하고 읽기만 하는 경우 오히려 데이터 검색 성능에 저하가 될 수 있습니다. 만약 그냥 데이터를 검색하는 데 사용하지 않고 텍스트 형식으로 출력하는데만 사용하신다면 그냥 필드를 text로 하여 json데이터를 text로 저장하는 것을 추천합니다.

참조

https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html

 

Object field type | Elasticsearch Guide [7.14] | Elastic

If you need to index arrays of objects instead of single objects, read Nested first.

www.elastic.co

 

 

bulk란

Elasticsearch를 사용하면서 여러개의 데이터를 넣을때 매번 하나씩 데이터를 넣는 일은 네트워크의 잦은 IO의 발생으로 성능을 저하 시킬 수 있는 요인입니다. 이러한 문제를 해결하는 것이 bulk입니다. bulk는 여러개의 처리를 모아 두었다가 한번에 처리하는 batch 처리 기능입니다.

 

bulk를 통해 데이터 넣기

bulk를 사용하여 여러개의 데이터를 한번의 요청으로 넣을 수 있습니다.

api POST http://localhost:9200/my-log-index-2021-08-25/_bulk
header Content-type: Application/json
body {"index": {"_index": "my-log-index-2021-08-25", "_id": 1}}
{"user_id":1, "level":"info", "timestamp": 1629893888, "action": "open_file", "message": "user1 file open"}
{"index": {"_index": "my-log-index-2021-08-25", "_id": 2}}
{"user_id":2, "level":"info", "timestamp": 1629893890, "action": "open_file", "message": "user2 file open"}
{"index": {"_index": "my-log-index-2021-08-25", "_id": 3}}
{"user_id":3, "level":"info", "timestamp": 1629893891, "action": "open_socket", "message": "user3 socket open"}
{"index": {"_index": "my-log-index-2021-08-25", "_id": 4}}
{"user_id":4, "level":"info", "timestamp": 1629893892, "action": "open_socket", "message": "user4 socket open"}
{"index": {"_index": "my-log-index-2021-08-25", "_id": 5}}
{"user_id":5, "level":"info", "timestamp": 1629893893, "action": "open_file", "message": "user5 file open"}
{"index": {"_index": "my-log-index-2021-08-25", "_id": 6}}
{"user_id":6, "level":"info", "timestamp": 1629893893, "action": "close_file", "message": "user6 close open"}
{"index": {"_index": "my-log-index-2021-08-25", "_id": 7}}
{"user_id":7, "level":"info", "timestamp": 1629893895, "action": "close_socket", "message": "user7 close socket"}

중요 ) 여기서 만약에 Postman, 크롬에 ARS와 같이 http요청을 보내는 RestClient 프로그램을 사용하신다면 body 마지막에 개행(\n) 즉 enter키를 한번 더 입력해야 됩니다.

 

데이터가 잘 들어갔는지 확인하기

api GET http://localhost:9200/my-log-index-2021-08-25/_search

 

bulk를 이용하여 데이터 입력, 수정, 삭제 한번에 하기

데이터 입력 뿐만 아니라 데이터 수정, 삭제를 한번의 bulk를 통하여 수행할 수 있습니다.

 

bulk로 입력, 수정, 삭제

api POST http://localhost:9200/my-log-index-2021-08-25/_bulk
header Content-type: Application/json
body {"index": {"_index": "my-log-index-2021-08-25", "_id": 8}}
{"user_id":8, "level":"debuf", "timestamp": 1629893993, "action": "close_file", "message": "user8 close open"}
{"index": {"_index": "my-log-index-2021-08-25", "_id": 9}}
{"user_id":9, "level":"warn", "timestamp": 1629893995, "action": "error", "message": "failed user9 connection"}
{"update": {"_index": "my-log-index-2021-08-25", "_id": 1}}
{"doc": {"level": "debug"}}
{"update": {"_index": "my-log-index-2021-08-25", "_id": 2}}
{"doc": {"level": "debug"}}
{"delete": {"_index": "my-log-index-2021-08-25", "_id": 6}}

중요 ) 여기서 만약에 Postman, 크롬에 ARS와 같이 http요청을 보내는 RestClient 프로그램을 사용하신다면 body 마지막에 개행(\n) 즉 enter키를 한번 더 입력해야 됩니다.

벌크의 내용

8번, 9번 유저의 데이터를 추가하고

1번, 2번 유저의 level필드의 값을 debug로 수정하고

6번 유저의 데이터를 삭제합니다.

 

결과 확인

크롬의 "Elasticsearch Head"라는 확장 프로그램을 사용하여 결과를 확인하겠습니다.

"Elasticsearch Head"의 설치 방법은 링크과 같습니다.

https://stdhsw.tistory.com/entry/Elasticsearch-%EC%9C%88%EB%8F%84%EC%9A%B010-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0

 

Elasticsearch 윈도우10에 설치하기

Elasticsearch 다운로드 https://www.elastic.co/kr/downloads/elasticsearch 해당 주소에서 elasticsearch를 다운로드합니다. Elasticsearch 실행하기 압축을 푼 폴더 위치에서 bin/elasticsearch.bat파일을 실..

stdhsw.tistory.com

 

인덱스의 구조는 이전 template 설정과 같습니다.
https://stdhsw.tistory.com/entry/Elasticsearch%EC%9D%98-Index-template-%EC%84%A4%EC%A0%95

현재 인덱스 구조

필드 타입
user_id long
level keyword
timestamp long
action keyword
message text

 

도큐먼트 생성하기

Elasticsearch index에 document를 생성하여 각각의 필드에 값을 설정하고 index에 데이터를 넣습니다.
user 1 추가하기

api PUT http://localhost:9200/my-log-index-2021-08-24/_doc/1
header Content-type: application/json
body {
    "user_id": 1,
    "level": "info",
    "timestamp": 1629792520,
    "action": "open_file",
    "message": "user file open"
}


user 2 추가하기

api PUT http://localhost:9200/my-log-index-2021-08-24/_doc/2
header Content-type: application/json
body {
    "user_id": 2,
    "level": "info",
    "timestamp": 1629792525,
    "action": "open_socket",
    "message": "user socket open"
}


덮어쓰기를 방지하기 위한 데이터 생성방법
기존처럼 PUT http://localhost:9200/my-log-index-2021-08-24/_doc/2 을 사용하여 데이터를 생성한다면 기존에 도큐먼트 아이디 2가 존재한다면 기존의 데이터는 지워지고 현재 데이터가 덮어써지는 문제가 발생합니다. 이러한 문제를 해결하기 위해 _create를 통하여 데이터를 생성할 수 있습니다.
만약 같은 도큐먼트 아이디가 존재 할 경우 status 409 version_conflict_engine_exception 에러를 반환합니다.

api POST http://localhost:9200/my-log-index-2021-08-24/_create/2
header Content-type: application/json
body {
    "user_id": 3,
    "level": "info",
    "timestamp": 1629792540,
    "action": "close_file",
    "message": "user close open"
}

 

도큐먼트 조회하기

지금까지 입력한 데이터를 조회하도록 하겠습니다.

인덱스에 모든 도큐먼트 조회하기

api GET http://localhost:9200/my-log-index-2021-08-24/_search

출력 결과


특정 도큐먼트 아이디를 이용하여 조회하기
이번에는 인덱스의 모든 데이터가 아닌 도큐먼트 아이디를 이용하여 해당 도큐먼트의 값을 가져오도록 하겠습니다.

api GET http://localhost:9200/my-log-index-2021-08-24/_doc/1

출력 결과


조건을 이용하여 데이터 조회하기
이번에는 특정 조건에 만족하는 데이터를 찾아 조회하도록 하겠습니다.
action 필드의 값이 "open_socket"인 것을 조회하도록 하겠습니다.

api GET http://localhost:9200/my-log-index-2021-08-24/_search?q=action:open_socket

출력 결과

 

도큐먼트 수정하기

데이터를 수정하기 전에 먼저 version의 값을 확인하면 좋습니다.

api GET http://localhost:9200/my-log-index-2021-08-24/_doc/2

출력 결과
출력 결과를 보면 알 수 있듯이 현재 수정을 하지 않은 상태에서는 version이 1입니다.


도큐먼트 아이디를 이용하여 수정하기
도큐먼트 수정하는 것도 조회와 마찬가지로 도큐먼트 아이디를 이용하여 level 필드의 값을 "debug"로 수정하도록 하겠습니다.
_update를 사용하면 기존에 도큐먼트 아이디가 존재하지 않는 다면 에러가 발생합니다.

api POST http://localhost:9200/my-log-index-2021-08-24/_update/2
header Content-type: application/json
body {
    "doc": {
        "level": "debug"
    }
}


버전 확인하기

api GET http://localhost:9200/my-log-index-2021-08-24/_doc/2

출력 결과
버전이 2로 증가하였습니다. 버전은 해당 도큐먼트 아이디의 변경된 횟수를 의미합니다. 버전을 통하여 해당 도큐먼트의 아이디가 수정된 적이 있는지 확인할 수 있습니다.

 

도큐먼트 개수 측정하기

인덱스의 모든 도큐먼트 개수 측정하기

api GET http://localhost:9200/my-log-index-2021-08-24/_count


특정 조건을 이용하여 개수 측정하기
level 필드에 값이 "debug"인 도큐먼트 개수 측정

api GET http://localhost:9200/my-log-index-2021-08-24/_count?q=level:debug

 

도큐먼트 삭제하기

특정 도큐먼트 삭제하기

api DELETE http://localhost:9200/my-log-index-2021-08-24/_doc/2


인덱스 삭제하기
인덱스를 삭제하여 인덱스의 모든 값을 삭제합니다.

api DELETE http://localhost:9200/my-log-index-2021-08-24



 

reindex를 사용하는 이유

Elasticsearch를 사용하다 보면 ilm(Index life management)로 인하여 일정 기간이 지나면 인덱스가 지워집니다. 그러한 상황에서 중요한 로그 데이터를 보관하기 위해서 기존의 인덱스를 복사해 두는 것이 중요합니다. 이럴 때 사용하는 것이 reindex입니다.

 

사용방법

api POST http://localhost:9200/_reindex
header Content-type: application/json
body {
  "source": {
    "index": "기존_인덱스_명"
  },
  "dest": {
    "index": "변경할_인덱스_명"
  }
}

 

reindex를 사용했던 상황

로그의 내용을 분석하기 위해 ilm을 통해 로그 인덱스가 삭제 되지 않도록 인덱스명 변경하기

api POST http://localhost:9200/_reindex
header Content-type: application/json
body {
  "source": {
    "index": "my-log-index-2021-08-24"
  },
  "dest": {
    "index": "backup-log-index"
  }
}

 

참조

https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html

 

Reindex API | Elasticsearch Guide [7.14] | Elastic

Deprecated in 7.6. Sort in reindex is deprecated. Sorting in reindex was never guaranteed to index documents in order and prevents further development of reindex such as resilience and performance improvements. If used in combination with max_docs, conside

www.elastic.co

 

Index ilm policy이란

ILM (Index Lifecycle Management)은 데이터 인덱스의 수명 주기를 관리하기 위한 기능입니다. ILM를 사용하면 데이터의 보존, 압축, 백업 및 삭제와 같은 다양한 관리 작업을 자동화하고 데이터 스토리지를 효율적으로 관리할 수 있는 기능을 제공하지만 이번 문서에서는 아주 간단하게 인덱스가 생성되고 일정 기간이 지나면 인덱스가 삭제되도록 하는 방법에 대하여 정리하였습니다.

보다 자세한 ILM 설명은 링크를 확인해주세요 (https://stdhsw.tistory.com/entry/Elasticsearch-%EC%B5%9C%EC%A0%81%ED%99%94-3-ilm-policy%EB%A1%9C-data-tiering-%ED%95%98%EA%B8%B0)

 

Index ilm policy 설정하기

PUT localhost:9200/_ilm/policy/my-policy
Content-Type: application/json

{
  "policy": {
    "phases": {
      "delete": {
        "min_age": "3d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

ilm policy 파라미터

  • delete.min_age : 인덱스가 생성되고 얼마만큼의 기간이 흐르면 지울지 설정합니다.

 

Index ilm policy 설정 확인 하기

GET localhost:9200/_ilm/policy/my-policy

### 결과
{
  "my-policy": {
    "version": 1,
    "modified_date": "2023-10-25T09:43:53.527Z",
    "policy": {
      "phases": {
        "delete": {
          "min_age": "3d",
          "actions": {
            "delete": {
              "delete_searchable_snapshot": true
            }
          }
        }
      }
    },
    "in_use_by": {
      "indices": [],
      "data_streams": [],
      "composable_templates": []
    }
  }
}

 

ILM Policy를 Index template에 적용하기

인덱스 template에 ILM 설정을 적용합니다. 인덱스 template에 대한 자세한 설명은 링크를 확인해 주세요 (https://stdhsw.tistory.com/entry/Elasticsearch%EC%9D%98-Index-template-%EC%84%A4%EC%A0%95)

PUT http://localhost:9200/_template/my-index-template
Content-Type: application/json

{
  "index_patterns": ["my-index*"],
  "settings": {
    "index": {
      "number_of_shards": "2",
      "number_of_replicas": "1",
      "refresh_interval": "5s",
      "lifecycle": {
        "name": "my-policy"
      }
    }
  },
  "mappings": {
    "properties": {
      "app": {
        "type": "keyword"
      },
      "level": {
        "type": "keyword"
      },
      "message": {
        "type": "text"
      }
    }
  }
}

Index template 파라미터

  • settings.index.lifecycle.name : 위에서 지정한 ILM의 명칭을 기입합니다.

위 설정을 적용하면 이제 "my-index"로 시작하는 모든 인덱스는 앞으로 생성되고 3일 뒤에 자동적으로 삭제가 되어 보다 효율적으로 디스크 사용량을 관리할 수 있습니다.

 

Index ilm policy 삭제하기

### delete
DELETE localhost:9200/_ilm/policy/my-policy

 

Index template 사용 이유

Elsticsearch에서 index의 template을 지정하지 않을 경우 인덱스를 생성할 때마다 shard 및 field type을 계속해서 지정을 해줘야 하는 귀찮은 문제가 발생합니다. 저 같은 경우 인덱스를 날짜 별로 생성하는 Rolling 방식을 이용하고 있는데 만약 template이 존재하지 않았다면 매일 00시마다 인덱스를 생성하는 노가다를 하게 될 겁니다.

template를 사용하게 되면 특정 패턴의 인덱스가 생성될때 template으로 지정된 설정이 자동으로 적용되어 인덱스가 생성되기 때문에 shard, refresh, field 타입이 자동으로 적용됩니다. 

Field data type 종류

default 타입으로 설정되어 생성됩니다. 문자열인 경우 keyword, text 둘 다 지정되며, 숫자인 경우 long 타입으로 설정됩니다. 기본 값으로 사용하는데 문제가 없다면 그냥 사용해도 되지만 최적화를 하거나 리소스의 자원을 아끼며, 상황에 맞게 Analyzer의 기능을 사용할 때는 반듯이 template을 생성하고 해당 template를 기반으로 인덱스가 생성되어 사용하는 것을 권장합니다.

각각의 필드의 타입에 대해서는 아래 elasticsearch 공식 document를 통하여 확인하여 주시기 바랍니다.

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html

 

Field data types | Elasticsearch Guide [8.10] | Elastic

Each field has a field data type, or field type. This type indicates the kind of data the field contains, such as strings or boolean values, and its intended use. For example, you can index strings to both text and keyword fields. However, text field value

www.elastic.co

Index ilm policy 설정하기

curl -X PUT http://localhost:9200/_ilm/policy/my-log-policy -H 'Content-Type: application/json' -d '
{
  "policy": {
    "phases": {
      "delete": {
        "min_age": "3d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}
'

먼저 인덱스 Template를 설정하기 전에 ilm에 대해서 간단하게 알아보겠습니다. ilm이란 Index Lifecycle Management로 인덱스의 생명 주기 및 인덱스 크기를 관리해 주는 기능입니다.  위 설정은 "my-log-policy"라는 ilm을 생성하는 API이며 파라미터의 대한 설명은 다음과 같습니다.

  • policy.phases.delete.min_age : 인덱스의 생명 주기입니다. 저는 3일로 설정하였습니다.
  • policy.phases.delete.action : 3일 뒤 수행하는 action입니다. 저는 인덱스가 생성된 지 3일이 지나면 해당 인덱스를 삭제하도록 설정하였습니다.

이번 문서는 인덱스 Template에 대한 설명이기 때문에 ilm policy의 삭제에 대해서만 간단하게 설정하였습니다. ilm의 기능으로는 삭제뿐만 아니라 hot, warm, cold, rollover등과 같은 여러 가지의 기능을 제공하고 있습니다.

Index template 설정하기

"my-log-template"이라는 template를 shard 개수를 4개, 각 샤드의 복제본 1개 그리고 5초마다 한 번씩 refresh를 수행하여 데이터가 검색될 수 있도록 설정을 하며 필드의 타입은 다음과 같이 설정하도록 하였습니다.

Field명 Type
level keyword
timestamp date
logger keyword
message text
msg로 시작하는 Field text
그밖에 문자열 keyword
그밖에 숫자 integer

 

curl -X PUT http://localhost:9200/_template/my-log-template -H 'Content-Type: application/json' -d '
{
  "index_patterns": [
    "my-log-*"
  ],
  "settings": {
    "index": {
      "number_of_shards": "4",
      "number_of_replicas": "1",
      "refresh_interval": "5s",
      "lifecycle": {
        "name": "my-log-policy"
      }
    }
  },
  "mappings": {
    "properties": {
      "level": {
        "type": "keyword"
      },
      "timestamp": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "logger": {
        "type": "keyword"
      },
      "message": {
        "type": "text"
      },
      "action": {
        "type": "keyword"
      }
    },
    "dynamic_templates": [
      {
        "message": {
          "match_mapping_type": "string",
          "match": "msg*",
          "mapping": {
            "type": "text"
          }
        }
      },
      {
        "default_strings": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      },
      {
        "default_number": {
          "match_mapping_type": "long",
          "mapping": {
            "type": "integer"
          }
        }
      }
    ]
  }
}
'

인덱스의 template은 인덱스 필드의 데이터 타입을 설정하는 것뿐만 아니라 인덱스의 전체적인 설정을 지정하는 기능을 합니다. 인덱스는 하나 이상의 샤드로 구성되어 있습니다. 샤드는 인덱스의 데이터를 분할되어 여러 노드에 나눠 저장되어 한 노드에 부하가 집중되는 것을 방지하며 replica 샤드를 통하여 고가용성(HA)을 보장하는 기능을 수행합니다. 이러한 설정을 인덱스의 template 설정을 통하여 지정할 수 있습니다. 위 request를 통하여 "my-log-template"라는 template을 설정하고 "my-log-"로 시작되는 모든 인덱스에 해당 설정이 적용됩니다. 파라미터에 대한 설명은 다음과 같습니다.

  • index_patterns : 해당 templat이 적용되는 인덱스명의 패턴을 입력합니다. 저는 "my-log-"로 시작되는 새롭게 생성되는 인덱스에 template이 적용되도록 설정하였습니다.
  • settings.index.number_of_shards : 인덱스는 하나 이상의 샤드로 구성되어 있는데 샤드의 개수를 늘려 여러 노드에 분할 저장될 수 있도록 설정할 수 있습니다. 해당 설정은 샤드의 개수를 지정하는 것인데 너무 많은 설정으로 구성하면 Elasticsearch의 Cluster 전체에 악영향을 미칠 수 있기 때문에 적당한 양의 샤드로 구성하는 것을 권장합니다. 저는 웬만하면 Data Node 개수의 배수로 설정하여 여러 Data Node에 골고루 분할할 수 있도록 설정합니다. 한 인덱스에 최대로 설정할 수 있는 샤드의 개수는 1024개입니다.
  • settings.index.number_of_replicas : 고가용성을 보장하기 위해 복제본 샤드의 개수를 설정합니다. 너무 많은 복제 샤드를 설정하면 데이터 보존의 안정성은 보장되지만 클러스터에 많은 부하가 발생할 수 있습니다. 저는 복제본 샤드를 1개로 구성하여 HA를 보장하지만 클러스터에 부하를 최소한으로 하도록 설정하여 사용합니다.
  • settings.index.refresh_interval : Elasticsearch는 데이터가 저장되면 바로 검색이 되지 않습니다. 인덱스에 refresh가 되어야지 검색이 가능해집니다. refresh가 1초마다 되면 실시간에 가깝게 검색을 수행할 수 있지만 데이터에 삽입되는 Indexing의 성능이 저하될 수 있습니다. 만약 실시간 검색이 중요하면 1초로 설정하지만 그렇지 않다면 어느 정도 긴 시간을 설정하는 것을 추천합니다. 저 같은 경우 로그 데이터 엄청 많은 양의 데이터가 발생할 수 있기 때문에 데이터가 안정적으로 들어오는 것이 중요하다고 판단하여 5초로 설정하였습니다. ML분야에 개발자 분들은 하루동안 데이터를 모으고 다음날 데이터를 분석할 때는 refresh가 하루에 한 번 또는 한 시간에 한번 수행하는 경우도 있다고 합니다.
  • settings.index.lifecycle.name : 인덱스의 lifecycle을 지정합니다. 위에서 설정한 ilm을 지정하여 인덱스가 생성되고 3일 뒤에 삭제 하도록 하였습니다.

다음은 데이터 필드 타입을 설정하는 부분입니다. https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html 해당 페이지에서 데이터 필드 타입에 대하여 알아보는 것을 권장합니다.

  • mappings.properties : 데이터 필드의 타입을 지정합니다. level필드는 keyword타입, timestamp는 date타입, logger는 keyword타입, message는 text타입, action은 keyword타입으로 설정하였습니다. 이 것 이외에 여러 데이터 타입이 있습니다.
  • mappings.dynamic_templates : dynamic_template은 데이터의 필드 명칭이 명확하지 않을때 사용하는 기능입니다.
  • mappings.dynamic_templates.message : message dynamic_template은 문자열로 입력된 msg로 시작되는 명칭의 필드가 들어오면 해당 필드의 타입은 text로 지정합니다. 
  • mappings.dynamic_templates.default_strings : 모든 문자열로 들어온 지정되지 않은 필드는 무조건 keyword 타입으로 설정합니다.
  • mappings.dynamic_templates.default_number : 모든 숫자형식으로 들어온 지정되지 않은 필드는 무조건 integer 타입으로 설정합니다.

Index template 확인

위 request를 통하여 생성된 인덱스 template을 확인합니다.

curl -XGET http://localhost:9200/_template/my-log-template

모든 Index template 가져오기

Elasticsearch에 설정된 모든 Index의 template를 가져옵니다.

curl -XGET http://localhost:9200/_template

Index template 삭제하기

더 이상 해당 Index의 template을 사용하지 않을 경우 Index template을 삭제합니다.

curl -XDELETE http://localhost:9200/_template/my-log-template

마지막으로

Elasticsearch의 template설정은 데이터 삽입 및 검색의 성능에 많은 영향을 미칠 수 있습니다. 자신의 환경 및 상황에 맞게 인덱스를 설정한다면 보다 더 효율적인 클러스터 운영이 가능해지기 때문에 데이터의 형식 및 검색할 때 어떻게 검색할 것인지를 파악하고 template를 반듯이 설정하는 것을 권장합니다.

 

 

Elasticsearch 다운로드

https://www.elastic.co/kr/downloads/elasticsearch 해당 주소에서 elasticsearch를 다운로드합니다.

Elasticsearch 다운로드

Elasticsearch 실행하기

압축을 푼 폴더 위치에서 bin/elasticsearch.bat파일을 실행하면 됩니다.

 

Elasticsearch 실행 확인하기 (크롬 Elasticsearch Head 설치하기)

Elasticsearch Tool의 경우 여러 가지가 있지만 저 같은 경우 크롬 확장 프로그램 중 하나인 Elasticsearch Head라는 프로그램을 사용하고 있습니다. https://chrome.google.com/webstore/category/extensions 링크에서 Elasticsearch Head를 검색하여 설치를 진행합니다.

 

설치하고 나면 확장 프로그램을 고정하여 편하게 사용합니다.

chrome://extensions/ 다음 링크를 들어가서 활성화 버튼을 클릭합니다.

크롬 우측상단에 Elasticsearch Head 아이콘이 추가된 걸 확인할 수 있습니다. 해당 아이콘을 클릭하면 Elasticsearch에 접속할 수 있는 화면이 나타납니다.

 

Elasticsearch Head로 Elasticsearch 접속하기

Connect창에 http://localhost:9200을 입력하여 Connect 버튼을 클릭하면 자동으로 연결이 됩니다.

아직 Elasticsearch의 포트번호를 디폴트로 설정했기 때문에 포트번호는 9200으로 설정되어 있습니다.

 

 

 

+ Recent posts