Azure LLM 앱 발화 데이터 저장소 — PostgreSQL로 시작하는 것이 왜 합리적인가

PG JSONB · Cosmos DB 의 실체 · RU/s 비용 · Citus 스테이징 · 역할 분리

MINERVA 사내 AI Agent 플랫폼 리팩토링 맥락에서 발화·대화·A/B 실험 메트릭의 저장소를 결정하는 과정. MS 아키텍트의 Cosmos DB 권고, “PG로 시작 → NoSQL 전환” 초기 직관, Azure Database for PostgreSQL(Flexible Server) + 조건부 Cosmos DB for PostgreSQL(Citus) 이라는 최종 결론에 도달한 분석. “비정형 = NoSQL” 이라는 2010 년대 프레임이 2020 년대에 어떻게 낡았는지, PG JSONB + GIN + pgvector 가 어디까지 커버하는지, Cosmos DB 가 단일 제품이 아니라 다중 API 플랫폼이라 “PG → Cosmos” 가 반드시 패러다임 전환이 아닌 이유, RU/s 과금이 append-heavy 워크로드에 왜 불리한지, 단일 DB 대신 “역할 분리” 설계가 왜 장기적으로 이기는지, 벤더 권고를 필터링해서 받는 DS 의 의사결정 프레임까지를 정리한다.

Engineering
Infra
Cloud
Data Science
저자

Kwangmin Kim

공개

2026년 04월 21일

1 문제 — 어디에 저장할 것인가

사내 AI Agent 플랫폼 (MINERVA) 리팩토링 맥락에서 발화·대화·A/B 실험 메트릭 을 어디에 저장할지 결정이 필요했다. 관계자 입장이 세 갈래로 갈렸다.

  • MS 아키텍트 권고: Cosmos DB (JSON 원문 그대로 적재).
  • DS 팀 초기 직관: PostgreSQL 로 시작, 비정형·규모 커지면 MongoDB 로 전환.
  • 최종 결론: Azure Database for PostgreSQL (Flexible Server) 시작 + 조건부 Cosmos DB for PostgreSQL (Citus).

이 글은 “왜 이 결론에 도달했는가” 를 data scientist 관점에서 정리한다. DS 배경이라 SQL 은 익숙하지만 NoSQL · 클라우드 DB 과금 구조 · 운영 현실에 감각이 약할 수 있는 사람 이 읽고 결정 프레임을 얻을 수 있도록 쓴다.

2 “비정형이면 NoSQL” 프레임의 한계

DS 교과서 · 튜토리얼에서 흔히 보는 이분법:

  • 정형 데이터 → RDB (PostgreSQL, MySQL)
  • 비정형 · 스키마 유연 → NoSQL (MongoDB)
  • 대규모 · 글로벌 → Cosmos DB / DynamoDB

이 프레임은 2010 년대 초반 PostgreSQL 이 아직 JSON 을 제대로 다루지 못하던 시절에 형성되었다. 지난 10 년 동안 PostgreSQL 이 JSONB, GIN 인덱스, 배열 타입, full-text search, pgvector 를 흡수하면서 상당 부분 낡았다.

2.1 PostgreSQL JSONB 가 해결하는 것

JSONB 는 JSON 을 이진 포맷으로 저장 하는 컬럼 타입이다. 텍스트 JSON 과 달리 내부 키에 인덱스를 걸 수 있고 쿼리 시 파싱 비용이 없다.

CREATE TABLE utterances (
  id          UUID PRIMARY KEY,
  user_id     UUID,
  created_at  TIMESTAMPTZ,
  arm_id      TEXT,              -- A/B 실험 arm
  payload     JSONB              -- 유연 영역: citations, retrieved_chunks, latency 등
);

-- GIN 인덱스: JSONB 내부 키 검색을 빠르게
CREATE INDEX idx_payload_gin ON utterances USING GIN (payload);

-- 쿼리 예
SELECT
  payload->>'query'                  AS query,
  payload->'citations'               AS citations,
  (payload->>'latency_ms')::float    AS latency
