클러스터 운영 중 일부 데이터 노드들의 디스크 수명이 다 되어 교체가 필요한 상황. (현재 하나의 노드에 디스크 3대를 꽂아 사용중이다.)

평소와 같이 교체하려던 중 난감한 상황에 맞닥뜨렸다.

 

디스크 수급 문제로 인해 기존 디스크의 1/2 사이즈인 디스크로밖에 교체를 할 수 없다!

...

Elasticsearch는 모든 노드가 동일한 양의 디스크를 가지고 있다고 가정하므로 각 노드의 디스크 용량이 다를 시 문제가 발생할 수 있다.

교체가 필요한 노드는 총 32대의 데이터 노드 중 9대...

 

이 문제를 해결할 수 있는 방법을 강구해보았다.

 

 

1. shard allocation 옵션 적용

참고: https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-cluster.html#disk-based-shard-allocation

 

cluster.routing.allocation.disk.watermark.low, high 옵션을 조정해 노드의 디스크 사용량이 초과될 경우 적재된 샤드의 노드 이동 및 신규 샤드 할당 제한한다.

덧붙여 cluster.routing.allocation 아래의 옵션을 조정해 샤드 리밸런싱 및 복구 수 제한을 두어 또 샤드의 이동으로 인해 발생할 수 있는 부하를 최대한 줄인다.

 

가장 간단하고 빠르게 적용할 수 있는 방법이다.

그러나!

아무리 줄이고 제한을 둔다고 해도 샤드 리밸런싱 자체가 노드와 클러스터에 큰 부하를 일으킬 수 있는 작업이고, 더욱이 대용량 데이터를 다룰 경우 그 부담이 매우 크다.

 

따라서 위 방법은 사용하지 않는 것으로 결정.

 

 

 

2. shard allocation filtering 적용

참고: https://www.elastic.co/guide/en/elasticsearch/reference/current/shard-allocation-filtering.html#shard-allocation-filtering

 

shard allocation filtering을 사용하면 특정 인덱스의 샤드 할당 위치를 제어할 수 있다.

기존 디스크를 사용하는 25대의 노드와 절반 사이즈의 디스크를 사용하는 9대의 노드를 두고 현재 적재되고 있는 인덱스 별로 디스크 노드 타입을 지정해 분리 적재할 수 있다.

따라서 클러스터의 디스크 용량을 효율적으로 관리할 수도 있고 데이터 일관성을 유지할 수 있어 관리하기도 편하다.

 

해볼만한 방법이다.

그러나...

 

무엇보다 유연성이 제한된다는 점이 마음에 걸렸다.

각 인덱스들이 전체 데이터 노드에 고루 분산되지 않고 나뉘어 저장될 경우 샤드의 리밸런싱에 제한이 있을 뿐 아니라 일부 노드에 부하가 집중될 수 있는 가능성이 커진다.

특정 인덱스의 데이터가 급증하거나 쿼리량이 급증할 경우, 혹은 노드가 다운될 경우 등의 상황에 전체 데이터 노드가 부하를 나눠 받는게 아니라 디스크 타입 별 노드 그룹에 영향을 미치니 말이다.

또한 데이터 특성 상 중요도가 높고 자주 사용되는 인덱스 타입들은 사이즈가 작아 구성 상 9대의 노드에만 분배할 수 있었기에 더욱 부담스러웠다.

 

 

3. Hot - Warm 아키텍쳐 적용

참고: https://www.elastic.co/kr/blog/implementing-hot-warm-cold-in-elasticsearch-with-index-lifecycle-management

 

인덱스의 사용빈도(즉, disk의 I/O 빈도)에 따라 tier를 나눠서 보관하는 방법.

자주 사용되는 인덱스는 성능이 좋은 Hot 노드에 저장하여 사용하고, 간혈적으로 사용하는 인덱스는 비교적 성능이 낮은 Warm 노드에 저장하여 구성할 수 있다.

현재 운영중인 클러스터에 적용한다면 총 32대의 노드 중 25대의 기존 디스크를 가진 노드들을 Hot 노드로, 나머지 9대의 노드를 Warm 노드로 구성한 뒤 Hot노드로 적재된 인덱스들 중 자주 사용하지 않는 오래된 인덱스들은 Warm노드로 이동시키는 정책을 설정할 수 있다.

 

가장 합리적인 방법이라고 생각했다.

샤드 필터링과 마찬가지로 디스크 사용량을 효율적으로 관리할 수 있는데다가 

무엇보다 인덱스 타입 별로 구분하는 것이 아닌 최신 데이터와 오래된 데이터를 분리하는 것이기 때문에 부하 또한 골고루 분산될 수 있기 때문이다.

더욱이 현재 적재되는 인덱스들은 daily 단위로 관리되고 있기 때문에 이동시킬 데이터를 구분하기도 쉬웠고 해당 정책을 비교적 간단히 적용할 수 있었다.

 

한가지 마음에 걸리는 건 hot노드에서 warm 노드로 인덱스가 이동할 때 클러스터에 부하가 있지 않을까 했던 것.

그러나 전체 인덱스 대비 1/8 규모의 인덱스만 이동하고 데이터 적재량과 사용량이 아주 드문 새벽 시간대에 이동 작업을 진행하므로 크게 무리는 없겠다는 판단이 섰고 진행했다.

 

적용 과정

1. elasticsearch.yml 파일을 이용해 노드 속성 warm노드로 변경 (노드 재시작 필요)

node.attr.box_type: warm
  • v7.10 이후 node.roles에 data_hot, data_warm 등 속성 지정이 가능하나 현재 노드에 box_type 속성으로 지정되어있어 해당 옵션으로 적용

 

