Grafana Loki에서 로그를 수집하는 방법에 대해 알아보자

Loki v3 기준으로 작성한 로그 수집하는 구조를 정리했습니다. 여기서는 수집기를 제외하고 Loki에서 로그를 수집하는데 쓰이는 컴포넌트들과 아키텍처에 대해서 공부한 바를 정리하고 있습니다.

Loki의 로그 수집 구조

                                  LOKI ARCHITECTURE & DATA FLOW
                                           
┌─────────────┐     ┌───────────────┐     ┌────────────────────────────┐     ┌───────────────────────┐
│             │     │               │     │                            │     │                       │
│   Client    │────▶│    Gateway    │────▶│         Distributor        │────▶│       Ingester        │
│             │     │               │     │    ┌─────────────────┐     │     │  ┌─────────────────┐  │
└─────────────┘     └───────────────┘     │    │ Kafka Producer  │─────┼────▶│  │ Kafka Consumer  │  │
                                          │    │ (Optional Path) │     │     │  │                 │  │
                                          │    └─────────────────┘     │     │  └─────────────────┘  │
                                          └────────────────────────────┘     └──────────┬────────────┘
                                                                            ┌───────────────────────┐
                                                                            │                       │
                                                                            │    Object Storage     │
                                                                            │    (S3/GCS/...)       │
                                                                            │                       │
                                                                            └───────────────────────┘

     데이터 흐름 설명:
     ───────────────
     
     1. Client → Gateway: 클라이언트가 로그 데이터를 게이트웨이로 전송
     
     2. Gateway → Distributor: 게이트웨이는 인증 후 distributor로 요청 전달
     
     3. Distributor → Ingester: distributor는 로그 검증, 샤딩, 라우팅 후 Ingester로 전송
        - 로그 유효성 검사
        - 스트림 샤딩
        - 해시 링 기반 라우팅
        - 복제 (replication)
     
     4. Distributor → Kafka (선택적): 설정에 따라 로그 데이터를 Kafka로도 전송
     
     5. Kafka → Ingester: Kafka에서 로그를 소비하여 별도의 Ingester 경로 제공
     
     6. Ingester → Storage: Ingester는 로그를 메모리에 유지하다가 청크로 압축하여 저장소에 플러시
        - 메모리에 로그 보관
        - 청크 압축 및 생성
        - WAL 관리
        - 주기적 플러시
     
     7. Kafka Ingester → Storage: Kafka 기반 Ingester도 동일하게 최종 스토리지에 데이터 저장

Loki의 핵심 개념: Chunk, Stream, Entity

Loki는 로그 데이터를 효율적으로 저장하고 쿼리하기 위해 Chunk, Stream, Entity라는 세 가지 핵심 개념을 사용합니다. 이 개념들은 Loki의 데이터 모델을 구성하는 기본 요소입니다.

Stream (스트림)

Stream은 Loki에서 로그 데이터를 구성하는 기본 단위입니다.

┌─────────────────────────────────────────────────────────┐
│                        Stream                           │
│                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │              │  │              │  │              │   │
│  │   Labels     │  │   Chunks     │  │  Metadata    │   │
│  │              │  │              │  │              │   │
│  │ - app=nginx  │  │ - Log Data   │  │ - Created    │   │
│  │ - env=prod   │  │ - Timestamps │  │ - Updated    │   │
│  │ - region=us  │  │ - Compressed │  │ - Stats      │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘
  • 정의: 고유한 라벨 집합으로 식별되는 로그 데이터의 연속
  • 특징:
    • 라벨 집합으로 고유하게 식별됨
    • 여러 Chunk로 구성됨
    • 시간 순서로 정렬된 로그 항목을 포함
  • 예시: {app="nginx", env="prod", region="us"}로 식별되는 스트림

Chunk (청크)

Chunk는 Stream 내의 로그 데이터를 저장하는 물리적 단위입니다.

