Index Template은 인덱스를 생성할 때 사용되는 설정 템플릿입니다. 새로운 인덱스가 생성될 때 매번 정의하는 번거러움을 인덱스 템플릿 설정을 통하여 해결할 수 있습니다. 인덱스 템플릿을 선언을 통해 자동적으로 새롭게 생성되는 인덱스에 설정을 적용하여 일관된 인덱스 구조와 매핑을 유지하고 반복적인 작업을 줄이는 데 사용됩니다.

 

Index Template 생성

실습으로 my-log라는 Index Template을 생성하여 데이터 필드가 잘 적용되는지 확인하도록 하겠습니다.

 

인덱스 생성 시 설정 파라미터에 대한 정보

https://opensearch.org/docs/1.2/opensearch/rest-api/index-apis/create-index/

 

Create index

Create index Introduced 1.0

opensearch.org

PUT _index_template/my-log
{
    "index_patterns": [
        "my-log-*"
    ],
    "template": {
        "settings": {
            "number_of_shards": 4,
            "number_of_replicas": 1,
            "refresh_interval": "5s",
            "opendistro.index_state_management.policy_id": "my-ism"
        },
        "mappings": {
            "properties": {
                "cluster": {
                    "type": "keyword"
                },
                "node": {
                    "type": "keyword"
                },
                "level": {
                    "type": "keyword"
                },
                "message": {
                    "type": "text"
                },
                "timestamp": {
                    "type": "long"
                }
            },
            "dynamic_templates": [
                {
                    "default_string": {
                        "match": "default*",
                        "match_mapping_type": "string",
                        "mapping": {
                            "type": "keyword"
                        }
                    }
                },
                {
                    "default_number": {
                        "match": "default*",
                        "match_mapping_type": "long",
                        "mapping": {
                            "type": "integer"
                        }
                    }
                }
            ]
        }
    }
}

index_patterns을 통해 "my-log-"로 시작하는 새롭게 생성된 인덱스에 해당 Template을 적용하도록 지정합니다.

 

number_of_shards 해당 인덱스의 샤드 개수를 설정합니다. Opensearch의 경우 Cluster가 여러 Data Node로 구성되는데 하나의 Data Node에 데이터가 집중되지 않도록 number_of_shards를 설정하여 여러 Data Node에 샤드를 골고루 배치할 수 있습니다.

 

number_of_replicas는 primary shard를 복제한 replica shard의 개수를 설정합니다. 위 설정에서는 1로 설정하여 각각의 primary shard가 복제한 샤드를 한 개씩 구성할 수 있도록 설정하여 HA 구성이 되도록 설정하였습니다.

 

refresh_interval은 간단하게 설명하면 검색할 수 있는 document를 refresh 하는 것을 의미합니다. Opensearch는 데이터가 들어왔다고 바로 검색이 가능한 것이 아니고 데이터가 들어오고 해당 인덱스가 refresh가 되었을 경우 검색이 가능하게 됩니다. refresh_interval을 1s로 설정하였다면 1초마다 refresh 되어 실시간처럼 검색이 가능하게 되지만 indexing 성능에 영향을 미칠 수 있습니다. 만약 본인의 환경이 실시간을 중요하게 여기지 않고 indexing 성능을 우선으로 한다면 refresh_interval값을 크게 가져가는 것을 추천합니다. 보다 자세한 정보를 원하신다면 "Lucene"에 대해 알아보는 것을 추천합니다.

 

opendistro.index_state_management.policy_id 설정은 ISM Policy 정책을 지정하는 것입니다. ISM은 Index state Management로 인덱스의 Lifecycle을 관리해 주는 역할을 수행합니다. 아래 링크에 정리해 두었으니 자세한 내용을 링크에서 확인해 주세요

https://stdhsw.tistory.com/entry/Opensearch-ISMIndex-State-Management

 

mappings.properties 설정은 데이터의 field의 타입을 지정합니다. string 타입에는 대표적으로 keyword, text 타입이 있는데 keyword는 Aggregation과 같이 통계를 내거나 반듯이 같은 값을 검색하는 데 사용되며 text의 경우 Analyzer를 통해 데이터를 분석하는 데 사용됩니다. 그 밖에 많은 field type이 존재하는데 자세한 내용은 Opensearch document를 확인해 주세요

https://opensearch.org/docs/latest/field-types/supported-field-types/index/

 

Supported field types

Supported field types

opensearch.org

mappings.dynamic_templates 설정은 properties로 지정하지 않은 field의 데이터가 들어올 경우 해당 데이터의 필드 타입을 지정합니다. dynamic_templates 은 어떠한 데이터 필드가 들어올지 명확하지 않거나 동일한 이름 형식의 필드가 너무 많을 경우 사용합니다. 위에서 설정된 default_string의 경우 필드의 이름이 default로 시작되며 데이터 형식이 string일 경우 필드의 타입을 keyword로 지정하는 설정이며 default_number의 경우 default로 시작하여 데이터 형식이 number일 경우 필드의 타입을 integer로 설정합니다.

 

Index Template 확인하기

방금 생성한 my-log Template을 확인합니다.

GET _index_template/my-log

 

테스트 해보기

