“VIP 기준이 뭐죠?”

유통사 BI Agent 프로젝트에서 있었던 일이다.

현업 담당자가 테스트 중에 Agent에게 물었다. “지난달 VIP 고객 매출 알려줘.” 시스템이 숫자를 뱉어냈는데, 담당자 표정이 좋지 않았다. “이거 뭔가 이상한데요. VIP 기준이 우리 팀이랑 다른 것 같아요.”

마케팅의 VIP와 CRM의 VIP가 달랐다. 매출도 마찬가지. 순매출이냐 총매출이냐에 따라 수억 단위로 차이가 난다.

처음 겪는 문제가 아니었다. DW를 클라우드로 옮기는 프로젝트에서도 봤고, 차세대 정보계를 여러 벤더와 1년 넘게 만들 때도 똑같았다. 벤더마다 “매출”, “원가"의 기준이 달라서 데이터 정합성 잡느라 몇 주씩 지연됐다. 용어 하나 안 맞으면 전체 일정이 밀린다. DW/BI 프로젝트를 하면서 이 문제가 안 나온 적이 없다.

엔터프라이즈 DW에는 테이블과 컬럼이 있다. 없는 건 맥락이다. “이 컬럼이 비즈니스에서 뭘 의미하는지"가 기계가 읽을 수 있는 형태로 어디에도 정의되어 있지 않다.

그래서 직접 만들기로 했다.

NL2SQL은 만능이 아니다

요즘 NL2SQL 도구가 많다. 자연어를 SQL로 바꿔주는 것 자체는 이미 된다.

실환경에 붙여보면 얘기가 달라진다. 벤치마크 점수가 높은 모델을 실제 DW에 연결하니까 체감 정확도가 확 떨어졌다. 카드사·통신사·공공데이터까지 내외부 데이터를 통합해 놓은 환경이었다. LLM이 이 복잡도(테이블 구조와 질문의 난이도)를 감당하지 못했다.

DW의 DDL을 열어보면 원인이 보인다. T_CUST_MST.CUST_GRD_CD, T_ORD_DTL.SALE_AMT 같은 약어 테이블이 수천 개다. 벤치마크 DB의 customer_name, order_date와는 차원이 다르다. 같은 회사인데도 사업부마다 테이블 네이밍 규칙이 다르고, “매출"이라는 단어 하나가 사업부마다 다른 테이블을 가리킨다.

파생 지표는 더 골치다. “순매출"은 단일 컬럼이 아니다. SUM(SALE_AMT) - SUM(RTN_AMT) - SUM(DC_AMT) 같은 계산식인데, 이 공식은 어떤 DDL에도 안 적혀 있다. 현업 머릿속에 있거나, 잘해야 누군가의 Excel 정의서 어딘가에 묻혀 있다.

NL2SQL의 병목은 SQL 생성 능력이 아니다. 맥락이 없다.

온톨로지라는 선택

대화형 BI에서 프롬프트 엔지니어링을 아무리 최적화해도 DDL만으로는 한계가 뚜렷했다. 멀티에이전트 아키텍처도 설계해 봤는데, 근본 문제는 같았다. LLM에게 줄 맥락 자체가 없다.

온톨로지를 붙이기로 했다.

거창한 얘기가 아니다. 실용적으로 보면 이런 것이다:

# 순매출 용어 정의
- term: 순매출
  definition: 총매출에서 반품과 에누리를 차감한 금액
  formula: SUM(SALE_AMT) - SUM(RTN_AMT) - SUM(DC_AMT)
  synonyms: [Net Sales, 순매출액, 넷세일즈]
  related_tables: [T_SALE_DTL, T_RTN_DTL]
  owner: 재무팀

이런 용어 정의를 메타데이터 카탈로그에 등록하고, NL2SQL 엔진의 RAG Store에 자동 동기화한다. LLM이 “순매출"이라는 단어를 봤을 때 어떤 테이블의 어떤 컬럼을 어떤 계산식으로 조합해야 하는지 알게 되는 구조다.

처리 흐름은 이렇다:

DataNexus 질의 처리 흐름

온톨로지 적용 전후 차이 -내부 목표치가 EX(Execution Accuracy) 기준 +15~20%p 향상이다. MVP에서 EX 80% 이상, 안정화 단계에서 90% 이상. 현실적인 수치인지는 만들어 보면서 검증한다.

DDL 붙여넣기로는 안 되는 이유

엔터프라이즈 DW의 DDL을 다 붙여넣으면 수만수십만 토큰이다. 테이블이 수백수천 개인 환경에서는 컨텍스트 윈도우에 다 못 넣는다. 넣더라도 LLM이 그 안에서 정확한 테이블을 골라내는 건 Needle-in-a-Haystack 문제다.

