데이터베이스 에이전트에게 SQL 정규식이 아닌 데이터베이스 수준의 읽기 전용 권한 부여하기
Giving a database agent read-only that the database enforces, not a regex over the SQL
핵심 요약
로컬 LLM을 활용한 NL2SQL 에이전트 개발 시, 데이터베이스 수준의 읽기 전용 권한 설정과 스키마 선택의 중요성을 공유함.
- 스키마 선택 — 컨텍스트 윈도우 제한으로 인해 관련 없는 테이블을 제외하는 것이 모델 정확도의 핵심임.
- 의미적 오류 — 작은 모델은 문법적으로는 완벽하지만 의미적으로 틀린 SQL을 생성할 위험이 있음.
- 읽기 전용 권한 — 정규식 대신 데이터베이스 네이티브 권한과 최소 권한 원칙을 적용해야 함.
- 로컬 모델의 이점 — 데이터 유출 없이 로컬 환경에서 NL2SQL을 실행할 수 있는 것이 가장 큰 장점임.
우리는 오픈소스(MIT)로 자가 호스팅 가능한 데이터베이스 클라이언트를 만들고 있는데, 여기에 호스팅된 API 대신 Ollama 같은 로컬 런타임을 가리키는 자연어-SQL(NL2SQL) 어시스턴트를 추가했어. 대부분의 NL2SQL 데모는 거대한 호스팅 모델을 쓰면서 실패하는 케이스는 슬쩍 감추거든. 그래서 우리가 실제 데이터베이스에 로컬 모델을 돌려보면서 배운 점들을 공유하려고 해. 이건 홍보글이 아니라 그냥 기록이니까, 우리 설정이 틀린 부분이 있다면 알려줘.
로컬 모델에서 문제가 터지는 지점들:
-
스키마 선택이 정확도를 결정함. 400개나 되는 테이블 스키마를 짧은 컨텍스트 윈도우에 다 때려 넣을 순 없잖아. 컨텍스트가 긴 호스팅 모델에선 관련 테이블을 골라내는 게 그냥 '있으면 좋은 기능' 정도지만, GPU에 맞추려고 4k~8k 컨텍스트로 돌리는 로컬 7B나 8B 모델에선 이게 전부야. 관련 없는 테이블까지 다 집어넣으면 모델이 엉뚱한 테이블을 잡고 삽질하다가 틀린 SQL을 뱉어내거든. 우리가 한 작업의 대부분은 프롬프팅이 아니라, 뭘 안 보낼지 결정하는 거였어.
-
겉보기엔 멀쩡한데 틀린 SQL. 작은 모델들은 문법적으로는 완벽한데 의미적으로는 틀린 SQL을 만들어내. 모양은 그럴싸한데 테이블이나 컬럼이 틀린 거지. 에러 없이 잘 돌아가고 결과값까지 나오니까 "실행되냐?"만 체크하는 방식으로는 걸러낼 수가 없어. 그래서 우리는 생성 단계와 실행 단계 사이에 무조건 사람을 끼워 넣는데, 모델이 작을수록 이 과정이 왜 필요한지 뼈저리게 느끼게 돼.
-
읽기 전용(Read-only)은 SQL 정규식 따위로 막을 게 아니라 데이터베이스 차원에서 강제해야 함. 우리는 Postgres에선 read-only 트랜잭션, SQLite에선 PRAGMA query_only 같은 걸로 막아버렸어. 근데 좀 놀랐던 건, 읽기 전용 트랜잭션이라고 해도 서버 측 파일 접근(COPY TO나 로컬 파일 함수 같은 거)까지는 못 막더라고. 그래서 어시스턴트한테는 데이터베이스 소유자 권한이 아니라, 최소 권한만 가진 별도의 역할을 부여해야 해.
로컬 엔드포인트의 장점은 뻔하지. 스키마, 샘플 데이터, 질문 내용이 기기 밖으로 절대 안 나간다는 거. 데이터 보안 때문에 외부 반출이 안 되는 환경이라면, 어시스턴트를 쓸 수 있느냐 없느냐를 가르는 결정적인 차이가 되지.
이 서브레딧에 묻고 싶은 게 있어. LLM을 실제 데이터베이스 앞에 붙여본 사람들, 다들 읽기 전용 설정은 어디서 어떻게 강제했어? 그리고 문법은 맞는데 의미가 틀린 쿼리가 그걸 믿는 사람한테 전달되지 않게 하려면 어떻게 해야 할까? 우리는 데이터베이스 자체의 읽기 전용 기능에 의존하고 실행 전에 사람이 확인하는 방식으로 결론 내렸는데, 다른 사람들은 어디까지 안전장치를 걸어두는지 궁금해.
