DevOps··5분 읽기·0

3. LLM에게 먹일 데이터 준비 — 전처리와 RAG

운영 데이터 품질이 분석 결과를 결정한다. 로그 전처리 5단계, 컨텍스트 전략, RAG 시스템 구축 방법과 알림이 LLM에 도달하는 전체 파이프라인을 설계합니다.

글꼴

LLM의 성능은 프롬프트만큼이나 입력 데이터의 품질에 좌우된다.

같은 Claude, 같은 프롬프트라도 쓸데없는 로그 999줄 속에 핵심 에러 1줄이 파묻혀 있으면 분석 품질이 떨어진다. 반대로, 전처리를 거쳐 핵심만 추린 데이터를 넣으면 훨씬 정확한 분석이 나온다.

이 편에서는 "날것의 운영 데이터 → LLM이 소화할 수 있는 깔끔한 입력"으로 변환하는 파이프라인을 설계한다. 그리고 알림이 LLM에 도달하는 전체 연동 경로까지 다룬다.


나쁜 입력 vs 좋은 입력

커뮤니티에서 자주 나오는 사례다. OOM 장애가 발생해서 LLM에 로그를 넣고 분석을 요청했는데, access log 1만 줄을 그대로 넣으면 "트래픽이 많습니다"라는 뻔한 답이 돌아온다. error log만 추려서 50줄을 넣으면 "BatchProcessor에서 메모리 해제가 안 되고 있고, 배포 직후부터 메모리가 선형 증가한 것으로 보아 메모리 릭으로 추정된다"는 분석이 나온다.

입력이 전부다.

나쁜 입력의 패턴은 정해져 있다. 10만 줄의 access log를 그대로 넣기. 중복 로그가 99%인 데이터(같은 에러가 1만 번 반복). 타임스탬프 형식이 뒤섞여 있는 데이터. 바이너리 로그.

좋은 입력도 패턴이 있다. 에러/경고 레벨만 필터링된 로그. 타임스탬프가 정규화되고 시간순 정렬된 데이터. 중복 제거 후 "이 에러가 1,523회 반복됨"으로 요약된 데이터. 관련 설정 파일 발췌가 함께 포함된 데이터.

구체적으로 보면 이렇다. 전처리 전:

2025-02-27 14:00:01 INFO GET /api/data 200 0.05s
2025-02-27 14:00:01 INFO GET /api/data 200 0.04s
(이런 줄이 9,990개)
2025-02-27 14:03:22 ERROR OOM killer invoked, process nginx killed
(이런 줄이 10개)

전처리 후:

[요약] 14:00~14:03 정상 요청 9,990건 (평균 응답 0.05s)
[ERROR] 14:03:22 OOM killer invoked, process nginx (pid 3847) killed
[ERROR] 14:03:22 connect() failed (111: Connection refused)
[CRIT]  14:03:23 open() failed (28: No space left on device)
... (에러 로그 10줄)
[시스템 상태] 메모리: 14.8GB/16GB (92%), 디스크: /var 98%
[최근 변경] 12:00 v2.3.2 배포 (BatchProcessor 기능 추가)

10,000줄이 15줄로 줄었다. 핵심 정보는 전부 살아있다. 오히려 시스템 상태와 변경 이력까지 추가됐다. 이 15줄을 넣으면 LLM이 "배포 후 메모리 증가 → 디스크 풀 → OOM"이라는 인과관계를 바로 파악한다.


로그 전처리 5단계

전처리 파이프라인은 이렇게 생겼다.

[원본 로그]


1. 파싱 — 타임스탬프, 레벨, 메시지 분리


2. 필터링 — ERROR/WARN만 추출 (또는 특정 서비스만)


3. 중복 압축 — 같은 메시지는 카운트로 요약


4. 시간순 정렬


5. 토큰 예산에 맞춰 잘라내기


[LLM 입력용 텍스트]

각 단계를 거치면 1만 줄의 로그가 100줄 이내로 줄어든다. 핵심 정보는 유지하면서.

5번째 단계가 중요하다. 모델의 컨텍스트 윈도우에 맞춰 입력을 조절해야 한다. 여기서 경로 A와 B의 전략이 갈린다.

컨텍스트 전략 — 무엇을 넣고 무엇을 버릴 것인가

세 가지 전략이 있다.

  • Truncate. 최신 N줄만 자르기. 단순하고 빠르다. 하지만 초기 증상을 놓칠 수 있다. "장애가 시작된 시점"의 로그가 잘려나가면 원인을 못 찾는다.
  • Priority. ERROR > WARN > INFO 순으로 우선순위를 매기고, 토큰 예산 안에서 중요한 것부터 채운다. 핵심을 놓치지 않지만, 맥락이 빠질 수 있다.
  • Summarize. 앞부분을 LLM으로 요약하고, 최신 부분은 상세하게 넣는다. 전체 맥락을 유지할 수 있지만, 요약에 LLM 호출이 한 번 더 필요하다.