┌─────────────────────────────────────────────────────────┐
│                        Chunk                            │
│                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │              │  │              │  │              │   │
│  │  Header      │  │  Log Data    │  │  Footer      │   │
│  │              │  │              │  │              │   │
│  │ - Start Time │  │ - Compressed │  │ - End Time   │   │
│  │ - End Time   │  │ - Log Lines  │  │ - Checksum   │   │
│  │ - Labels     │  │ - Timestamps │  │ - Stats      │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘
  • 정의: 특정 시간 범위의 로그 데이터를 압축하여 저장하는 단위
  • 특징:
    • 시간 기반으로 생성 (일반적으로 1시간)
    • 크기 기반으로 생성 (기본값 1.5MB)
    • 압축 알고리즘으로 압축 (Snappy, Gzip 등)
    • 메모리에 유지되다가 스토리지로 플러시됨
  • 생명주기:
    1. 메모리에서 생성 및 관리
    2. 크기 또는 시간 제한에 도달하면 완료
    3. 플러시 큐에 추가
    4. 스토리지에 플러시
    5. 인덱스에 등록

Entity (엔티티)

Entity는 Loki에서 데이터를 구성하는 논리적 단위로, 테넌트(Tenant)와 스트림을 포함합니다.

┌─────────────────────────────────────────────────────────┐
│                        Entity                           │
│                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │              │  │              │  │              │   │
│  │   Tenant     │  │   Streams    │  │  Metadata    │   │
│  │              │  │              │  │              │   │
│  │ - ID         │  │ - Label Sets │  │ - Quotas     │   │
│  │ - Access     │  │ - Chunks     │  │ - Limits     │   │
│  │ - Permissions│  │ - Data       │  │ - Stats      │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘
  • 정의: 테넌트(Tenant)와 그 테넌트에 속한 모든 스트림을 포함하는 논리적 단위
  • 특징:
    • 멀티테넌트 지원의 기본 단위
    • 테넌트별 리소스 제한 및 할당량 적용
    • 테넌트별 접근 제어 및 권한 관리
  • 구성 요소:
    • 테넌트 ID 및 메타데이터
    • 테넌트에 속한 모든 스트림
    • 테넌트별 설정 및 제한

데이터 모델 관계

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│              │     │              │     │              │
│   Entity     │────▶│   Stream     │────▶│   Chunk      │
│  (Tenant)    │     │              │     │              │
│              │     │              │     │              │
└──────────────┘     └──────────────┘     └──────────────┘
       │                    │                    │
       │                    │                    │
       ▼                    ▼                    ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│              │     │              │     │              │
│  Quotas      │     │  Labels      │     │  Log Data    │
│  Limits      │     │  Metadata    │     │  Timestamps  │
│  Permissions │     │  Stats       │     │  Compression │
└──────────────┘     └──────────────┘     └──────────────┘
  • Entity → Stream: 하나의 Entity는 여러 Stream을 포함
  • Stream → Chunk: 하나의 Stream은 여러 Chunk로 구성
  • Chunk: 실제 로그 데이터를 압축하여 저장하는 물리적 단위

데이터 흐름

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│              │     │              │     │              │
│  Log Data    │────▶│  Stream      │────▶│  Chunk       │
│              │     │  Creation    │     │  Creation    │
└──────────────┘     └──────────────┘     └──────────────┘
       │                    │                    │
       │                    │                    │
       ▼                    ▼                    ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│              │     │              │     │              │
│  Labels      │     │  Metadata    │     │  Compression │
│  Extraction  │     │  Update      │     │  Storage     │
│              │     │              │     │              │
└──────────────┘     └──────────────┘     └──────────────┘
  1. 로그 데이터가 들어오면 라벨을 추출하여 Stream을 식별
  2. Stream이 없으면 새로 생성
  3. Stream에 로그 데이터를 추가하고 Chunk로 관리
  4. Chunk가 완료되면 압축하여 스토리지에 저장
  5. 인덱스에 Chunk 정보를 등록하여 쿼리 가능하게 함

Loki의 index 저장소: TSDB vs BoltDB

Loki는 v2.8 이전까지는 BoltDB를 기본 index 저장소로 사용했으나, 이후부터는 TSDB(Time Series Database)를 권장하고 있습니다.