지정된 Template이 잘 적용되는지 bulk를 이용하여 실제 데이터를 넣어 확인합니다. (참고로 bulk는 HTTP Body의 가장 마지막 라인에 enter를 한번 더 넣어줘야지 Error가 발생하지 않습니다.)

POST _bulk
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992028}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992038}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992048}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-2", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992029}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-2", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992030}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-2", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992031}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-2", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992033}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "err", "message": "[err] failed data send", "timestamp": 1688992048}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "warn", "message": "[warn] json format is not valid", "timestamp": 1688992041}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "err", "message": "[err] failed data send", "timestamp": 1688992042}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-2", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992031, "default-text": "APP Test"}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-2", "level": "debug", "message": "[debug] data send successful", "timestamp": 1688992033, "default-text": "APP Test"}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "err", "message": "[err] failed data send", "timestamp": 1688992048, "default-text": "APP Test"}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "warn", "message": "[warn] json format is not valid", "timestamp": 1688992041, "default-number": 111111}
{ "index": { "_index": "my-log-2023-07-10"} }
{ "cluster": "my-test-cluster", "node": "worker-1", "level": "err", "message": "[err] failed data send", "timestamp": 1688992042, "default-number": 222222}

 

정상적으로 데이터가 들어갔는지 확인해 봅니다.

POST my-log-2023-07-10/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ]
    }
  },
  "from": 0,
  "size": 20
}

 

마지막으로 저장된 데이터와 해당 데이터의 필드 타입이 어떻게 설정되었는지 확인합니다.

GET my-log-2023-07-10/

결과는 다음과 같습니다.

{
    "my-log-2023-07-10": {
        "aliases": {},
        "mappings": {
            "dynamic_templates": [
                {
                    "default_string": {
                        "match": "default*",
                        "match_mapping_type": "string",
                        "mapping": {
                            "type": "keyword"
                        }
                    }
                },
                {
                    "default_number": {
                        "match": "default*",
                        "match_mapping_type": "long",
                        "mapping": {
                            "type": "integer"
                        }
                    }
                }
            ],
            "properties": {
                "cluster": {
                    "type": "keyword"
                },
                "default-number": {
                    "type": "integer"
                },
                "default-text": {
                    "type": "keyword"
                },
                "level": {
                    "type": "keyword"
                },
                "message": {
                    "type": "text"
                },
                "node": {
                    "type": "keyword"
                },
                "timestamp": {
                    "type": "long"
                }
            }
        },
        "settings": {
            "index": {
                "opendistro": {
                    "index_state_management": {
                        "policy_id": "my-ism"
                    }
                },
                "refresh_interval": "5s",
                "number_of_shards": "4",
                "provided_name": "my-log-2023-07-10",
                "creation_date": "1688992258343",
                "number_of_replicas": "1",
                "uuid": "n24SvkagRl2OI0NcHb5SXw",
                "version": {
                    "created": "136297827"
                }
            }
        }
    }
}

template으로 지정한 필드의 경우 정상적으로 필드의 타입이 적용된 것을 확인할 수 있습니다. 그리고 default-number와 default-text의 경우 직접적으로 지정하지는 않았지만 dynamic_templates 설정을 통하여 자동적으로 문자열 타입은 keyword로 지정되고 숫자 타입은 integer로 설정된 것을 확인할 수 있습니다.

 

commit template을 사용하면 커밋 메시지의 구조와 형식을 일관되게 유지할 수 있습니다. 팀의 구성원들이 동일한 template을 사용하면 commit message가 일관성 있게 작성되어 코드 기록과 변경 내용을 파악하기 쉬워집니다. template을 설정하면 커밋 작성 과정이 자동화되고 개발자는 템플릿에 정의된 섹션에 적절한 내용을 작성하기만 하면 되기 때문에 git commit이 간편해집니다.

1. git commit template 파일 만들기

mkdir ~/git
vim ~/git/.gitmessage

2. ~/git/.gitmessage 파일 작성하기

생성한 파일에 커밋 템플릿을 작성합니다. 

 

# 다음과 같은 형식으로 제목을 작성합니다.
# [type]: 제목
[fea|docs|mis|fix|test|ref]: 제목

# 설명을 입력합니다.
describe:

#=========================
# fea : 새로운 기능 추가
# docs : 문서 수정
# mis : 실수로 빼먹은 내용
# fix : 버그 수정
# test : 테스트 코드
# ref : 코드 리팩토링
#=========================

3. 작성한 파일 적용하기

Git의 commit.template 설정을 업데이트합니다.

git config --global commit.template ~/git/.gitmessage

위 명령어는 ~/git/.gitmessage.txt 파일을 전역 설정으로 사용하도록 Git에 알려줍니다. --global 옵션을 사용하면 현재 사용자의 전역 Git 설정에 영향을 미치게 됩니다. 이제 git commit 명령어를 실행하면 설정한 템플릿이 기본적으로 열리며, 해당 템플릿에 따라 커밋 메시지를 작성할 수 있습니다.

 

'git' 카테고리의 다른 글

주니어 개발자가 알면 좋은 git 명령어 정리  (0) 2023.07.12
git config  (0) 2023.07.11

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를 반듯이 설정하는 것을 권장합니다.

 

+ Recent posts