Quick Comparison
복잡한 query를 재사용하고 항상 원본을 읽어야 하면 VIEW, 읽기 비용을 낮추는 대신 refresh 시점의 결과만 허용할 수 있으면 materialized view를 씁니다. CONCURRENTLY refresh는 populated materialized view의 전체 행을 고유하게 식별하는 plain-column unique index가 필요하고, transaction block 안에서 실행할 수 없습니다.
| 표면 | 조회 시점 | 저장 여부 | 주 용도 |
|---|---|---|---|
VIEW | 조회할 때 원본 쿼리 실행 | 결과 저장 안 함 | 복잡한 쿼리 표면화 |
MATERIALIZED VIEW | refresh 시점 결과 저장 | 결과 저장 | 비싼 집계 캐시 |
기본 구조
VIEW는 저장된 SELECT 정의다
VIEW는 결과를 저장하지 않고 SELECT 정의를 저장합니다. view를 조회하면 PostgreSQL이 원본 테이블에 대한 쿼리처럼 풀어서 실행합니다. 복잡한 JOIN, 필터, 컬럼 alias를 하나의 안정적인 조회 표면으로 감출 때 유용합니다.
CREATE VIEW active_users AS
SELECT id, email, created_at
FROM users
WHERE deleted_at IS NULL;view는 보안 경계처럼 보일 수 있지만, 권한과 RLS, security_barrier 같은 설정을 함께 이해해야 합니다. 단순히 컬럼을 숨기기 위해 view를 만들었다고 해서 모든 우회 경로가 자동으로 막히지는 않습니다.
materialized view는 결과를 저장한다
materialized view는 SELECT 결과를 물리적으로 저장합니다. 조회는 빠를 수 있지만 원본 테이블이 바뀌어도 결과가 자동으로 갱신되지 않습니다. 최신 값이 필요하면 REFRESH MATERIALIZED VIEW를 실행해야 합니다.
CREATE MATERIALIZED VIEW daily_sales AS
SELECT date_trunc('day', created_at) AS day, sum(total) AS revenue
FROM orders
GROUP BY 1;
REFRESH MATERIALIZED VIEW daily_sales;대시보드 집계, 외부 데이터 snapshot, 비싼 join 결과 캐시처럼 "조금 늦어도 되는 읽기"에 맞습니다. 항상 최신이어야 하는 거래성 조회에는 맞지 않습니다.
concurrent refresh는 unique index가 필요하다
REFRESH MATERIALIZED VIEW는 기본적으로 refresh 동안 materialized view를 읽을 수 없게 합니다. CONCURRENTLY 옵션을 쓰면 기존 결과를 계속 읽을 수 있지만, materialized view가 이미 데이터로 채워져 있고 전체 행을 덮는 plain-column unique index가 필요합니다. predicate·expression index는 조건을 충족하지 않으며, CONCURRENTLY refresh는 transaction block 안에서 실행할 수 없습니다.
CREATE UNIQUE INDEX daily_sales_day_idx ON daily_sales (day);
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales;concurrent refresh는 일반 refresh보다 더 많은 작업을 할 수 있습니다. 데이터량, 갱신 주기, 조회 지연 허용 범위를 같이 보고 선택해야 합니다.
선택 기준
| 상황 | 적합한 선택 |
|---|---|
| 복잡한 SELECT를 재사용 | VIEW |
| 권한을 좁힌 조회 표면 제공 | VIEW + 권한/RLS 검토 |
| 비싼 집계를 캐시 | MATERIALIZED VIEW |
| 항상 최신 결과 필요 | 원본 쿼리 또는 일반 table 설계 |
| refresh 중 조회 유지 | REFRESH MATERIALIZED VIEW CONCURRENTLY |
주의할 점
materialized view는 원본 데이터가 바뀌어도 자동으로 최신화되지 않습니다. refresh 주기와 지연 허용 범위를 정하지 않고 쓰면 대시보드나 리포트가 오래된 값을 보여 줄 수 있습니다. 반대로 일반 view는 결과를 저장하지 않으므로 성능 캐시로 기대하면 안 됩니다.
view를 너무 많이 겹쳐 쌓으면 실제 실행 쿼리를 이해하기 어려워집니다. 성능 문제가 생기면 view 정의를 펼친 실제 SQL과 실행 계획을 확인해야 합니다.
참고 링크
3 sources