본문으로 건너뛰기

[Grafana Loki] 파서의 문자열 인턴 셋에서 키 충돌 결과 캐싱 버그 수정

PR 링크: grafana/loki#19984 상태: Merged | 변경: +70 / -50

들어가며

Grafana Loki의 로그 파서(JSON, Logfmt, Regexp, Unpack)에서는 문자열 인턴 최적화(internedStringSet)를 사용하여 동일한 키 문자열의 반복 할당을 방지합니다. 그런데 createNew 콜백 함수 안에서 기존 라벨과의 충돌 감지(duplicate suffix 추가)와 파서 힌트 체크를 함께 수행하고 그 결과를 캐싱했습니다. 이로 인해 첫 번째 스트림에서의 충돌 결과가 다른 스트림에도 그대로 적용되는 미묘한 버그가 발생했습니다.

핵심 코드 분석

Before: 충돌 감지를 캐시 내부에서 수행

// JSONParser
sanitizedKey, ok := j.keys.Get(key, func() (string, bool) {
    field := sanitizeLabelKey(string(key), true)
    if j.lbs.BaseHas(field) {
        field = field + duplicateSuffix  // 충돌 시 "_extracted" 추가
    }
    if !j.lbs.ParserLabelHints().ShouldExtract(field) {
        return "", false
    }
    return field, true  // 이 결과가 캐싱됨! 다른 스트림에도 동일 적용
})

After: 캐시에는 정제된 키만, 충돌 감지는 밖에서

// JSONParser
sanitizedKey, ok := j.keys.Get(key, func() (string, bool) {
    field := sanitizeLabelKey(string(key), true)
    if len(field) == 0 {
        return "", false
    }
    return field, true  // 순수하게 정제된 키만 캐싱
})
if !ok {
    return nil
}

// 충돌 감지는 캐시 밖에서 스트림별로 수행
if j.lbs.BaseHas(sanitizedKey) {
    sanitizedKey = sanitizedKey + duplicateSuffix
}

if !j.lbs.ParserLabelHints().ShouldExtract(sanitizedKey) ||
   j.lbs.ParserLabelHints().Extracted(sanitizedKey) {
    return nil
}

왜 이게 좋은가

  1. 스트림별 정확한 충돌 감지: 라벨 충돌은 각 스트림의 기존 라벨셋에 따라 달라지므로, 캐시 밖에서 매번 확인해야 정확하다.
  2. 인턴 캐시의 순수성 보장: internedStringSet은 바이트 슬라이스를 문자열로 변환하는 순수 매핑만 캐싱하고, 부수 효과가 있는 로직은 분리했다.
  3. 4개 파서 동시 수정: JSON, Logfmt, Regexp, Unpack 파서 모두에 동일한 패턴을 적용하여 일관성을 확보했다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글