Post

🚨 RAG만 붙이면 끝인 줄 알았죠? 실무에서 뼈맞고 도입한 GraphRAG 밑바닥 파헤치기

🚨 RAG만 붙이면 끝인 줄 알았죠? 실무에서 뼈맞고 도입한 GraphRAG 밑바닥 파헤치기

[Metadata]


🎯 The Hook: “우리 AI는 왜 사내 바보가 되었을까?”

“개발자님, 우리 사내 위키랑 JIRA 티켓 전부 Vector DB에 넣었는데, 왜 ‘지난 3분기 결제 모듈(Payment Gateway) 장애와 연관된 고객사 불만 패턴’을 물어보면 자꾸 엉뚱한 신규 입사자용 결제 매뉴얼만 읽어주죠?”

이 질문 받아본 분들, 당장 손들어보세요. 저도 작년에 기획팀 회의에서 이 질문을 받고 등골이 서늘했거든요. 솔직히 까놓고 말해서, 요즘 개나 소나 튜토리얼 30분 보고 랭체인(LangChain) 엮어서 “우리도 RAG 도입했습니다!”라고 자랑하는 시대입니다. 문서를 청크(Chunk) 단위로 쪼개고, 텍스트 임베딩해서, 코사인 유사도(Cosine Similarity)로 Top-K 뽑아 LLM에 던져주는 방식. 참 쉽고 빠르죠?

그런데 막상 프로덕션(Production)에 올려보면 현실은 시궁창입니다. 단순 FAQ 검색이면 몰라도, 현업에서 요구하는 ‘복합적 추론(Multi-hop reasoning)’이나 ‘전체 맥락 파악’ 앞에서는 기존 RAG는 철저하게 바보가 됩니다. 문맥은 뚝뚝 끊기고, 키워드만 겹치는 엉뚱한 문서를 물고 와서 그럴싸한 헛소리(Hallucination)를 작렬하죠.

💡 한 마디로 요약하면? “단순 Vector Search 기반 RAG는 파편화된 지식을 ‘연결’하지 못합니다. 지식의 ‘관계’를 매핑하고 군집화(Clustering)하는 GraphRAG만이 실무 수준의 복합 추론을 가능하게 만드는 유일한 해법입니다.”


🔥 Deep Dive: GraphRAG, 밑바닥까지 뜯어보자

“그럼 Graph DB 하나 띄우면 해결되나요?” 천만의 말씀입니다. GraphRAG의 핵심은 단순히 Neo4j 같은 그래프 데이터베이스를 쓴다는 게 아닙니다. 데이터 인덱싱(Indexing) 과정 자체를 LLM을 이용해 완전히 재설계했다는 데 있습니다.

기존 방식과 무엇이 다른지, 10년 차의 시선으로 깐깐하게 비교해 보겠습니다.

📊 Naive RAG vs GraphRAG 아키텍처 비교

비교 항목Naive Vector RAGKnowledge Graph RAG (GraphRAG)
인덱싱 방식텍스트 청킹 → 임베딩 모델 (Single-pass)LLM 기반 엔티티/관계 추출 → 그래프 구축 → 커뮤니티 요약 (Multi-pass)
검색 대상파편화된 K개의 텍스트 덩어리서로 연결된 노드(Node), 엣지(Edge), 그리고 계층화된 커뮤니티 요약본
검색 메커니즘코사인 유사도 기반 근사 최근접 이웃(ANN) 검색Cypher 기반 그래프 순회(Traversal) 및 Map-Reduce 요약 병합
강점‘정확한 키워드’가 포함된 단일 문서 검색 (빠르고 인프라 구축이 저렴함)‘전체 데이터셋’을 관통하는 글로벌 질문 (트렌드, 패턴, 인과관계 분석)
약점문맥 붕괴, ‘전체 흐름’ 요약 불가 (Lost in the middle 현상 심화)인덱싱 비용 폭발 (LLM API 호출 지옥), 긴 인덱싱 및 쿼리 지연 시간

🛠️ Under the Hood: 인덱싱 파이프라인의 실체

GraphRAG의 진짜 마법은 인덱싱 타임에 일어납니다. 문서를 단순히 임베딩하는 게 아니라, LLM에게 프롬프트를 날려 Entity(개체)Relationship(관계)를 강제로 뽑아냅니다. 여기서 끝이 아닙니다. 추출된 그래프 노드들을 Leiden 알고리즘을 사용해 커뮤니티(Community)로 군집화합니다.

