DevOps/Loki

Loki에서 인덱싱이란

peanutwalnut 2025. 6. 5. 09:57

Loki에서 “인덱싱”이란, 결국 “어떤 로그 라인이 어떤 레이블(label) 조합(stream)에 속하고, 언제(타임스탬프 범위) 수집되었는지”를 빠르게 조회할 수 있도록 메타데이터를 별도의 구조(인덱스)로 저장하는 과정을 말합니다. Loki가 실제로 사용하는 인덱싱 방식을 단계별로 풀어보면 다음과 같습니다.

 

 

1. 로그는 “스트림(stream)” 단위로 취급한다

  • Loki에서 하나의 “스트림”이란, 동일한 레이블(예: {job="app", instance="host1"} 같은 키=값 쌍) 을 가진 로그들의 묶음을 뜻합니다.
  • Promtail(혹은 다른 Loki 클라이언트)이 로그를 Push API로 보낼 때, 각 로그 라인은 반드시 하나 이상의 레이블 셋(label set)을 동반합니다.
    • 예:
       
      {job="syslog", instance="host1"} 2025-06-05T10:00:00Z “message A” {job="syslog", instance="host1"} 2025-06-05T10:00:01Z “message B”
    • 위 두 라인은 {job=syslog, instance=host1}이라는 하나의 스트림(stream)으로 묶입니다.

2. 메모리 상에서 “Chunk” 단위로 로그를 모은다

  1. 메모리 버퍼(Chunk Builder)
    • Loki 메인 프로세스가 로그를 수신하면, 같은 스트림에 속하는 로그를 내부 메모리의 “Chunk Builder”로 전달하여 묶기 시작합니다.
    • Chunk Builder는 “현재 스트림의 청크가 목표 압축 크기(chunk_target_size) 에 도달하거나,
      • max_chunk_age(예: 2시간) 이 경과하거나,
      • chunk_idle_period(예: 30분) 동안 해당 스트림에 새 로그가 없을 때
        이런 조건들을 하나라도 만족하면 “이제 메모리 상에서 하나의 청크(chunk) 완성”으로 간주합니다.
  2. 청크 생성 시점
    • 예를 들어 {job="app", instance="host1"} 스트림에서 10:00부터 로그가 들어오다가,
      • 압축된 용량이 1.5MB(기본 chunk_target_size) 이상이 되거나,
      • 2시간이 지나서 12:00가 되었거나,
      • 30분 동안 로그가 유입되지 않아 10:30까지 들어온 후 한동안 들어오지 않으면
    • 그 시점에 “지금까지 메모리에 모은 로그(시작 타임에서 마지막 타임까지의 범위)”가 하나의 청크로 플러시(쓰기)됩니다.

3. 청크가 디스크에 저장될 때 “인덱스” 정보를 같이 만든다

  1. 청크 내부 구조
    • 완성된 청크는 실제 로그 메시지(타임스탬프+메세지)들만 snappy/LZ4 같은 압축 알고리즘으로 묶은 바이너리 바이트 스트림입니다.
    • 이 청크 파일만으로는 “특정 레이블 값이 어느 시간 구간에 있는지”를 빠르게 찾기 어렵습니다.
  2. 인덱스(Index) 정보란?
    • 그래서 Loki는 “이 청크에 어떤 스트림(stream) 레이블 조합이 들어 있고, 그 스트림 로그가 어떤 시작 시각(startTimestamp)–끝 시각(endTimestamp) 범위를 커버하는지”를 별도 인덱스에 기록합니다.
    • 또한, 같은 스트림 내부라도 “청크 내부에서 이 타임스탬프에 해당하는 로그가 물리적으로 청크 파일 내 어느 바이트 위치(offset)에 저장되었는지” 같은 정보도 함께 남겨 두면, 실제 쿼리할 때 해당 바이트 위치만 부분적으로 읽어서 디코딩할 수 있습니다.
  3. TSDB 기반 인덱스
    • Loki는 TSDB(Time Series Database)의 인덱싱 엔진을 그대로 차용해서, “레이블 값(label value)”별로 posting list(=해당 레이블값을 가진 모든 스트림 ID와 타임스탬프 범위 정보) 구조를 만듭니다.
    • 구체적으로:
      1. Series ID(또는 Stream ID): {job="syslog", instance="host1"} 같은 레이블 조합은 내부적으로 64비트 숫자 ID(Series ID) 하나로 치환됩니다.
      2. Label 인덱스:
        • job="syslog"라는 레이블 값이 붙은 시리즈(스트림) 정보와 해당 시리즈에 청크로 저장된 “시간 범위(start–end)”를 매핑하는 인덱스 테이블
        • instance="host1"라는 레이블 값이 붙은 시리즈 정보와 시간 범위를 매핑하는 인덱스 테이블
      3. Timestamp 범위: 청크 파일 명이나 내부 메타데이터에 “이 청크는 2025-06-05T10:00:00부터 2025-06-05T11:30:00 구간의 로그를 담고 있다” 같은 정보가 붙습니다.
      4. Chunk 참조 정보:
        • “이 스트림 ID에 속한 청크 파일 x는, 시작 10:00, 끝 11:30이고, 실제 바이너리 데이터는 /loki/chunks/…/chunkfile_x에 있다”라는 연결 고리(메타데이터)도 저장됩니다.
    • 이 모든 정보는 TSDB 인덱스 디렉터리 아래에, “하루치 인덱스” 단위로 묶여 파일(예: LevelDB/TSDB) 형태로 들어갑니다.

4. schema_config로 “인덱스 분할(period)”을 관리한다

  1. schema_config.configs[].index.period: 24h
    • 앞서 설명했듯이, Loki는 인덱스를 하루(24시간) 단위로 잘게 쪼개어 관리하도록 설정됐습니다.
    • 그 결과,
      • 2023-11-27 00:00~24:00 사이에 수집된 모든 로그의 “인덱스”는 /loki/index_2023-11-27_<숫자>/… 디렉터리에 저장되고,
      • 2023-11-28 00:00~24:00 사이 수집된 로그는 /loki/index_2023-11-28_<숫자>/… 디렉터리에 저장됩니다.
  2. 이유
    • 쿼리 성능: 특정 기간(예: 2025-06-05 00:00~2025-06-05 23:59) 로그를 조회할 때, Loki는 “해당 기간에 해당하는 인덱스 디렉터리(full path)를 곧바로 찾아가서” 검색할 수 있습니다.
    • Retention(보관) 정책:
      • Compactor(또는 Index Table Manager)가 “7일 전 인덱스 디렉터리는 다 지워도 된다”라고 하면, /loki/index_2025-05-29_*, /loki/index_2025-05-28_* 등을 쉽게 삭제할 수 있습니다.
      • “청크”도 마찬가지로, 인덱스를 기준으로 어느 시기에 생성된 청크인지 파악하기가 용이해집니다.