본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 05. 00:05

LLM 에이전트가 여전히 NoSQL 데이터베이스를 쿼리하지 못하는 이유

요약

LLM 에이전트가 SQL과 달리 NoSQL 데이터베이스 쿼리에 어려움을 겪는 기술적 이유를 분석합니다. SQL의 명확성과 풍부한 학습 데이터에 비해, NoSQL은 데이터베이스마다 상이한 쿼리 모델과 데이터 구조를 가지고 있어 에이전트가 학습하기 어렵다는 점을 지적합니다.

핵심 포인트

  • SQL은 명확한 구문과 방대한 학습 데이터 덕분에 LLM이 매우 잘 작성함
  • NoSQL은 DB별로 고유한 쿼리 모델과 접근 패턴을 가져 인터페이스가 파편화됨
  • DynamoDB와 같은 NoSQL은 파티션 키 등 특수한 데이터 모델 이해가 필수적임
  • NoSQL 생태계는 SQL에 비해 LLM을 위한 학습 데이터와 도구가 부족함

SQL 데이터베이스의 경우, LLM은 SQL을 작성하는 데 매우 뛰어납니다. SQL은 정밀하고, 표현력이 풍부하며, 모호하지 않습니다. LLM은 이를 잘 작성합니다. Postgres에 MCP 서버를 연결하면 에이전트가 직접적이고 효율적으로 쿼리를 작성할 수 있습니다. 에이전트가 NoSQL 데이터베이스를 다루는 것은 훨씬 더 어렵습니다. 그리고 얼마나 많은 운영 데이터가 그 안에 저장되어 있는지를 고려할 때, 이에 대한 논의가 더 많지 않다는 점이 놀랍습니다.

SQL이 LLM에 매우 잘 작동하는 이유

SQL이 언어 모델(Language Models)에게 자연스러운 인터페이스인 핵심 이유는 명확성(Specificity)입니다. SQL 쿼리는 당신이 원하는 데이터가 정확히 무엇인지, 어떤 형태인지, 그리고 어떤 조건인지 정확하게 말해줍니다. 모델이 헤쳐 나가야 할 모호함이 없습니다.

또한 SQL은 LLM 학습 데이터에서 가장 잘 표현된 언어 중 하나입니다. 수십 년간의 문서, Stack Overflow 답변, 교과서, 그리고 오픈 소스 코드는 모델이 학습할 수 있는 거대한 코퍼스(Corpus)를 생성했습니다. 구문(Syntax)은 일관적입니다. 의미론(Semantics)은 잘 정의되어 있습니다. LLM이 SQL을 작성할 때, 그것은 깊고 신뢰할 수 있는 토대를 활용하는 것입니다.

또한 도구 호출(Tool calls)에 깔끔하게 매핑됩니다. 에이전트는 쿼리 문자열과 함께 run_sql을 호출하고 행(Rows)을 돌려받습니다. 인터페이스는 단순하며, 입력과 출력은 타입이 지정되어 있고, 오류를 포착할 수 있습니다. 이는 에이전트와 데이터 계층(Data layer) 사이의 잘 정의된 계약입니다.

NoSQL 문제

NoSQL은 마찬가지가 아니며, 근본적인 원인은 단순히 구문(Syntax) 때문만이 아닙니다. 공유된 인터페이스가 없다는 점이 문제입니다. 각 주요 NoSQL 데이터베이스는 고유한 쿼리 모델, 고유한 액세스 패턴, 그리고 고유한 제약 조건을 가지고 있습니다. LLM이 이들 중 어느 하나라도 효과적으로 쿼리하려면, 구문뿐만 아니라 근본적인 데이터 모델(Data model)을 이해해야 합니다.

MongoDB는 JSON 필터와 집계 파이프라인 (Aggregation pipelines)을 중심으로 구축된 문서 쿼리 구문을 사용합니다. DynamoDB는 파티션 키 (Partition keys)와 인덱스 액세스 패턴 (Index access patterns)을 중심으로 구축된 API 스타일의 표현 언어를 사용합니다. Cassandra는 CQL을 사용하는데, 이는 SQL과 유사해 보이지만 테이블이 파티션되는 방식에 따라 허용되는 쿼리에 엄격한 제한이 있습니다. Redis는 쿼리보다는 명령 (Commands)을 노출합니다. Neo4j는 고유한 패턴 매칭 구문을 가진 그래프 쿼리 언어인 Cypher를 사용합니다.

