2026년 2월 ~ 5월
본 프로젝트는 멘토링 프로그램의 일환으로 진행되는 기술 검증(PoC)이다. 실무 과제 해결이 목적이 아니라, NL2SQL 기술을 직접 구현하고 그 과정에서 마주치는 한계를 체계적으로 정의·해결하며 관련 지식을 학습하는 것이 핵심 목표이다.
주제 선정 배경: 데이터 분석 수요가 증가하고 있으나 비즈니스 부서는 SQL 이해도 부족으로 직접 쿼리 작성이 어려운 상황이다. 이를 해결하는 자연어 기반 분석 에이전트를 구축해보면서, LLM 기반 Text-to-SQL의 실제 한계(Schema Linking, 환각, 결과 검증 등)를 정량적으로 측정하고, AWS 네이티브 서비스(Bedrock, Redshift, KB)와 Agentic 아키텍처 패턴을 실험적으로 검증한다.
학습 목표:
| 항목 | 내용 |
|---|---|
| 프로그램 | AWS GenAI Mentoring — 주제: NL2SQL (Text-to-SQL Agent) |
| 기간 | 2026년 2월 ~ 5월 |
| 기술 스택 | AWS Bedrock (Claude Opus 4.7) + Redshift Serverless + EC2 (Node.js) + ALB + Route 53 |
| 초기 도메인 | 은행 수신상품 (적금/예금) — products, customers, accounts 3개 테이블 |
| 전환 도메인 | 이커머스 (주문/상품/고객/배송/결제/마케팅/집계/운영) — 42개 테이블 / 170K+ rows |
| 핵심 원칙 | 응답시간보다 정확도 우선 · 실험 결과는 정량 수치로 기록 |
| Direct SQL 모드 | Bedrock KB 모드 | |
|---|---|---|
| 스키마 조회 | 우리가 information_schema.columns로 전체 스키마를 Redshift에서 가져와 프롬프트에 넣음 | KB가 Glue/Redshift 메타데이터를 내부적으로 보유 |
| SQL 생성 | Claude가 프롬프트 보고 직접 생성 | KB의 GenerateQuery API가 생성 |
| 정확도 튜닝 | 프롬프트에 컬럼값 힌트 하드코딩 | Curated Queries + 테이블 Description |
| 에러 처리 | SQL 실행 실패 시 Claude가 수정 (최대 2회) | 동일 |
| 장점 | 직접 제어 가능, 디버깅 용이 | 프롬프트 관리 불필요, AWS 네이티브 |
| 단점 | 스키마/값 매핑을 수동 관리 | KB 설정에 의존, 복잡 쿼리 한계 |
PoC 계획서의 원칙 "AWS Native 서비스를 최대한 활용하여 커스텀 개발을 최소화"에 따라, 스키마 분석/SQL 생성/메타데이터 관리를 KB에 위임하는 구조를 채택. Direct SQL 모드는 비교 테스트 및 fallback 용도로 병행.
| 모드 | 문제 |
|---|---|
| Direct SQL | 전체 스키마가 프롬프트에 다 들어가서 LLM이 유사한 테이블 중 엉뚱한 것을 선택. 테이블 20개 이상이면 컨텍스트 윈도우 압박. |
| Bedrock KB | Curated Queries에 등록되지 않은 새로운 유형의 질문에서 KB도 잘못된 테이블을 선택하거나 SQL 생성 자체를 실패. |
현재 에러 수정 루프는 SQL 실행이 실패한 경우만 잡는다. "실행은 됐지만 결과가 틀린 경우" (예: cif_status = 'ACTIVE'로 잘못 매핑하여 0건 반환)는 감지하지 못한다.
LLM은 스키마 구조는 인식하지만 실제 저장된 데이터 값을 알지 못하는 구조적 한계가 존재. 동일 질문에도 매번 다른 WHERE 조건이 생성될 수 있음. (NL2SQL 오류 유형 중 컬럼 예측 오류가 32.3%로 가장 높은 비중)
각 테이블의 메타데이터(이름, 설명, 컬럼 설명, 관련 키워드)를 벡터 임베딩으로 저장해두고, 사용자 질문과 유사도 검색으로 관련 테이블만 뽑아내는 구조.
SQL 실행 결과를 LLM이 한 번 더 검증하는 단계 추가. "이 결과가 질문의 의도에 맞는지", "결과 범위가 합리적인지" 체크.
Phase 1에서 발견한 문제를 해결하기 위해 Phase 2 실험에 앞서 확정한 4가지 결정 사항.
Phase 1에서는 Direct SQL 모드와 Bedrock KB 모드 2가지를 병행 운영했으나, Phase 2 실험을 위해 KB 모드를 완전히 제거하기로 결정.
KB 방식 vs Direct SQL + Schema Linking 방식 비교:
| 항목 | Bedrock KB | Direct SQL + Schema Linking (채택) |
|---|---|---|
| SQL 생성 주체 | KB 내부 (AWS 블랙박스) | 우리가 직접 Claude에 프롬프트 작성 |
| Schema Linking | KB 내부에서 자동 처리 (과정 불투명) | RAG 임베딩 검색으로 직접 구현 |
| 튜닝 방법 | Table Descriptions + Curated Queries (콘솔 수동) | 프롬프트 엔지니어링 + 임베딩 메타데이터 (코드 관리) |
| 디버깅 | 어떤 단계에서 실패했는지 추적 불가 | 각 단계 로그 가능 |
| 기여도 분석 | "KB가 알아서 해줌" (어디서 개선됐는지 불명) | 단계별 분리 측정 가능 |
| 재현성 | KB 콘솔 설정에 의존 | 코드와 데이터로 완전 재현 가능 |
Phase 2는 Direct SQL 모드 단일 경로로 단순화. Phase 1의 KB 관련 코드, UI 토글, Curated Queries 설정은 모두 제거.
Phase 1에서는 Claude Sonnet 4를 사용했으나, Phase 2 실험의 정밀도 향상과 최신 모델의 개선 효과 확인을 위해 Claude Opus 4.7로 교체.
| 항목 | 이전 (Sonnet 4) | 이후 (Opus 4.7) |
|---|---|---|
| Model ID | apac.anthropic.claude-sonnet-4-20250514-v1:0 | global.anthropic.claude-opus-4-7 |
| 추론 능력 | 일반 작업 적합 | 복잡 추론/코드 생성에 강점 |
| 속도/비용 | 빠르고 저렴 | 느리지만 정확도 높음 |
Schema Linking/Reflection Agent 효과를 측정할 때 모델 변화가 변수를 오염시키면 안 되므로, Baseline도 Opus 4.7로 재측정하여 동일 모델 기준으로 전후 비교.
Spider 2.0 벤치마크의 ground-truth 구조를 참고하되, 데이터는 이커머스 도메인에 맞게 직접 설계. 정답 SQL은 사전에 작성하고 Redshift에서 실행 검증.
| 지표 | 정의 |
|---|---|
| Table Selection Accuracy | LLM이 올바른 테이블을 선택한 비율 (기대 테이블 집합이 생성 SQL에 포함되는지) |
| SQL Validity Rate | 생성된 SQL이 문법 오류 없이 실행 가능한 비율 |
| Execution Accuracy (EX) | 생성된 SQL의 실행 결과가 정답 SQL 실행 결과와 일치하는 비율 (핵심 지표) |
| Retry Recovery Rate | 1차 SQL 실패 후 자동 수정 루프로 성공한 비율 |
| Step | 작업 | 상태 | 상세 |
|---|---|---|---|
| 1 | 데이터셋 설계 | 완료 | 이커머스 DW 구조 42개 테이블 설계 (8개 도메인). 합성 데이터 170,237건 생성. S3 업로드 + Redshift COPY 적재 완료. |
| 2 | 테스트 세트 작성 | 완료 | 질문 30개 + 정답 SQL + 정답 결과 세트 작성 (EASY 10, MEDIUM 10, HARD 10). 30개 정답 SQL 모두 Redshift에서 정상 실행 확인. |
| 3 | 앱 코드 전환 | 완료 | server.js에서 banking 스키마 하드코딩 제거, Opus 4.7 채택, KB 로직 삭제, UI 탭 추가(Agent/보고서/데이터셋), ALB + HTTPS 도메인 연결. |
| 4 | Baseline 측정 | 완료 | 30개 질문을 Claude Opus 4.7 Direct SQL 모드(전체 스키마 311개 컬럼 프롬프트 주입)로 실행. 전체 Execution Accuracy 33.3% (EASY 60%, MEDIUM 20%, HARD 20%). Table Selection 100%, SQL Validity 100%. |
| 5 | Reflection Agent 구현 + 측정 | 진행중 | SQL 실행 결과를 LLM이 검증하는 단계 추가. 의심스러우면 SQL 재생성 후 재실행. 같은 30개 질문으로 정확도 비교. |
| 6 | Schema Linking 구현 + 측정 | 예정 | 테이블 메타데이터를 Bedrock Titan Embedding으로 벡터화 → 유사도 검색으로 관련 테이블 3~5개만 필터링 → LLM에 전달. 같은 30개 질문으로 정확도 비교. |
| 7 | 보고서 | 예정 | Step 4~6 전후 비교 수치 정리. 각 기법의 기여도 분석. 한계점 및 향후 개선 방향. |
스키마 구성 (42개 테이블 / 8개 도메인):
| 도메인 | 테이블 수 | 주요 테이블 |
|---|---|---|
| 고객 | 6 | customers, customer_addresses, customer_segments, customer_membership, customer_preferences, customer_activity_log |
| 상품 | 8 | product_categories, product_brands, products, product_variants, product_inventory, product_reviews, product_images, product_tags |
| 주문 | 6 | cart_items, orders, order_items, order_status_history, order_returns, order_cancellations |
| 결제 | 4 | payment_methods, payments, refunds, installments |
| 배송 | 4 | shipping_carriers, shipping_zones, shipments, delivery_tracking |
| 마케팅 | 5 | campaigns, coupons, coupon_usage, promotions, recommendations |
| 집계 (DW Mart) | 5 | daily_sales_summary, monthly_sales_summary, category_sales_summary, customer_ltv_summary, product_performance_summary |
| 운영 | 4 | warehouses, sellers, seller_payouts, customer_service_tickets |
Schema Linking을 의도적으로 어렵게 만드는 장치:
status가 여러 테이블에 있지만 각각 다른 값 체계 (주문/결제/배송/상품 상태)product_inventory.stock_quantity (재고) vs order_items.quantity (판매수량)데이터 규모 (총 170,237건):
| 테이블 그룹 | 건수 |
|---|---|
| customers / customer_* | ~6,700 |
| products / product_* | ~10,800 |
| orders / order_items / order_* | ~38,500 |
| payments / refunds / installments | ~8,900 |
| shipments / delivery_tracking | ~26,600 |
| customer_activity_log | 50,000 |
| recommendations | 20,000 |
| 집계 테이블 5개 | ~6,300 |
| 기타 (마케팅/운영) | ~2,400 |
인프라 변경 사항:
nl2sql-poc-data-278248527347nl2sql-redshift-role (S3 읽기 권한)nl2sql.jungjunh.people.aws.dev HTTPS 도메인 구성테스트 세트 (test_set.json, 30문항):
| 난이도 | 개수 | 특징 |
|---|---|---|
| EASY | 10 | 1-2 테이블 단순 조회 / 집계. 예: "전체 고객 수", "DIAMOND 등급 회원 수", "월별 매출 추이" |
| MEDIUM | 10 | 2-3 테이블 JOIN + 집계. 예: "브랜드별 상품 개수와 평균가", "결제수단별 총 결제액", "VIP 세그먼트 총 구매 금액" |
| HARD | 10 | CTE, 윈도우 함수, 4+ 테이블 JOIN, 서브쿼리. 예: "매출 증감률 TOP 3 카테고리", "카테고리별 재구매율", "창고 지역별 배송 완료율" |
각 질문마다 question, expected_tables (정답 테이블 목록), ground_truth_sql (정답 SQL)을 명시. 30개 정답 SQL 모두 Redshift에서 정상 실행 확인.
앱 코드 전환 (server.js + index.html):
SCHEMA = 'ecommerce' 상수 도입, 모든 스키마 참조를 이 상수로 통일buildSQLPrompt에 이커머스 도메인 규칙 주입 (주문/결제/배송/상품 상태값, 유사 테이블 구분 가이드)난이도별 지표:
| 난이도 | Table Selection | SQL Validity | Execution Accuracy |
|---|---|---|---|
| EASY (n=10) | 100.0% | 100.0% | 60.0% |
| MEDIUM (n=10) | 100.0% | 100.0% | 20.0% |
| HARD (n=10) | 100.0% | 100.0% | 20.0% |
| ALL (n=30) | 100.0% | 100.0% | 33.3% |
Sonnet 4 (Phase 1) vs Opus 4.7 (Phase 2) 비교:
| 지표 | Sonnet 4 | Opus 4.7 | 차이 |
|---|---|---|---|
| Table Selection | 86.7% | 100.0% | +13.3%p |
| SQL Validity | 93.3% | 100.0% | +6.7%p |
| Execution Accuracy | 26.7% | 33.3% | +6.7%p |
실패 유형 분석 (Opus 4.7 기준, 20건):
| 유형 | 건수 | 의미 |
|---|---|---|
| 테이블 오선택 | 0건 | Opus 4.7은 42개 테이블 중에서 올바른 테이블을 전부 찾아냄 |
| SQL 실행 실패 | 0건 | 문법/실행 오류 전혀 없음. 모든 SQL이 Redshift에서 정상 실행 |
| 결과 불일치 (SQL은 정상) | 20건 | 모든 실패가 이 유형. 컬럼 이름 차이, ORDER BY 절 누락, 과도한 JOIN, ROUND 처리 차이 등 |
Opus 4.7이 자주 보이는 실패 패턴 예시:
coupons.used_count 기반, Opus는 coupons JOIN coupon_usage로 실제 사용 건수 집계 (의미는 같지만 결과 포맷 다름)total_orders, net_sales, Opus는 추가 컬럼까지 반환GenAI Mentoring — NL2SQL 프로젝트 · AWS Bedrock + Redshift Serverless · 2026