본문 바로가기

Log,Monitorings

[Log] Grafana Loki 로그 Write 시 참고 사항

반응형

 
 
해당 글에서는 운영 시 참고하면 좋을 내용들만 정리합니다.
 
쓰기와 읽기로 나누어 각각 어떤 부분을 고려하며 Loki를 운영하면 좋은지에 대해 정리합니다.
 
정석적인 내용은 아니기에 참고용으로만 봐주세요.

 


1. 로그 쓰기

로그 쓰기 작업은 다양한 방식으로 수행할 수 있다.
 
Log Client로부터 로그를 수집 및 정재한 뒤에 Loki로 바로 전송할 수도 있고 Kafka 같은 Queue를 두어 전송할 수도 있다.
 
Grafana Loki에서는 Promtail, Grafana Agent를 위주로 설명하고 있지만 필수는 아니며 fluentBit, Logstash, fluentD, Vector 등 다양한 Log Client는 물론 AWS Lambda와 같은 Serverless에서 loki HTTP 요청을 통해서도 로그를 전송할 수 있다.
 

1.1. Replication Factor (복제 요소)

복제 요소란 Distributor에서 동일한 Log에 대해 복제본을 구성하여 RF 개수 만큼 여러 Ingester에게 전달하여 '로그의 유실을 최소화'하는 목적으로 사용할 수 있다.
 
https://grafana.com/docs/loki/latest/get-started/components/#replication-factor

 

Loki components |  Grafana Loki documentation

Open source Loki components Distributor The distributor service is responsible for handling incoming streams by clients. It’s the first stop in the write path for log data. Once the distributor receives a set of streams, each stream is validated for corr

grafana.com

 
로그의 유실을 최소화한다는 것은 Ingester가 롤아웃을 하거나 다시 시작하는 과정에서 로그 쓰기(Write)가 중단 없이 계속되도록 허용하는 장치라고 이해하면 된다.
 
아래의 quorum 공식에 따라 Distirbutor가 몇대의 Ingester에게 쓰기를 성공해야 하는지 알 수 있다.
즉, RF = 3일 경우 2대의 Ingester로부터 쓰기 성공 응답이 와야함을 의미한다.
 
floor(replication_factor / 2) + 1
 
 
하지만 RF = 3 설정은 치명적인 단점이 존재한다. 바로 동일한 Copy의 로그 3개가 그대로 장기 저장소(ex. S3)에 저장 된다는 것이다.
 
Ingester에서 장기 저장소에 Flush 하는 과정을 살펴보자.
 
1. Distributor는 RF의 개수에 따라 여러 Ingester로 라우팅 -> 중복된 청크 데이터를 가지고 있음
 
2. 특정 조건에 만족하면 Ingester는 장기 저장소로 flush 한다.
Ingester #1이 Stream {app: test}에 대해 S3에 flush
..
Ingester #2이 Stream {app: test}에 대해 S3에 flush
..
Ingester #3이 Stream {app: test}에 대해 S3에 flush
..
 
그럼 과연 중복된 Chunk에 대해서는 대체 누가 중복성 제거를 담당할까? Compactor? 바로 Ingester가 수행한다.
(Compactor는 Retention, Index De-duplication만 담당한다.)
 
Ingester는 중복 청크 플러시를 방지하기 위해 다음의 De-duplication 과정을 수행한다.
 
1. Ingester #1가 Stream {app: test}에 대해 S3로 Flush 할 때 청크 캐시를 확인하고 청크 캐시에 캐시가 된 경우 Ingester는 S3로 Flush 하지 않는다. 만약 캐시가 되어 있지 않다면 S3로 Flush 한다.
-> Write Back
 
2. Ingester #2가 Stream {app: test}에 대해 S3로 Flush 할 때 위와 동일한 과정을 반복한다.
 
그렇다면 위의 과정에서 과연 중복성제거가 잘 수행될까? 답은 '아니다'라고 말할 수 있을 것 같다.
 
그 이유로는 Loki를 롤링 업데이트 하게 되면 PDB에 의해 Ingester가 1대씩 재시작이 수행된다.
 
이 말은 바로 각 Ingester 인스턴스들이 서로 다른 시간에 로그 수집을 시작했고 이로 인해 서로 다른 청크 데이터를 가지고 있음을 의미한다.
 
