안녕하세요, Bevis(이춘호)입니다. 앞으로 리텐션 블로그에서 지금까지 경험하였던 데이터팀 빌딩, 운영, 데이터 엔지니어링, 등을 이야기를 나누려 합니다.
그 첫 인사로, 그간 데이터팀 운영하면서 계속 반복되었던 문제를 해결하고 지속적으로 시도하였던 데이터 민주화에 더 가까워질만한 결과물을 개발하였고, 그 이야기에 대해 나눠보려고 합니다.
글 시작에 앞서 이 글이 도움이 되실만한 분들에 대해 적었습니다.자신의 경험과 고민에 얼마나 도움이 될지 함께 생각해보면서 읽어 주셨으면 합니다.
이런 분들께 도움이 될 것 같아요
data 담당자
- ad-hoc 으로 데이터 추출 및 확인 요청 업무가 쌓여서 고민이 있어요
- 데이터 웨어하우스 구축 및 데이터 마트 구축 및 운영에 고민이 있어요
- BI 대시보드를 제공하는데 활용도가 너무 낮아요.
비즈니스 담당자
- 반복 정리하는 업무를 빠르게 처리하고 자동화 (대시보드 / 차트) 시키고 싶어요
- 데이터에 궁금증이 있는데, 데이터를 잘 몰라서 혼자서 시작하기 어려워서 고민이에요.
- 여러 비즈니스 질문에 빠르게 답을 얻고 계속 쪼개가면서 분석해서 인사이트를 얻고 싶어요
- 정리된 인사이트를 팀 또는 동료에게 공유하고 활용하고 싶어요.
들어가며 — 완벽하지 않아도 공유합니다
한국에서 무언가를 공개한다는 건 꽤 무거운 일입니다.
"아직 부족한데..."
"좀 더 다듬고 나서..."
"완성되면 그때 보여줘야지."
저도 그랬습니다. 10년 넘게 데이터 분석가, 데이터 사이언티스트, 데이터 엔지니어링을 하면서, 항상 완벽한 상태가 되어야 공개할 수 있다고 생각했습니다.
그런데 해외 커뮤니티를 보면 분위기가 다릅니다. Product Hunt에 올라오는 제품들, Hacker News에 공유되는 사이드 프로젝트들. 그들은 이렇게 말합니다.
"I built this thing. It's not perfect, but it works. Try it and let me know what you think."
완벽하지 않아도 일단 작동하면 공유합니다. 그리고 피드백을 받으며 함께 만들어갑니다.
이 글도 그런 마음으로 씁니다. Discovery는 아직 완벽하지 않습니다. 하지만 작동합니다. 그리고 저는 이게 꽤 쓸만하다고 생각합니다.
데이터는 충분히 있었지만, 꺼내 쓰는 데는 늘 사람이 필요했습니다. 이 글은 Discovery Chat v3.1을 만들면서
- 왜 DSL(v2.0)을 선택했고
- 왜 그 선택이 실패했으며
- 어떻게 작은 모델 중심 구조로 방향을 틀었는지
를 솔직하게 기록한 구축기입니다.
PART 1. 왜 만들었나 — 데이터 팀에만 쌓이는 부채
지난 10년간 데이터 엔지니어링과 분석 시스템을 만들며, 여러 조직(SOCAR, 콴다, 숨고 등)에서 반복적으로 마주친 장면이 있습니다.
"이 쿼리 하나만 뽑아주세요."
문제는 이 요청이 하나가 아니라는 점이었습니다.
- 데이터 조회 1건 평균 소요 시간: 약 15분
- 데이터 팀 월 요청량: 약 1,000건
- 실제 분석·모델링에 쓰는 시간: 지속 감소
비즈니스 팀의 체감은 더 나빴습니다.
- SQL을 모르는 80%의 직원은 데이터 접근 불가
- 의사결정은 실시간이 아닌 주 단위 리포트
- "데이터는 있는데 왜 바로 못 보죠?"라는 질문 반복
대안도 있었습니다. 하지만 모두 아쉬웠습니다.
- BI 도구: 높은 비용, 긴 러닝커브
- LLM 직접 연동: 정확도 불안정, API 비용 급증
그래서 질문은 하나로 수렴했습니다.
"SQL 없이, 새 도구를 배우지 않고, 지금 쓰는 BigQuery 위에서 바로 답을 얻을 수 없을까?"
PART 2. 첫 번째 시도 — DSL(Domain Specific Language) 과 큰 모델 (v2)
이 선택은 즉흥이 아니었습니다. v2에서 DSL을 채택한 데에는 분명한 이유가 있었습니다.
왜 DSL(Domain Specific Language) 을 선택했는가
Text-to-SQL에서 가장 위험한 요소는 모호함(Ambiguity) 이었습니다.
- 같은 질문, 다른 해석
- 지표 정의의 불일치
- 자연어는 정확하지만 실행은 불안정
그래서 질문을 바로 SQL로 바꾸지 않고, 의미를 한 번 고정하는 중간 계층을 두기로 했습니다.
그 결과물이 DSL (Domain Specific Language) 이었습니다.
v2 DSL 설계 방식
DSL은 “질문을 SQL로 만들기 전에, 규칙대로 정리해두는 ‘주문서(레시피)’”
이 주문서를 한 번 만들면
- 지표 정의를 항상 같은 기준으로 적용할 수 있고
- 말이 안 되는 조합은 SQL 만들기 전에 차단할 수 있으며
- 항상 같은 형태의 SQL이 생성되어 안정성이 올라갑니다.
1️⃣ Metric Store 기반으로 DSL 구조를 먼저 고정
v2에서는 SQL 을 즉흥적으로 만들지 않고, Metric Store 의 지표 정의 방식을 DSL로 가져왔습니다.
예시) conversion_rate를 “지난달, 채널별로” 보고 싶다면 DSL은 이렇게 정리됩니다.
METRIC: conversion_rate
GRAIN: session
DIMENSIONS: channel
DATE_RANGE: last_month
이렇게 "주문서" 형태로 고정해두면:
- 지표 계산(분자/분모, 필터, 조인 규칙 등)이 항상 동일한 정의로 적용되고
- metric과 dimension, grain이 충돌하는 잘못된 조합을 미리 막고
- SQL이 흔들리지 않고 일관된 패턴으로 생성됩니다.
2️⃣ 자연어는 바로 SQL로 보내지 않고, 먼저 슬롯으로 정리
사용자가 말하는 질문은 표현이 제각각이라 바로 SQL로 보내면 결과가 흔들릴 수 있습니다.
그래서 먼저 질문에서 필요한 정보만 뽑아 슬롯(slot) 으로 정리합니다.
"지난달 채널별 전환율"
→ METRIC=conversion_rate
→ DIMENSION=channel
→ DATE_RANGE=last_month
(여기서 중요한 점은, 슬롯은 “의도”를 뽑는 단계이고, 정확한 규칙/제약은 아직 적용되지 않은 상태라는 겁니다.)
3️⃣ 질문 → 슬롯 → DSL → SQL 로 단계 분리
v2는 아래 흐름으로 동작합니다.자연어를 바로 SQL로 보내지 않고,
- 질문 → 슬롯: 자연어에서 의도(지표/차원/기간)를 추출
- 슬롯 → DSL: Metric Store 규칙을 적용해 빠진 요소(GRAIN 등)를 채우고, 불가능한 조합을 검증
- DSL → SQL
- : “검증된 주문서”를 기준으로 안정적으로 SQL 생성
즉, 자연어를 바로 SQL로 번역하는 대신,
중간에 DSL(주문서)을 두어 ‘정의의 일관성’과 ‘생성 안정성’을 확보한 설계입니다.
모델 선택 전략 (v2)
모델 스케일에 대한 전제
LLM에서 흔히 말하는 7B / 70B / 405B는 파라미터 수를 의미하며, 이는 단순 성능이 아니라 다음 능력과 직결됩니다.
- 암묵적 추론 (implicit reasoning)
- 긴 context 유지
- 모호한 질문의 자동 구조화
Small Model (7–8B급)
Llama 3 8B, Mistral 7B, Qwen 7B 등 오픈소스 모델 기준
- 빠르고 저렴함
- 규칙이 명확할수록 강함
한계
- 질문에 숨어 있는 전제 생성 불가
- Metric 간 관계 추론 불가
- Context 누적 시 오류 발생
👉 "해석된 지시를 실행하는 모델"
Large Model (70B급)
Llama 3.1 70B, Qwen 72B 등
- 모호한 질문 구조화 가능
- 비교 기준·기간·의도 추론 가능
한계
- 높은 비용
- 느린 응답
- 런타임 사용 시 시스템 비용 폭증
👉 "생각을 대신해주는 모델"
Frontier Model (405B급 이상)
Llama 3.1 405B, GPT-4, Claude Opus 등
- 최고 수준의 추론 능력
- 복잡한 다단계 질문 처리
한계
- 매우 높은 비용 (토큰당 10배 이상)
- 실시간 서비스에 부적합한 지연 시간
- 비용 예측 불가
👉 "비용을 무시할 수 있을 때만 쓰는 모델"
❌ v2의 실제 실패 사례
사용자 질문
"이번 달 매출, 지난달보다 잘 나왔어?"
Small Model (7B)
- 매출 정의 불명확 (GMV? Net Revenue?)
- 비교 방식 미정
- 기간 기준 모호
→ 임의 가정으로 잘못된 SQL 생성
Large Model (70B)
- 전월 대비
- GMV 기준
- 캘린더 월 기준
→ 성공했지만 모델 추론에 전적으로 의존
이 구조는 비싸고, 예측 불가능하며, 확장 불가했습니다.
PART 3. 모델 선택의 현실 — 상용 LLM과 우리가 선택한 길
"그럼 그냥 제일 좋은 모델 쓰면 되는 거 아닌가?"
실제로 검토했던 선택지는 다음과 같았습니다.
주요 상용 LLM 비교 (2024–2025 기준)
| 모델 | 파라미터급 | Context Window | 특징 | 한계 |
|---|---|---|---|---|
| GPT‑3.5 | ~20B 추정 | ~16k | 저렴, 빠름 | 추론 약함 |
| GPT‑4o | ~1.8T 추정 | 128k | 강력한 추론 | 비용·지연 |
| Claude 3.5 Sonnet | 중대형 | 200k | 긴 문맥 | SQL 불안정 |
| Claude 3 Opus | 초대형 | 200k | 최고 추론 | 매우 고비용 |
| Gemini 1.5 Flash | 경량급 | 1M+ | 고속·저비용 | 추론 제한 |
| Llama 3.1 | 8B/70B/405B | 128k | 오픈소스 | 자체 호스팅 필요 |
Context는 최대 입력 기준이며, 유효 추론 길이는 더 짧습니다.
Context Size의 오해
Context는 기억력이지 판단력이 아닙니다.
- 지표 정의 충돌 ❌
- 비용 폭증 쿼리 ❌
- 보안 위반 ❌
을 자동으로 해결해주지 않습니다.
우리가 Small Model을 선택한 이유
Discovery Chat v3.1에서 사용하는 모델은
- Small‑scale 상용 모델 (7–8B급 역할)
- Context: 8k–16k 실사용 범위
입니다.
이 모델이 추론하지 않아도 되도록 시스템을 설계한다.
능력 차이를 어떻게 메웠는가
| 대형 모델 능력 | 시스템 대체 방식 |
|---|---|
| 암묵적 추론 | Ambiguity Resolution |
| 맥락 유지 | 상태 머신 |
| 의미 판단 | Goal Achievement Evaluator |
| 안전성 | Real‑World Data Handler |
우리는 더 좋은 모델을 이긴 게 아니라, 모델이 필요 없는 영역을 늘렸다.
PART 4. 결과 (예상)
- 평균 조회 시간: 15분 → 10-15초 (사람 → discovery)
- 정확도: 65% → 85% (자체 테스트 결과)
- 데이터팀 요청량: 90% 감소 (자체 테스트 결과 예상되는 discovery 커버 비율)
PART 5. SQL만 생성하는 게 아닙니다 — 데이터 파이프라인 전체를 설계했습니다
Text-to-SQL이 Discovery의 전부는 아닙니다.
질문에 답하는 것만으로는 부족합니다. 실제 비즈니스 환경에서는 데이터가 실시간으로 흘러야 하고, 분석하기 좋은 형태로 정제되어야 하고, 반복 작업은 자동화되어야 하고, 결과는 시각화되어야 합니다.
왜 이 5가지를 묶었나?
많은 조직이 이런 상황입니다:
- CDC(Change Data Capture)는 Debezium으로
- CDC : 운영 DB에서 데이터가 변경(생성,삭제,업데이트)을 감지하고 데이터를 전송
- 변환은 dbt로
- 스케줄링은 Airflow로
- 질의는 직접 SQL로
- 시각화는 Tableau나 Looker로
5개의 도구, 5개의 학습 곡선, 5개의 운영 포인트.
우리는 이걸 하나의 플랫폼에서 해결하고 싶었습니다.
┌──────────────────────────────────────────────────────────────────────┐
│ Discovery Platform │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ① 데이터 수집 │
│ ┌─────────────┐ │
│ │ Debezium │ 운영 DB 변경 → 실시간 CDC │
│ └──────┬──────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ BigQuery (Raw) │ 원본 데이터 적재 │
│ └──────┬──────────────┘ │
│ │ │
│ ② 데이터 변환 (Airflow가 스케줄링) │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Airflow │ ────▶ │ dbt │ raw → staging → mart │
│ │ (스케줄러) │ 트리거 │ (변환기) │ │
│ └─────────────┘ └──────┬──────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ BigQuery (Mart) │ 분석용 마트 테이블 │
│ └──────┬──────────────┘ │
│ │ │
│ ③ 데이터 활용 │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Discovery Chat │ 자연어 → SQL → 실행 │
│ └──────┬──────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Chart & Dashboard │ 시각화 & 공유 │
│ └─────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
🔄 Debezium — 실시간 데이터 수집
"데이터가 바뀌면 바로 반영되나요?"
운영 DB의 변경 사항을 실시간으로 BigQuery에 동기화합니다.
- MySQL, PostgreSQL 등 소스 DB의 변경 감지
- CDC(Change Data Capture) 기반 증분 동기화
- 배치 ETL 대비 지연 시간 최소화 (초 단위)
매일 새벽에 배치 돌리고 아침에 확인하던 시대는 끝났습니다. 주문이 들어오면 즉시 분석할 수 있습니다.
🏗️ dbt — 데이터 마트 구축
"원본 데이터가 너무 복잡해서 바로 분석할 수가 없어요."
수집된 raw 데이터를 분석하기 좋은 형태로 변환합니다.
- Staging → Intermediate → Mart 레이어 구조
- 비즈니스 로직을 SQL로 버전 관리
- 테스트와 문서화 자동화
dbt가 중요한 이유는 Discovery의 정확도와 직결되기 때문입니다.
Raw 데이터에 직접 질문:
"매출 보여줘" → orders, order_items, products, refunds...
어떤 테이블? 어떤 조인? 환불은 빼야 하나?
dbt 마트에 질문:
"매출 보여줘" → mart_daily_revenue 테이블 하나로 끝
이미 정제된 데이터, 명확한 정의
잘 설계된 데이터 마트 = 더 정확한 Text-to-SQL
⚙️ Airflow — 데이터 파이프라인 자동화
"이 리포트 매일 아침마다 돌려주세요."
반복되는 데이터 작업을 스케줄링하고 모니터링합니다.
- dbt 모델 빌드 스케줄링
- 일간/주간/월간 리포트 자동 생성
- 의존성 관리 (A 작업 끝나면 B 실행)
- 실패 시 알림 및 재시도
데이터 팀이 매일 아침 수동으로 쿼리 돌리던 일, 이제 Airflow가 대신합니다.
💬 Discovery Chat — 자연어 질의
"SQL 몰라도 데이터 볼 수 있나요?"
이 글의 주인공입니다. 자연어로 질문하면 SQL을 생성하고 실행합니다.
- 작은 모델 + 시스템 설계로 대형 모델 수준의 정확도
- 멀티턴 대화로 점진적 분석
- 컨텍스트 유지 (이전 질문 기억)
테이블 탐색 과정 예시


