AI 생성 SQL에는 '조용한 실패(Silent Failure)' 문제가 있습니다. 이를 잡아내는 방법을 소개합니다.
요약
LLM 기반 Text-to-SQL 생성 시 발생하는 '조용한 실패(Silent Failure)' 문제를 다룹니다. 구문 오류 없이 잘못된 결과값을 반환하는 위험성을 경고하며, 이를 검증하기 위한 차등 테스트(Differential Testing) 방식의 도구 difftest-sql을 소개합니다.
핵심 포인트
- Text-to-SQL의 조용한 실패는 잘못된 데이터로 의사결정을 유도함
- 팬아웃(Fan-out)이나 잘못된 필터링 등 논리적 오류가 주요 원인
- 차등 테스트를 통해 구조적으로 다른 두 쿼리의 결과를 비교 검증
- difftest-sql 도구로 쿼리 결과의 일치 여부를 판정하여 신뢰성 확보
데이터 웨어하우스(Data Warehouse) 근처에서 시간을 보내본 적이 있다면, 가장 무서운 버그는 에러를 던지는 버그가 아니라는 것을 이미 알고 있을 것입니다. 그것은 아무도 의문을 제기하지 않은 채, 그 수치를 바탕으로 이미 의사결정이 내려질 때까지 깨끗하고 자신감 넘치는, 하지만 '틀린' 숫자를 반환하는 버그입니다.
우리는 이제 대규모로 LLM(Large Language Models)을 사용하여 SQL을 생성하고 있으며, 바로 그 실패 모드(Failure Mode)에 훨씬 더 넓은 공격 표면(Surface Area)을 제공했습니다. 저는 이것이 이론적인 문제가 아니라 왜 실제적인 문제인지 살펴보고, 이 문제의 특정 부분을 잡아내기 위해 제가 만든 작은 도구를 공유하고자 합니다.
실패처럼 보이지 않는 실패
Text-to-SQL의 핵심 가치는 훌륭합니다. 비즈니스 사용자가 일상적인 영어로 질문을 하면, 모델이 쿼리(Query)를 작성하고, 데이터 팀을 기다릴 필요 없이 답변을 얻는 것입니다. 그리고 많은 경우, 이것은 실제로 잘 작동합니다.
문제는 꼬리 부분(Tail)에 있습니다. 때때로 생성된 쿼리는 구문 오류(Syntax Error)도, 예외(Exception)도, 경고(Warning)도 없이 완벽하게 실행되지만, 결과는 단순히 틀린 경우가 있습니다. 조인(Join)이 팬 아웃(Fan out)되어 매출을 중복 계산합니다. 필터(Filter)가 미묘하게 잘못되어 행(Row)의 3분의 1을 조용히 누락시킵니다. GROUP BY가 잘못된 입도(Grain)로 설정됩니다. 쿼리는 실행되고, 대시보드는 채워지며, 숫자는 합리적으로 보이고, 그대로 배포됩니다.
Gartner의 자주 인용되는 추정치에 따르면, 평균적인 기업이 낮은 데이터 품질로 인해 입는 비용은 연간 약 1,290만 달러에 달합니다. 그리고 구체적인 공개 사례들도 존재합니다. 2022년, Unity Software는 대형 고객으로부터 잘못된 데이터를 수집하여 모델의 출력을 오염시켰고, 이로 인해 1억 1,000만 달러 이상의 매출 손실과 훨씬 더 큰 시가총액 타격을 입었다고 밝혔습니다.
이 사례들이 구체적으로 Text-to-SQL에 관한 이야기는 아닙니다. 하지만 이들은 동일한 실패 유형에 속합니다. 즉, 잘못된 데이터가 시스템을 통해 흐르며 잘못된 출력을 생성하고, 아무도 알아차리기 전에 문제없이 실행되어 의사결정에 도달하는 것입니다. LLM(대규모 언어 모델)이 생성한 SQL은 이 문제를 새로 만드는 것이 아니라, 더 많은 쿼리를 더 빠르게, 더 표면적인 확신을 가지고 인간의 검토는 줄이면서 생성함으로써 이 문제를 가속화합니다.
접근 방식: SQL을 위한 차등 테스트 (Differential Testing)
제가 만든 도구인 difftest-sql은 컴파일러 및 데이터베이스 테스트의 오래된 아이디어인 **차등 테스트 (Differential Testing)**를 차용했습니다. 정답이 맞다는 것을 쉽게 증명할 수 없다면, 두 번째의 독립적인 정답을 생성하여 두 결과가 일치하는지 확인하는 방식입니다.
구체적으로는 다음과 같습니다: 주어진 질문에 대해, 기본 쿼리(Primary Query)와 다른 방식으로 동일한 결과를 계산해야 하는 구조적으로 다른 (Structurally Different) 대조 쿼리(Control Query)를 생성합니다. 그 후 두 쿼리를 모두 실행하고 결과를 비교하여 일치(Agreement), 불일치(Disagreement), 또는 "독립적으로 검증 불가(Not independently verifiable)" 중 하나의 판정을 반환합니다.
이 사고 모델은 쿼리에 적용된 복식부기 (Double-entry Bookkeeping)와 같습니다. 하나의 도출 과정을 신뢰하는 것이 아니라, 두 가지 독립적인 방식으로 정답을 도출하고 이를 대조하는 것입니다.
다음은 데모에서 팬아웃 (Fan-out) 버그를 잡아내는 모습입니다:
$ difftest "What is the total revenue?" --db demo.duckdb --offline
total_revenue
...
기본 쿼리는 주문(Orders) 테이블을 품목(Line Items) 테이블과 조인(Join)하여 합산했는데, 이 조인 과정에서 총액이 850으로 부풀려졌습니다. 대조 쿼리는 올바른 입도 (Grain)에서 동일한 수치를 계산하여 350을 얻었습니다. 불일치 (Disagreement)가 자동으로 플래그 처리되었으며, 수동적인 SQL 검토 없이도 특정 셀과 그 차이(Gap)를 명시해 주었습니다.
궁금해하실 분들을 위한 작동 원리
몇 가지 설계 결정이 여기서 핵심적인 역할을 했습니다.
궁금해하실 분들을 위한 작동 원리
몇 가지 설계 결정이 여기서 핵심적인 역할을 했습니다.
상관관계 제거(Decorrelation)는 구조적이어야 하며, 외형적일 수는 없습니다. 단순히 주 쿼리를 재작성한 제어 쿼리는 아무것도 얻을 수 없으며, 같은 실수를 저지를 것입니다. 따라서 제어 쿼리는 진정으로 다른 실행 경로(다른 조인 전략, 서브쿼리, 다른 집계 단위)에 강제됩니다. 핵심은 두 독립적인 파생 과정이 동일한 기계적 오류를 범할 확률이 낮다는 것이므로, 실제 버그는 불일치로 나타난다는 것입니다.
독립성(Independence)은 SQL 텍스트가 아닌 쿼리 플랜에서 확인됩니다. 이것은 제가 설계 검토(design review)에서 지적하고 싶은 부분입니다. 두 쿼리는 매우 다르게 읽힐 수 있지만 여전히 동일한 플랜으로 최적화될 수 있으며, 이 경우 항상 일치하여 아무것도 증명하지 못합니다. 따라서 해당 도구는 EXPLAIN을 실행하고 **최적화된 논리 플랜(optimized logical plans)**을 비교하며, 외형적인 노이즈(카디널리티 추정치, 출력 열 별칭)를 정상화한 후에 두 쿼리가 실제로 독립적인지 판단합니다. 같은 플랜이면 거부됩니다.
비교는 ==가 아니라 계약(contract)입니다. 결과 집합은 위치별로 열이 정렬되고(유형-카테고리 검사를 통해 보호됨), 양쪽 모두 표준 순서로 정렬되어 행 순서가 잘못된 불일치를 유발할 수 없게 하며, 값들은 허용 오차를 거쳐 비교됩니다: 두 값의 평균에 대한 0.01% 상대 오차 범위 내에서, 절대적인 하한선은 0 근처입니다. NULL은 NULL과 같고; NULL 대 실제 값은 불일치로 간주됩니다.
판결은 정직하게 유지됩니다. 세 가지 결과만 있으며, 조작된 신뢰 점수는 없습니다. 비교할 진정으로 구별되는 쿼리 형태가 없다면, 가장하는 대신
과장하기보다는 경계를 명확히 하는 쪽을 택하겠습니다.
이 방식은 불일치(disagreement)를 감지하는 것이지, 정답(correctness)을 증명하는 것이 아닙니다.
이 도구는 기계적인 오류(mechanical errors), 팬아웃(fan-outs), 중복 계산(double-counting), 잘못된 입도(wrong grain), 그리고 두 개의 독립적인 쿼리가 서로 갈라지는 경우를 안정적으로 잡아냅니다. 하지만 모델이 두 쿼리 모두에서 의도를 동일하게 오해하는 의미론적 오류(semantic errors)는 잡아내지 못합니다. 만약 "매출(revenue)"에서 환불 금액을 제외해야 하는데 모델이 두 파생 과정 모두에서 이를 일관되게 누락한다면, 두 쿼리는 서로 일치하게 될 것이고 도구는 일치한다고 보고할 것입니다. 도구의 관점에서는 두 쿼리가 실제로 일치하기 때문입니다.
일치(Agreement)는 신뢰 신호(confidence signal)이지 보증(guarantee)이 아닙니다. 저는 이 구분을 중심으로 전체 시스템을 설계했습니다. 검증하는 범위를 과장하는 검증 도구는 차라리 없는 것보다 못하기 때문입니다.
현재 상태
이것은 초기 단계인 v0.1 프로젝트이며, 제가 실제 운영 환경(production)에서 실행해 본 것은 아닙니다. API 키 없이 완전히 오프라인으로 작동하며(복제(clone) 후 단일 명령어로 시드된 버그를 잡아낼 수 있습니다), 전체 테스트 스위트(test suite)가 갖춰져 있으며, 모든 설계 결정 사항이 문서화되어 있습니다.
제가 이 프로젝트를 만든 이유는 이것이 실재하며, 비용이 많이 들고, 이상할 정도로 논의가 부족한 문제이기 때문입니다. 저는 단순히 "SQL을 검토하세요"라는 식의 권고 대신, 이 문제를 잡아내기 위한 구체적이고 원칙적인 시도를 하고 싶었습니다.
핵심 아이디어는 유효하다고 생각합니다. 만약 여러분이 Text-to-SQL 또는 데이터 신뢰성(data reliability) 분야에서 일하며 이 방식의 허점이나 나아가야 할 방향을 발견한다면, 진심으로 의견을 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기