BoltDB (v2.8 이전)

  • 특징

    • 단일 파일 기반의 키-값 저장소
    • ACID 트랜잭션 지원
    • 임베디드 데이터베이스로 간단한 배포
  • 한계점

    • 단일 파일 구조로 인한 확장성 제한
    • 대규모 데이터에서 성능 저하
    • 동시성 처리의 제약
    • 백업과 복구가 어려움
  • BoltDB Components

┌─────────────────────────────────────────────────────────────────────────┐
│                              BoltDB                                     │
│                                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────────┐                  │
│  │              │  │              │  │               │                  │
│  │   Buckets    │  │ Key-Value    │  │ Transactions  │                  │
│  │              │  │  Pairs       │  │               │                  │
│  │ - Namespace  │  │ - Data       │  │ - ACID        │                  │
│  │ - Hierarchy  │  │ - Index      │  │ - Isolation   │                  │
│  │ - Access     │  │ - Metadata   │  │ - Consistency │                  │
│  └──────────────┘  └──────────────┘  └───────────────┘                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

TSDB (v2.8 이후)

  • 장점

    • 시계열 데이터에 최적화된 구조
    • 효율적인 인덱싱과 쿼리 성능
    • 수평적 확장 지원
    • 더 나은 메모리 관리
  • 특징

    • 청크 기반 데이터 관리
    • 시계열 인덱싱
    • 블룸 필터를 통한 쿼리 최적화 (v3 실험 기능)
  • TSDB Components

┌─────────────────────────────────────────────────────────────────────────┐
│                              TSDB                                       │
│                                                                         │
│  ┌──────────────┐  ┌───────────────┐  ┌──────────────┐                  │
│  │              │  │               │  │              │                  │
│  │   Index      │  │   Chunks      │  │ Metadata     │                  │
│  │              │  │               │  │              │                  │
│  │ - Time       │  │ - Log Data    │  │ - Retention  │                  │
│  │ - Labels     │  │ - Compression │  │ - Settings   │                  │
│  │ - Bloom      │  │ - Timestamp   │  │ - Stats      │                  │
│  └──────────────┘  └───────────────┘  └──────────────┘                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

v2.8부터 TSDB를 권장하는 이유

  1. 성능 향상

    • 더 빠른 쿼리 응답 시간
    • 효율적인 메모리 사용
    • 향상된 동시성 처리
  2. 확장성

    • 수평적 확장 지원
    • 대규모 데이터셋 처리 가능
    • 분산 환경에서의 안정성
  3. 유지보수성

    • 더 쉬운 백업과 복구
    • 개선된 모니터링
    • 단순화된 운영

Data Flow

┌──────────────┐       ┌────────────────────┐
│              │       │                    │
│  Log Data    │──────▶│  Chunk Creation    │
│              │       │  (Compression)     │
└──────────────┘       └────────────────────┘
         │                          │
         │                          ▼
         │                  ┌──────────────┐
         │                  │  Chunk Store │  (S3, GCS, etc)
         │                  └──────────────┘
┌────────────────────┐
│ Index Generation   │
│ (labels, ts)       │
└────────────────────┘
┌──────────────┐
│ Index Store  │  (BoltDB or TSDB)
└──────────────┘

실제로 TSDB로 저장소를 수정하고 컴포넌트들의 메모리 사용률과 쿼리 속도가 빨라진 것을 체감할 수 있습니다.

Loki의 압축 방식

Loki는 다양한 압축 방식을 지원하며, v3부터는 Snappy 압축을 권장하고 있습니다.

지원하는 압축 방식

  1. Snappy (권장)

    • Google에서 개발한 압축 알고리즘
    • 매우 빠른 압축/해제 속도
    • 적절한 압축률
    • CPU 사용량이 낮음
  2. Gzip

    • 이전 버전의 기본 압축 방식
    • 높은 압축률
    • 상대적으로 느린 압축/해제 속도
    • CPU 사용량이 높음
  3. LZ4

    • 매우 빠른 압축/해제 속도
    • Snappy보다 약간 낮은 압축률
    • CPU 사용량이 낮음
  4. Zstd

    • Facebook에서 개발한 압축 알고리즘
    • 높은 압축률
    • 빠른 압축/해제 속도
    • 다양한 압축 레벨 지원