이것들은 동일한 문제의 변형이 아닙니다. 각각은 에이전트가 데이터가 저장되고 검색되는 방식에 대해 서로 다른 멘탈 모델 (Mental model)을 내재화할 것을 요구합니다. 수십 년간 축적된 SQL 문서와 대등할 만한 학습 데이터가 존재하지 않습니다. 또한 개발자들의 관심이 이들 모두에 분산되어 있기 때문에, 각 데이터베이스를 둘러싼 도구와 생태계는 SQL 데이터베이스가 누리는 것보다 취약합니다.

구체적인 DynamoDB 예시

특히 DynamoDB를 예로 들어보겠습니다. DynamoDB는 효율적인 쿼리를 위해 파티션 키 (Partition key)를 필요로 합니다. 파티션 키가 없으면 전체 테이블 스캔 (Full table scan)을 수행하게 되는데, 이는 테이블의 모든 항목을 읽은 다음 필터를 적용하는 방식입니다. 또한 JOIN, 네이티브 집계 (Native aggregates), 또는 GROUP BY를 지원하지 않습니다.

에이전트가 다음과 같은 질문에 답해야 한다고 가정해 봅시다: "이번 주에 지역별로 주문이 몇 건 발생했는지 찾아줘."

SQL에서는 다음과 같이 간단합니다:

SELECT region, COUNT(*) AS order_count
FROM orders
WHERE created_at >= CURRENT_DATE() - 7
...

DynamoDB에서 에이전트는 해당 쿼리를 작성할 수 없습니다. 대신 코드를 작성해야 합니다:

const results = {};
let lastKey;

...

이는 더 많은 코드를 필요로 하고, 검토 (Audit)하기 더 어려우며, 에이전트가 DynamoDB의 페이지네이션 모델 (Pagination model)을 이해해야 함을 의미합니다. 또한 인덱스를 타는 대신 전체 테이블 스캔을 실행하게 되는데, 이는 대규모 테이블에서 비용이 많이 듭니다. 게다가 이것은 단순한 질문입니다. 두 테이블 간의 JOIN이 포함되는 모든 작업은 훨씬 더 복잡해집니다.

LLM은 이미 매우 많은 것을 할 수 있습니다. DynamoDB처럼 널리 사용되는 데이터베이스를 상대로 이토록 간단한 작업조차 여전히 신뢰성 있게 처리하지 못한다는 점은 매우 답답한 일입니다.

팀들이 이에 대처하는 방식

전용 솔루션을 찾기 전에, DynamoDB를 사용하는 대부분의 팀은 일련의 임시방편(workarounds)을 거치게 됩니다.

첫 번째 옵션은 AWS 자체의 DynamoDB용 SQL 유사 구문인 PartiQL입니다. 이는 SDK와 콘솔에 내장되어 있어 별도의 설정이 필요하지 않습니다. 에이전트는 추가 인프라 없이 DynamoDB에 직접 쿼리를 작성할 수 있습니다. 한계점은 PartiQL이 쿼리 엔진이 아닌 구문 계층(syntax layer)이라는 점입니다. 이는 SQL 스타일의 문장을 DynamoDB의 네이티브 API 호출로 변환하며, 이는 DynamoDB의 모든 쿼리 제약 사항을 그대로 물려받음을 의미합니다. 에이전트는 효율적인 쿼리를 위해 여전히 파티션 키(partition key)를 제공해야 합니다. JOIN, GROUP BY, 그리고 네이티브 집계 함수(aggregates)가 없습니다. 구문은 SQL처럼 보이지만, 액세스 모델(access model)은 변하지 않았습니다.

두 번째는 S3로 내보낸 후 Athena로 쿼리하는 방식입니다. 엔지니어들은 DynamoDB 데이터를 S3로 내보내는 파이프라인을 구축하고, 내보낸 파일을 쿼리할 수 있도록 Athena를 구성합니다. 일단 이러한 인프라가 갖춰지면, 에이전트는 Athena를 대상으로 직접 SQL을 작성할 수 있습니다. 트레이드오프(tradeoff)는 데이터의 최신성(freshness)입니다. 내보내기가 실시간으로 이루어지지 않기 때문에, 에이전트의 결과는 마지막 내보내기 시점만큼만 최신 상태를 유지합니다. 또한 엔지니어들은 파이프라인을 유지 관리하고 장애 여부를 모니터링해야 합니다.