보안도 걸린다. 기업 내부 스키마를 외부 API에 통째로 보낼 수 없다. 같은 “매출"이라도 A그룹사 사용자와 B그룹사 사용자가 봐야 하는 범위가 다르다. Row-level Security가 필요한 환경이다.

가장 큰 문제는 지속성이다. DDL은 바뀐다. 비즈니스 용어 정의도 바뀐다. 차세대 정보계를 오픈하고 나서도 단계별 릴리즈가 이어지면 매번 메타데이터가 바뀐다. 일회성 프롬프트가 아니라, 변경을 감지해서 자동으로 RAG Store를 갱신하는 파이프라인이 필요하다.

따로 플랫폼을 만들어야겠다고 생각했다.

DataNexus가 하려는 것

네 가지 컴포넌트로 구성된다.

DataNexus 전체 구성도

  1. 메타데이터 카탈로그 -비즈니스 용어 정의, 테이블 메타, 데이터 계보를 한 곳에서 관리한다. 온톨로지의 원천(Source of Truth).
  2. NL2SQL 엔진 -자연어를 SQL로 변환하되, 온톨로지에서 가져온 맥락을 프롬프트에 주입한다. DDL만 던져주는 방식과 정확도 차이가 확 난다.
  3. 문서 지식엔진 -사업보고서, 정책문서 같은 비정형 데이터를 GraphRAG + 벡터 하이브리드로 검색한다.
  4. 그래프 DB -온톨로지를 지식 그래프로 저장한다. 그룹사별 Multi-DB 격리까지.

카탈로그에서 정의한 온톨로지가 NL2SQL과 문서검색에 자동 동기화되어, 사용자 질문에 맥락이 붙는 구조다. 각 컴포넌트에 사용한 오픈소스와 선정 이유는 다음 글에서 다룬다.

왜 지금인가

범용 모델은 빠르게 좋아지고 있다. 단순 기획이나 문서 생성은 곧 commodity가 된다. 차이를 만들려면 기업 데이터의 맥락을 구조화해서 모델에 주입하는 시스템이 있어야 한다.

우리 회사의 “순매출” 정의가 뭔지, LLM이 아무리 똑똑해져도 모른다. 기업 내부에만 있는 지식이기 때문이다.

LLM 연구에서는 이걸 “Non-verifiable Domain” 이라고 부른다. 수학이나 코딩은 정답을 자동 검증할 수 있는데, 기업 내부의 암묵적 지식은 외부에서 판별할 방법이 없다. 이런 데이터 위에 쌓이는 경쟁 우위가 “Data Moat” 다.

이 우위가 영원할 거라고 보진 않는다. 범용 모델의 일반화 속도를 DataNexus의 데이터 축적 속도가 앞서야 한다.

Data Moat를 쌓는 방법은 이렇다:

  • 온톨로지 기반 맥락 -도메인 전문가가 용어를 정제할수록 두꺼워지는 메타데이터 카탈로그
  • 역할별 해석 -같은 질문이라도 재무팀과 마케팅팀에 다른 응답을 주는 페르소나 최적화. 사용 패턴이 쌓일수록 개인화된다.
  • 시간축 지식 그래프 -Temporal Knowledge Graph로 “작년 4분기 기준 VIP 정의"와 “올해 기준 VIP 정의"를 구분
  • 비공개 데이터 자산 -그룹사별 그래프 DB 격리 + Row-level Security. 각 그룹사의 데이터가 독립 자산이 된다.

올해 상반기까지 MVP를 내고 데이터 축적 루프를 돌리는 게 목표다.

이 블로그의 목적

DataNexus를 만들면서 부딪히는 의사결정, 삽질, 해결 과정을 기록한다.

다룰 것들:

  • 기술 스택을 선정한 과정 (후보군 탈락 사유 포함)
  • 메타데이터 카탈로그의 Business Glossary를 온톨로지로 쓸 때의 한계와 우회
  • SKOS 호환 레이어를 넣은 이유
  • NL2SQL 엔진의 User-Aware 설계와 Row-level Security
  • CQ(Competency Questions)로 온톨로지를 사전 검증하는 법
  • Query Router에서 결정론적 vs 확률론적 라우팅을 나누는 기준
  • 에이전트 태스크를 쪼개는 79% Rule

이론보다는 실제로 부딪힌 문제와 그걸 어떻게 풀었는지 (또는 아직 못 풀었는지)를 쓸 예정이다.

다음 글

DataNexus의 기술 스택 -4개의 오픈소스를 이 조합으로 결정하기까지의 과정. 후보군에서 탈락한 것들과 그 이유를 정리한다.


DataNexus를 설계하고 구축하는 과정을 기록합니다. GitHub | LinkedIn