Tempo Ingester Flush Mechanism & OOM
1. 정의 (Definition)
Tempo Ingester가 메모리에 데이터를 쌓아두는 방식(Head Block)과 이를 디스크(WAL/Backend)로 내리는 조건(Flush)에 대한 메커니즘입니다. 특히 **Active Trace(진행 중인 트레이스)**가 Flush 조건(max_block_bytes)을 무시하고 메모리를 계속 점유할 수 있는 구조적 이유를 설명합니다.
2. 핵심 메커니즘 (How it Works)
Ingester는 들어오는 Span 데이터를 Head Block이라는 메모리 공간에 저장합니다. 이 Head Block은 다음 조건 중 하나를 만족할 때 Cut (완료 처리)되고 디스크(WAL)로 Flush 됩니다.
Flush Trigger 조건
max_block_bytes(Size Limit): 블록에 쌓인 “완료된(Complete) Trace들의 총합” + 기타 메타데이터 크기가 설정값(예: 64MB)을 초과할 때.max_block_duration(Time Limit): 블록이 생성된 지 일정 시간(예: 30분)이 지났을 때.- Shutdown: Ingester가 종료될 때.
”Active Trace”의 함정 (The Pitfall)
가장 중요한 점은 “아직 Span이 계속 들어오고 있는(Active) Trace”는 완료된 것으로 간주되지 않아 Block Size 계산에서 제외되거나, Block이 Cut되더라도 다음 Block으로 이월(Carry-over)될 수 있다는 점입니다.
Why? Trace ID가 같은 Span들은 하나의 Trace 객체로 묶여야 하므로, Trace가 끝났다는 신호(Timeout 등)가 오기 전까지는 메모리에서 해제할 수 없습니다.
3. 시각화 (Visualization)
sequenceDiagram participant Client participant Ingester(Mem) participant WAL(Disk) Note over Ingester(Mem): Head Block 생성 (Size: 0) Client->>Ingester(Mem): Trace A (10MB) 전송 Note over Ingester(Mem): Trace A: Active (Mem: 10MB) Client->>Ingester(Mem): Trace B (5MB) 전송 Note over Ingester(Mem): Trace B: Active (Mem: 15MB) Client->>Ingester(Mem): Trace B 완료 (End) Note over Ingester(Mem): Trace B: Complete (Flush 대상) Client->>Ingester(Mem): Trace A (100MB) 추가 전송 (누적) Note over Ingester(Mem): Trace A: Active (Mem: 115MB) Note over Ingester(Mem): max_block_bytes(64MB) 초과 감지! Ingester(Mem)->>WAL(Disk): Flush 시도 (Block Cut) Note right of Ingester(Mem): Trace B는 Flush됨.<br/>하지만 Trace A는 아직 Active 상태라<br/>메모리에 남아있거나 다음 블록으로 넘어감. Note over Ingester(Mem): **결국 메모리는 해제되지 않음 (OOM 위험)**
4. OOM 발생 원인 (Root Cause of OOM)
CashtalkAPI 사례처럼, 클라이언트가 하나의 Trace ID로 작은 Span을 끊임없이(수만 번) 보내면 다음과 같은 일이 벌어집니다.
- Ingester는 해당 Trace를 “진행 중”으로 판단.
max_block_bytes(64MB)가 차서 Flush를 시도함.- 다른 완료된 Trace들은 Flush 되고 메모리에서 비워짐.
- 하지만 100MB가 넘는 거대 Active Trace는 메모리에 그대로 남음.
- 이 상태에서 또 다른 거대 Trace가 들어오거나, 메모리 사용량이 Limit(16GB)을 치면 OOM Killed.
5. 결론 및 대응
Tempo의 max_block_bytes 설정은 **“여러 개의 작은 Trace들이 쌓이는 것”**을 방어하는 기제이지, **“하나의 거대한 Trace”**를 자르는 가위가 아닙니다.
따라서 이를 막으려면:
- Ingestion Rate Limit: 데이터 유입 속도 자체를 제한 (
ingestion_rate_limit_bytes). - External Proxy: NGINX 등에서 HTTP Body Size를 원천 차단.
- Client Fix: 클라이언트가 불필요하게 큰 Trace를 만들지 않도록 수정.
References
- Tempo Configuration Docs: Link
- Related Incident: SRE@Ingester_OOM_RCA