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_idlong 타입으로 매핑되어 있었다.

2. 명시적 매핑 템플릿 생성

동적 매핑에 의존하지 않고 인덱스 템플릿으로 필드 타입을 명시했다.

PUT _template/logs_template
{
  "index_patterns": ["logs-*"],
  "mappings": {
    "properties": {
      "user_id": { "type": "keyword" },
      "timestamp": { "type": "date" },
      "level": { "type": "keyword" },
      "message": { "type": "text" }
    }
  }
}

user_idkeyword 타입으로 통일했다. 숫자도 문자열로 처리되므로 양쪽 서비스 모두 문제없이 인덱싱된다.

3. 애플리케이션 로그 포맷 통일

근본적으로는 각 서비스에서 보내는 로그 스키마를 통일하는 것이 맞다. 공통 로깅 라이브러리를 만들어 배포하고, user_id는 항상 문자열로 변환하도록 수정했다.

// logger.js
const formatLog = (data) => ({
  ...data,
  user_id: String(data.user_id),
  timestamp: new Date().toISOString()
});

교훈

동적 매핑은 편리하지만 프로덕션에서는 위험하다. 특히 여러 소스에서 데이터가 들어오는 경우 명시적 매핑 템플릿이 필수다. 첫 데이터의 타입에 의존하는 것은 예측 불가능한 에러를 만든다.