안녕하세요 AI전략팀 정진욱입니다.
저는 LLM이 정형 데이터를 이해하고 사용자의 비즈니스 요구사항을 SQL 쿼리로 변환하는 NL2SQL(Natural Language to SQL) 및 에이전틱 워크플로우 설계를 담당하고 있습니다. 최근 생성형 AI의 발전으로 데이터 분석의 문턱이 낮아지고 있지만, LLM에게 실제 DB에 대한 컨텍스트를 전달하는 과정은 쉬운일이 아닙니다. 또한, 수만 개의 컬럼이 얽힌 기업용 DB 환경을 이해한 에이전트를 설계하는 것은 차원이 다른 문제입니다.
단순한 프롬프트 주입을 넘어, 복잡한 조인 로직을 풀어냈던 저의 고민과 아키텍쳐적으로 해결해 나간 과정을 공유하고자 합니다.
1. QueryError: “메모리가 부족합니다”
NL2SQL 개발 과정에서 실제 기업 환경에 맞추기 위해, 자사 플랫폼인 XAIOps의 DB를 연결하여 테스트를 하였습니다. XAIOps 플랫폼은 모니터링 플랫폼으로 DB에는 5초, 30초 주기의 시스템 데이터가 저장되고 있습니다.
테스트 과정에서 “지난달 WAS 서버 GC 문제 좀 찾아줘”라는 질문을 던졌고, LLM이 아주 그럴듯한 SQL을 생성하였습니다. 하지만 결과는 Query Failed. 그것도 단순한 문법 오류가 아니라 DB 노드 메모리 제한 초과였습니다.
단 1개월치 데이터를 조회했을 뿐인데, 할당된 메모리(약 600MB)를 순식간에 소진하고 쿼리가 강제 종료되었습니다.
1-1. Root Cause: JOIN 조건의 부족
다음은 LLM이 생성한 쿼리입니다
얼핏 보면
target_id(서버 ID)로 잘 묶은 것 같지만, 여기엔 치명적인 실수가 있습니다. 바로 time 컬럼에 대한 조인 조건이 빠졌다는 점입니다.일반적인 RDBMS의 마스터-디테일 테이블(예: 유저-주문내역)이라면 ID 조인만으로 충분했을지 모릅니다. 하지만 우리가 다루는 데이터는 시계열(Time-Series) 데이터입니다.
시계열 데이터에서 시간(
time) 조건을 빼고 ID로만 조인하면 해당되는 데이터가 너무 많아지는 Cartesian Explosion이 발생합니다.- 상황 가정: 1분 단위로 데이터가 쌓이는 서버 A가 있습니다.
- 데이터 양: 1개월 = 30일 * 24시간 * 60분 = 약 43,200row
- 잘못된 JOIN: ON s.target_id = p.target_id
- 서버 A의 11월 1일 00:00 데이터가, 반대쪽 테이블의 11월 한 달 치 모든 데이터(43,200개)와 매핑됩니다.
- 서버 A의 11월 1일 00:01 데이터도, 마찬가지로 43,200개와 매핑됩니다.
- 결과: 억 건
단 하나의 서버, 단 한 달 치 데이터를 조회하려 했을 뿐인데 내부적으로는 18억 건의 임시 테이블을 생성하니 메모리를 초과하였습니다.
1-2. 도메인 지식의 중요성
NL2SQL을 구현할 때 스키마만 컨텍스트로 제공하면 끝날 것이라 생각하기 쉽습니다. 하지만 LLM은 데이터의 형태는 알지만, 데이터의 특성은 모릅니다. 따라서 다음과 같은 시스템 프롬프트를 추가하여 시계열 테이블의 특성을 명시적으로 주입하였습니다.
2. Dynamic Few-Shot 샘플링: “과거의 성공에서 배운다”
NL2SQL의 정확도를 높이기 위해서 많이 사용되는 방식이 Few-shot 샘플입니다. 실제 사용자의 질의와 그에 대응하는 SQL 쿼리를 예시로 제공하는 방식이죠. 하지만 다른 데이터베이스에도 고정된 예시를 사용하는 것은 바람직하지 않습니다.
2-1. 고정된 예시의 함정
고정된 예시는 특정 도메인 질문에는 강하지만, 질문의 의도가 조금만 비껴가도 LLM이 예시 쿼리의 구조를 강박적으로 따라 하려다가 스키마 환각을 일으킵니다. 또한, 불필요한 예시는 컨텍스트 윈도우를 낭비하여 정작 중요한 스키마 설명을 가리는 노이즈가 됩니다.
스키마 환각(Schema Hallucination)은 모델이 사용자의 자연어 질문을 SQL 쿼리로 변환할 때, 존재하지 않는 데이터베이스 테이블이나 컬럼을 참조하거나, 질문의 구절을 실제 데이터베이스 스키마와 잘못 매핑하는 현상을 의미합니다.
2-2. Solution: 사용자 피드백 기반 데이터 플라이휠
Data Flywheel은 데이터와 학습 모델이 상호작용하면서 성능을 지속적으로 개선하는 일종의 선순환 구조를 뜻합니다.
우리는 사용자가 긍정적인 평가를 내린 성공 사례를 실시간으로 학습 가이드로 활용하는 RAG 기반의 ‘개인화된 동적 퓨샷’ 구조를 설계했습니다.
- Positive Feedback Indexing: 사용자가 ‘좋아요’를 누른
{ 자연어 질문 : 생성된 SQL }쌍을 벡터 DB에 저장합니다. 이때 단순히 쿼리쌍만 저장하는 것이 아니라, 해당 쿼리를 생성한사용자정보를 메타데이터로 함께 저장합니다.
- Semantic Retrieval: 새로운 질문이 들어오면, 질문의 벡터 값과 가장 유사한 과거 성공 사례 최대 8개를 실시간으로 검색합니다. 메타데이터 필터링을 통해 해당 사용자가 과거에 사용했던 쿼리 패턴을 검색합니다.
- Dynamic Context Injection: 검색된 ‘검증된 예시’를 프롬프트에 주입하여 LLM에게 컨텍스트를 제공합니다.
이 방식의 핵심은 LLM이 별도의 파인튜닝 없이도 ‘사용자별 특화 비즈니스 맥락’을 실시간으로 습득(In-Context Learning)한다는 점에 있습니다. 예를 들어, 같은 ‘매출’이라도 부서마다 의미가 다를 수가 있습니다. 개인화된 퓨샷은 해당 사용자가 과거에 성공했던 ‘매출’ 계산 식을 우선 참조하게 함으로써, LLM이 사용자의 의도에 맞는 비즈니스 맥락을 정확히 추론하게 만듭니다.
- Zero-Shot의 한계 극복: 특정 도메인에서만 통용되는 고유 명사나 복잡한 집계 로직을 시스템 프롬프트에 모두 적는 것은 불가능합니다. 하지만 이 구조를 통해 사용자의 피드백이 쌓일수록 에이전트는 해당 사용자의 업무 스타일을 학습하며, 시간이 지날수록 쿼리 정확도가 향상되는 ‘데이터 플라이휠’ 효과를 거둘 수 있습니다.
실제로 이 방식을 도입한 결과, 복잡한 비즈니스 맥락이 포함된 질의에 대한 성공률을 기존 대비 20% 이상 끌어올릴 수 있었습니다. “질문과 유사한 예시가 제공될 때 In-Context Learning 효율이 극대화된다”는 연구 결과를 실문적으로 증명한 사례였습니다.
3. 스키마 컨텍스트 설계의 3단계 진화 과정