압축 방식 선택 가이드

  1. Snappy 선택 시나리오

    • 빠른 압축/해제가 필요한 경우
    • CPU 리소스가 제한적인 환경
    • 적절한 압축률로 충분한 경우
  2. Gzip 선택 시나리오

    • 높은 압축률이 필요한 경우
    • 저장 공간이 중요한 경우
    • CPU 리소스가 충분한 경우
  3. LZ4 선택 시나리오

    • 극단적으로 빠른 압축/해제가 필요한 경우
    • 메모리 사용량이 중요한 경우
  4. Zstd 선택 시나리오

    • 높은 압축률과 빠른 속도가 모두 필요한 경우
    • 다양한 압축 레벨 조정이 필요한 경우

압축 방식별 특성 비교

┌─────────┬──────────┬──────────┬──────────┬──────────┐
│ Type    │ Ratio    │ Speed    │ CPU      │ Memory   │
├─────────┼──────────┼──────────┼──────────┼──────────┤
│ Snappy  │ Medium   │ Very Fast│ Low      │ Low      │
│ Gzip    │ High     │ Slow     │ High     │ Medium   │
│ LZ4     │ Medium   │ Very Fast│ Low      │ Low      │
│ Zstd    │ High     │ Fast     │ Medium   │ Medium   │
└─────────┴──────────┴──────────┴──────────┴──────────┘

Distributor: 로그 데이터 수집과 분산의 핵심

Loki 아키텍처에서 Distributor는 로그 데이터 수집의 첫 번째 단계로, 클라이언트로부터 받은 로그를 검증하고 Ingester에 효율적으로 분산시키는 핵심 컴포넌트입니다.

Distributor의 주요 책임

  • 로그 데이터 수신 및 유효성 검증
  • 대용량 스트림의 샤딩과 분할
  • 해시 링 기반 라우팅 및 복제
  • 속도 제한 및 할당량 적용
  • Kafka 또는 Ingester로의 데이터 전달

데이터 검증 메커니즘

클라이언트에서 들어오는 모든 로그 데이터는 엄격한 검증 과정을 거칩니다.

  • 라벨 검증: 형식, 길이, 유효성 검사
  • 필수 라벨 확인: 테넌트별 강제 라벨 적용
  • 수집 차단 정책: 정책에 따라 특정 로그 수집 차단
  • 개별 로그 검증: 크기, 타임스탬프 등 검사
  • 속도 제한: 테넌트별 수집 속도 제한 적용

스트림 샤딩 전략

대용량 로그 스트림을 효율적으로 처리하기 위해 두 가지 샤딩 전략을 사용합니다.

1. 속도 기반 샤딩

  • 높은 수집 속도의 스트림을 여러 샤드로 분할
  • 각 샤드는 고유한 해시 키를 부여받아 Ingester에 고르게 분산

2. 시간 기반 샤딩

  • 로그 타임스탬프를 기준으로 시간 청크로 분할
  • 각 시간 샤드는 독립적인 스트림으로 처리
  • 지연 도착 로그에 대한 특별 처리

해시 링과 복제 전략