Ingester는 Chunk Cache를 확인할 때 Hashed 된 Memcached 혹은 In-Memory Cache Key를 확인하는데 이는 당연히 청크 데이터를 기반으로 해싱되기 때문에 Ingester #1, #2, #3은 서로 다른 Cache Key를 가지고 있고 중복성 제거 또한 잘 수행되지 않는다.
 
 
아래는 약 4년전 Grafana Loki 블로그 글이다.
 
https://grafana.com/blog/2020/02/19/how-loki-reduces-log-storage/

 

How Loki Reduces Log Storage | Grafana Labs

Peter Štibraný   ·   11 Feb 2020   ·   6 min read How We’re (Ab)using Hashicorp’s Consul at Grafana Labs

grafana.com

 
해당 글에서는 Chunk Flush의 동기화 주기를 맞춰 동일한 모든 Ingester가 Chunk 데이터를 유지하도록 하고 이로 인해 동일한 Chunk Data를 가질 수 있기에 중복성제거를 할 수 있다고 말하는 것 같다.
 
하지만 필자가 테스트 했을 때 Chunk Cache를 Memcached로도 해보고 In-Memory로도 해보았지만 드라마틱한 효과는 보지 못했으며 중복성 제거가 제대로 동작되기가 어려운 것 같다.. 
또한 이 글은 4년전 글이기 때문에 최신 업데이트된 내용은 없다.

 
필자는 이러한 이유로 인해 로그 유실을 최소화하며 RF = 1로 변경하는 방안을 검토중이다.

 

만약 Ingester에서 Chunk flush 시 Deduplication이 잘 수행될 경우 당연히 RF=1은 검토하지 않아도 된다.

 

하지만 필자의 경우 완벽까진 아니지만 절반 정도만이라도 중복성 제거를 잘 수행할 수 있는 방법을 아직 찾지 못했기에 RF=1을 최후의 보후로 검토중이다.

 
중간에 Kafka를 두고 Loki 재시작 시 로그 전송을 멈춘 뒤 강제로 Flush 하고 재시작을 하면 정식적인 방법은 아닌 것 같지만 만약 잘 된다면 다음의 효과를 볼 수 있을 것이라 생각한다.
 
1. 쿼리어 부하 감소 -> 검색 속도 개선
2. 스토리지 용량 절감
3. S3 API CALL 횟수 절감으로 인한 비용 절감
 
(참고로 S3에 중복된 Chunk가 있어도 Querier, Query Frontend에서 중복성 제거를 처리하기 때문에 사용자 입장에서는 중복된 로그는 감지하지 못한다.)
 


 

2.  timestamp 순서

(동일 Stream이 여러 Pod로부터 전달될경우 타임스탬프를 보장하는 설정이 꽤 효과적일 수 있다.)

 

 

해당 내용에 대해 아래 Grafana Blog에 자세히 설명되어 있으니 아래 글을 먼저 읽어봐도 좋다.


https://grafana.com/blog/2024/01/04/the-concise-guide-to-loki-how-to-work-with-out-of-order-and-older-logs/

 

The concise guide to Loki: How to work with out-of-order and older logs | Grafana Labs

Path: blog/2024-01-04-the-concise-guide-to-loki-how-to-work-with-out-of-order-and-older-logs.list-en.md Copied!

grafana.com

 
타임스탬프 순서란, Loki가 Log Client로부터 로그를 받을 때 순서를 얘기한다.
 
만약 현재 시간이 13:19인데 Client(fleuntBit)에서 13:17에 대한 로그를 보낸다면 이는 Out Of Order(순서에 맞지 않은) 로그가 되는 것이다.

 
관련 설정값은 다음과 같다.
 
      reject_old_samples: true
      reject_old_samples_max_age: 1w
 
위 설정의 의미로는 다음과 같다.
오래된 timestamp 로그를 허용할지 말지에 대한 설정이며 만약 허용할 경우 최대 1w전 로그까지 허용한다는 뜻이다.
 
그렇다면 오래된 로그 허용에 대해 유심히 살펴봐야 하는 것일까?
 
예시로 Stream이 {app: my-test-app, env: production} 인 청크가 15분 마다 s3로 flush 된다고 가정해본다.
 
13:00 ~ 13:15 동안 저장된 Chunk #1 (순서가 잘 맞음)
13:00 로그
13:01 로그
13:02 로그
13:03 로그
13:13 로그
13:14 로그
 
13:16 ~ 13:30동안 저장된 Chunk #2 (중간에 순서가 하나 안맞음)
13:16 로그
13:17 로그
13:01 로그
13:18 로그
13:19 로그
13:20 로그
 