LLM에게 DB 스키마를 어떻게 설명할 것인가는 NL2SQL의 성능을 결정짓는 가장 핵심적인 연구 과제였습니다. 저희 플랫폼은 세 단계의 시행착오를 거쳐 진화했습니다.
3-1. DDL 및 Sample Value 자동 주입
Naive Approach
초기에는 자동화를 위해 DDL 전체와 컬럼별 Sample Value를 가져와 컨텍스트로 넣었습니다.
- 장점: 개발 공수가 적고 자동화가 쉽습니다.
- 단점: 수백 개의 테이블이 있는 실환경에서는 토큰이 부족해지고, LLM이 노이즈 정보에 혼란을 느껴 엉뚱한 컬럼을 선택하는 빈도가 높았습니다.
3-2. 카테고리성 데이터 보강
Data-Driven Approach
특정 컬럼이 카테고리형(예: 평가지표, 인스턴스 타입)일 때, 유니크한 값들을 미리 추출해 프롬프트에 포함했습니다.
- 장점: “gc 관련 문제” 같은 질의에서 LLM이
jvm_gc_issue와 같이 존재하지 않는 값을 생성해내는 환각(Hallucination)을 크게 줄일 수 있었습니다. 대신 미리 제공된 유니크 값 목록을 참조하여jvm_gc_time,jvm_gc_count이라는 정확한 값을 포함한 쿼리를 생성할 수 있게 되었습니다.
- 단점: 데이터 양이 방대한 경우 이 값을 실시간으로 추출하는 데 과도한 latency가 발생하여 실제 서비스 적용에 어려움을 겪었습니다.
3-3. Semantic Metadata Management 레이어
Human-in-the-loop
결국 최적의 성능을 위해, 플랫폼 단에서 테이블과 칼럼에 대한 ‘의미적 설명’을 관리하는 레이어를 구축했습니다.
- Trino 추상화: 고객사의 DB가 무엇이든 Trino로 묶어 LLM이 표준 SQL에만 집중하게 했습니다.
- 관계의 명시화: 사용자가 플랫폼에서 “이 테이블과 저 테이블은 어떤 조건으로 조인해야 한다”는 설명을 추가할 수 있게 했습니다. 1번 사례에서 겪은 Cartesian Explosion도 이 메타데이터 레이어에 “조인 시 시간 조건 필수”라는 설명을 명시함으로써 근본적으로 해결했습니다.
4. 도메인 지식이 담긴 프롬프트가 정답을 만든다
LLM은 SQL 문법은 이해하더라도, 우리 데이터의 맥락은 모른다
정형화된 로직이 지배하는 데이터베이스 시스템과 확률론적인 답변을 내놓는 LLM 사이의 간극을 메우는 것은 결국 정교한 엔지니어링의 몫입니다. ‘카르티시안 폭발’을 막기 위한 강제 규칙을 설계하고, 사용자 피드백을 지능으로 환원하는 동적 퓨샷 파이프라인을 구축하며, 수백 개의 테이블 속에서 의미를 찾는 시멘틱 메타데이터 레이어를 정립하는 과정은 단순한 코드 작성을 넘어선 데이터 지식의 아키텍쳐화였습니다.
향후 과제: 실전을 위한 내부 데이터셋 구축
현재 저희 엔진은 BIRD 벤치마크와 같은 공인 데이터셋에서 준수한 성능을 보이고 있습니다. 하지만 테크 블로그에서 공유한 여러 사례처럼, 실제 기업용 데이터베이스는 벤치마크 데이터셋보다 훨씬 불친절하고 복잡합니다.
앞으로는 범용 데이터셋에 안주하지 않고, 우리 플랫폼이 직면한 상황에 맞는 내부 벤치마크를 구축할 계획입니다. 이를 통해 단순한 쿼리 생성을 넘어, 사용자 질의에 모호한 비즈니스 의도를 완벽하게 이해하고 그에 맞는 데이터를 가져올 수 있도록 진화시킬 예정입니다.
출처
함께 보면 좋은 아티클