FROM utterances
WHERE arm_id = 'treatment'
  AND payload @> '{"has_citation": true}'    -- JSONB containment
  AND created_at > NOW() - INTERVAL '7 days';

MongoDB 문서 쿼리와 거의 같은 유연성을 준다. 차이점:

PostgreSQL JSONB MongoDB
스키마 변경 빈도 기본 필드는 정형, 유연 영역만 JSONB 모든 필드가 유연
수평 확장 단일 인스턴스 + 읽기 replica (수백 GB 까지 편안) 샤딩이 기본 내장
분석 쿼리 네이티브 SQL — 조인·window·CTE aggregation pipeline ($group/$match) — 문법 복잡
트랜잭션 ACID 완전 지원 4.0+ 멀티문서 트랜잭션 지원 (제약 있음)

발화 데이터의 현실: 대부분의 LLM 앱에서 발화 스키마는 월 단위로 크게 바뀌지 않는다. 바뀌는 건 보통 “새 metric 필드 추가” 수준이고 이건 JSONB 하위 키 추가로 끝난다. 스키마가 진짜 매일 뒤집히는 케이스 (예: 외부 파트너 데이터 수집, IoT 센서 이벤트) 가 아니면 JSONB 로 충분하다.

3 분석 관점에서 PostgreSQL 이 유리한 이유

A/B 실험 분석은 본질적으로 GROUP BY + window function + join 이다. LLM 앱 평가 쿼리의 전형적 예:

-- arm별 평균 지표 + 신뢰구간
SELECT
  arm_id,
  COUNT(*)                                            AS n,
  AVG((payload->>'latency_ms')::float)                AS mean_latency,
  STDDEV((payload->>'latency_ms')::float)             AS sd_latency,
  AVG((payload->>'relevance_score')::float)           AS mean_relevance,
  PERCENTILE_CONT(0.95) WITHIN GROUP
    (ORDER BY (payload->>'latency_ms')::float)        AS p95_latency
FROM utterances
WHERE created_at > NOW() - INTERVAL '14 days'
GROUP BY arm_id;

-- 사용자별 첫 turn vs 이후 turn 비교 (window)
SELECT
  user_id,
  created_at,
  payload->>'relevance_score' AS score,
  ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS turn_idx
FROM utterances;

MongoDB 도 할 수는 있지만:

  • aggregation pipeline 문법$group, $match, $project, $lookup 등으로 복잡.
  • 팀에서 분석하려면 결국 pandas 로 덤프해 돌리는 빈도가 높아져 IO 부담이 커진다.
  • SQL 을 쓰는 DS 팀에게는 러닝 커브가 발생한다.

이건 엔지니어링 선택이 아니라 팀 생산성 문제다. DS 가 직접 쿼리를 쓸 조직이라면 SQL 기반이 압도적으로 편하다.

4 Cosmos DB 의 실체 — 한 제품이 아니라 플랫폼

여기서 많이 혼동한다. 처음엔 “Cosmos DB = MongoDB 류 문서 DB” 로 단순화하기 쉽지만, 실제로 Cosmos DB 는 여러 API 를 지원하는 플랫폼 이다. 어떤 API 를 쓰느냐에 따라 내부 모델과 이전 비용이 전혀 다르다.

API 내부 모델 SQL 유지? PG 에서 이전 비용
Cosmos DB for NoSQL (Core) 문서 (JSON) + 전용 쿼리 언어 X 높음 (패러다임 전환)
Cosmos DB for MongoDB MongoDB wire protocol X 높음
Cosmos DB for Cassandra wide column X 매우 높음
Cosmos DB for PostgreSQL Citus (분산 PG) O 낮음
Cosmos DB for Gremlin 그래프 X 매우 높음
Cosmos DB for Table Key-Value X 낮음 (단순 KV 만)

