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` 증가
- 아래의 스크립트를 참고해도 된다.
- https://github.com/somaz94/script-collection/blob/main/bash/elastic-script/delete_old_indices_kr.sh
신규 인덱스 — 인덱스 템플릿 설정
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: 둘 다 적용 (권장)
가장 안전한 방법은 두 가지를 모두 적용하는 것이다.
- Filebeat에서 불필요한 req 필드를 제거하여 Elasticsearch에 불필요한 데이터가 적재되지 않도록 한다.
- `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
- Elasticsearch 공식 문서 — Mapping Limit Settings
- Elasticsearch 공식 문서 — Dynamic Mapping
- Elasticsearch 공식 문서 — Update Index Settings API
- Elasticsearch 공식 문서 — Index Templates
- Filebeat 공식 문서 — drop_fields Processor
- Pino 공식 문서 — Serializers
- Pino 공식 문서 — pino-http
Somaz | DevOps Engineer | Kubernetes & Cloud Infrastructure Specialist
'Trouble Shooting' 카테고리의 다른 글
| Jenkins 서버 정전 후 복구 - 플러그인 버전 불일치 해결 가이드 (0) | 2026.01.23 |
|---|---|
| Supermicro 서버 IPMI 설정 및 팬 제어 가이드 (1) | 2026.01.20 |
| Kubernetes Redis 클러스터 장애 처리 및 복구 가이드 (1) | 2026.01.13 |
| GitLab VM 장애 복구: NBD 마운트와 백업 복원으로 서비스 재구축하기 (2) | 2025.12.10 |
| NVIDIA Driver/Library Version Mismatch 오류 해결하기 (0) | 2025.09.17 |