Trouble Shooting

Elasticsearch index.mapping.total_fields.limit 초과 에러 해결 가이드

Somaz 2026. 7. 20. 00:00
728x90
반응형

Overview

Node.js 로깅 라이브러리를 Winston에서 Pino로 마이그레이션한 후, Elasticsearch에서 `Limit of total fields [1000] has been exceeded` 에러가 발생하는 경우가 있다. 이는 Pino가 기본적으로 req 객체(헤더, 쿼리 파라미터, 메서드 등)를 자동으로 직렬화하여 로그에 포함시키기 때문이다.

 

이 글에서는 해당 에러의 원인을 분석하고, Filebeat에서 불필요한 필드를 제거하는 방법과 Elasticsearch의 `total_fields.limit` 설정을 조정하는 방법을 다룬다.

 

 

 

 


 

 

1. 문제 상황

 

1.1 에러 메시지

Pino 기반 로그를 Filebeat → Logstash → Elasticsearch 파이프라인으로 수집하던 중, 다음과 같은 에러가 발생했다.

IllegalArgumentException: Limit of total fields [1000] has been exceeded while adding new fields

 

 

1.2 왜 발생하는가?

Elasticsearch는 모든 인덱스에 기본적으로 `index.mapping.total_fields.limit: 1000` 설정이 적용되어 있다. 이 설정은 하나의 인덱스에 생성할 수 있는 필드(매핑) 수의 최대값을 제한한다. 별도로 설정하지 않아도 기본값으로 항상 걸려 있는 제한이다.

 

 

 

1.3 Winston에서는 왜 문제가 없었는가?

Winston은 HTTP 요청 객체(req)를 로그에 자동으로 포함시키지 않는다. 따라서 로그에 포함되는 필드 수가 1000개 미만이었기 때문에 문제가 발생하지 않았다.

 

반면 Pino는 기본 직렬화 동작으로 req 객체를 자동으로 포함시킨다.

{
  "level": 30,
  "time": 1707600000000,
  "msg": "request completed",
  "req": {
    "method": "GET",
    "url": "/api/users",
    "headers": {
      "host": "example.com",
      "user-agent": "Mozilla/5.0...",
      "accept": "application/json",
      "accept-language": "ko-KR,ko;q=0.9",
      "accept-encoding": "gzip, deflate, br",
      "connection": "keep-alive",
      "cache-control": "no-cache",
      "cookie": "session=abc123...",
      "x-forwarded-for": "192.168.1.1",
      "x-request-id": "uuid-1234"
      // ... ~17개 이상의 헤더 필드
    },
    "query": {
      "page": "1",
      "limit": "20"
    },
    "remoteAddress": "192.168.1.1",
    "remotePort": 54321
  },
  "res": {
    "statusCode": 200
  },
  "responseTime": 42
}
  • 이러한 중첩 필드들이 Elasticsearch에서 각각 개별 필드로 매핑되면서 기존 필드 수 + Pino의 `req.*` 필드가 합산되어 1000개를 초과하게 된다.

 

 

 

 


 

 

 

 

 

2. 해결 방법

총 세 가지 접근 방식이 있으며, 상황에 따라 하나만 적용하거나 병행할 수 있다.

방법 장점 단점
Filebeat에서 req 필드 제거 ES 설정 변경 불필요, 중복 데이터 제거 Pino 원본 로그와 다름
ES `total_fields.limit` 증가 설정 한 줄, req 데이터 보존 기존 인덱스는 별도 API 호출 필요
둘 다 적용 가장 안전 변경점 2개

 

 

 

 

2.1 방법 1: Filebeat에서 req 필드 제거

Filebeat 설정에서 `drop_fields` 프로세서를 사용하여 Elasticsearch로 전송하기 전에 req 필드를 제거한다.

 

 

`filebeat.yml`

filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/app/*.log
    json.keys_under_root: true
    json.overwrite_keys: true
    json.add_error_key: true

processors:
  - drop_fields:
      fields: ["req"]
      ignore_missing: true
  • req 필드 전체를 제거하면 `req.headers.*`, `req.query.*`, `req.method` 등 모든 하위 필드가 함께 제거된다.

 

 

만약 req의 일부 필드만 유지하고 싶다면, script 프로세서를 사용한다.

processors:
  - script:
      lang: javascript
      source: >
        function process(event) {
          var req = event.Get("req");
          if (req) {
            // method와 url만 보존하고 나머지 제거
            event.Put("req_method", req.method || "");
            event.Put("req_url", req.url || "");
            event.Delete("req");
          }
        }

 

 

설정 변경 후 Filebeat를 재시작한다.

# linux
sudo systemctl restart filebeat

# kubernetes
kubectl rollout restart -n <namesapce> deployment filebeat

 

 

 

2.2 방법 2: `Elasticsearch total_fields.limit` 증가

 

 

신규 인덱스 — 인덱스 템플릿 설정

 

Logstash 또는 인덱스 템플릿을 통해 신규 생성되는 인덱스에 limit 값을 설정한다.

PUT _index_template/app-logs-template
{
  "index_patterns": ["app-logs-*"],
  "template": {
    "settings": {
      "index.mapping.total_fields.limit": 2000
    }
  },
  "priority": 100
}

 

 

Logstash에서 설정하는 경우 output 블록에서 템플릿을 지정한다.

output {
  elasticsearch {
    hosts => ["https://elasticsearch:9200"]
    index => "app-logs-%{+YYYY.MM.dd}"
    template => "/etc/logstash/templates/app-logs-template.json"
    template_name => "app-logs-template"
    template_overwrite => true
  }
}

 

 

`app-logs-template.json` 파일 내용

{
  "index_patterns": ["app-logs-*"],
  "settings": {
    "index.mapping.total_fields.limit": 2000
  }
}

 

 

기존 인덱스 — API 호출로 변경

 

이미 생성된 인덱스는 템플릿 변경이 소급 적용되지 않으므로, 직접 API를 호출해야 한다.

# 특정 인덱스
curl -X PUT "https://elasticsearch:9200/app-logs-2025.02.11/_settings" \
  -H "Content-Type: application/json" \
  -d '{
    "index.mapping.total_fields.limit": 2000
  }'

# 와일드카드로 여러 인덱스 일괄 변경
curl -X PUT "https://elasticsearch:9200/app-logs-*/_settings" \
  -H "Content-Type: application/json" \
  -d '{
    "index.mapping.total_fields.limit": 2000
  }'

 

 