“PG 로 시작했다가 규모 나면 Cosmos 로 간다” 가 반드시 SQL → 문서 DB 패러다임 전환을 의미하지 않는다. Cosmos DB for PostgreSQL (Citus 기반) 로 가면 SQL · JSONB · 분석 쿼리가 그대로 유지되고, 단지 테이블이 여러 노드에 샤딩 (분산 저장) 될 뿐이다.

4.1 Citus 가 무엇인가

Citus 는 PostgreSQL 확장으로, 하나의 논리 테이블을 여러 물리 노드에 분산 시키는 기능이다. 테이블 생성 시 “이 컬럼을 샤딩 키로 써라” 선언하면 Citus 가 알아서 처리한다.

CREATE TABLE utterances (...);
SELECT create_distributed_table('utterances', 'user_id');
-- 이제 utterances는 user_id 해시에 따라 여러 노드에 분산 저장됨

SELECT 쿼리는 Citus 가 각 노드에 분산해 실행하고 결과를 합친다. 애플리케이션 코드는 거의 바뀌지 않는다. Cosmos DB for PostgreSQL 은 이 Citus 를 Azure 가 관리형으로 운영해 주는 것이다.

5 Cosmos DB 의 비용 구조 — RU/s

Cosmos DB for NoSQL 계열은 Request Unit per second (RU/s) 로 과금된다. 요청 하나당 RU 소모량이 달라서 비용 예측이 까다롭다.

작업 대략 RU 소모
1KB 문서 읽기 1 RU
1KB 문서 쓰기 5 RU
쿼리 (필터) 스캔한 문서 수 × 복잡도 비례

Provisioned Throughput 모드는 실제 사용량과 무관하게 예약한 RU/s 만큼 과금 된다. 즉 피크 대비 프로비저닝하면 유휴 시간에도 비용이 나간다. Autoscale · Serverless 옵션이 있지만 각각 다른 제약 (최소 RU, 컨테이너 사이즈 한도) 이 있어 가볍게 돌리기 어렵다.

동일 워크로드 기준 실무 체감:

  • Cosmos DB for NoSQL: Azure Database for PostgreSQL 대비 2~5 배 비용 이 흔히 나온다.
  • trace · 로그같이 append-heavy 한 워크로드는 쓰기 RU 가 5 배 들어 더 나쁘다.

5.1 왜 MS 아키텍트는 Cosmos DB 를 권장했나

제품 자체는 훌륭하다. 글로벌 배포, 자동 인덱싱, 다중 API, 강력한 SLA. 기술적으로 “할 수 있다” 는 말은 맞다.

다만 벤더 영업 인센티브 측면에서도 Cosmos DB 가 Azure Database for PostgreSQL 보다 ARR (연간 반복 매출) 가 크고 MACC (Microsoft Azure Consumption Commitment) 소진에도 유리하다. 이건 비난할 일이 아니라 구조적 현실 이다. MS 아키텍트는 MS Azure 제품 풀 안에서 최선을 제안하지, “차라리 오픈소스 PG 를 셀프 호스팅하세요” 라는 제안은 하지 않는다.

결론: 아키텍처 결정은 벤더 권고를 필터링해서 받아야 한다. 요구사항 (규모·쿼리 패턴·팀 역량·비용) 을 자기 기준으로 먼저 정의하고, 그 위에서 벤더 옵션을 평가한다.

6 추천 스테이징 경로

“한번에 최종 아키텍처로 가기” 가 아니라 “현재 규모에 맞는 것으로 시작 + 명확한 전환 트리거” 전략이 합리적이다.

단계 저장소 언제 상대 비용
1 Azure Database for PostgreSQL (Flexible Server) 지금 ~ 운영 오픈 초기
2 동일 + 읽기 replica + pgvector 확장 분석 워크로드 분리 · 임베딩 검색 필요
3 Cosmos DB for PostgreSQL (Citus) 단일 PG 쓰기 TPS 한계 돌파 5~10×

대부분의 사내 LLM 앱은 1~2 단계에서 수년을 버틴다. 3 단계까지 가는 건 연간 발화 TB+ 규모다.

6.1 1 단계: Azure Database for PostgreSQL — Flexible Server