경로별 권장 전략은 이렇다.

  • 경로 A (Claude, 200K). 컨텍스트가 넉넉하다. 웬만한 장애는 로그 + 설정 파일 + Runbook + 과거 이력까지 한꺼번에 넣을 수 있다. Truncate나 Priority 없이 전처리만 잘 하면 된다.
  • 경로 B (로컬 30~70B, 32K~128K). 컨텍스트가 상대적으로 좁다. Priority 전략으로 핵심만 추려야 한다. 그리고 컨텍스트에 못 넣는 맥락은 RAG로 보완한다. 경로 B에서 RAG가 특히 중요한 이유가 이거다.

RAG — 과거 지식을 연결하기

RAG(Retrieval-Augmented Generation)는 한 마디로 "LLM에게 참고 자료를 붙여주는 것"이다.

LLM은 일반적인 Linux 지식은 알지만, 우리 팀의 운영 환경은 모른다. "지난달 web-03에서 OOM이 났을 때 배치 작업 메모리 제한으로 해결했다"는 정보는 우리 팀만 알고 있다.

RAG가 이 문제를 해결한다.

구체적으로 어떻게 동작하는지 보자. 벡터 DB에 이런 문서들이 저장되어 있다고 하자.

[문서 1] PM-2024-03: api-02 OOM 장애
  원인: BatchProcessor.processLargeDataset에서 대량 데이터 처리 시
  메모리 해제 안 됨. 배포 직후 메모리 선형 증가.
  해결: 스트리밍 처리로 코드 수정.
 
[문서 2] PM-2024-07: web-03 OOM 장애
  원인: 로그 버퍼가 JVM 힙 내에서 무한 축적.
  해결: 로그 버퍼 크기 제한 설정 추가.
 
[문서 3] RB-001: 웹 서버 502 에러 대응
  1. nginx 프로세스 확인 2. upstream 헬스체크 3. API 로그 확인...

지금 "api-service OOM, BatchProcessor에서 에러"라는 장애가 발생하면, 벡터 검색이 문서 1을 Top 1으로 가져온다. "이전에도 같은 서비스의 같은 클래스에서 비슷한 문제가 있었고, 스트리밍 처리로 해결했다"는 맥락이 프롬프트에 들어간다.

이 맥락이 있고 없고의 차이는 크다. RAG 없이 분석하면 "OOM이니까 힙 사이즈를 늘려보세요"라는 일반적인 답이 나온다. RAG로 과거 이력을 붙이면 "이전에도 BatchProcessor에서 같은 패턴의 OOM이 발생했고, 스트리밍 처리로 해결한 이력이 있다. 이번에도 같은 원인일 가능성이 높다"는 구체적인 답이 나온다.

[현재 장애 로그]

     ├─→ 벡터 DB 검색 → "유사 과거 장애 3건"


[프롬프트]
  "현재 상황: (로그)"
  "과거 유사 사례 1: (2024-03 OOM, 원인: 배치 메모리 릭)"
  "과거 유사 사례 2: (2024-07 OOM, 원인: 로그 버퍼 미설정)"
  "위 정보를 참고하여 현재 장애를 분석하세요."


[LLM 응답] — 과거 이력을 참고한 정확한 분석

과거 장애 보고서, Runbook, 아키텍처 문서를 벡터 DB(ChromaDB, FAISS 등)에 저장해둔다. 장애가 발생하면 현재 상황과 유사한 문서를 검색해서 프롬프트에 삽입한다. LLM은 이 참고 자료를 보고 "이전에도 같은 서비스에서 비슷한 문제가 있었다"는 맥락을 활용할 수 있다.

경로 B에서 RAG가 필수인 이유가 여기에 있다. 컨텍스트 윈도우가 32K밖에 안 되면, 로그만 넣어도 꽉 찬다. 과거 이력은 RAG로 검색해서 핵심만 붙여야 한다. 경로 A는 200K라 RAG 없이도 상당히 많은 정보를 넣을 수 있지만, RAG를 쓰면 더 정확해진다.

벡터 DB에 무엇을 저장하고 어떻게 축적할 것인가 — 포스트모템 자동 저장과 지식 선순환 구조 — 는 7편에서 다룬다.


RAG 시스템 구축 — 실제로 어떻게 만드나