Kibana Dev Tools에서도 동일하게 실행할 수 있다,

PUT app-logs-*/_settings
{
  "index.mapping.total_fields.limit": 2000
}

 

 

 

 

 

2.3 방법 3: 둘 다 적용 (권장)

가장 안전한 방법은 두 가지를 모두 적용하는 것이다.

  1. Filebeat에서 불필요한 req 필드를 제거하여 Elasticsearch에 불필요한 데이터가 적재되지 않도록 한다.
  2. `total_fields.limit` 을 여유 있게 설정하여 향후 다른 필드가 추가되더라도 에러가 발생하지 않도록 한다.
 
# filebeat.yml — req 필드 제거
processors:
  - drop_fields:
      fields: ["req"]
      ignore_missing: true
 
 
// ES 인덱스 템플릿 — limit 여유 확보
{
  "index_patterns": ["app-logs-*"],
  "settings": {
    "index.mapping.total_fields.limit": 2000
  }
}

 

 

 

 

 

 


 

 

 

3. 적용 후 확인

 

 

3.1 현재 필드 수 확인

변경 후 인덱스의 필드 수가 limit 이하인지 확인한다.

# 인덱스의 매핑 필드 수 확인
curl -s "https://elasticsearch:9200/app-logs-2025.02.11/_mapping" | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
for idx, mapping in data.items():
    props = mapping['mappings'].get('properties', {})
    count = len(props)
    print(f'{idx}: {count} top-level fields')
"

 

 

3.2 현재 limit 설정 확인

curl -s "https://elasticsearch:9200/app-logs-2025.02.11/_settings" | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
for idx, settings in data.items():
    limit = settings['settings']['index']['mapping']['total_fields']['limit']
    print(f'{idx}: total_fields.limit = {limit}')
"

 

 

3.3 Filebeat 로그 확인

Filebeat에서 req 필드가 정상적으로 제거되고 있는지 확인한다.

# Filebeat 디버그 로그 확인
sudo filebeat -e -d "processors"

# 또는 Filebeat 로그 파일 확인
tail -f /var/log/filebeat/filebeat.log

 

 

 

4. 추가 고려사항

 

 

4.1 Pino 레벨에서 직렬화 커스터마이징

근본적으로 Pino가 `req` 객체를 직렬화하는 방식을 변경할 수도 있다.

const pino = require('pino');

const logger = pino({
  serializers: {
    // req 직렬화에서 headers 제외
    req: (req) => ({
      method: req.method,
      url: req.url,
      // headers는 제외
    }),
  },
});
  • 이 방법은 로그 원본 자체를 변경하므로, Filebeat/Logstash 설정 없이도 문제를 해결할 수 있다.

 

 

4.2 Dynamic Mapping 제어

Elasticsearch의 Dynamic Mapping을 `strict` 또는 `false` 로 설정하여 예상치 못한 필드가 매핑되는 것을 방지할 수도 있다.

PUT _index_template/app-logs-template
{
  "index_patterns": ["app-logs-*"],
  "template": {
    "settings": {
      "index.mapping.total_fields.limit": 2000
    },
    "mappings": {
      "dynamic": "false"
    }
  }
}
  • `dynamic: false` 로 설정하면 매핑에 정의되지 않은 필드는 인덱싱은 되지만 검색은 불가능하다.
  • 필드 매핑이 무한히 늘어나는 것을 방지할 수 있다.

 

 

4.3 `total_fields.limit` 값을 너무 높이지 말 것

limit 값을 무작정 높이는 것은 권장되지 않는다. 필드 수가 많아질수록 클러스터 상태(Cluster State) 크기가 증가하고, 이는 마스터 노드의 메모리 사용량 증가와 클러스터 안정성 저하로 이어질 수 있다. 일반적으로 2000~5000 사이에서 설정하되, 근본적으로는 불필요한 필드를 줄이는 것이 올바른 방향이다.

 

 

 

 


 

 

 

마무리

`index.mapping.total_fields.limit` 에러는 Elasticsearch를 운영하면서 자주 마주치는 문제 중 하나이다. 특히 로깅 라이브러리를 변경하거나, 새로운 데이터 소스를 추가할 때 필드 수가 급증하면서 발생하기 쉽다.

 

 

핵심 정리하면 다음과 같다.

  • Elasticsearch는 기본적으로 인덱스당 필드 수를 1000개로 제한한다.
  • Pino는 req 객체를 자동으로 직렬화하여 다수의 중첩 필드를 생성한다.
  • 해결은 불필요한 필드 제거(Filebeat/Pino)와 limit 상향(ES 설정)을 병행하는 것이 가장 안전하다.
  • 근본적으로는 Elasticsearch에 적재되는 필드 수를 최소화하는 방향이 클러스터 건강에 유리하다.

 

 

 

 

 

 

 


Reference

 

 

 

 

 

 

 

Somaz | DevOps Engineer | Kubernetes & Cloud Infrastructure Specialist

728x90
반응형