왜 raw PostgreSQL 이 아니라 Azure managed 버전인가:

  • 백업 · PITR (Point-In-Time Recovery) · 장애조치 자동.
  • Azure Monitor 메트릭 · 로깅 통합.
  • Entra ID 인증 지원 (별도 DB 계정 없이 AD 계정으로 로그인).
  • VNet 통합 (Private Endpoint).
  • pgvector, PostGIS, pg_cron 같은 주요 확장 native 지원.
  • HA 옵션 (동기 복제 standby) 을 스위치로 켬.

클라이언트 관점에선 psql 로 접속하면 똑같은 PostgreSQL 이다. 운영 부담만 Azure 가 가져간다.

6.2 2 단계: pgvector 로 분석 DB 에 임베딩 합치기

pgvector 는 PostgreSQL 에 벡터 컬럼 타입과 유사도 연산자 를 추가하는 확장이다.

CREATE EXTENSION vector;

CREATE TABLE utterances (
  id          UUID PRIMARY KEY,
  query       TEXT,
  query_emb   vector(1536),          -- OpenAI embedding 차원
  payload     JSONB,
  created_at  TIMESTAMPTZ
);

CREATE INDEX ON utterances USING ivfflat (query_emb vector_cosine_ops);

-- "이 질문과 비슷한 과거 질문 top 10"
SELECT query, 1 - (query_emb <=> $1) AS similarity
FROM utterances
ORDER BY query_emb <=> $1
LIMIT 10;

이걸 분석 DB 에 넣어두면:

  • “사용자들이 반복하는 질문 클러스터” 분석.
  • “실패한 응답과 비슷한 패턴의 다른 응답” 추적.
  • RAG 성능 저하 원인을 벡터 공간에서 탐색.

주의: 이건 RAG 검색용 Vector Store (Azure AI Search) 와는 역할이 다르다. AI Search 는 실시간 검색 최적화용이고, pgvector 는 분석 DB 의 부가 기능으로 “오프라인 탐색” 에 쓴다. 중복이 아니라 역할 분리다.

6.3 3 단계: Cosmos DB for PostgreSQL (Citus)

단일 PG 인스턴스 상한 체감:

  • 저장: Flexible Server 최대 32TB.
  • 쓰기 TPS: 수천 ~ 수만 (하드웨어 · 워크로드 의존).
  • 동시 연결: 수천 (PgBouncer 같은 커넥션 풀러 필수).

사내 LLM 앱에서 이 한계에 실제로 부딪히는 경우는 드물다. 사용자 수천 명 × 하루 발화 수십 건 = 연간 수백 GB 수준이고, Flexible Server General Purpose 4vCPU/16GB 면 충분하다.

7 전환 트리거 기준 — “규모 커지면” 을 구체 조건으로

추상적으로 “규모 커지면 전환” 이라고 두면 실제 판단이 어렵다. 다음 조건 중 둘 이상 동시 충족 을 전환 검토 기준으로 삼는다.

  1. JSONB + GIN 으로 잡을 수 없는 쿼리 패턴이 3 개 이상 누적 (예: 깊이 5+ 중첩 조건부 집계).
  2. 쓰기 TPS 가 단일 인스턴스 최대값의 60% 초과 상태로 주 단위 지속.
  3. 분석 쿼리가 읽기 replica 에서도 p95 > 5 초로 악화.
  4. 발화 스키마가 월 단위로 top-level 구조 변경 (JSONB 하위 키 추가 수준 아님).
  5. 연간 스토리지 증가율이 Flexible Server 최대치 (32TB) 도달을 2 년 내로 투영.

1~3 은 기술 지표, 4~5 는 도메인 변화 신호. 하나만 걸리면 PG 내부에서 튜닝 (파티셔닝 · 인덱싱 · read replica) 으로 해결 가능. 두 개 이상이면 구조적 한계로 본다.

8 역할 분리 — 모든 데이터를 한 DB 에 넣지 않는다

