Loki는 왜 lable인가

hi·2026년 3월 14일

Loki 로그 시스템 이해하기 — 왜 Loki는 Elasticsearch와 다르게 설계되었을까

최근 Observability 스택에서 Loki + Grafana + Tempo + Prometheus 조합이 많이 사용되고 있다.
하지만 처음 Loki를 접하면 이런 의문이 생긴다.

  • 왜 Loki는 로그를 label 기반으로 검색할까?
  • trace_id 같은 값을 label로 두면 안 될까?
  • Elasticsearch처럼 모든 필드를 index하면 더 좋은 것 아닌가?

이 글에서는 Loki의 핵심 개념인

  • Label
  • Stream
  • Chunk
  • Index
  • Cardinality

를 중심으로 Loki의 설계 철학을 정리한다.


1. 로그 시스템의 두 가지 철학

로그 시스템은 크게 두 가지 방향으로 설계된다.

1️⃣ 검색 중심 시스템 (ELK / Elasticsearch)

목표

로그를 자유롭게 빠르게 검색하는 것

예 로그

{
  "service": "order",
  "tenantId": "acme",
  "userId": "42",
  "trace_id": "abc",
  "message": "order created"
}

Elasticsearch는 대부분의 필드를 index한다.

index
 ├ service
 ├ tenantId
 ├ userId
 ├ trace_id
 └ message

그래서 이런 검색이 가능하다.

tenantId = acme AND status = FAILED
userId = 42
trace_id = abc

장점

  • 자유로운 검색
  • 빠른 query

단점

  • index 비용이 큼
  • 저장 비용이 매우 큼
  • 운영이 복잡

보통

로그 1GB
→ index 포함 3~5GB 저장

2️⃣ 저장 중심 시스템 (Loki)

Loki의 철학은 완전히 다르다.

로그를 최대한 싸게 저장하는 것

Loki는 index를 최소화한다.

그래서 로그는 이렇게 저장된다.

{labels} log line

{service="order"} order created

Loki는 label만 index한다.

index
 ├ service
 ├ namespace
 ├ cluster

나머지 데이터는 log line으로 저장된다.

장점

  • 저장 비용 매우 낮음
  • 운영 단순
  • 대규모 로그 저장 가능

단점

  • 자유로운 검색은 제한적

2. Loki의 핵심 개념

Loki를 이해하려면 다음 개념을 알아야 한다.

Label
Stream
Chunk

3. Label

Label은 로그의 메타데이터이다.

{service="order", pod="order-1"}

이 label들은 Loki에서 index 역할을 한다.

그래서 쿼리는 항상 label로 시작한다.

{service="order"}

이렇게 하면 Loki는

service=order

인 로그만 찾는다.

4. Stream

Loki에서 가장 중요한 개념은 stream이다.

정의

같은 label 조합을 가진 로그들의 그룹

{service="order", pod="order-1"}

이 label을 가진 로그들은 모두 같은 stream에 속한다.

stream
 ├ log
 ├ log
 ├ log

label이 달라지면 새로운 stream이 생성된다.

{service="order", pod="order-1"}
{service="order", pod="order-2"}
stream1
stream2

5. Chunk

로그는 stream 내부에서 chunk 단위로 저장된다.

stream
 └ chunk
      ├ log
      ├ log
      ├ log

보통

1 chunk ≈ 1MB

chunk는 압축되어 object storage(S3 등)에 저장된다.

6. Loki 쿼리 동작 방식

Loki 쿼리는 다음 단계로 동작한다.

1️⃣ label index lookup

{service="order"}

→ order 관련 stream 찾기

2️⃣ stream 선택

{service="order", pod="order-1"}
{service="order", pod="order-2"}

3️⃣ chunk scan

선택된 stream의 chunk를 읽는다.

4️⃣ log filter

|= "error"

같은 조건을 적용한다.

7. Cardinality 문제

Loki에서 가장 중요한 개념은 cardinality이다.

정의

값의 종류 개수

low cardinality

service
 ├ order
 ├ payment
 ├ user

값이 적다.

high cardinality

trace_id
user_id
request_id

값이 매우 많다.

8. 왜 trace_id를 label로 두면 안 되는가

trace_id는 요청마다 생성된다.

trace_id
a1
a2
a3
a4

만약 trace_id를 label로 사용하면

{trace_id=a1}
{trace_id=a2}
{trace_id=a3}

요청마다 새로운 stream이 생긴다.

요청 수 = stream 수

이 현상을

stream explosion

이라고 한다.

9. Stream explosion이 발생하면

세 가지 문제가 발생한다.

1️⃣ Ingester 메모리 폭발

Loki는 active stream을 메모리에 유지한다.

stream이 많아지면 메모리가 터진다.

2️⃣ tiny chunk 문제

stream마다 chunk가 생긴다.

1KB chunk
2KB chunk
3KB chunk

원래는

1MB chunk

여야 한다.

그래서 storage 효율이 나빠진다.

3️⃣ ingestion 성능 저하

로그 쓰기 과정에서

stream lookup

이 계속 발생한다.

stream이 많아지면 ingestion 속도가 떨어진다.

10. 좋은 label 설계

Loki에서 label은 low cardinality만 사용해야 한다.

좋은 label

service
namespace
cluster
pod

나쁜 label

trace_id
user_id
request_id
session_id

11. Loki vs Elasticsearch 정리

항목ElasticsearchLoki
목표검색저장
index많음최소
비용높음낮음
검색 자유도높음제한적
운영 난이도높음낮음

12. 언제 Loki를 사용해야 할까

Loki가 잘 맞는 경우

  • Grafana 기반 observability
  • 로그 저장 비용이 중요한 경우
  • 메트릭 / 트레이스 기반 장애 분석
  • 대규모 로그 저장

13. 언제 Elasticsearch가 좋은가

Elasticsearch가 잘 맞는 경우

  • 로그를 데이터 분석에 사용
  • 복잡한 검색이 많은 경우
  • 보안 로그 분석
  • 감사 로그 분석

결론

Loki는 Elasticsearch보다 검색 기능이 약한 대신 저장 비용이 훨씬 낮다.

핵심 차이는 이것이다.

Elasticsearch → 검색 최적화
Loki → 저장 최적화

그래서 Loki에서는

low cardinality label

설계가 매우 중요하다.

0개의 댓글