그래프 RAG 기반 채널 지식 시스템 구축기
Perry • Software Enginner
안녕하세요, 채널톡 소프트웨어 엔지니어 페리입니다.
저희 팀은 AI 코딩 도구를 비교적 일찍 도입하고 적극적으로 활용해왔습니다. Cursor에 플랜 모드가 생기기도 전에 이미 플랜/액트 모드를 에이전트 지시사항으로 분리해서 쓰고 있었고, 전면 도입한 지도 벌써 1년이 넘었습니다.
그런데 잘 쓰면 쓸수록, 현재 방식의 천장도 보이기 시작했습니다. 이 글은 그 천장이 무엇이었는지, 그리고 그래프 RAG라는 접근법으로 어떻게 뚫어보려 했는지에 대한 이야기입니다.
LLM에는 컨텍스트 윈도우라는 물리적 한계가 존재합니다. 대화가 길어지거나 코드베이스가 커지면 다양한 문제가 발생합니다.
앞서 했던 결정을 잊어버리고 일관성 없는 코드를 생성하거나
이미 있는 유틸리티를 모른 채 중복 구현을 하거나
심지어 방금 수정한 부분을 다시 원래대로 돌려놓는 경우도 생깁니다
작은 스크립트나 단일 파일 수준에서는 괜찮지만, 실제 프로덕션 코드베이스에서는 이런 한계가 빠르게 드러납니다. 하지만 이 문제는 모델의 발전과 도구의 개선으로 점차 완화되고 있는 영역이기도 합니다. 진짜 문제는 따로 있었습니다.
채널톡은 MSA(Microservice Architecture) 로 구성되어 있습니다. 하나의 기능을 구현하려면 여러 서비스가 어떻게 엮여 있는지 알아야 합니다. 그런데 AI 코딩 도구는 현재 열려 있는 레포지토리의 코드만 볼 수 있습니다.
구체적인 예를 들어보겠습니다. 채널톡에서 가장 핵심적인 엔티티 중 하나인 UserChat(고객과 매니저 간의 1:1 채팅 세션)에 새로운 필드를 추가해야 한다고 가정해봅시다.
AI 코딩 도구에게 "UserChat에 필드를 추가해줘"라고 요청하면, 도구는 현재 열려 있는 레포(예: ch-dropwizard)의 코드를 열심히 분석해서 이렇게 답합니다.
"이 서비스 내의 UserChat 관련 API들을 수정하면 됩니다."
틀린 말은 아닙니다. 하지만 실제로는 어떨까요?
ch-app-store 서비스가 ch-dropwizard의 UserChat API를 호출하고 있고
cht-app-messenger 서비스가 ch-app-store를 통해 간접적으로 UserChat 데이터에 접근하고 있으며
ch-desk-web 프론트엔드가 여러 경로로 UserChat 데이터를 소비하고 있습니다
(AI 코딩 도구는 현재 열려 있는 단일 레포만 볼 수 있어, 서비스 간 의존성을 파악하지 못합니다.)
단일 레포만 보면 1개 서비스만 수정하면 될 것 같지만, 실제로는 3~4개 서비스에 영향이 갑니다. 이런 숨은 의존성을 모르고 코드를 배포하면 장애로 이어질 수 있습니다. 마치 Cursor나 Claude Code를 갖고 하나의 마이크로서비스 코드만 열어놓고 작업하면서, 다른 서비스는 전혀 보지 못하는 것과 같습니다.
이것이 AI 코딩 도구의 진짜 천장입니다. 코드를 잘 짜는 것과 시스템을 이해하고 안전하게 변경하는 것은 전혀 다른 문제이며, 현재의 AI 도구는 후자에서 크게 부족합니다.
"그러면 RAG를 쓰면 되지 않나요?"라고 생각하실 수 있습니다. 일반적인 벡터 기반 RAG(Retrieval Augmented Generation) 는 문서를 임베딩 벡터로 변환하고, 질문과 의미적으로 유사한 문서를 찾아 LLM에게 제공하는 방식입니다. "UserChat이 뭔가요?"와 같은 질문에는 잘 답할 수 있습니다.
하지만 우리가 진짜 필요한 것은 이런 질문에 대한 답입니다.
"UserChat을 수정하면 어떤 서비스들이 영향을 받는지, 그리고 그 서비스를 호출하는 또 다른 서비스는 어디인지 알려줘."
이것은 "A를 바꾸면 B에 어떤 영향이 가는지" 라는 관계 추적 문제입니다. 벡터 검색은 "의미적으로 비슷한 문서"를 찾는 데 강하지만, 이런 다단계 의존성(멀티홉) 을 자연스럽게 추적하기에는 적합하지 않습니다. 엔티티 A → 서비스 B → API C → 서비스 D로 이어지는 호출 체인을 벡터 유사도로는 발견할 수 없기 때문입니다.
우리에게 필요한 것은 관계 중심의 지식 표현과 탐색 — 바로 그래프 RAG였습니다.
그래프 DB가 관계 추적에 왜 강한지, 간단한 비교를 통해 살펴보겠습니다.
"ch-dropwizard 서비스를 호출하는 서비스를, 3단계까지 찾아라."
관계형 DB(PostgreSQL)로 구현하면 이런 쿼리가 필요합니다.
-- 3단계를 찾으려면 5~6개의 JOIN이 필요
SELECT s3.service_name
FROM services s1
LEFT JOIN service_calls sc1 ON s1.id = sc1.from_service_id
LEFT JOIN services s2 ON sc1.to_service_id = s2.id
LEFT JOIN service_calls sc2 ON s2.id = sc2.from_service_id
LEFT JOIN services s3 ON sc2.to_service_id = s3.id
WHERE s1.service_name = 'ch-dropwizard';
-- N단계면 JOIN이 (N+1)개 필요
같은 질문을 그래프 DB(Neo4j)의 Cypher 쿼리로 표현하면 이렇게 됩니다.
// 한 줄로 표현 가능
MATCH (s1:Service {name: 'ch-dropwizard'})
-[:CALLS*1..3]->
(s2:Service)
RETURN s2.name
// 깊이와 관계없이 동일한 패턴
(관계형 DB에서는 깊이가 깊어질수록 JOIN이 늘어나지만, 그래프 DB에서는 동일한 패턴으로 표현할 수 있습니다.)
차이가 명확합니다. 관계형 DB에서는 깊이가 깊어질수록 쿼리가 복잡해지고 성능도 저하되지만, 그래프 DB에서는 깊이와 관계없이 동일한 패턴으로 표현할 수 있습니다. 이는 그래프 DB가 인덱스 프리 인접성(Index-Free Adjacency) 이라는 특성을 가지고 있기 때문입니다. 각 노드가 자신과 연결된 노드에 대한 직접 참조를 가지고 있어, 관계를 따라가는 탐색이 본질적으로 빠릅니다.
이러한 그래프의 강점을 활용하여, 저희는 MSA 환경에서 AI가 전체 시스템 관점의 판단을 내릴 수 있도록 살아있는 지식 그래프를 구축하기로 했습니다. 이 시스템의 이름은 Channel Knowledge입니다.
Channel Knowledge가 답할 수 있어야 하는 세 가지 핵심 질문은 다음과 같습니다.
어떤 도메인이 어느 서비스에 있는지 — AI의 탐색 범위를 최소화합니다. "UserChat은 ch-dropwizard가 소유하고, ch-app-store가 접근한다"는 정보를 즉시 알 수 있습니다.
서비스 간 영향도는 어떻게 되는지 — 코드 변경의 파급 효과를 사전에 파악합니다. "이 API를 바꾸면 어떤 서비스들이 호출하고 있는지" 실시간으로 조회할 수 있습니다.
API 간 다단계 호출 체인은 어떻게 이어지는지 — 직접 의존뿐 아니라, 간접 의존까지 추적하여 숨은 의존성을 발견합니다.
Channel Knowledge는 크게 세 계층으로 구성됩니다.
(코드 분석(Layer 1) → 지식 저장소(Layer 2) → 쿼리 인터페이스(Layer 3)로 이어지는 전체 구조)
Layer 1 — 코드 분석: GitHub의 마이크로서비스 코드를 자동으로 파싱하여 API, 테이블, 큐, 이벤트 등 구조 정보를 추출합니다.
Layer 2 — 지식 저장소: 추출된 정보에 도메인 지식을 더해 Neo4j 그래프 DB에 저장하고, Git 기반 YAML 파일로 버전 관리합니다.
Layer 3 — 쿼리 인터페이스: MCP(Model Context Protocol) 서버를 통해 Claude Code나 Cursor 같은 AI 도구가 지식 그래프를 실시간으로 조회합니다.
각 계층을 자세히 살펴보겠습니다.
첫 번째 단계는 마이크로서비스의 소스 코드를 자동으로 분석하여 구조 정보를 추출하는 것입니다. 채널톡의 서비스들은 Java, Go, TypeScript 등 다양한 언어로 작성되어 있기 때문에, 각 언어별 전용 파서를 구현했습니다.
언어 | 파싱 도구 | 추출 대상 |
|---|---|---|
Java | tree-sitter | Dropwizard |
Go | tree-sitter | DSL 라우팅 패턴, SQLBoiler ORM, |
TypeScript | @babel/parser | axios/fetch 패턴, API 호출 추적 |
이 파서들이 코드에서 추출하는 것은 다음과 같습니다.
REST API 엔드포인트: 각 서비스가 제공하는 API의 경로, HTTP 메서드, 파라미터
데이터베이스 테이블/컬럼: DynamoDB, PostgreSQL 테이블의 스키마 정보
SQS 큐: 서비스 간 비동기 메시지 통신 채널
이벤트: 도메인 이벤트 발행/구독 패턴
실제 분석 결과를 보면, 이 자동화된 파싱이 얼마나 많은 정보를 빠르게 추출하는지 체감할 수 있습니다.
서비스 | 언어 | API | DB 테이블 | 큐 |
|---|---|---|---|---|
ch-dropwizard | Java | 3,847개 | PostgreSQL 75개 + DynamoDB 43개 | SQS 17개 |
cht-app-messenger | Go | 51개 | - | - |
ch-app-store | Go | 106개 | - | 이벤트 5개 |
합계 | 4,004개 | 118개 | 22개 |
사람이 이 모든 정보를 수작업으로 정리한다면 몇 주는 걸릴 작업입니다. 정적 분석을 통해 이 과정을 자동화함으로써, 지식 그래프의 기반 데이터를 빠르게 구축할 수 있었습니다.
하지만 여기서 중요한 한계가 있었습니다. 정적 분석은 "무엇이 있는지" 는 알려주지만, "그것이 무엇을 의미하는지" 는 알려주지 않습니다. user_chats_v4라는 테이블이 있다는 건 알 수 있지만, 이것이 "고객과 매니저 간의 1:1 채팅 세션"이라는 비즈니스 의미까지는 코드만으로 알 수 없습니다.
정적 분석의 한계를 극복하기 위해, 저희는 LLM을 활용한 반자동 프로세스를 설계했습니다.
(코드에서 추출한 구조 정보에 LLM과 도메인 전문가의 지식을 더해 의미 있는 지식 그래프를 구축하는 과정)
1단계 (자동): 코드 파싱 → JSON 스키마 (1,029개 컬럼 정보)
2단계 (자동): JSON → 엔티티 YAML 기본 구조 생성 (LLM 활용)
3단계 (수동): 도메인 전문가가 비즈니스 의미 추가 및 검수
예를 들어, user_chats_v4 테이블의 1단계 추출 결과에서 2단계로 넘어가면 LLM이 다음과 같은 기본 구조를 자동 생성합니다.
name: UserChat
name_ko: 사용자 채팅
bounded_context: messaging
schema_source:
service: ch-dropwizard
database: dynamodb
table: user_chats_v4
attributes:
- name: id
type: S
javaType: String
constraints: [HASH_KEY]
description: 채팅 고유 식별자
- name: channelId
type: S
javaType: String
description: 채널 식별자
# ... 100개 이상의 속성
3단계에서 도메인 전문가가 비즈니스 의미를 추가합니다.
description: >
고객(User)과 매니저(Manager) 간의 1:1 채팅 세션.
Channel.io 메시징의 핵심 엔티티로, 여러 메시지를 포함하며
상태와 라이프사이클을 가짐.
lifecycle:
states: [open, assigned, snoozed, closed, resolved]
transitions:
- from: open
to: assigned
trigger: 매니저 배정
relationships:
- entity: Message
type: has_many
description: 하나의 채팅은 여러 메시지를 포함
- entity: User
type: belongs_to
description: 채팅을 시작한 고객
- entity: Manager
type: belongs_to
description: 배정된 상담 매니저
이 과정을 통해 최종적으로 다음과 같은 도메인 지식이 완성되었습니다.
지식 유형 | 수량 | 설명 |
|---|---|---|
Bounded Context | 7개 | Messaging, Automation, Marketing, AppStore 등 |
Domain Entity | 42개 | UserChat, Message, Workflow, Campaign 등 |
Business Rule | 19개 | 채팅 배정 규칙, 메시지 발송 규칙 등 |
Workflow | 14개 | 채팅 생명주기, 워크플로우 실행 등 |
Glossary | 50+개 | 도메인 용어 사전 |
추출한 구조 정보와 도메인 지식을 Neo4j 그래프 DB에 넣으면, 비로소 관계를 따라 탐색할 수 있는 지식 그래프가 완성됩니다. 이 그래프의 스키마 설계는 프로젝트가 진화하면서 세 단계를 거쳤습니다.
Phase 1: 인프라 매핑 — 서비스, API, 테이블 간의 기본 관계를 구축했습니다.
Service (3개) → APIEndpoint (4,000+개) → DatabaseTable (230개)
Phase 2: 도메인 레이어 추가 — DDD(Domain-Driven Design) 개념을 그래프에 녹여냈습니다.
BoundedContext (7개) → DomainEntity (42개) → BusinessRule (19개)
Phase 3: 스키마 레벨 상세화 — 테이블 컬럼 수준까지 내려가 정밀한 매핑을 달성했습니다.
TableColumn (1,029개) — 타입, 제약조건, Java 매핑까지
최종적으로 완성된 그래프의 핵심 구조는 다음과 같습니다.
(9개 노드 타입과 핵심 관계 타입을 보여주는 그래프 스키마)
노드 타입 (9개):
Service — 마이크로서비스 (ch-dropwizard, ch-app-store, cht-app-messenger 등)
APIEndpoint — REST API 엔드포인트 (경로, 메서드, RPC 메서드 포함)
DomainEntity — 비즈니스 엔티티 (UserChat, Message, Workflow 등)
BoundedContext — DDD 바운디드 컨텍스트 (Messaging, Automation 등)
BusinessRule — 비즈니스 규칙 (배정 규칙, 검증 규칙 등)
DomainWorkflow — 비즈니스 워크플로우 (채팅 생명주기 등)
DatabaseTable — 데이터베이스 테이블
TableColumn — 테이블 컬럼 (타입, 제약조건 포함)
EventQueue — 메시지 큐 (SQS 등)
핵심 관계 타입:
PROVIDES — 서비스가 API를 제공 (Service → APIEndpoint)
IMPLEMENTS / ACCESSES — 서비스가 엔티티를 소유/접근 (Service → DomainEntity)
READS / WRITES / DELETES — API가 엔티티를 조작 (APIEndpoint → DomainEntity)
CALLS_API — API가 다른 API를 호출 (APIEndpoint → APIEndpoint) ← 멀티홉의 핵심
HAS_MANY / BELONGS_TO_ENTITY — 엔티티 간 관계 (DomainEntity ↔ DomainEntity)
STORED_IN — 엔티티가 테이블에 저장됨 (DomainEntity → DatabaseTable)
GOVERNED_BY — 엔티티가 규칙에 의해 관리됨 (DomainEntity → BusinessRule)
현재 그래프의 규모는 9개 노드 타입, 10개 이상의 관계 타입, 6,000+개 노드, 수만 개의 엣지로 구성되어 있습니다.
그래프 DB는 관계 탐색에 강력하지만, 데이터 관리 측면에서는 도전이 있습니다. 그래프에 직접 데이터를 편집하는 것은 불편하고, 변경 이력을 추적하기도 어렵습니다. 이 문제를 해결하기 위해 저희는 Git 기반의 SSOT(Single Source of Truth) 전략을 채택했습니다.
channel-knowledge-poc/
└── knowledge/ # ← 모든 지식의 원본
├── services/ # 서비스 카탈로그
│ └── backend/
│ ├── ch-dropwizard/
│ │ ├── catalog.yaml # 서비스 메타데이터
│ │ ├── dependencies.yaml # API, DB, 큐 매핑
│ │ └── schemas/ # 226개 DB 스키마 파일
│ ├── cht-app-messenger/
│ └── ch-app-store/
└── domain/ # 도메인 지식
├── entities/ # 42개 엔티티 YAML
├── rules/ # 19개 비즈니스 규칙
├── workflows/ # 14개 워크플로우 문서
├── bounded-contexts/ # 컨텍스트 맵
└── glossary.yaml # 도메인 용어 사전
이 접근법의 장점은 명확합니다.
사람이 직접 편집 가능: YAML/Markdown 파일이므로 IDE에서 바로 편집할 수 있습니다.
버전 관리: Git을 통해 누가, 언제, 어떤 지식을 추가/변경했는지 추적됩니다.
자동 + 수동 공존: LLM이 자동 생성한 부분과 도메인 전문가가 수동 추가한 부분이 하나의 파일에 공존합니다.
Neo4j 동기화: pnpm populate 명령 하나로 knowledge/ 디렉토리의 모든 YAML 파일을 Neo4j에 로드합니다.
코드가 변경되면 증분 분석을 통해 knowledge/ 디렉토리를 자동 최신화할 수 있도록 자동화 인프라도 구축해두었습니다. 지식 그래프는 살아있는 시스템이어야 하기 때문입니다.
지식 그래프가 아무리 풍부해도, AI 코딩 도구가 접근할 수 없으면 의미가 없습니다. 여기서 MCP(Model Context Protocol) 가 등장합니다.
MCP는 Claude Code나 Cursor 같은 AI 도구가 외부 데이터 소스를 조회할 수 있게 해주는 프로토콜입니다. 저희는 Neo4j를 쿼리하는 20개의 전용 MCP 도구를 개발했습니다.
카테고리 | 도구 수 | 주요 도구 |
|---|---|---|
엔티티 | 10개 | getDomainEntity, |
API | 4개 | getAPIDetails, |
서비스 | 8개 | getServiceDomainInfo, |
컨텍스트 | 4개 | getAllBoundedContexts, |
워크플로우 | 2개 | getWorkflowsInvolvingEntity, |
이 도구들 덕분에 개발자가 Claude Code에서 코드를 작성하는 중에 "이 엔티티를 수정하면 어디가 영향받아?"라고 물으면, MCP 도구가 자동으로 실행되어 Neo4j 그래프를 조회하고, 영향 범위를 즉시 보여줍니다.
(개발자가 Claude Code에서 질문하면 MCP 서버가 Neo4j를 쿼리하여 결과를 반환하는 흐름)
Channel Knowledge의 가장 핵심적인 차별점은 멀티홉 추적입니다. 이 기능이 왜 중요하고, 어떻게 구현되었는지 자세히 살펴보겠습니다.
멀티홉(Multi-hop) 이란 직접적인 의존성(1-hop)뿐 아니라, 간접적인 의존성(2-hop, 3-hop...)까지 따라가며 추적하는 것을 말합니다.
실제 시나리오로 살펴보겠습니다. "ch-dropwizard의 UserChat 관련 API에 필드를 추가"했을 때의 영향 범위입니다.
0-hop: ch-dropwizard가 UserChat API를 제공 (당연함)
1-hop: ch-app-store, ch-desk-web이 이 API를 직접 호출 → 호환성 체크 필요
2-hop: cht-app-messenger가 ch-app-store의 native function을 통해 간접 호출
3-hop: ch-desk-web이 cht-app-messenger의 특정 API를 통해 또 다른 간접 경로로 접근
(UserChat API를 기준으로 0-hop부터 3-hop까지의 의존성 체인. 직접 의존만 보면 1개 서비스지만, 멀티홉 추적하면 3~4개 서비스가 영향받습니다.)
직접 의존만 보면 1개 서비스, 멀티홉 추적하면 3~4개 서비스입니다. 이 차이가 안전한 코드 변경과 장애의 차이를 만듭니다.
멀티홉 추적의 핵심은 Neo4j의 가변 길이 경로 패턴(Variable-length Path Pattern) 입니다. analyzeEntityImpact 도구의 핵심 Cypher 쿼리를 간략화하면 다음과 같습니다.
// 1. 대상 엔티티 찾기
MATCH (entity:DomainEntity {name: 'UserChat'})
// 2. 직접 영향 서비스 찾기 (IMPLEMENTS 또는 ACCESSES)
MATCH (directService:Service)-[:IMPLEMENTS|ACCESSES]->(entity)
// 3. 이 엔티티를 조작하는 API 찾기
MATCH (api:APIEndpoint)-[:READS|WRITES]->(entity)
// 4. 핵심: 해당 API를 호출하는 API 체인을 1~3단계까지 추적
MATCH callPath = (callerApi:APIEndpoint)-[:CALLS_API*1..3]->(api)
WHERE callerApi <> api
// 5. 호출 경로 전체를 반환
RETURN entity.name,
[node IN nodes(callPath) |
node.serviceName + ':' + coalesce(node.rpcMethod, node.path)
] AS callChain,
length(callPath) AS hops
[:CALLS_API*1..3]이 바로 멀티홉의 핵심입니다. 이 패턴 하나로 1단계부터 3단계까지의 모든 간접 호출 체인을 한 번에 탐색합니다. 관계형 DB였다면 각 단계마다 JOIN을 추가해야 했을 것이지만, 그래프 DB에서는 *1..3 숫자만 바꾸면 됩니다.
MSA 환경에서의 서비스 간 통신은 REST API 호출만 있는 것이 아닙니다. 채널톡에서는 RPC(Remote Procedure Call) 와 Native Function 호출도 활발하게 사용됩니다.
예를 들어, ch-app-store 서비스가 ch-dropwizard의 기능을 호출할 때 REST API가 아닌 내부 RPC를 통해 호출하는 경우가 있습니다. 이런 호출까지 놓치지 않기 위해, 저희는 다음과 같은 추가 분석을 수행합니다.
Go 코드의 delegate function 분석: register.go 파일에서 native function 등록 패턴을 파싱
Java 코드의 RPC 엔드포인트 분석: Dropwizard의 RPC 어노테이션을 추출
API 간 호출 관계 어노테이션: 수동으로 확인된 API 간 호출 관계를 YAML로 기록
이렇게 추출된 RPC 호출도 CALLS_API 관계로 그래프에 기록되므로, 멀티홉 추적 시 REST API 호출과 RPC 호출이 섞인 복잡한 호출 체인도 자연스럽게 추적됩니다.
Channel Knowledge의 위력을 보여주는 가장 대표적인 활용 사례입니다. Claude Code에서 다음과 같이 질문하면,
"MCP를 이용해서 UserChat에 새 필드를 하나 추가했을 때 어떤 영향이 있는지 판단해줘."
MCP 도구(analyzeEntityImpact)가 자동 실행되어 다음과 같은 분석 결과를 돌려줍니다.
📊 영향 범위 요약
1️⃣ 직접 영향 서비스 (2개)
- ch-dropwizard: 핵심 백엔드 서비스 (Java)
- cht-app-messenger: 메신저 앱 서비스 (Node.js)
2️⃣ 간접 영향 서비스 (1개)
- ch-app-store: updateUserChatStateByUser API를 통해 ch-dropwizard를 호출
3️⃣ 영향받는 워크플로우 (10개)
- ChatLifecycle, ChatSessionLifecycle, MessageSending,
WorkflowExecution, CommandExecution, ExtensionExecution,
MacroExecution, FaqAutoResponse, UserOnboarding, TeamLifecycle
4️⃣ 영향받는 API (58개)
- UserChat CRUD: 생성, 조회, 수정, 삭제 API
- UserChat 상태 관리: open, close, assign, snooze 등
- UserChat 검색: state, medium, contact 등 조건별 검색
- 메시지 관련: UserChat 내 메시지 작성/조회 API
- 팀/매니저 관리: 배정, 초대, 참여 등
5️⃣ 연관 엔티티 (4개)
- User, Team, Manager, ChatSession이 결과는 MCP 도구가 없었다면 개발자가 여러 서비스의 코드를 직접 뒤져보며 몇 시간에 걸쳐 파악해야 했을 정보입니다. 단일 레포만 봤다면 놓쳤을 간접 영향 서비스 1개와 숨은 의존성을 즉시 발견할 수 있습니다.
실제 개발 과정에서 Channel Knowledge가 어떻게 활용되는지 정리하면 다음과 같습니다.
(개발자가 코드 변경을 계획할 때 Channel Knowledge를 통해 영향 범위를 사전에 파악하는 흐름)
개발자: Claude Code에서 "UserChat에 lastActiveAt 필드를 추가하려 해. 영향 범위를 분석해줘" 요청
Claude Code: MCP 도구(analyzeEntityImpact)를 자동 호출
MCP 서버: Neo4j에 Cypher 쿼리 실행, 멀티홉 추적 수행
결과 반환: 직접/간접 영향 서비스, 영향받는 API 58개, 워크플로우 10개 등
개발자: 전체 시스템 관점에서 안전한 변경 계획 수립
기존에는 "일단 수정하고, 문제가 터지면 고치자"였던 것이, "수정 전에 영향 범위를 파악하고, 안전하게 변경하자"로 바뀐 것입니다.
이 프로젝트를 진행하면서 몇 가지 중요한 교훈을 얻었습니다.
1. 구문(Syntax)은 자동으로, 의미(Semantics)는 사람의 손으로
정적 분석만으로는 코드의 구조는 파악할 수 있지만 비즈니스 의미까지는 알 수 없습니다. LLM을 활용한 반자동 프로세스(자동 추출 → LLM 기본 구조 생성 → 도메인 전문가 검수)가 이 간극을 효과적으로 메웁니다. 완전 자동화를 추구하기보다는, 기계가 잘하는 것(구조 추출)과 사람이 잘하는 것(의미 부여)을 적절히 분리하는 것이 핵심이었습니다.
2. 그래프 DB의 강점과 약점을 인정하라
Neo4j는 관계 추적에 압도적이지만, 데이터 입력과 관리 측면에서는 관계형 DB보다 불편합니다. Cypher 쿼리를 작성하고 디버깅하는 것도 SQL만큼 생태계가 성숙하지 않습니다. 이 약점을 Git 기반 SSOT(YAML 파일로 지식 관리 → Neo4j에 일괄 로드)로 보완한 것이 프로젝트의 핵심 설계 결정 중 하나였습니다.
3. MSA 환경의 지식은 "살아있는 시스템"이어야 한다
마이크로서비스는 끊임없이 변합니다. API가 추가되고, 엔티티가 변경되고, 서비스 간 의존 관계가 달라집니다. 한 번 구축하고 끝나는 정적 문서가 아니라, 코드 변경에 맞춰 자동으로 최신화되는 살아있는 시스템이 필요합니다. 이를 위해 증분 분석 자동화 인프라를 미리 구축해둔 것이 중요했습니다.
Channel Knowledge는 아직 POC(Proof of Concept) 단계이며, 앞으로 다음과 같은 확장을 계획하고 있습니다.
분석 범위 확대: 현재 3개 서비스에서 채널톡의 모든 서비스로 확장
리니어(Linear) 연동: 팀, 사람, 이슈와 각 기능을 연결하여 "이 기능은 누가 담당하는지", "관련 이슈는 무엇인지"까지 답할 수 있는 완전한 지식 시스템 구축
프로덕션 배포: 주기적인 증분 분석을 통한 지식 자동 최신화
LLM 생성 Cypher 쿼리: 사전 정의된 MCP 도구 쿼리를 넘어, LLM이 자연어 질문을 직접 Cypher 쿼리로 변환하여 실행하는 일반적인 Graph RAG 달성
이 글에서는 AI 코딩 도구의 한계, 특히 MSA 환경에서 서비스 간 의존성을 파악하지 못하는 근본적인 문제를 정의하고, 그래프 RAG라는 접근법으로 이를 해결한 Channel Knowledge 시스템의 설계와 구현을 공유드렸습니다.
핵심을 요약하면 다음과 같습니다.
AI의 시야를 단일 레포에서 전체 시스템으로 확장 — 코드 분석 + 도메인 지식 + 그래프 DB의 조합으로, AI가 MSA 전체를 조망할 수 있게 했습니다.
관계 추적이 핵심 — 벡터 검색으로는 할 수 없는 다단계 의존성 추적(멀티홉)을 그래프 DB로 구현하여, 숨은 의존성까지 발견합니다.
코드 탐색이 아닌 즉시 조회 — MCP 서버를 통해 AI 코딩 도구가 실시간으로 아키텍처 정보를 조회하여, 안전한 코드 변경을 지원합니다.
결국 우리가 만든 것은 "AI가 우리 시스템을 이해할 수 있게 해주는 눈"입니다. 코드를 잘 짜는 것을 넘어, 시스템을 이해하고 안전하게 변경하는 것. 이것이 AI 코딩의 다음 단계라고 믿습니다.
채널톡에서는 이처럼 AI 기술을 적극적으로 활용하여 개발 생산성을 높이고, 복잡한 MSA 환경에서의 기술적 도전을 해결하기 위해 끊임없이 노력하고 있습니다. 이러한 도전에 함께하며 성장하고 싶으신 분들은 언제든지 채널톡의 문을 두드려 주세요!
We Make a Future Classic Product