2. ILM에 warm phase 추가

PUT _ilm/policy/index-policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "8d",
        "actions": {
          "allocate": {
            "require": {
              "box_type": "warm"
            }
          }
        }
      },
      "delete": {
        "min_age": "10d",
        "actions": {
          "delete": {
            "delete_searchable_snapshot": true
          }
        }
      }
    }
  }
}
  • hot노드에 적재된 인덱스들이 8일이 지나면 warm 노드로 이동하며 10일이 지나면 클러스터에서 제거된다.
  • 각 노드 타입 별 디스크 사이즈와 인덱스 별 사이즈를 계산하여 적절한 이동 주기를 계산한 후 적용하였다.

 

 

3. 인덱스 템플릿에 ILM 추가

PUT _template/index_template
{
  "index_patterns": ["test-*"], 
  "settings": {
    "index.lifecycle.name": "index-policy"
  }
}
  • 이미 인덱스 템플릿을 이용해 인덱스들을 관리하고 있다면 해당 과정은 적용하지 않아도 된다 (ilm정책 자동 업데이트)
  • 롤오버 작업이 포함된 ILM정책의 경우 템플릿에 rollover_alias 추가 및 앨리어스에 is_write_index 설정이 필요하다

 

4. 적재된 인덱스에 ilm 적용해 warm노드 임의 이동 (필요 시)

PUT test-2024-03-10/_settings
{
  "index.lifecycle.name": "index-policy" 
}
  • 기존 인덱스에 ILM이 적용되지 않았을 경우 적용한다.

 

(인덱스 warm노드 임의 이동)

PUT test-2024-03-10/_settings
{
  "settings": {
    "index.routing.allocation.require.box_type": "warm"
  }
}
  • ILM을 적용하지 않고 임의로 warm노드로 이동할 수도 있다. (이동만 한다. 삭제는 알아서...)

 

결과

 

전체 노드의 disk 사용량을 60% 이하로 유지하며 클러스터 부하 없이 안정적으로 유지 중이다!

 

 

간단 회고

내키진 않지만 플랫폼에서 권장하지 않는 구성을 해야만 할 때가 있다.

이번 이슈의 경우 그런 어려운 상황을 극복함과 동시에 현재 운영중인 클러스터의 안정성을 더 높였다는 성취감이 있어 더욱 뿌듯했다. (그래프가 생각했던 것 보다 더 이쁘기도 하다 ^_^)

'Elasticsearch' 카테고리의 다른 글

nested list field size 집계  (1) 2023.12.23
keyword list 필드 string으로 합치기  (1) 2023.12.23
NGram analyzer  (0) 2023.12.14
Elasticsearh Enrich processor  (1) 2023.10.28
ElasticSearch(oss) vs OpenSearch  (0) 2023.10.28

list 형태로 적재된 nested field의 전체 size 구하기

 

POST products/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "must": [
              {
                "nested": {
                  "path": "products",
                  "query": {
                    "exists": {
                      "field": "products"
                    }
                  }
                }
              }
            ] 
          }
        }
      ]
    }
  },
  "aggs": {
    "agg_hour": {
      "date_histogram": {
        "field": "time",
        "calendar_interval": "1h",
        "time_zone": "Asia/Seoul",
        "min_doc_count": 1
      },
      "aggs": {
        "products_agg": {
          "sum": {
            "script": {
              "inline": "params._source.products.size()"
            }
          }
        }
      }
    }
  }
}

 

products 인덱스에 nested list 형태로 적재되어있는 products 필드의 길이를 계산하여 시간대 별로 합산하여 집계

  • query 옵션으로 products 필드가 존재하는 문서만 집계 대상에 포함
  • aggs 옵션에 sum 메트릭 이용하여 products의 size의 합 집계
  • date_histogram 이용해 시간대 별 집계 적용

 

 

 

 

 

'Elasticsearch' 카테고리의 다른 글

Elasticsearch 디스크 불균등 이슈 해결하기  (0) 2024.03.10
keyword list 필드 string으로 합치기  (1) 2023.12.23
NGram analyzer  (0) 2023.12.14
Elasticsearh Enrich processor  (1) 2023.10.28
ElasticSearch(oss) vs OpenSearch  (0) 2023.10.28

keyword list형태의 필드를 하나의 string 으로 합쳐 재색인

  • reindex 및 script 사용

 

 

POST _reindex?wait_for_completion=false
{
  "source": {
    "index": "keywords"
  },
  "dest": {
    "index": "keywords_to_string"
  },
  "script": {
    "source": """
       if (ctx._source.kwd != null) {
          StringBuilder combinedValues = new StringBuilder();
          for (item in ctx._source.kwd) {
            combinedValues.append(item);
            combinedValues.append(" ");
          }
          
          ctx._source.str = combinedValues.toString().trim();
          ctx._source.remove('kwd');
        }
      """,
    "lang": "painless"
  }
}

keywords 인덱스에 keyword list 형태로 적재되어있는 kwd 필드를 하나의 string으로 합쳐서 keywords_to_string 인덱스의 str 필드로 적재

  • script 옵션 이용해 string 생성 코드 작성
  • 스페이스(" ")로 구분자 적용
  • 기존 kwd 필드 제거 필요할 경우 remove 이용해 제거

'Elasticsearch' 카테고리의 다른 글

Elasticsearch 디스크 불균등 이슈 해결하기  (0) 2024.03.10
nested list field size 집계  (1) 2023.12.23
NGram analyzer  (0) 2023.12.14
Elasticsearh Enrich processor  (1) 2023.10.28
ElasticSearch(oss) vs OpenSearch  (0) 2023.10.28

+ Recent posts