ラベル一つでコストが30倍になる
한국어 원문으로 표시합니다.
목표
Loki 는 로그 내용을 색인하지 않습니다. 라벨만 색인합니다. 그래서 싸고, 그래서 라벨을 잘못 잡으면 터집니다.
이 실습은 그걸 말로 듣는 대신 직접 터뜨려 봅니다.
시작
cp -r /opt/lab/loki/* . && chmod +x *.sh
setsid nohup loki -config.file=loki.yaml > loki.log 2>&1 </dev/null &
curl -s localhost:3100/ready # "ready" 가 될 때까지 20초쯤
export LOKI_ADDR=http://localhost:3100
setsid nohup 을 붙이세요. 그냥 & 로 띄우면 셸이 바뀔 때 같이 죽습니다.
도구
| 파일 | 하는 일 |
|---|---|
push.sh |
로그 한 줄 넣기 — 첫 인자가 라벨 JSON |
streams.sh |
지금 스트림 개수 (-v 면 조합까지) |
logcli |
LogQL 질의 |
LogQL 은 라벨 선택으로 시작한다
{app="web", level="error"} |= "timeout" | logfmt | status="500"
└── 색인으로 좁힌다 ──┘ └── 여기부터는 훑어 읽는다 ──────────┘
앞의 중괄호가 읽을 범위를 정하고, 뒤는 그 범위 안을 읽으며 거릅니다. 범위를 안 좁히면 전부 읽습니다.
단계
- 띄우고 한 줄 →
01-boot.txt - 라벨 조합 = 스트림 →
02-streams.txt - 내용은 색인 안 됨 →
03-notindexed.md - 카디널리티 폭발 →
04-explode.txt - 같은 정보를 내용에 →
05-fix.txt - 파서로 꺼내 쓰기 →
06-parser.txt - 라벨 판단 기준 →
07-rule.md - 정리 →
08-notes.md
참고
4단계와 5단계의 숫자 차이가 이 실습의 전부입니다. 같은 정보를 어디에 두느냐로 스트림이 30개 늘거나 1개 늡니다.
Loki 를 띄우고 한 줄 넣는다
/opt/lab/loki/ 를 복사해 Loki 를 띄우고 로그 한 줄을 넣은 뒤, 그 줄을 다시 꺼내 01-boot.txt 에 남기세요.
cp -r /opt/lab/loki/* . && chmod +x *.sh
setsid nohup loki -config.file=loki.yaml > loki.log 2>&1 </dev/null &
curl -s localhost:3100/ready # ready 가 될 때까지 20초쯤 걸린다
./push.sh '{"app":"web"}' 'GET /health 200'
export LOKI_ADDR=http://localhost:3100
logcli query --limit=5 --since=1h '{app="web"}'
{app="web"} 처럼 중괄호 안이 라벨 선택입니다. LogQL 은 여기서 시작합니다.
라벨 조합 하나가 스트림 하나
라벨 조합이 서로 다른 로그를 여러 줄 넣고, 스트림 개수가 조합 수만큼 늘어나는 것을 02-streams.txt 에 남기세요.
./streams.sh -v 로 지금 조합을 볼 수 있습니다. app 2종 × level 2종을 넣으면 스트림이 4개가 됩니다.
이 숫자가 Loki 비용의 대부분을 정합니다. 스트림마다 별도의 청크와 색인 항목이 생기기 때문입니다.
내용은 색인되지 않는다
라벨에 없는 문자열로 로그를 찾아 보고(|=), 그것이 색인이 아니라 훑어 읽기라는 것을 03-notindexed.md 에 적으세요.
logcli query --since=1h '{app="web"} |= "500"'. 잘 됩니다 — 다만 {app="web"} 으로 좁힌 범위 안을 전부 읽어서 찾은 것입니다.
그래서 LogQL 은 항상 라벨 선택으로 시작해야 합니다. {app=~".+"} |= "500" 은 전체를 훑습니다. Loki 가 Elasticsearch 보다 싼 이유이자, 라벨 설계가 중요한 이유입니다.
카디널리티를 일부러 터뜨린다
request_id 를 라벨로 넣어 로그 30줄을 밀어 넣고, 스트림 개수가 어떻게 되는지 04-explode.txt 에 남기세요.
for i in $(seq 30); do ./push.sh "{\"app\":\"bad\",\"request_id\":\"r$i\"}" "req $i"; done
./streams.sh
넣기 전후 개수를 둘 다 남기세요. 30줄에 스트림 30개가 늘어납니다 — 줄마다 스트림 하나입니다.
같은 정보를 내용에 둔다
같은 request_id 30건을 내용에 넣어 다시 밀어 넣고, 이번에는 스트림이 몇 개 느는지 05-fix.txt 에 남기세요.
라벨은 {"app":"good"} 하나로 두고, 줄 내용에 request_id=r1 status=200 dur=12ms 처럼 씁니다. 스트림이 1개만 늡니다.
한 줄 차이로 30배입니다. 그리고 잃는 것은 없습니다 — 다음 단계에서 파서로 그대로 꺼내 씁니다.
내용에서 값을 꺼내 쓴다
| logfmt 파서로 내용 안의 status 를 조건으로 걸어 원하는 줄만 찾고 06-parser.txt 에 남기세요.
logcli query --since=1h '{app="good"} | logfmt | status="200"'. 파서는 질의할 때 도므로 색인을 늘리지 않습니다.
이게 핵심입니다 — 라벨에 안 넣어도 필터링 능력은 그대로입니다. 잃는 것은 '색인으로 즉시 좁히기' 뿐이고, 얻는 것은 스트림 폭발을 안 겪는 것입니다.
라벨에 넣어도 되는 것을 가른다
07-rule.md 에 라벨로 써도 되는 것 3개와 쓰면 안 되는 것 3개를 이유와 함께 적으세요.
판단 기준은 하나입니다 — 값의 가짓수가 시간이 지나도 늘지 않는가. app, env, level 은 갇혀 있습니다. user_id, request_id, trace_id, ip, url 은 무한히 늘어납니다.
실무에서 가장 자주 하는 실수는 pod 이나 container_id 입니다 — 배포할 때마다 새 값이 생겨서, 서서히 터집니다.
세 가지를 정리한다
08-notes.md 에 세 줄 이상. 스트림이 무엇인지, 내용 필터가 색인과 어떻게 다른지, 라벨 판단 기준.
본문에 스트림, 색인, 가짓수 가 들어가야 합니다.