실제 저희 팀이 파이프라인에 적용했던 엔티티 추출(Entity Extraction)용 의사코드(Pseudo-code)와 설정 데이터를 보시죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# GraphRAG 엔티티 추출기 핵심 로직 (LangGraph 기반 단순화)
def extract_graph_entities(chunk_text: str) -> dict:
    system_prompt = """
    당신은 사내 인프라 데이터 분석 전문가입니다. 주어진 텍스트에서 주요 개체(Person, System, Incident, Team)를 추출하고,
    이들 간의 관계를 JSON 배열 형태로 반환하세요.
    반드시 다음 스키마를 따르세요:
    [{"source": "A", "target": "B", "relation": "CAUSED_BY", "weight": 8.5}]
    """
    
    response = llm.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": chunk_text}
        ],
        temperature=0.0 # 환각 방지를 위해 극도로 보수적인 세팅
    )
    return json.loads(response.choices[0].message.content)

이 과정에서 성능 튜닝을 위해 아래와 같이 파라미터를 제어합니다.

1
2
3
4
5
6
7
# settings.yaml 예시 (GraphRAG 파라미터 튜닝)
snapshots:
  graphml: true
entity_extraction:
  prompt: "prompts/entity_extraction.txt"
  entity_types: ["organization", "person", "incident", "microservice", "database"]
  max_gleanings: 2 # 추출 정확도를 높이기 위한 멀티 패스 재귀 호출 횟수 (비용 폭발의 주범!)

이렇게 구축된 지식 그래프는 아래와 같은 Cypher 쿼리(Neo4j)를 통해 멀티 홉 검색이 가능해집니다.

1
2
3
4
// 특정 장애(Incident)와 연관된 시스템, 그리고 그 시스템을 관리하는 팀을 최대 3-depth까지 탐색
MATCH (i:Incident {id: 'INC-2025-104'})-[r1:AFFECTS]->(s:System)-[r2:MANAGED_BY]->(t:Team)
RETURN i.description, s.name, t.contact
ORDER BY r1.weight DESC LIMIT 5;

이게 무슨 뜻이냐고요? 단순히 텍스트를 매칭하는 게 아니라, “장애 티켓 A → 결제 서버 B 영향 → C팀 담당”이라는 데이터베이스 수준의 논리적 연결 고리를 LLM에게 ‘명시적(Explicit)’으로 떠먹여 준다는 겁니다. 중간 연결 고리가 누락될 일이 없으니 환각이 생길 래야 생길 수가 없죠.


🚀 Pragmatic Use Cases: 실무 트러블슈팅과 대규모 트래픽 대처

“와, 이론은 기가 막히네요. 당장 프로덕션에 올립시다!” …기획자님, 제발 잠깐만요. 이거 그냥 돌리면 인프라 팀장이 서버실에서 몽둥이 들고 쫓아옵니다.

1. 인덱싱 비용의 악몽 (LLM Token 폭발)

Microsoft 논문에서도 언급되지만, GraphRAG는 인덱싱 단계에서 LLM을 미친 듯이 호출합니다. 저희 사내 위키와 JIRA 티켓 약 5만 건을 초기 인덱싱할 때, GPT-4o 기준으로 비용을 시뮬레이션 해보니 거의 1,200달러가 넘게 찍히더라고요. 단순 임베딩 모델 돌릴 땐 5달러면 끝났을 텐데 말이죠.

해결책: 하이브리드 인덱싱 & SLM(Small Language Model) 활용 비용 효율성을 맞추기 위해 저희는 인덱싱 파이프라인을 이원화했습니다.

  • 엔티티 추출 (Heavy Job): 사내 GPU 서버에 Llama-3-8B-Instruct 모델을 vLLM으로 띄워서 처리했습니다. 텍스트 추출용으로는 8B 모델을 프롬프트 엔지니어링만 잘해도 훌륭하게 동작합니다.
  • 커뮤니티 요약 및 최종 답변 (Quality Job): 복합 추론이 필요한 Map-Reduce 병합 단계에서만 GPT-4o를 사용해 퀄리티를 보장했습니다.

2. 레거시 연동과 점진적 업데이트 (CDC 적용)