그런데 "벡터 DB에 저장해서 검색한다"는 말만으로는 실제로 만들 수가 없다. 구축에 필요한 걸 정리해본다.

  • 벡터 DB 선택 : ChromaDB가 가장 시작하기 쉽다. 설치도 간단하고 Python에서 바로 쓸 수 있다. 데이터가 많지 않은 초기 단계에서는 충분하다. 규모가 커지면 Weaviate나 Qdrant 같은 전용 솔루션을 고려하면 된다. FAISS는 Meta가 만든 검색 라이브러리인데, DB라기보다는 인덱스에 가깝다. 영속성이나 관리 기능이 필요하면 ChromaDB 쪽이 편하다.
  • 임베딩 모델 : 문서를 벡터로 변환하는 모델이 필요하다. 경로 A라면 Claude의 임베딩이 아니라 별도 임베딩 API를 쓰게 된다. OpenAI의 text-embedding-3-small이 가성비가 좋고, 경로 B라면 로컬에서 돌릴 수 있는 오픈소스 임베딩 모델(BGE, E5 등)을 쓰면 된다. 임베딩 모델은 LLM과 달리 가벼워서 CPU에서도 충분히 돌아간다.한국어 포스트모템과 Runbook이 많다면 다국어 임베딩 모델을 써야 한다. BGE-M3가 한국어를 잘 지원하는 편이다.
  • 청킹 : 포스트모템 하나가 2,000자라면 그대로 넣어도 된다. 하지만 10페이지짜리 아키텍처 문서를 통째로 하나의 벡터로 만들면 검색 정확도가 떨어진다. 문서를 적절한 크기로 잘라야 한다. 500~1,000자 단위가 일반적이다. 포스트모템은 "요약 + 원인 + 조치"를 각각 별도 청크로 나누는 게 좋다. 원인을 검색하고 싶을 때 요약이 딸려오면 노이즈가 되니까.
  • 검색 품질 확인 : RAG를 만들어놓고 검색이 제대로 되는지 확인 안 하는 경우가 많다. 간단한 방법은, 과거 장애 3~5건을 골라서 "이 장애가 발생했을 때 관련 문서가 Top 3에 나오는가?"를 수동으로 확인하는 거다. 이것만 해도 임베딩 모델이나 청킹 전략이 맞는지 감을 잡을 수 있다.

검색 결과가 안 좋으면 대부분 청킹이 문제다. 너무 크게 자르면 관련 없는 내용이 섞이고, 너무 잘게 자르면 맥락이 빠진다.


연동 아키텍처 — 알림이 LLM에 도달하는 경로

전처리된 데이터를 어떻게 LLM에 전달하는가. 이 "배관 공사"가 빠져있으면 아무것도 동작하지 않는다.

전체 흐름은 이렇다.

[Prometheus]
     │ 알림 발생

[Alertmanager]
     │ 웹훅 전송

[Python 파이프라인 서버]
     │ 1. 알림 수신
     │ 2. 관련 로그 수집 (로그 파일 or Loki API)
     │ 3. 전처리 (필터링, 중복 제거, 토큰 맞춤)
     │ 4. RAG 검색 (유사 과거 장애)
     │ 5. 프롬프트 조립
     │ 6. LLM 호출
     │    ├─ 경로 A: Claude API
     │    └─ 경로 B: 사내 vLLM 서버
     │ 7. 결과 파싱

[Slack / PagerDuty / Teams]
     분석 결과 전송

경로 A든 B든 이 구조는 동일하다. 6번 단계에서 API 엔드포인트만 다를 뿐이다.

# 경로 A
response = claude_client.messages.create(model="claude-sonnet-4-20250514", ...)
 
# 경로 B
response = requests.post("http://내부서버:8000/v1/chat/completions", ...)

한 줄 차이다. 나머지 코드는 전부 같다. 이게 "아키텍처는 하나, 실행 환경만 다르다"는 말의 의미다.

MCP를 쓰면 이 배관을 더 간결하게 만들 수 있다. MCP 서버가 Prometheus, Grafana, PagerDuty에 직접 연결되어 데이터를 가져오기 때문에, Python 파이프라인에서 각 도구의 API를 직접 호출하는 코드를 줄일 수 있다. Google SRE가 2026년에 공개한 Gemini CLI 사례에서도 MCP로 모니터링 도구를 연결해서 실시간 데이터를 LLM에 공급하는 구조를 쓰고 있다.


다음 편에서는 이 파이프라인 위에서 LLM이 "평소와 다른 패턴"을 감지하는 이상 탐지 시스템을 설계한다. 프롬프트를 어떻게 짜야 정탐률이 올라가는지, 알림 15개를 어떻게 하나의 장애로 묶는지를 다룬다.

이 글이 어떠셨나요?

이 글이 도움이 되셨나요?
공유:

관련 포스트

뉴스레터 구독

새 글이 올라오면 이메일로 알려드려요.

댓글

댓글을 불러오는 중...