자연어 질의 실행 결과 예시

📊 Chart — 즉석 데이터 시각화
"이거 차트로 보여줄 수 있어요?"
SQL 결과를 바로 차트로 변환합니다.
- 라인, 바, 파이, 히트맵 등 다양한 차트 타입
- 자연어로 차트 요청 가능 ("이거 추이 그래프로 보여줘")
- 공유 가능한 링크 생성
숫자 나열된 테이블 보고 "이게 뭔 소리야" 하던 시대는 끝났습니다.

📈 Dashboard — 비즈니스 대시보드
"매일 보는 지표들 한 화면에 모아주세요."
자주 확인하는 지표들을 대시보드로 구성합니다.
- 여러 차트를 한 화면에 배치
- 실시간 새로고침
- 팀별/역할별 맞춤 대시보드
Tableau나 Looker 없이도, Discovery 안에서 대시보드까지 해결됩니다.

이 구조의 핵심: 데이터 마트가 Text-to-SQL의 정확도를 결정한다
많은 Text-to-SQL 솔루션이 실패하는 이유는 원본 데이터에 직접 질의하기 때문입니다.
| 접근 방식 | 문제점 |
|---|---|
| Raw 테이블 직접 질의 | 수십 개 테이블, 복잡한 조인, 모호한 컬럼명 |
| LLM이 알아서 추론 | 비용 폭증, 정확도 불안정, 환각 |
Discovery는 다릅니다:
| Discovery 접근 방식 | 장점 |
|---|---|
| dbt로 마트 구축 | 비즈니스 로직이 이미 반영된 깨끗한 테이블 |
| 마트에 질의 | 단순한 구조, 명확한 의미, 높은 정확도 |
"좋은 데이터 마트 위에서 작은 모델도 빛난다."
PART 6. 실제 테스트 — Frontier 모델을 이겼습니다
"작은 모델로 정말 대형 모델만큼 할 수 있을까?"
이 질문에 답하기 위해, 실제 비즈니스 페르소나 시나리오로 **v3.1(Gemini 2.0 Flash)**과 Claude Opus 4.5를 비교 테스트했습니다.
결론부터 말씀드리면: v3.1이 이겼습니다.
테스트 설계
4가지 실제 비즈니스 역할을 설정하고, 각 역할이 실제로 물어볼 법한 질문을 던졌습니다.
| 페르소나 | 목표 | 질문 예시 |
|---|---|---|
| 주니어 마케터 | 어제 방문자·페이지뷰 확인 | "어제 방문자 수가 얼마야?" |
| 퍼포먼스 마케터 | 유입 소스별 전환율·매출 비교 | "소스별 구매 전환율도 같이 보여줘" |
| 경영진 | 이번 달 vs 지난달 매출 비교 | "지난달이랑 비교해서 어때?" |
| MD(상품기획) | 카테고리별 반품률·브랜드별 매출 | "반품률 높은 카테고리 Top 5는?" |
결과: 같은 품질, 더 빠른 속도, 그리고 결정적 차이
| 지표 | v3.1 (Small Model) | Opus 4.5 (Frontier) | 승자 |
|---|---|---|---|
| 목표 달성률 | 100% | 100% | - |
| 품질 점수 (OSS) | 84.44 | 84.44 | - |
| 평균 응답 시간 | 9.0초 | 10.8초 | ⚡ v3.1 |
| 컨텍스트 유지율 | 100% | 100% | - |
| 고난도 쿼리 | ✅ 성공 | ❌ 실패 | 🏆 v3.1 |
일반적인 시나리오에서는 동등했습니다. 하지만 진짜 어려운 문제에서 승부가 갈렸습니다.
고난도 쿼리: v3.1만 성공했습니다
단순 시나리오를 넘어, 다중 데이터 소스(Ecommerce + GA4)를 조인하는 복잡한 쿼리를 테스트했습니다.
질문: "ecommerce 테이블에서 가장 많이 구매한 고객들이 GA4에서 어떤 제품을 몇 회 보고 구매했는가?"
| 시스템 | 결과 |
|---|---|
| v3.1 | ✅ 성공 — 정상적으로 SQL 생성 및 실행 |
| Opus 4.5 | ❌ 실패 — 에러 발생, SQL 생성 불가 |
Frontier급 모델이 실패한 곳에서, 작은 모델이 성공했습니다.
왜 이런 일이 가능했을까요?
왜 v3.1이 이겼나?
대형 모델은 모든 것을 모델 추론에 의존합니다. 질문이 복잡해질수록 추론 부담이 기하급수적으로 늘어납니다. 그리고 어느 순간 한계에 부딪힙니다.
반면 v3.1은 복잡한 추론을 시스템이 분담합니다.
Frontier 모델 접근법:
질문 → [모델이 모든 것을 추론] → SQL
↑ 복잡도가 높아지면 실패
v3.1 접근법:
질문 → [시스템: 테이블 매칭]
→ [시스템: 컬럼 매핑]
→ [시스템: 조인 경로 탐색]
→ [모델: SQL 생성] → SQL
↑ 모델의 부담이 적어 안정적
모델이 해야 할 일을 줄이니, 복잡한 질문에서도 안정적으로 작동했습니다.
큰 모델이 똑똑해서 이기는 게 아닙니다.
작은 모델이 똑똑하게 도움받아서 이기는 겁니다.
실제 대화 예시 ① — 경영진: "지난달이랑 비교해서 어때?"
경영진이 가장 많이 묻는 질문입니다. 단순히 숫자 하나가 아니라, 비교와 맥락이 필요한 질문이죠.
👤 경영진: 이번 달 총 주문건수와 판매된 아이템수는?
🤖 v3.1:
SELECT
COUNT(DISTINCT order_id) AS total_orders,
SUM(num_of_item) AS total_items
FROM `bq_project.thelook_ecommerce.orders`
WHERE DATE(created_at) >= DATE_TRUNC(CURRENT_DATE(), MONTH)
AND DATE(created_at) < DATE_ADD(DATE_TRUNC(CURRENT_DATE(), MONTH), INTERVAL 1 MONTH)
이번 달 주문 건수와 총 아이템 수를 조회했습니다.
👤 경영진: 지난달이랑 비교해서 어때?
🤖 v3.1:
WITH current_month AS (
SELECT
COUNT(DISTINCT order_id) AS total_orders,
SUM(num_of_item) AS total_items,
COUNT(DISTINCT user_id) AS unique_customers,
COUNTIF(status = 'Shipped') AS shipped_orders
FROM `bq_project.thelook_ecommerce.orders`
WHERE DATE(created_at) >= DATE_TRUNC(CURRENT_DATE(), MONTH)
),
previous_month AS (
SELECT
COUNT(DISTINCT order_id) AS total_orders,
SUM(num_of_item) AS total_items,
COUNT(DISTINCT user_id) AS unique_customers,
COUNTIF(status = 'Shipped') AS shipped_orders
FROM `bq_project.thelook_ecommerce.orders`
WHERE DATE(created_at) >= DATE_TRUNC(DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH), MONTH)
AND DATE(created_at) < DATE_TRUNC(CURRENT_DATE(), MONTH)
)
SELECT '이번 달' AS period, * FROM current_month
UNION ALL
SELECT '지난 달' AS period, * FROM previous_month
UNION ALL
SELECT '변화율 (%)' AS period,
ROUND(SAFE_DIVIDE(cm.total_orders - pm.total_orders, pm.total_orders) * 100, 2),
ROUND(SAFE_DIVIDE(cm.total_items - pm.total_items, pm.total_items) * 100, 2),
ROUND(SAFE_DIVIDE(cm.unique_customers - pm.unique_customers, pm.unique_customers) * 100, 2),
ROUND(SAFE_DIVIDE(cm.shipped_orders - pm.shipped_orders, pm.shipped_orders) * 100, 2)
FROM current_month cm, previous_month pm
"지난달이랑 비교해서 어때?" 라는 모호한 질문을 받고, v3.1은 자동으로:CTE(Common Table Expression)로 당월/전월 분리주문 수, 아이템 수, 고객 수, 배송 건수 4가지 지표 비교증감률까지 계산해서 한 번에 제공
이런 복잡한 비교 쿼리를 2턴 만에, 추가 질문 없이 생성했습니다.
실제 대화 예시 ② — MD: "반품률 높은 카테고리 Top 5는?"
상품기획(MD)이 자주 확인하는 지표입니다. 단순 매출이 아니라 반품률이라는 파생 지표가 필요하고, 여러 테이블을 조인해야 합니다.
👤 MD: 카테고리별 총 매출 보여줘
🤖 v3.1:
SELECT
p.category,
SUM(oi.sale_price) AS total_revenue
FROM `bq_project.thelook_ecommerce.orders` o
JOIN `bq_project.thelook_ecommerce.order_items` oi ON o.order_id = oi.order_id
JOIN `bq_project.thelook_ecommerce.products` p ON oi.product_id = p.id
WHERE o.status NOT IN ('Cancelled', 'Returned')
GROUP BY p.category
ORDER BY total_revenue DESC
취소/반품 제외한 카테고리별 매출을 조회했습니다.
👤 MD: 반품률이 높은 카테고리 Top 5는?
🤖 v3.1:
SELECT
p.category,
COUNT(DISTINCT o.order_id) AS total_orders,
COUNT(DISTINCT CASE WHEN o.returned_at IS NOT NULL THEN o.order_id END) AS returned_orders,
ROUND(COUNT(DISTINCT CASE WHEN o.returned_at IS NOT NULL THEN o.order_id END) * 100.0
/ COUNT(DISTINCT o.order_id), 2) AS return_rate_percentage
FROM `bq_project.thelook_ecommerce.orders` o
JOIN `bq_project.thelook_ecommerce.order_items` oi ON o.order_id = oi.order_id
JOIN `bq_project.thelook_ecommerce.products` p ON oi.product_id = p.id
GROUP BY p.category
HAVING COUNT(DISTINCT o.order_id) > 0
ORDER BY return_rate_percentage DESC
LIMIT 5
📊 실제 결과:
| 카테고리 | 총 주문 | 반품 건수 | 반품률 |
|---|---|---|---|
| Leggings | 3,164 | 339 | 10.71% |
| Pants & Capris | 3,465 | 368 | 10.62% |
| Socks & Hosiery | 3,625 | 381 | 10.51% |
| Outerwear & Coats | 8,854 | 919 | 10.38% |
| Fashion Hoodies & Sweatshirts | 11,507 | 1,178 | 10.24% |
👤 MD: 브랜드별로 매출 비교해줘
🤖 v3.1:
SELECT
p.brand,
COUNT(DISTINCT o.order_id) AS total_orders,
SUM(oi.sale_price) AS total_revenue
FROM `bq_project.thelook_ecommerce.orders` o
JOIN `bq_project.thelook_ecommerce.order_items` oi ON o.order_id = oi.order_id
JOIN `bq_project.thelook_ecommerce.products` p ON oi.product_id = p.id
WHERE o.status NOT IN ('Cancelled', 'Returned')
GROUP BY p.brand
ORDER BY total_revenue DESC
📊 실제 결과 (상위 3개):
| 브랜드 | 총 주문 | 총 매출 |
|---|---|---|
| Diesel | 1,138 | $154,187 |
| Calvin Klein | 2,387 | $153,578 |
| Carhartt | 1,978 | $138,337 |
총 2,749개 브랜드의 매출 데이터를 3턴 대화로 조회했습니다.
이 대화에서 주목할 점
- 3테이블 JOIN 자동 처리: orders → order_items → products를 알아서 연결
- 파생 지표 계산: "반품률"이라는 단어만으로
CASE WHEN+ 비율 계산 수행 - 컨텍스트 유지: "브랜드별로"라고만 해도 이전 대화의 맥락(매출, 취소/반품 제외)을 유지
- 실제 실행 가능한 SQL: BigQuery에서 바로 돌릴 수 있는 완성된 쿼리
PART 7. 엔지니어를 위한 투명성 리포트 (Skeptics Welcome)
"85% 정확도? Spider 데이터셋인가요?"
"테이블 10개짜리 데모 환경 아닌가요?"
"환각(Hallucination)은 어떻게 방어하죠?"
현업 엔지니어라면 당연히 드는 의문들입니다. 데이터 엔지니어 커뮤니티에서 가장 많이 받는 날카로운 질문들에 대해 투명하게 공개합니다.
Q1. 정확도 85%의 기준이 무엇인가요?
단순히 SQL 텍스트가 정답과 일치하는지 보는 'String Match'가 아닙니다.
**Execution Accuracy (실행 정확도)**를 기준으로 합니다.
생성된 SQL을 실제 BigQuery에서 실행했을 때, 정답 쿼리의 결과 집합(Result Set)과 동일한 데이터를 반환하는 비율입니다.
Q2. 어떤 환경에서 테스트했나요?
Spider나 WikiSQL 같은 학술용 데이터셋이 아닙니다. 실제 커머스 기업의 프로덕션 복제 환경입니다.
- 테이블 수: 154개 (Bigquery Public 마트 및 GA4 가상 이벤트 테이블)
- 데이터 규모: 수천만 건 이상의 Row
- 조인 복잡도: 평균 3~4개 테이블 조인, 최대 7개 조인 포함
- 질문 난이도: 단순 조회를 넘어 윈도우 함수, CTE, 비율 계산이 포함된 복합 질문
또한, 객관성/실제 환경에 근접하기 위해 BigQuery Public Dataset [TheLook E-commerce / TheLook E-commerce 기반한 Google Analytics 4 가상 이벤트 테이블] 로 자체 테스트를 진행하였습니다.
Q3. 환각(Hallucination)은 어떻게 막나요?
LLM에게 스키마 전체를 주고 "알아서 짜줘"라고 하지 않습니다. Discovery의 3단계 가드레일이 환각을 통제합니다.
- Schema Filtering: 벡터 검색으로 질문과 관련 없는 테이블은 아예 프롬프트에서 배제합니다. (n개 중 top-k개만 주입)
- Graph-based Join Path: 조인 조건(ON 절)을 LLM이 상상하지 못하게 합니다. ERD 메타데이터에 정의된 검증된 조인 경로만 사용하도록 강제합니다.
- Pre-computation Check: 없는 컬럼이나 잘못된 함수 사용을 SQL 생성 직후, 실행 전에 문법 검사기(Dry-run)로 잡아냅니다.
Q4. 나머지 15% 실패는 어떤 경우인가요?
솔직히 말씀드리면, 아직 **'상식 밖의 데이터 구조'**나 **'극도로 모호한 한국어 표현'**에서는 실패합니다.
예를 들어, DB 컬럼명이 c_001 같이 암호처럼 되어 있는데 메타데이터가 없는 경우, 또는 "그거 좀 챙겨줘" 같은 맥락 의존적 질문 등입니다.
이 실패 사례(Edge Cases)들은 다음 포스팅에서 가감 없이 공개하고, 이를 극복하기 위한 Human-in-the-loop 피드백 루프를 다룰 예정입니다.
PART 8. 왜 이 방식이 통했나
- BigQuery 네이티브 — 새로운 도구 학습 불필요
- 작은 모델 + 시스템 설계 — 모델이 추론하지 않아도 되는 구조
- dbt 데이터 마트 — 잘 정제된 데이터 위에서 더 높은 정확도
- 비용 예측 가능 — Frontier 모델 API 비용 폭탄 없음
- 실패를 사전에 차단 — 모호함을 시스템이 먼저 해결
- 올인원 파이프라인 — 수집부터 시각화까지 한 플랫폼에서
마치며 — 함께 만들어갈 분을 찾습니다
Discovery는 더 똑똑한 모델을 쓴 결과가 아닙니다.
똑똑함을 시스템으로 옮긴 결과입니다.
실제 테스트에서 v3.1은 Opus 4.5(현재 최고 수준의 Frontier 모델)보다 더 나은 성능을 보여줬습니다. 일반적인 시나리오에서는 동등했고, 복잡한 다중 소스 쿼리에서는 Frontier 모델이 실패한 곳에서 성공했습니다.
하지만 솔직히 말씀드리면, 아직 15%는 실패합니다.
모든 질문에 완벽하게 답하지 못합니다. 예상치 못한 엣지 케이스도 있고, 특정 도메인에서는 아직 학습이 부족합니다. 완벽한 제품이 아닙니다.
그래도 저는 이 글을 씁니다.
왜냐하면 완벽해질 때까지 기다리면 영원히 공개하지 못할 것 같았기 때문입니다. 그리고 무엇보다, 혼자서는 그 15%를 줄이기 어렵습니다.
🚀 Discovery 베타 테스터를 모집합니다
지금 Discovery는 베타 테스트 단계입니다.
DataWarehouse 없이 product DB에서 직접 쿼리를 처리하고 계신 분, BigQuery를 쓰고 계신 분, 데이터 팀의 SQL 요청에 지쳐있는 분, 비즈니스 팀이 직접 데이터를 볼 수 있으면 좋겠다고 생각하시는 분.
함께 써보고, 피드백 주시고, 같이 만들어가실 분을 찾습니다.
"It's not perfect, but it works. And it beat the frontier model. Try it and let me know what you think."
완벽하지 않습니다. 하지만 작동합니다. 그리고 가장 비싼 모델보다 더 어려운 문제를 풀었습니다. 여러분의 피드백으로 더 좋아질 수 있습니다.
관심 있으신 분은 아래 링크로 신청해주세요.