세 번째는 보조 저장소로 스트리밍하는 방식입니다. 엔지니어들은 DynamoDB의 변경 사항을 Redshift, OpenSearch 또는 관계형 데이터베이스(relational database)로 전달하고, 에이전트는 복제본(replica)을 쿼리합니다. 인프라가 구축되면 에이전트는 적절한 쿼리 인터페이스를 갖게 됩니다. 하지만 이는 원본이 아닌 데이터의 복사본을 쿼리하는 것입니다. 엔지니어들은 파이프라인을 유지 관리해야 하며, 파이프라인에 지연이 발생하거나 실패할 경우 에이전트의 결과는 잘못된 값을 내놓게 되지만 무언가 잘못되었다는 명확한 신호는 나타나지 않습니다.

이 방법들 중 어느 것도 불합리한 선택은 아닙니다. PartiQL은 마찰이 가장 적은 시작점이지만 금방 한계에 부딪힙니다. 인프라 기반의 접근 방식은 에이전트에게 적절한 쿼리 인터페이스를 제공하지만, 엔지니어링 오버헤드와 결코 완전히 최신일 수 없는 데이터라는 비용을 치러야 합니다.

우리가 만든 것

우리는 DynamoDB를 대상으로 MCP 서버를 구축하면서 정확히 이 문제에 직면했습니다. 에이전트는 데이터에 대해 추론은 잘 수행할 수 있었지만, 데이터를 실제로 추출하려면 파티션 키 (partition key) 레이아웃, GSI 구조, 그리고 액세스 패턴 (access patterns)에 대한 지식이 필요했는데, 에이전트가 이러한 지식을 사전에 갖추고 있다고 기대하는 것은 현실적이지 않았습니다.

그래서 우리는 DynamoDB 위에 SQL 엔진을 구축하고 이를 DynamoSQL이라고 불렀습니다. 에이전트가 ANSI SQL SELECT 문을 제출하면, DynamoSQL은 이를 파싱하고, 쿼리 계획 (query plan)을 수립하며, 테이블의 실제 인덱스 메타데이터를 검사하는 옵티마이저 (optimizer)를 실행합니다. 옵티마이저는 최적의 액세스 경로를 선택합니다. WHERE 절이 지원하는 경우 파티션 키 쿼리 (partition key Query)를, 일치하는 인덱스가 있는 경우 GSI 쿼리 (GSI Query)를, 또는 최후의 수단으로 스캔 (Scan)을 선택합니다. 에이전트는 이 중 어떤 것도 설정하지 않습니다. 그저 SQL을 작성할 뿐입니다.

실행 계층 (execution layer)은 DynamoDB가 네이티브하게 수행할 수 없는 작업들을 처리합니다. 조인 (JOINs)은 여러 번의 DynamoDB API 호출을 통해 실행됩니다. GROUP BY, 집계 (aggregates), 서브쿼리 (subqueries), CTE, UNION, ORDER BY, 그리고 HAVING 모두 엔진 내에서 작동합니다. 결과는 행 (rows) 형태로 반환됩니다. 에이전트는 기저의 데이터 모델에 대해 아무것도 알 필요가 없습니다.

MCP 서버는 단일 run_sql 도구를 노출합니다. 에이전트가 쿼리를 작성하면 엔진이 이를 실행하고, 결과는 일반적인 SQL 데이터베이스에서 기대할 수 있는 것과 동일한 깔끔한 행 세트로 반환됩니다.

현재 상황

SQL 데이터베이스와 LLM 에이전트는 자연스럽게 잘 맞습니다. 인터페이스가 깔끔하고, 의미론 (semantics)이 잘 정의되어 있으며, 툴링 (tooling)이 성숙해 있기 때문입니다. NoSQL 데이터베이스는 여전히 대부분 타협안에 불과합니다. 한계가 명확한 구문 계층이거나, 엔지니어링 오버헤드가 필요하면서 에이전트에게 결코 완전히 최신 데이터를 제공하지 못하는 내보내기 파이프라인 및 보조 저장소(secondary stores) 중 하나입니다.

NoSQL 데이터베이스 간의 파편화로 인해 이 문제를 한 번에 해결하여 모든 곳에 적용하기는 어렵습니다. 하지만 DynamoDB를 사용하는 팀에게는 SQL 계층 접근 방식이 실무에서 잘 작동합니다. 에이전트는 원하는 것을 작성하고, 엔진은 이를 효율적으로 가져오는 방법을 찾아냅니다.

이것이 모든 NoSQL 데이터베이스에 걸쳐 올바른 접근 방식인지에 대한 더 넓은 질문은 여전히 미결 상태로 남아 있습니다. 하지만 현상 유지(status quo)에 따르는 비용은 실재하며, 현재 받고 있는 것보다 더 많은 관심을 기울일 가치가 있습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0