13:00 ~ 13:15 동안 저장된 로그는 자신이 가지고 있는 로그들의 StartTime과 EndTime이 13:00 ~ 13:15 그대로 설정되지만
13:16 ~ 13:30 동안 저장된 로그는 자신이 가지고 있는 로그들의 StartTime과 EndTime을 13:01 ~ 13:30 으로 설정해야 한다.
 
→ 즉, Querier에서 13:00 ~ 13:10 의 로그를 읽을 때 순서가 안 맞는 13:01 로그 하나 때문에 Chunk #2를 읽어들어야 함.
 
청크 데이터가 각각 1M라고 한다면 굳이 1M만 읽어도 되는 로그를 2M씩이나 읽어야 함을 의미한다.
 
 
일반적으로 Log Client에서는 Application의 Timestamp 기준이 아닌 로그를 전송 시 기준 Timestamp로 보내는게 일반적이다.
 
따라서 DaemonSet으로 배포된 Log Client들 중 1대가 로그가 밀렸다가 보내지게 될 경우 이런 현상이 발생할 수도 있고 그 외 다양한 네트워크 상황에 따라 로그 전송 지연이 발생할 수도 있음을 의미한다.
 
그렇다면 이를 어떻게 해결하는게 좋을까?
 
방법이야 다양하겠지만 필자는 위에 언급한 Kafka를 통해 해결하려고 하고 있다.
 
1) Log Client에서는 특정 Key를 기준으로 특정 Partition에 데이터를 전송하도록 파티셔닝 정책을 가져간다.
ex. app이란 Key를 기준으로 파티셔닝 될 수 있도록 함
 
2) Consumer는 Partition과 1:1 매핑을 맺고 Loki로 로그를 전송한다.
-> 이 때 Consumer가 현재시각에 해당하는 Timestamp를 만들어 Loki timestamp로 전달한다.
-> 로그 순서 보장
 
주의할 점으로 몇가지가 있다.
 
1) app마다 로그를 발생시키는 양이 달라 Consumer마다 처리해야하는 로그 양이 다를 것이다. Consumer lag을 잘 모니터링 해야한다.
2) 1에서 언급한대로 파티션 별로 로그 불균형이 발생할 수 있다.

(이는 적용해본 결과 크게 불균형이 발생하지 않는다. 고로 큰 문제는 되지 않는다.)

 


몇 가지 더 정리하려고 했지만 글이 너무 길어질 것 같아서 이정도로만 정리하고 마무리하려고 한다. (오랜만에 글쓰니 힘듬)
키워드만 나열할테니 나머지는 필요하시면 조사해보시길 바랍니다.
 


- Loki label에 대한 이해
https://grafana.com/blog/2023/12/20/the-concise-guide-to-grafana-loki-everything-you-need-to-know-about-labels/

 

The concise guide to Grafana Loki: Everything you need to know about labels | Grafana Labs

The concise guide to Grafana Loki: Everything you need to know about labels • December 20, 2023 • 11 min Welcome to Part 2 of “The concise guide to Loki,” a multi-part series where I cover some of the most important topics around our favorite loggi

grafana.com

 
- Stream에 대한 이해
 
- Ingester에서 Log를 전달 받고 In-Memory Chunk(Head, Blocks)에서 어떤 조건으로 S3에 플러싱되는지
>> chunk_target_size, chunk_idle_period, chunk_max_age(chunk flush 조건)
>> chunk_block_size(Head -> Blocks 이동 조건)
 
- automatic stream sharding
https://grafana.com/docs/loki/latest/operations/automatic-stream-sharding/

 

Automatic stream sharding |  Grafana Loki documentation

Open source Automatic stream sharding Automatic stream sharding will attempt to keep streams under a desired_rate by adding new labels and values to existing streams. When properly tuned, this should eliminate issues where log producers are rate limited du

grafana.com

 

 

 

이 중 stream timestamp 순서 보장이 은근히 큰 효과를 보았다. 로그 순서가 보장되면서 querier는 더 적은 data를 긁어오게 되었다.

 

추가로 rf=1의 경우 최후의 보루로 생각하고 적용하는게 좋다. consumer들을 중지하고 loki 재시작하는 등 귀찮은 작업들이 필요해지기 때문이다.

 

다음 글에는 실제 사용자 입장에서 느끼기에 가장 중요한 읽기 성능 관련하여 분석해보도록 하겠다.
읽기 튜닝에 따라 속도가 n배 차이날 수 있기 때문

반응형