Loki는 [DSKit(https://github.com/grafana/dskit)]의 해시 링을 사용하여 로그 데이터의 분산과 복제를 관리합니다.

  • 로그 스트림 해싱: 라벨 기반으로 로그 스트림 해시 계산
  • 복제 세트 결정: 해시 값에 따라 담당 Ingester 결정
  • 복제: 설정된 복제 요소(일반적으로 3)에 따라 여러 Ingester에 데이터 복제
  • 병렬 전송: 여러 Ingester에 동시에 데이터 전송
  • 장애 처리: 일부 Ingester 실패해도 복제본 덕분에 데이터 보존

Kafka 통합

Loki는 Ingester 외에도 Kafka로 로그를 전송할 수 있습니다.

  • 독립적 설정: Ingester와 Kafka 전송을 독립적으로 활성화 가능
  • 테넌트별 토픽: 테넌트별 커스텀 토픽 매핑 지원
  • 메시지 형식: 로그 데이터를 프로토버퍼로 직렬화하여 전송
  • 테넌트 정보 전달: 테넌트 ID를 레코드 키와 헤더에 포함

성능 최적화

Distributor는 다음과 같은 성능 최적화 기법을 적용합니다.

  • 병렬 처리: 워커 풀을 통한 동시 요청 처리 (PushWorkerCount 설정)
  • 스트림 샤딩: 대용량 스트림의 부하 분산
  • 캐싱: 라벨 파싱 결과 캐싱을 통한 반복 작업 최소화
  • 비동기 처리: Ingester 전송을 비동기로 처리하여 응답 시간 단축

Ingester: 로그 데이터 저장과 관리의 핵심

Ingester는 Loki 아키텍처에서 로그 데이터를 실제로 저장하고 관리하는 핵심 컴포넌트입니다. Distributor로부터 수신한 로그 데이터를 메모리에 유지하고, 압축하여 청크로 만든 후 장기 저장소에 플러시하는 역할을 담당합니다.

Ingester의 주요 구성 요소


┌─────────────────────────────────────────────────────────────────────────┐
│                               Ingester                                  │
│                                                                         │
│  ┌──────────────┐                                                       │
│  │              │                                                       │
│  │  gRPC/HTTP   │                                                       │
│  │   Service    │───┐                                                   │
│  │              │   │                                                   │
│  └──────────────┘   │                                                   │
│                     │                                                   │
│                     ▼                                                   │
│  ┌──────────────────────────────────────────────────────────────┐       │
│  │                      Instance Manager                        │       │
│  │                                                              │       │
│  │  ┌────────────────────────┐      ┌────────────────────────┐  │       │
│  │  │       Tenant A         │      │       Tenant B         │  │       │
│  │  │                        │      │                        │  │       │
│  │  │  ┌─────────────────┐   │      │  ┌─────────────────┐   │  │       │
│  │  │  │    Stream 1     │   │      │  │    Stream 1     │   │  │       │
│  │  │  │  ┌───────────┐  │   │      │  │  ┌───────────┐  │   │  │       │
│  │  │  │  │  Chunk 1  │  │   │      │  │  │  Chunk 1  │  │   │  │       │
│  │  │  │  └───────────┘  │   │      │  │  └───────────┘  │   │  │       │
│  │  │  │  ┌───────────┐  │   │      │  │  ┌───────────┐  │   │  │       │
│  │  │  │  │  Chunk 2  │  │   │      │  │  │  Chunk 2  │  │   │  │       │
│  │  │  │  └───────────┘  │   │      │  │  └───────────┘  │   │  │       │
│  │  │  └─────────────────┘   │      │  └─────────────────┘   │  │       │
│  │  │                        │      │                        │  │       │
│  │  │  ┌─────────────────┐   │      │  ┌─────────────────┐   │  │       │
│  │  │  │    Stream 2     │   │      │  │    Stream 2     │   │  │       │
│  │  │  │  ┌───────────┐  │   │      │  │  ┌───────────┐  │   │  │       │
│  │  │  │  │  Chunk 1  │  │   │      │  │  │  Chunk 1  │  │   │  │       │
│  │  │  │  └───────────┘  │   │      │  │  └───────────┘  │   │  │       │
│  │  │  └─────────────────┘   │      │  └─────────────────┘   │  │       │
│  │  └────────────────────────┘      └────────────────────────┘  │       │
│  └──────────────────────────────────────────────────────────────┘       │
│                     ▲                                                   │
│                     │                                                   │
│  ┌──────────────┐   │                                                   │
│  │              │   │                                                   │
│  │    Kafka     │───┘                                                   │
│  │   Consumer   │                                                       │
│  │              │                                                       │
│  └──────────────┘                                                       │
│                                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌─────────────┐  │
│  │              │  │              │  │              │  │             │  │
│  │ Memory Mgmt  │  │  WAL Manager │  │ Flush Manager│  │ Shard Mgmt  │  │
│  │              │  │              │  │              │  │             │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  └─────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

1. 데이터 수신 및 저장 경로

  • gRPC/HTTP 서비스
    • Push(): Distributor로부터 로그 데이터를 수신
    • QuerySample(): 쿼리 요청 처리
    • Label(): 라벨 관련 쿼리 처리
  • Kafka 소비자
    • Kafka 토픽에서 로그 데이터를 소비
    • 분산 환경에서 데이터 버퍼링 제공
    • 장애 복구 시 데이터 재처리 지원

2. 테넌트 및 스트림 관리

  • 인스턴스 (Tenant)
    • 각 테넌트별로 별도의 인스턴스 생성
    • 테넌트 격리를 통한 보안 및 리소스 관리
    • 테넌트별 스트림 맵 관리
  • 스트림
    • 각 로그 스트림(고유한 라벨 집합)별 데이터 관리
    • 스트림 한도 및 속도 제한 적용
    • 청크 생성 및 관리
  • 청크
    • 로그 데이터가 압축된 형태로 저장되는 단위
    • 시간 기반 롤오버 (MaxChunkAge 설정)
    • 크기 기반 롤오버 (MaxChunkSize 설정)

3. 시스템 관리 기능

  • Write-Ahead Log (WAL)
    • 메모리 데이터의 내구성 보장
    • 충돌/재시작 시 데이터 복구 지원
    • 정기적인 체크포인트 생성
  • 플러시 관리
    • 청크를 장기 저장소(오브젝트 스토리지)로 전송
    • 시간 기반, 크기 기반, 수동 플러시 지원
    • 플러시 큐 및 속도 제한 관리
  • 메모리 관리
    • 메모리 사용량 모니터링 및 제한
    • 메모리 압박 시 조기 플러시 시작
    • 테넌트별 메모리 할당량 관리

Ingester의 주요 작동 방식


┌──────────────┐    ┌─────────────────────────────────────────┐    ┌───────────────────────────────────────┐
│              │    │             Distributor                 │    │              Ingester                 │
│              │    │                                         │    │                                       │
│    Client    │───▶│  ┌─────────┐   ┌─────────┐  ┌────────┐  │    │  ┌─────────┐   ┌─────────┐  ┌───────┐ │
│              │    │  │         │   │         │  │        │  │    │  │         │   │         │  │       │ │
│              │    │  │Validator│──▶│Hash Ring│─▶│ Direct │──┼────┼─▶│ Stream  │──▶│  Chunk  │─▶│  WAL  │ │
└──────────────┘    │  │         │   │         │  │  Path  │  │    │  │ Manager │   │ Creator │  │Manager│ │
                    │  └─────────┘   └─────────┘  └────────┘  │    │  └─────────┘   └─────────┘  └───────┘ │
                    │       │                                 │    │       ▲                        │      │
                    │       │                                 │    │       │                        │      │
                    │       ▼                                 │    │       │                        │      │
                    │  ┌─────────┐   ┌─────────────────┐      │    │  ┌─────────┐                   │      │
                    │  │         │   │                 │      │    │  │         │                   │      │
                    │  │  Rate   │   │  Kafka Producer │──────┼────┼─▶│ Kafka   │                   │      │
                    │  │ Limiter │   │ (Optional Path) │      │    │  │Consumer │                   │      │
                    │  │         │   │                 │      │    │  │         │                   │      │
                    │  └─────────┘   └─────────────────┘      │    │  └─────────┘                   │      │
                    │                                         │    │                                │      │
                    └─────────────────────────────────────────┘    └────────────────────────────────┼──────┘
                                                                                        ┌─────────────────┐
                                                                                        │                 │
                                                                                        │ Object Storage  │
                                                                                        │ (S3/GCS/Azure)  │
                                                                                        │                 │
                                                                                        └─────────────────┘

1. 데이터 수신 흐름

  • 로그 데이터 수신
    • gRPC/HTTP를 통해 Distributor로부터 직접 수신
    • 또는 Kafka 토픽에서 소비
  • 적절한 테넌트 인스턴스 식별
    • 요청의 테넌트 ID를 기반으로 해당 테넌트 인스턴스 조회
    • 없는 경우 새 인스턴스 생성
  • 스트림 식별 및 추가
    • 라벨 해시를 통해 스트림 식별
    • 스트림이 존재하지 않으면 새로 생성
    • 스트림 수 제한 확인
  • 로그 항목 추가
    • 타임스탬프 순서 검증
    • 현재 활성 청크에 로그 항목 추가
    • 청크가 없거나 가득 찬 경우 새 청크 생성
  • WAL에 변경사항 기록
    • 데이터 추가시 WAL에도 기록
    • 정기적인 체크포인트 생성

2. 청크 라이프사이클 관리


┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│                 │     │                 │     │                 │
│  Active Chunk   │────▶│ Chunk Complete  │────▶│  Add to Flush   │
│    Creation     │     │    Decision     │     │     Queue       │
│                 │     │                 │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│                 │     │                 │     │                 │
│   Free Chunk    │◀────│  Update Index   │◀────│    Flush to     │
│     Memory      │     │                 │     │     Storage     │
│                 │     │                 │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘
  • 청크 생성
    • 새 로그 스트림 또는 활성 청크가 없을 때 생성
    • 각 청크는 메모리에서 압축된 형태로 관리
  • 청크 완료 결정
    • 시간 기반: MaxChunkAge 초과
    • 크기 기반: MaxChunkSize 초과
    • 메모리 압박 시 강제 완료
  • 플러시 큐에 추가
    • 완료된 청크는 플러시 큐에 추가
    • 우선순위에 따라 플러시 순서 결정
  • 스토리지에 플러시
    • 청크 데이터 장기 스토리지(오브젝트 스토리지)로 전송
    • 인덱스 정보 업데이트
  • 메모리 해제
    • 성공적으로 플러시된 청크는 메모리에서 해제
    • 인덱스 정보는 메모리에 유지 (쿼리 지원)

3. 재시작 및 복구 과정

  • WAL 디렉토리 검사
    • 시작 시 WAL 디렉토리 확인
    • 체크포인트 파일 확인
  • 체크포인트 복구
    • 최신 체크포인트 파일 로드
    • 테넌트, 스트림, 청크 구조 복원
  • WAL 재생
    • 체크포인트 이후의 WAL 항목 재생
    • 누락된 데이터 복구
  • 청크 상태 복원
    • 플러시 상태 복원
    • 활성/완료 청크 구분

Ingester의 고급 기능

1. 테일링(Tailing) 지원

  • 실시간 로그 스트리밍을 위한 테일러(Tailer) 구현
  • WebSocket 또는 gRPC 스트림을 통한 실시간 로그 제공
  • 라벨 기반 필터링 지원

2. 스트림 소유권 관리

  • 링 해시 기반의 스트림 소유권 결정
  • 스케일 업/다운 시 원활한 소유권 이전
  • 타임스탬프 기반 소유권 결정 지원

3. 제한 및 속도 관리

  • 테넌트별 스트림 수 제한
  • 테넌트별 수집 속도 제한
  • 메모리 사용량 기반 제한

4. Kafka 통합

  • Kafka 기반 로그 수집 지원
  • 컨슈머 그룹 기반 병렬 소비
  • 오프셋 관리 및 실패 처리

Ingester 설정 최적화

메모리 관리

  • MaxChunkAge: 청크 유지 최대 시간 (기본값: 1h)
  • MaxChunkSize: 청크 최대 크기 (기본값: 1.5MB)
  • RetainPeriod: 플러시 후 메모리에 유지 기간

플러시 설정

  • FlushCheckPeriod: 플러시 주기 확인 간격
  • MaxChunksPerTenant: 테넌트당 최대 청크 수
  • ConcurrentFlushes: 동시 플러시 작업 수

WAL 설정

  • WALEnabled: WAL 활성화 여부
  • CheckpointDuration: 체크포인트 생성 주기
  • Recover: 시작 시 WAL 복구 여부
  • Recover: 시작 시 WAL 복구 여부

컴팩터(Compactor): 스토리지 최적화와 데이터 수명 주기 관리

컴팩터는 Loki 아키텍처의 핵심 구성 요소로, 스토리지 최적화와 데이터 수명 주기 관리를 담당합니다.

컴팩터의 주요 책임

  • 인덱스 압축: 여러 작은 인덱스 파일을 더 효율적인 큰 파일로 압축
  • 데이터 보존 관리: 구성된 보존 기간을 초과한 데이터 제거
  • 삭제 요청 처리: 특정 로그 라인이나 레이블과 일치하는 데이터 선택적 삭제
  • 스토리지 효율성 향상: 압축을 통한 스토리지 사용량 최적화
  • 쿼리 성능 개선: 더 적고 큰 파일로 압축하여 쿼리 속도 향상

설계 원칙

  • 단일 실행 보장: 링(Ring) 기반 리더 선출 메커니즘으로 항상 하나의 컴팩터만 압축 작업 수행
  • 주기적 실행: 구성 가능한 간격(기본값 10분)으로 압축 작업 주기적 실행
  • 병렬 처리: 여러 테이블을 동시에 압축할 수 있는 병렬 처리 기능
  • 격리된 작업 공간: 압축 작업을 위한 독립적인 작업 디렉토리 사용

컴팩터 작동 방식

1. 시작 및 등록 과정

  • 링 등록: 컴팩터가 시작되면 링에 등록하고 JOINING 상태로 진입
  • 하위 서비스 시작: 필요한 하위 서비스(subservices) 시작
  • 상태 변경: 상태를 ACTIVE로 변경
  • 리더 선출: 링 내에서 리더 선출 프로세스 시작

2. 리더 선출 메커니즘

  • 단일 리더: dskit 링 구현을 활용하여 단일 리더 선출
  • 중복 방지: 여러 컴팩터 인스턴스가 배포된 경우에도 항상 하나의 인스턴스만 압축 작업 수행
  • 리더 기반 태스크: 선출된 리더만 압축 작업과 데이터 보존 관리 수행

3. 테이블 압축 로직

  • 테이블 잠금: 동시에 여러 작업이 같은 테이블에 접근하는 것을 방지
  • 압축 필요성 평가: 테이블에 압축되지 않은 파일이 있는지 확인
  • 테이블 초기화: 압축을 위해 테이블 객체 초기화
  • 압축 실행: 테이블 컴팩터를 사용하여 압축 수행
  • 보존 적용: 활성화된 경우 만료된 데이터 식별 및 제거
  • 정리: 임시 파일 정리 및 테이블 잠금 해제

4. 병렬 처리와 성능 최적화

  • 병렬 테이블 처리: MaxCompactionParallelism 설정으로 병렬로 처리할 테이블 수 제어
  • 병렬 업로드: UploadParallelism 설정으로 압축된 파일 업로드 병렬화
  • 효율적 리소스 사용: 병렬 처리를 통한 대규모 클러스터에서의 압축 성능 향상

컴팩터 구성 및 튜닝

  • CompactionInterval: 압축 작업 간의 간격 (기본값: 10분)
  • ApplyRetentionInterval: 보존 정책 적용 간격
  • MaxCompactionParallelism: 병렬로 압축할 테이블 수 (기본값: 1)
  • UploadParallelism: 압축된 파일 업로드를 위한 병렬 수준 (기본값: 10)
  • RetentionEnabled: 데이터 보존 기능 활성화 여부 (기본값: false)
  • DeleteRequestCancelPeriod: 삭제 요청이 실행되기 전 취소 가능한 기간 (기본값: 24시간)
┌─────────────────────────────────────────────────────────────────────────┐
│                              Compactor                                  │
│                                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │              │  │              │  │              │  │              │ │
│  │ Ring Leader  │  │   Index      │  │  Retention   │  │    Delete    │ │
│  │  Election    │  │ Compaction   │  │  Management  │  │   Requests   │ │
│  │              │  │              │  │              │  │              │ │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘ │
│          │                │                 │                 │         │
│          ▼                ▼                 ▼                 ▼         │
│  ┌──────────────────────────────────────────────────────────────────┐   │
│  │                                                                  │   │
│  │                         Object Storage                           │   │
│  │                      (S3/GCS/Azure/etc)                          │   │
│  │                                                                  │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

이 시리즈의 게시물

댓글