[Grafana Loki] Bitmap.Slice에서 바이트 정렬 경계의 off-by-one 패닉 수정
PR 링크: grafana/loki#21354 상태: Merged | 변경: +23 / -1
들어가며
비트맵은 불리언 값의 배열을 메모리 효율적으로 저장하는 자료구조로, Loki의 쿼리 엔진에서 널러빌리티(nullable) 마킹 등에 사용된다. Bitmap.Slice(i, j) 메서드는 비트맵의 일부를 추출하는데, 슬라이스 끝 위치가 정확히 바이트 경계(8의 배수)에 떨어질 때 endWord 계산이 1바이트를 초과하여 out-of-bounds 패닉이 발생하는 버그가 있었다.
핵심 코드 분석
Before: 잘못된 endWord 계산
startWord = (bmap.off + i) / 8
endWord = ((bmap.off + j) / 8) + 1
(off + j)가 8의 배수일 때, 예를 들어 off=0, j=65536이면:
endWord = (65536 / 8) + 1 = 8193- 실제 데이터는 8192바이트만 존재하므로 인덱스 초과
After: 올림 나눗셈 적용
startWord = (bmap.off + i) / 8
endWord = ((bmap.off + j) + 7) / 8
같은 예시에서:
endWord = (65536 + 7) / 8 = 8192- 정확히 필요한 바이트 수만 참조
왜 이게 좋은가
- 올림 나눗셈 관용구:
(n + d - 1) / d는 양의 정수에 대한 올림 나눗셈의 표준 패턴이다.n이d의 배수일 때 불필요한 +1을 방지하고, 배수가 아닐 때는 올바르게 올림한다. - 회귀 테스트 추가: 65536비트(정확히 8192바이트) 비트맵과 24비트 비트맵의 바이트 정렬 경계에서 패닉이 발생하지 않음을 검증하는 테스트를 추가했다.
- 한 줄 수정: 핵심 변경은 수식 한 줄이지만, 대규모 비트맵 슬라이싱에서 발생하는 런타임 패닉을 완전히 제거한다.
바이트/비트 경계 계산에서 off-by-one은 매우 흔한 버그 유형이다. 특히 / 8 + 1 패턴은 (n + 7) / 8로 대체해야 하는 대표적인 예시다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Open WebUI] 채팅 제목 업데이트 시 DB 컨텍스트를 단일 세션으로 통합하여 역직렬화 2회 제거
- 현재글 : [Grafana Loki] Bitmap.Slice에서 바이트 정렬 경계의 off-by-one 패닉 수정
- 다음글 [Loki] 캐시 최대 크기 초과 시 조기 중단으로 OOM 방지
댓글