Loki — 로그를 색인하지 않는 로그 저장소 · 넣는 쪽의 한도 · 실습
재시도를 켜 두고 퇴근했더니 다음 날 큐가 가득 차 있었다
목표
Loki 가 들어오는 요청을 거절하는 네 가지 이유를 직접 만들어 보고, 400 과 429 를 서로 다르게 다루는 클라이언트를 만들어 한 줄도 잃지 않고 저장합니다.
왜 중요한가
넣는 쪽의 거절은 두 종류다. 라벨 문법·라벨 개수·줄 길이 위반은 400 이고, 이런 줄은 몇 번을 다시 보내도 영원히 거절된다. 스트림당 속도 초과는 429 이고, 이쪽은 기다렸다 다시 보내면 성공한다. 둘을 같은 코드로 다루면 한쪽은 큐를 채우고 다른 쪽은 자료를 잃는다. 속도 제한이 스트림 단위라는 점도 중요하다 — 카디널리티를 줄이려고 라벨을 합치면 한 스트림에 트래픽이 몰려 429 가 나고, 429 를 피하려고 나누면 스트림 수가 늘어난다. 두 힘이 반대로 당기는 자리에서 균형을 잡는 것이 이 코스가 가르치려는 판단이다.
단계
1. /root/lk-ingest-limits 에서 Loki 를 띄우고 date +%s 를 /root/lk-ingest-limits/anchor.txt 에 적으세요. 그리고 http://localhost:3100/config 에서 다섯 한도를 읽어 /root/lk-ingest-limits/limits.tsv 에 머리글 없이 다섯 줄, 탭으로 나눈 두 칸 <설정이름><탭><값> 으로 적습니다. 순서는 per_stream_rate_limit, per_stream_rate_limit_burst, max_line_size, max_label_names_per_series, unordered_writes 입니다.
2. 네 가지 push 를 던져 상태 코드를 /root/lk-ingest-limits/labels.tsv 에 머리글 없이 네 줄, 탭으로 나눈 두 칸 <이름><탭><코드> 로 적으세요. 이름과 내용은 순서대로 — ok(라벨 app="probe"), digit(라벨 이름이 1bad), dash(라벨 이름이 bad-name), toomany(라벨 16개, 이름은 l0 부터 l15) 입니다. 타임스탬프는 모두 기준 시각을 쓰고 본문은 아무 짧은 문자열이면 됩니다.
3. 1단계 표의 max_line_size 를 넘는 줄 하나를 만들어 보내세요. 명령줄로는 못 넘기므로 몸통을 파일로 만들어 --data-binary @파일 로 보냅니다. 결과를 /root/lk-ingest-limits/03-size.txt 에 세 줄로 적으세요 — code=<정수>, limit_bytes=<본문에 적힌 한도 바이트>, sent_bytes=<보낸 줄의 길이>.
4. /root/lk-ingest-limits/loki-low.yaml 을 써서 3300 포트(gRPC 9097)에 Loki 를 하나 더 띄우세요. 저장 경로는 /tmp/lokilow 아래이고 limits_config.per_stream_rate_limit 은 4KB, per_stream_rate_limit_burst 는 8KB 입니다. 그 서버의 /config 에서 두 값을 읽어 /root/lk-ingest-limits/04-low.txt 에 rate=<값> 과 burst=<값> 두 줄로 적습니다.
5. 3300 포트의 Loki 에 900바이트짜리 줄을 한 스트림({"app":"flood"})으로 연달아 보내 429 를 받으세요. /root/lk-ingest-limits/05-429.txt 에 두 줄을 적습니다 — first_429=<처음 429 가 난 순번, 1부터> 와 limit_in_body=<본문에 적힌 한도 문자열>. 그리고 429 응답 본문을 통째로 /root/lk-ingest-limits/429.body 에 저장하세요.
6. 1·4단계에서 읽은 두 값과 5단계의 순번으로 버스트를 설명하세요. /root/lk-ingest-limits/06-burst.txt 에 네 줄을 적습니다 — rate_bytes=<초당 허용 바이트 정수>, burst_bytes=<버스트 바이트 정수>, sent_bytes=<429 가 나기까지 보낸 본문 바이트 합>, why=<공백 뺀 40자 이상 한 문장>.
7. /root/lk-ingest-limits/send.sh 를 만들어 {"app":"keep"} 스트림에 900바이트 줄 20개를 3300 포트로 보내되, 429 를 받으면 지수 백오프로 다시 시도해 전부 저장되게 하세요. 타임스탬프는 기준 시각에서 과거 20초 동안 한 줄에 1초씩 씁니다. 그리고 /root/lk-ingest-limits/07-send.txt 에 sent=20, stored=<저장된 줄 수>, retries=<재시도 횟수> 세 줄을 적으세요. 저장된 줄 수는 질의해서 확인합니다.
8. 초당 12,000바이트를 내보내는 서비스가 있다고 할 때, 4단계의 한도 아래로 들어가려면 스트림을 최소 몇 개로 나눠야 하는지 계산해 /root/lk-ingest-limits/08-design.txt 에 네 줄로 적으세요 — throughput_bytes=12000, limit_bytes=<4단계 한도의 바이트>, streams_needed=<올림한 정수>, tradeoff=<공백 뺀 60자 이상>. 마지막 줄에는 스트림을 늘리는 것의 대가를 함께 적습니다.
참고
- 작업 디렉터리는
/root/lk-ingest-limits입니다. 기본 한도의 Loki 는 1단계에서, 낮은 한도의 두 번째는 4단계에서 띄웁니다. - 아주 긴 줄은 명령줄 인자로 넘길 수 없습니다(
Argument list too long). 몸통 JSON 을 파일로 만들어--data-binary @파일로 보내세요. - 같은 스트림에 같은 타임스탬프로 같은 내용을 보내면 중복으로 접힙니다. 줄을 여러 개 보낼 때는 타임스탬프를 하나씩 다르게 주세요.
- 두 번째 Loki 는
http_listen_port만 바꾸면 gRPC 가 겹쳐 죽습니다. 저장 경로도 겹치면 안 됩니다. - 흔한 실수: 400 에 재시도를 거는 것. 고칠 수 있는 거절과 기다려야 하는 거절을 코드로 갈라야 합니다.
- [요청 검증과 속도 제한](https://grafana.com/docs/loki/latest/operations/request-validation-rate-limits/) · [설정 문서](https://grafana.com/docs/loki/latest/configure/) · [HTTP API](https://grafana.com/docs/loki/latest/reference/loki-http-api/) · [라벨](https://grafana.com/docs/loki/latest/get-started/labels/)
단계 8개
- 서버가 들고 있는 한도를 읽는다
- 라벨 이름 규칙을 어기면 400 이다
- 줄 하나가 너무 길면 요청 전체가 떨어진다
- 낮은 한도를 건 두 번째 Loki 를 띄운다
- 진짜 429 를 받아 본다
- 처음 몇 번은 왜 통과했나
- 응용 ① — 백오프 재시도로 한 줄도 잃지 않는다
- 응용 ② — 한도 아래로 들어가는 설계