데이터베이스는 매일 업데이트 되는데, 매번 전체 그래프를 다시 그릴 순 없잖아요? 저희는 Debezium을 활용한 CDC(Change Data Capture) 패턴을 적용했습니다. JIRA나 Confluence에 웹훅(Webhook)을 걸어 Kafka 토픽으로 이벤트를 쏩니다. 이벤트 컨슈머(Consumer)가 변경된 텍스트만 청킹한 뒤, LLM에 통과시켜 새로운 노드와 엣지를 뽑아냅니다. 여기서 핵심은 기존 그래프 데이터베이스(Neo4j)에 MERGE 쿼리를 날려 엣지 가중치(Weight)만 업데이트하거나, 새로운 관계를 이어붙이는 겁니다. 이러면 전체 문서를 재인덱싱(Re-indexing)할 때 발생하는 막대한 비용을 하루 몇 십 센트 수준으로 방어할 수 있습니다.


🛠️ Honest Review & Trade-offs: 10년 차가 본 치명적 한계

GraphRAG, 정말 훌륭한 아키텍처입니다. 하지만 시니어 엔지니어로서 실무 도입을 고민하신다면 이 ‘매운맛’ 트레이드오프(Trade-offs)를 반드시 감당하셔야 합니다.

  1. TTFT(Time To First Token) 지연 시간 문제
    • 글로벌 쿼리(전체 문서의 트렌드를 묻는 질문)를 던지면, 내부적으로 Map-Reduce를 돕기 위해 수많은 커뮤니티 요약본을 동시에 LLM에 던지고 합치는 과정을 거칩니다. 결과적으로 사용자가 첫 응답을 받기까지 빠르면 3초, 길면 8초 이상 걸립니다. 성격 급한 한국인 유저들? “엔터 눌렀는데 왜 멈췄냐”고 새로고침 광클합니다. 반드시 스트리밍(Streaming) UI와 진행 상태(Progress) 로직을 프론트엔드에 곁들여야 합니다.
  2. 운영 복잡도 (Operational Complexity)
    • Vector DB(Milvus나 Pinecone) 하나만 덜렁 띄우던 시절이 그립게 될 겁니다. Graph DB 튜닝은 기본이고, Leiden 커뮤니티 군집화 파라미터(Resolution) 조절 등 관리 포인트가 3배로 늘어납니다. 예를 들어, Resolution 값을 1.0으로 주면 너무 큰 군집이 생겨 답변이 뭉뚱그려지고, 3.0 이상으로 주면 군집이 너무 잘게 쪼개져 멀티 홉 추론의 장점이 사라집니다. 이 최적의 하이퍼파라미터를 찾는 과정이 순도 100% 노가다입니다.
  3. 정말 모든 데이터에 ‘관계’가 필요한가?
    • 사내 규정집, 연차 신청 매뉴얼 같은 단순 정보는 그냥 Vector Search가 훨씬 낫습니다. 오버엔지니어링 하지 마세요.

💡 Closing Thoughts: 결국, 우리 인프라의 미래는 어디로?

솔직히 처음 Microsoft가 GraphRAG 논문을 냈을 땐, “또 논문용 벤치마크 뻥튀기겠지” 하며 의심했습니다. 하지만 실제로 사내 장애 이력과 고객 CS 데이터를 엮어내는 파이프라인을 구축해 보니, 이건 단순한 ‘검색’을 넘어선 ‘사내 인텔리전스(Intelligence)’의 진화라는 걸 뼈저리게 체감했습니다.

“이 장애, 예전에도 비슷한 일 있었어?” 라는 시니어 개발자의 직감(Intuition). 이제는 그 모호한 직감을 GraphRAG가 시스템적으로 매핑하고 대체하는 시대가 왔습니다.

물론 당장 모든 RAG 시스템을 Graph로 엎으라는 말은 아닙니다. 하지만 여러분의 팀이 ‘단순 검색’에 지쳐 ‘추론’을 갈망하고 있다면, 그리고 경영진의 “우리 AI는 왜 이리 멍청해?”라는 압박에 시달리고 있다면, 다가오는 주말에 시간 내서 Neo4j 컨테이너부터 한 번 띄워보시길 강력히 권합니다.

초기 구축의 고통은 100% 엔지니어의 몫이지만, 그 결과물은 분명 여러분의 다음 연봉 협상 테이블에 가장 강력한 무기가 되어줄 테니까요. 🔥

References

  • https://arxiv.org/abs/2404.16130
  • https://github.com/microsoft/graphrag
  • https://neo4j.com/developer/graph-rag/
This post is licensed under CC BY 4.0 by the author.