본문 바로가기
Database2026년 8월 6일5분 읽기

머티리얼라이즈드 뷰 — 무거운 집계를 미리 계산해두기

YS
김영삼
조회 5
머티리얼라이즈드 뷰 — 무거운 집계를 미리 계산해두기

머티리얼라이즈드 뷰(materialized view)는 쿼리의 결과를 실제 테이블처럼 디스크에 저장해 두는 뷰다. 일반 뷰가 조회할 때마다 원본 쿼리를 다시 실행하는 반면, 머티리얼라이즈드 뷰는 미리 계산된 결과를 그대로 읽는다. 무거운 집계나 조인을 반복 조회할 때 응답 시간을 극적으로 줄여준다.

핵심 트레이드오프는 하나다. 빠른 대신 데이터가 실시간이 아니다. 계산해 둔 시점의 스냅샷이라, 원본이 바뀌어도 갱신(refresh) 전까지는 옛 값이다. 이 성질을 이해하고 쓰면 아주 좋은 도구이고, 모르고 쓰면 "왜 숫자가 안 맞냐"는 소리를 듣는다.

일반 뷰와 뭐가 다른가

일반 뷰는 이름 붙은 쿼리에 불과하다. 조회하는 순간 뒤의 무거운 집계가 매번 다시 돈다. 대시보드처럼 같은 집계를 초당 수십 번 조회하면 그 비용이 누적된다.

-- 일반 뷰: 조회할 때마다 GROUP BY 재실행
CREATE VIEW daily_sales AS
SELECT date_trunc('day', created_at) AS day,
       count(*) AS orders,
       sum(amount) AS revenue
FROM orders
GROUP BY 1;
-- 머티리얼라이즈드 뷰: 결과를 저장
CREATE MATERIALIZED VIEW daily_sales_mv AS
SELECT date_trunc('day', created_at) AS day,
       count(*) AS orders,
       sum(amount) AS revenue
FROM orders
GROUP BY 1;

daily_sales_mv를 조회하면 집계가 다시 도는 게 아니라 저장된 표를 읽는다. 인덱스도 걸 수 있어서 일반 테이블처럼 다룬다. 나는 관리자 통계 페이지가 매번 수 초씩 걸리던 걸 이걸로 밀리초대로 낮춘 적이 있다.

갱신 전략이 전부

머티리얼라이즈드 뷰의 성패는 "언제, 어떻게 갱신하느냐"에 달렸다. 기본 갱신은 전체를 다시 계산한다.

-- 전체 재계산 (이 동안 조회가 잠긴다)
REFRESH MATERIALIZED VIEW daily_sales_mv;
-- 갱신 중에도 조회를 막지 않으려면 (유니크 인덱스 필요)
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales_mv;

여기서 CONCURRENTLY가 실무의 핵심이다. 이걸 안 붙이면 갱신하는 동안 뷰가 잠겨서 사용자 조회가 막힌다. 붙이면 기존 데이터를 계속 서빙하면서 뒤에서 갱신하는데, 전제 조건으로 뷰에 유니크 인덱스가 있어야 한다. 이걸 몰라서 프로덕션에서 갱신할 때마다 대시보드가 잠깐씩 멈추는 걸 겪고 나서야 배웠다.

방식조회 잠김전제 조건
REFRESH갱신 중 잠김없음
REFRESH CONCURRENTLY잠기지 않음유니크 인덱스 필수

언제 쓰고, 언제 피할까

머티리얼라이즈드 뷰는 읽기는 잦고 데이터의 실시간성은 덜 중요한 곳에 맞는다.

  • 일·주·월 단위 집계 리포트, 대시보드 지표 — 몇 분 지연은 대개 문제없다.
  • 여러 테이블을 무겁게 조인해야 나오는 화면 — 미리 조인해 두면 조회가 가볍다.
  • 랭킹·인기글처럼 계산은 비싼데 초 단위 정확성이 필요 없는 목록.

반대로 주문 잔액, 재고, 결제 상태처럼 항상 최신이어야 하는 데이터에는 쓰면 안 된다. 스냅샷이 옛 값을 보여주는 순간 사고다. 이런 데이터는 그냥 원본을 조회하거나, 인덱스·쿼리 최적화로 풀어야 한다.

갱신은 보통 크론이나 스케줄러로 주기적으로 돌린다. "5분마다", "매 정시에" 같은 식이다. 갱신이 원본 부하와 겹치지 않게 트래픽 낮은 시간에 잡는 요령도 필요하다. 원본이 아주 크면 전체 재계산 대신, 증분 갱신을 위해 별도 집계 테이블을 트리거·배치로 유지하는 편이 나을 때도 있다. 머티리얼라이즈드 뷰는 만능이 아니라 "적당히 최신이어도 되는 무거운 조회"라는 구간을 정확히 겨냥한 도구다.

자주 묻는 질문

머티리얼라이즈드 뷰는 자동으로 갱신되나요?

PostgreSQL에서는 자동 갱신이 없습니다. REFRESH를 직접 호출하거나 스케줄러·크론으로 주기 실행해야 합니다. 실시간 자동 반영이 필요하면 트리거로 집계 테이블을 유지하는 방식을 검토하세요.

CONCURRENTLY 갱신에 유니크 인덱스가 왜 필요한가요?

기존 데이터와 새로 계산한 데이터를 행 단위로 비교해 바뀐 부분만 반영하기 때문입니다. 각 행을 식별할 유니크 키가 없으면 이 비교가 불가능해서 유니크 인덱스가 전제 조건입니다.

일반 뷰를 인덱싱할 수는 없나요?

일반 뷰 자체에는 데이터가 없어 인덱스를 걸 수 없습니다. 인덱스는 실제 저장된 데이터에 거는 것이라, 결과를 저장하는 머티리얼라이즈드 뷰에서만 가능합니다.

SQL Server나 MySQL에도 있나요?

SQL Server는 인덱스드 뷰(indexed view), Oracle은 materialized view로 유사 기능을 제공합니다. MySQL에는 네이티브 머티리얼라이즈드 뷰가 없어, 집계 테이블을 스케줄러나 트리거로 직접 유지하는 방식으로 대체합니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록