Elasticsearch 동적 매핑 때문에 발생한 타입 충돌 해결
문제 상황
여러 마이크로서비스의 로그를 Elasticsearch로 수집하는 중, 특정 시점부터 인덱싱 실패가 발생했다. 에러 메시지를 확인하니 user_id 필드의 타입 충돌이었다.
mapper_parsing_exception: failed to parse field [user_id] of type [long]
A 서비스는 user_id를 숫자로, B 서비스는 문자열로 보내고 있었다. 동적 매핑 환경에서 먼저 들어온 데이터 타입으로 필드가 고정되면서 생긴 문제였다.
해결 과정
1. 기존 인덱스 매핑 확인
GET /logs-2020.01/_mapping
예상대로 user_id가 long 타입으로 매핑되어 있었다.
2. 명시적 매핑 템플릿 생성
동적 매핑에 의존하지 않고 인덱스 템플릿으로 필드 타입을 명시했다.
PUT _template/logs_template
{
"index_patterns": ["logs-*"],
"mappings": {
"properties": {
"user_id": { "type": "keyword" },
"timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text" }
}
}
}
user_id를 keyword 타입으로 통일했다. 숫자도 문자열로 처리되므로 양쪽 서비스 모두 문제없이 인덱싱된다.
3. 애플리케이션 로그 포맷 통일
근본적으로는 각 서비스에서 보내는 로그 스키마를 통일하는 것이 맞다. 공통 로깅 라이브러리를 만들어 배포하고, user_id는 항상 문자열로 변환하도록 수정했다.
// logger.js
const formatLog = (data) => ({
...data,
user_id: String(data.user_id),
timestamp: new Date().toISOString()
});
교훈
동적 매핑은 편리하지만 프로덕션에서는 위험하다. 특히 여러 소스에서 데이터가 들어오는 경우 명시적 매핑 템플릿이 필수다. 첫 데이터의 타입에 의존하는 것은 예측 불가능한 에러를 만든다.