[Loki] Bloom Filter로 ExceedsLimits 요청의 백엔드 트래픽 대폭 감소
PR #20823 - feat: add bloom filter to cache known streams for ExceedsLimits requests
들어가며
Grafana Loki의 ingest limits frontend는 매 push 요청마다 ExceedsLimits RPC를 백엔드에 전송하여 스트림이 파티션 제한을 초과하는지 확인합니다. 대부분의 스트림은 한번 허용되면 계속 허용되므로, 이미 허용된 스트림에 대한 반복적인 RPC 호출은 낭비입니다. 이 PR은 블룸 필터를 사용하여 이미 알려진 스트림을 캐싱합니다.
핵심 코드 분석
캐시 클라이언트 구조
type cacheLimitsClient struct {
ttl time.Duration
onMiss limitsClient
mtx sync.RWMutex
knownStreams *bloom.BloomFilter
lastExpired time.Time
}
ExceedsLimits 캐시 히트 경로
func (c *cacheLimitsClient) ExceedsLimits(ctx context.Context, req *proto.ExceedsLimitsRequest) ([]*proto.ExceedsLimitsResponse, error) {
c.expireTTL()
// 모든 스트림이 이미 알려진 경우 -> RPC 건너뛰기
if c.hasKnownStreams(req) {
return []*proto.ExceedsLimitsResponse{}, nil
}
// 캐시 미스 -> 백엔드에 RPC 호출
resps, err := c.onMiss.ExceedsLimits(ctx, req)
// 허용된 스트림만 캐시에 추가 (거부된 스트림 제외)
// ...
}
TTL 기반 만료 (더블 체크 락킹)
func (c *cacheLimitsClient) expireTTL() {
// Fast path: 읽기 락으로 TTL 확인
c.mtx.RLock()
lastExpired := c.lastExpired
c.mtx.RUnlock()
if time.Since(lastExpired) <= c.ttl {
return
}
// 쓰기 락으로 이중 확인 후 캐시 클리어
c.mtx.Lock()
defer c.mtx.Unlock()
if time.Since(c.lastExpired) > c.ttl {
c.knownStreams.ClearAll()
c.lastExpired = time.Now()
}
}
스트림 인코딩
func encodeStreamToBuf(b *bytes.Buffer, tenant string, s *proto.StreamMetadata) {
b.Write([]byte(tenant))
_ = binary.Write(b, binary.LittleEndian, s.StreamHash)
}
테넌트 이름과 스트림 해시를 결합하여 블룸 필터의 키로 사용합니다.
왜 이게 좋은가
- 프론트엔드->백엔드 트래픽 대폭 감소: 이미 허용된 스트림(대다수)에 대한 RPC 호출을 완전히 건너뜁니다.
- 블룸 필터의 적절한 사용: false positive(실제로는 없는데 있다고 판단)가 발생하면 정상적으로 RPC를 호출하므로 안전합니다. false negative는 발생하지 않습니다.
- 랜덤 지터로 Thundering Herd 방지: TTL 만료 시 모든 인스턴스가 동시에 캐시를 클리어하지 않도록 랜덤 지터를 추가합니다.
- 거부된 스트림은 캐싱 안 함: 거부된 스트림은 상태가 변할 수 있으므로 의도적으로 캐싱하지 않습니다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Ray Data] 클러스터 오토스케일러에 논리 메모리 사용률 지표를 추가하여 스케일링 정확도 향상
- 현재글 : [Loki] Bloom Filter로 ExceedsLimits 요청의 백엔드 트래픽 대폭 감소
- 다음글 [pydantic-ai] 로컬 테스트 성능 개선: coverage 분리와 fixture 최적화
댓글