실무 최적 설계는 DB 하나가 아니라 유형별 역할 분리 다.

데이터 유형 저장소 이유
발화 · 대화 · A/B arm · citation PostgreSQL (JSONB) 분석 · 조인 · 집계, 장기 보존
원시 trace / LLM raw I/O Blob Storage + App Insights append-only, 대용량, 드물게 조회
세션 캐시 Redis 또는 PG 테이블 휘발성, 고속 조회
Vector (RAG 검색) Azure AI Search 검색 최적화
Vector (분석용) PostgreSQL + pgvector 분석 DB 부가

“모든 것을 Cosmos DB 에 넣어라” 가 over-engineering 인 이유: Cosmos DB 는 모든 유형을 수용할 수 있지만, RU/s 과금 특성상 append-heavy trace 를 넣으면 비용이 기하급수로 오른다. Trace 를 Blob + App Insights 로 보내면 TB 당 수 달러, Cosmos DB 로 넣으면 TB 당 수백 달러 이상.

역할 분리 = 각 저장소의 강점에 맞는 데이터만 넣는 것. 단일 DB 로 단순화하려는 유혹은 흔히 “개발 편의” 를 명분으로 하지만, 장기적으로는 비용 · 성능 양쪽에서 손해다.

9 Data Scientist 관점의 교훈

이 논의에서 재정리한 세 가지를 원칙으로 정리한다.

9.1 1. “비정형 → NoSQL” 은 2010 년대 프레임이다

2020 년대에는 PG JSONB 가 상당히 많은 비정형 케이스를 커버하고, 분석 편의성을 양보하지 않는다. DS 가 자주 쓰는 프레임을 10 년 주기로 한 번씩 재검토 할 가치가 있다.

9.2 2. Cosmos DB 는 제품명이 아니라 플랫폼이다

“PG → Cosmos” 가 반드시 패러다임 전환은 아니다. Cosmos for PostgreSQL (Citus) 로 가면 SQL · JSONB · 분석 쿼리가 유지된다. 제품명을 단일 블랙박스로 보지 말고 내부 API · 엔진이 무엇인지 들여다보는 습관이 중요하다.

9.3 3. 벤더 권고는 필터링해서 듣는다

MS 아키텍트의 Cosmos DB 권고는 기술적으로 가능한 제안이지만, 비용 · 운영 · 팀 역량 관점에서 맞는 답이 아닐 수 있다. 회의록에 “우리 요구사항 기준 PG Flexible Server 로 결정, 근거: 분석 편의성 · 비용 · JSONB 성숙도” 한 줄 남겨두는 것이 나중 감사 · 보고에 도움이 된다.

엔지니어링은 DS 가 종종 “상식 없음” 을 느끼는 영역이지만, 결정 프레임을 “비용 · 성능 · 팀 생산성을 변수로 둔 최적화 문제” 로 보면 DS 의 분석 감각이 오히려 강점이 된다. 결정 근거를 수치로 적어두는 습관이 그대로 통한다.

10 한 줄 요약

“비정형이면 MongoDB / Cosmos” 는 낡은 프레임이다. PostgreSQL JSONB + GIN 인덱스가 LLM 앱 발화 데이터의 비정형성을 충분히 커버하고, 분석 쿼리 (A/B arm 비교 · window · join) 편의성에서 압도적 우위를 보인다. Azure Database for PostgreSQL (Flexible Server) 로 시작 → 필요 시 읽기 replica + pgvector → 진짜 규모가 나면 Cosmos DB for PostgreSQL (Citus, SQL 유지) 로 무중단 확장. 단일 DB 에 모든 것을 넣지 말고 trace 는 Blob + App Insights, 세션은 Redis/PG 로 역할을 분리 한다. 벤더 권고는 MACC 소진 · ARR 구조를 감안해 요구사항 기준으로 필터링 해서 받는다.

11 관련 주제

Azure 시리즈

데이터 아키텍처

AWS 비교

MINERVA 프로젝트 맥락

Subscribe

Enjoy this blog? Get notified of new posts by email: