AWS Unicorn Day 세션 정리 - BIRD-SQL 기반으로 Redash MCP 강화하기
AWS Unicorn Day를 다녀와서 키노트에서 다뤄진 Text2SQL과 BIRD-SQL 벤치마크 관련 인사이트를 정리하고, 사내에서 운영하고 있는 Redash MCP에 어떻게 적용해 강화했는지 공유합니다. 데이터팀의 비정형 분석 요청 부담을 줄이는 게 목표였고, 결과적으로 5가지 전략을 단계적으로 도입했습니다.
1. 배경: Redash MCP를 운영하며 부딪힌 한계
1.1 도입 당시 기대
기존 데이터 요청 흐름은 예전 글에서 정리한 것처럼 OLTP → OLAP 전환과 Redash 셀프 서비스로 한 단계 개선했지만, 여전히 "비개발자가 직접 SQL을 작성한다"는 진입 장벽이 남아 있었습니다. Redash 위에 Claude를 붙인 MCP 환경을 도입하면 자연어로 분석 요청을 처리할 수 있을 것이라 기대했습니다.
1.2 실제로 부딪힌 문제
운영해보니 다음 문제들이 반복적으로 나왔습니다:
- 모호한 질문에 그대로 SQL 생성 → "매출 알려줘" 같은 질문에 LLM이 임의의 가정을 넣어 결과가 틀어짐
- 170개 테이블을 전부 컨텍스트로 주입 → 토큰 비용 증가, 잘못된 테이블 선택률 상승
- DB 특수 규칙 미반영 →
payment_status = 'paid',application_status_log최신 행 기준 같은 도메인 규칙을 매번 틀림 - 같은 실수의 반복 → 틀린 케이스가 다음 호출에 누적되지 않아 같은 실수가 반복됨
- 단순 COUNT 쿼리에도 Opus 사용 → 비용 비효율
왜 이게 문제인가? 정확도가 낮으면 결국 사용자가 데이터팀에게 다시 묻게 되고, 도입 목적인 "데이터팀 부담 감소"가 무력화됩니다.
2. AWS Unicorn Day 키노트에서 얻은 힌트
키노트에서 Text2SQL 정확도를 끌어올리는 방법론으로 BIRD-SQL 벤치마크가 소개되었습니다. BIRD-SQL은 대규모 실제 데이터베이스를 기반으로 Text2SQL 성능을 평가하는 벤치마크인데, 핵심은 "어떻게 평가하느냐"뿐 아니라 "정확도를 어떻게 끌어올리느냐"의 패턴이 잘 정립되어 있다는 점이었습니다.
2.1 적용 가능한 5가지 전략
| 전략 | 한 줄 요약 | 난이도 |
|---|---|---|
| Multi-turn | 모호한 질문은 SQL 생성 전에 되묻기 | ⭐ |
| Few-shot | 자주 틀리는 패턴은 프롬프트에 예시로 고정 | ⭐⭐ |
| Schema Pruning | 관련 테이블만 추려서 컨텍스트 주입 | ⭐⭐⭐ |
| Feedback Loop | 틀린 쿼리를 Few-shot 예시로 재활용 | ⭐⭐ |
| Dynamic Model Selection | 복잡도에 따라 Haiku/Sonnet/Opus 분기 | ⭐⭐ |
왜 이 조합이 효과적인가? 단순히 더 큰 모델을 쓰는 것보다 "질문을 어떻게 다듬고, 어떤 컨텍스트를 줄이고, 어떤 모델로 라우팅하느냐"가 정확도와 비용 양쪽에서 더 큰 레버리지를 가진다는 게 핵심이었습니다.
3. Multi-turn — 모호한 질문엔 SQL 생성 전 되묻기
3.1 도입 전후
기존에는 "매출 알려줘"라는 질문을 받으면 LLM이 즉시 payment 테이블 기반 SQL을 생성하고, 사용자가 결과를 보고 나서야 "아, 나는 daily_order 기준이 필요한 거였다"고 알아채는 흐름이었습니다.
도입 후에는 다음 판단 로직을 시스템 프롬프트에 명시했습니다:
질문을 받으면 다음 세 가지 중 하나를 선택하라:
1. SQL 생성 — 조건이 충분히 명확할 때
2. 되묻기 — 기간/테이블/필터 조건이 불분명할 때
3. 부연 설명 — 질문 자체가 불가능하거나 데이터가 없을 때
3.2 실제 적용 사례
| 질문 | 판단 | 되묻는 내용 |
|---|---|---|
| "매출 알려줘" | 되묻기 | "payment 기준인가요, daily_order 기준인가요? 기간은요?" |
| "지난달 payment 완료 건수 알려줘" | SQL 생성 | — |
| "크리에이터 현황" | 되묻기 | "전체 수인가요, 활동 상태만인가요? creator_status 필터 필요한가요?" |
효과: 첫 응답 정확도가 눈에 띄게 올라갔고, 사용자도 "어떤 정보를 추가로 줘야 할지" 학습하게 되어 자연스럽게 더 정제된 질문을 던지게 됐습니다.
4. Schema Pruning — 170개 테이블을 다 넘기지 않기
4.1 점수화 기반 테이블 추출
전체 테이블 DDL을 컨텍스트에 넣는 대신, LLM에게 먼저 관련 테이블 후보를 추리게 하고 그 결과를 받아 Top-K 테이블의 DDL만 다음 단계에 주입하도록 파이프라인을 두 단계로 분리했습니다.
user_question
↓
[1단계] 관련 테이블 후보 추출 (LLM)
예) "지난달 크리에이터별 매칭 요청 수"
→ creator, matching_request, matching_request_log ...
↓
[2단계] Top-K 테이블의 DDL + 샘플값만 주입 (보통 5~7개)
↓
SQL 생성
4.2 도메인별 테이블 맵 구축
LLM이 매번 추론하지 않아도 되도록, 자주 쓰는 도메인은 정적 맵으로 고정했습니다:
가입/회원 → user, profile, signup_type, user_status
매칭 요청 → matching_request, matching_request_log, matching_request_attachment
지원서 → application, application_status, application_status_log
프로젝트 → project, project_status, project_log
결제 → payment, payment_status, payment_product
크리에이터 → creator, creator_status, creator_career
포트폴리오 → portfolio, portfolio_status
일별 집계 → daily_order, daily_package
로그/분석 → session_log, search_log, signout_log
⚠️ 주의: 점수화 튜닝을 잘못하면 필요한 테이블이 잘려나갑니다. application_status_log 같은 로그 테이블이 빠지면 최신 상태를 못 가져오는 케이스가 발생해서, 초기에는 보수적으로 Top-7~10으로 시작했습니다.
효과: 평균 토큰 사용량이 크게 줄었고, "엉뚱한 테이블을 쓴 SQL"이 발생하는 비율이 가장 크게 떨어진 전략이었습니다.
5. Few-shot — 우리 DB 규칙을 프롬프트에 고정
5.1 도입 배경
LLM은 우리 DB의 특수한 규칙을 모릅니다. payment_status의 완료 상태가 'paid'인지 'completed'인지, application의 최종 수락 상태가 무엇인지 — 이런 건 매번 틀렸습니다.
5.2 예시 주입 방식
프롬프트에 다음 형식으로 예시를 누적했습니다:
-- Q: 이번 달 결제 완료 건수
SELECT COUNT(*) FROM payment
WHERE payment_status_id = (SELECT id FROM payment_status WHERE name = 'paid')
AND created_at >= DATE_FORMAT(NOW(), '%Y-%m-01')
-- Q: 최종 수락된 지원서 수
SELECT COUNT(*) FROM application a
JOIN application_status_log asl ON a.id = asl.application_id
WHERE asl.status = 'accepted'
AND asl.id = (SELECT MAX(id) FROM application_status_log WHERE application_id = a.id)
5.3 관리 방식
- 처음에는 자주 틀리는 패턴 5~10개로 시작
- 새로운 오답 패턴이 나오면 뒤의 Feedback Loop와 연결해 누적
- 마크다운 파일과 DB 테이블 양쪽으로 버전 관리
효과: 도메인 규칙으로 인한 오답이 가장 빠르게 줄어든 영역입니다. 투입 시간 대비 효과가 가장 좋아서 우선순위 1번으로 두는 걸 추천합니다.
6. Feedback Loop — 틀린 쿼리를 자산으로 만들기
6.1 수집 구조
결과 메시지마다 👍 / 👎 피드백 UI를 붙이고, 👎인 경우 다음 메타데이터를 함께 수집했습니다:
{
"question": "지난달 완료된 프로젝트 수",
"generated_sql": "SELECT COUNT(*) FROM project WHERE status = 'done'",
"correct_sql": "SELECT COUNT(*) FROM project p JOIN project_status ps ON p.status_id = ps.id WHERE ps.name = 'completed' AND p.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)",
"error_type": "상태값 오류 + 테이블 관계 미파악",
"added_to_fewshot": true
}
6.2 순환 구조
SQL 생성 → 결과 반환
↓
사용자 피드백 수집 (👍 / 👎 + 코멘트)
↓
👎인 경우
├─ 원인 분류: 테이블 오선택 / 조건 누락 / 기간 오류 / 상태값 오류
├─ 올바른 SQL 작성
└─ Few-shot 예시 DB에 추가
↓
다음 프롬프트에 자동 반영
왜 이 구조가 중요한가? 틀린 쿼리는 그 자체로 "도메인 규칙 학습 데이터"입니다. 버리지 않고 Few-shot으로 누적하면, 같은 실수가 다시 나올 확률이 점점 줄어듭니다.
7. Dynamic Model Selection — 복잡도에 따라 모델 분기
7.1 분류 기준
| 난이도 | 조건 | 모델 | 예시 |
|---|---|---|---|
| 🟢 단순 | 테이블 1개, 단순 집계/필터 | Haiku | "오늘 가입자 수" |
| 🟡 중간 | 테이블 2~3개 조인, 기간 비교 | Sonnet | "iOS vs Android 가입자 비율" |
| 🔴 복잡 | 4개+ 조인, 서브쿼리, 전환율 계산 | Opus | "매칭 요청 후 계약 전환율 + 평균 소요일" |
7.2 구현 방식
LLM에게 난이도를 먼저 분류하게 한 뒤, 그 결과에 따라 본 SQL 생성 호출의 model 파라미터를 분기시켰습니다:
다음 질문의 SQL 난이도를 판단하라.
- simple: 테이블 1개, 단순 조건
- medium: 2~3개 테이블 조인 또는 기간 비교
- complex: 서브쿼리, 4개 이상 조인, 전환율/비율 계산 포함
질문: "{user_question}"
효과: 단순 질문 비중이 생각보다 높아서, 비용은 떨어지면서 응답 속도는 오히려 빨라지는 구간이 생겼습니다.
8. 품질 측정: BIRD-SQL 방법론 적용
8.1 내부 평가셋 구축
"잘 되는 것 같다"는 감으로 운영하면 언제 품질이 떨어졌는지 알 수 없습니다. BIRD-SQL이 쓰는 평가 방식을 그대로 가져와 내부 평가셋을 구축했습니다.
기존 데이터팀에 들어온 슬랙/이메일 요청과 정답 SQL 쌍을 모았습니다:
최소 100개 → 의미있는 신호
200~300개 → 신뢰할 수 있는 평가
각 항목에는 다음을 기록합니다:
{
"question": "지난달 신규 가입자 수",
"answer_sql": "SELECT COUNT(*) FROM user WHERE created_at >= ...",
"difficulty": "simple",
"domain": "user",
"evidence": "가입 완료 = user_status.name = 'active'"
}
8.2 측정 지표
| 지표 | 측정 방법 |
|---|---|
| Execution Accuracy (EX) | 생성 SQL 실행 결과 == 정답 SQL 실행 결과 비율 |
| 응답 지연 | SQL 실행 완료까지 걸린 시간 (ms) |
| 토큰 사용량 | 프롬프트 + 응답 토큰 합계 |
8.3 회귀 테스트 사이클
모델 교체 / 프롬프트 변경 / 스키마 변경
↓
평가셋 전체 돌리기
↓
EX 기준점 대비 하락? → 배포 블록
EX 유지 또는 향상? → 배포
↓
실사용에서 👎 나오면 평가셋에 추가
왜 이게 중요한가? 프롬프트나 모델을 바꿀 때마다 정성적으로만 비교하면 어떤 변경이 회귀를 일으켰는지 추적이 안 됩니다. 자동화된 회귀 테스트는 "안전하게 실험할 수 있는 환경"을 만들어줍니다.
9. 적용 우선순위 제안
한 번에 다 적용할 필요는 없습니다. 다음 순서를 추천합니다:
| 순서 | 작업 | 예상 효과 | 소요 시간 |
|---|---|---|---|
| 1 | Few-shot 예시 10개 추가 (자주 틀리는 패턴 중심) | 즉각적인 정확도 향상 | 반나절 |
| 2 | 도메인별 테이블 맵 작성 → Schema Pruning 적용 | 토큰 절감 + 오류 감소 | 1~2일 |
| 3 | 👍/👎 피드백 UI 추가 + 수집 로그 구축 | Feedback Loop 기반 마련 | 2~3일 |
| 4 | 난이도 분류 → Dynamic Model Selection | 비용 최적화 | 1일 |
| 5 | 내부 평가셋 100개 구축 → 회귀 테스트 파이프라인 | 품질 안정화 | 1주 |
왜 이 순서인가? Few-shot은 가장 적은 노력으로 가장 빠른 효과를 보고, Schema Pruning은 도메인 맵만 정리하면 바로 효과가 나옵니다. Feedback Loop와 회귀 테스트는 누적이 필요한 인프라성 작업이라 뒤로 미뤄도 됩니다.
10. 핵심 인사이트
10.1 LLM 정확도는 모델 크기보다 "컨텍스트 설계"가 좌우한다
같은 Sonnet이라도 170개 테이블을 전부 주입한 호출과, Schema Pruning으로 5개만 추린 호출의 정확도 차이가 컸습니다. 모델을 키우기 전에 컨텍스트부터 정제하는 게 비용 대비 효과가 압도적이었습니다.
10.2 "틀린 답"은 버리지 말고 자산화한다
Feedback Loop의 본질은 "도메인 지식이 점진적으로 시스템에 누적되는 구조를 만드는 것"입니다. 사람이 같은 실수를 두 번 안 하듯, 시스템도 같은 실수를 두 번 하지 않도록 설계해야 합니다.
10.3 정성 평가만으로는 회귀를 잡을 수 없다
BIRD-SQL 방법론을 가져온 가장 큰 이유는 "체감"이 아니라 "숫자"로 회귀를 판단할 수 있게 하기 위해서였습니다. 평가셋이 없으면 프롬프트를 바꿀 때마다 운에 맡기게 됩니다.
요약
- AWS Unicorn Day 키노트에서 BIRD-SQL 기반 Text2SQL 정확도 향상 전략을 접하고, 운영 중이던 Redash MCP에 5가지 전략을 단계적으로 적용했습니다.
- Multi-turn으로 모호한 질문은 되묻고, Schema Pruning으로 170개 테이블을 5~7개로 추리고, Few-shot으로 도메인 규칙을 고정시켜 정확도와 토큰 비용을 동시에 개선했습니다.
- Feedback Loop로 틀린 쿼리를 Few-shot 예시로 재활용해 같은 실수가 반복되지 않도록 했고, Dynamic Model Selection으로 단순 질문은 Haiku, 복잡한 분석은 Opus로 라우팅해 비용을 최적화했습니다.
- BIRD-SQL 평가 방법론을 내부에 이식해 Execution Accuracy 기준의 회귀 테스트 파이프라인을 구축했고, 프롬프트·모델 변경 시 정량적으로 품질 회귀를 감지할 수 있게 됐습니다.
- 핵심 교훈은 "모델 크기보다 컨텍스트 설계가 정확도를 더 크게 좌우한다"는 점과 "틀린 답은 버리지 말고 자산화해야 한다"는 점이었습니다.