구체화 뷰
고친 사람 github-actions[bot]
구체화 뷰는 질의 결과를 미리 계산해 저장해 두고 그 저장본을 읽게 해 줍니다. 읽을 때마다 원본 테이블을 다시 훑는 일을 없앱니다. 대신 원본이 바뀌어도 저장본은 따라 바뀌지 않으므로 누군가 다시 채워 줘야 합니다.
쉽고 빠른 이해
구체화 뷰는 오래 걸리는 계산을 한 번만 하고 그 답을 놓아두는 장치입니다. 지역별 매출 합계처럼 수백만 행을 훑어야 나오는 값이 그런 계산입니다.
이게 없으면 같은 합계를 요청이 올 때마다 처음부터 다시 구합니다. 화면 하나를 여는 데 몇 초가 걸리고, 그 부담이 원본 테이블에 그대로 쌓입니다.
도는 순서는 이렇습니다:
- 만들 때 질의를 한 번 돌려 결과를 저장합니다
- 그 뒤의 읽기는 저장된 결과만 봅니다
- 원본이 바뀌면 갱신을 걸어 저장본을 다시 채웁니다
대가는 뒤처짐입니다. 갱신과 갱신 사이에 저장본은 원본보다 낡은 값을 들고 있습니다. 저장 공간도 원본과 따로 한 벌 더 듭니다.
상세
도서관 입구에 붙는 대출 순위표를 떠올려 보십시오. 누가 물을 때마다 사서가 대출 기록을 처음부터 세지는 않습니다. 전날 밤에 한 번 세어 표를 만들어 두고 낮에는 그 표를 가리킵니다. 대신 오늘 아침에 빌려 간 책은 그 표에 없습니다.
구체화 뷰가 데이터베이스에서 하는 일이 그 순위표와 같습니다. 질의 결과를 한 번 계산해 저장해 두고, 그 뒤로는 저장본을 읽습니다. 질의는 데이터베이스에 던지는 물음이고, 그 답을 만드는 데 드는 계산이 클수록 이 방식이 버는 것이 커집니다.
테이블 · 뷰와 무엇이 다른가
세 대상이 이름부터 비슷해서 헷갈립니다. 가르는 잣대는 하나입니다. 저장된 것이 무엇이고, 읽을 때 계산이 도는가.
뷰는 질의문에 이름을 붙여 둔 것입니다. 저장되는 것은 질의문뿐이라 읽을 때마다 그 질의가 다시 돕니다. 그래서 뷰가 주는 값은 언제나 원본과 맞습니다.
구체화 뷰는 결과 행까지 저장합니다. 읽을 때 계산이 없는 대신, 그 결과가 원본과 어긋난 채로 남을 수 있습니다.
| 저장하는 것 | 읽을 때 도는 계산 | 원본과의 일치 | |
|---|---|---|---|
| 테이블 | 데이터 그 자체 | 없음 | 원본이다 |
| 뷰 | 질의문 | 매번 전체 질의 | 언제나 맞다 |
| 구체화 뷰 | 질의문 + 결과 행 | 없음 | 갱신 전까지 어긋난다 |
문법도 이 차이를 그대로 드러냅니다. 아래는 여러 데이터베이스가 공통으로 쓰는 모양입니다.
CREATE MATERIALIZED VIEW 지역별_매출 AS
SELECT 지역, SUM(금액) FROM 주문 GROUP BY 지역; -- 질의가 한 번 돈다
SELECT * FROM 지역별_매출; -- 저장본을 읽는다
첫 문장이 저장본을 만들어 채웁니다. 둘째 문장은 주문 테이블을 건드리지 않고 이미 채워진 결과만 읽습니다. 같은 문장을 뷰에 던졌다면 그때마다 합계를 다시 구했을 겁니다.
언제 다시 채우나
만들 때 한 번 채우고 나면, 그 뒤로는 저절로 최신이 되지 않습니다. 다시 채우는 일을 갱신이라 부르고, 이 갱신을 누가 언제 거느냐가 구체화 뷰를 굴리는 일의 대부분입니다.
거는 방법은 대개 셋입니다. 사람이나 배치 작업이 갱신 명령을 던지거나, 정해진 주기마다 자동으로 돌거나, 원본이 바뀔 때 데이터베이스가 따라 고쳐 줍니다.
갱신 방식도 둘로 갈립니다. 전체 갱신은 질의를 처음부터 다시 돌려 결과를 통째로 새로 씁니다. 증분 갱신은 지난 갱신 뒤에 바뀐 행만 찾아 그만큼만 반영합니다.
증분 갱신은 손대는 행이 적은 대신 아무 질의에나 되지는 않습니다. 바뀐 행 하나로 결과가 얼마나 달라지는지 계산할 수 있어야 하는데, 합계나 개수는 그게 되고 중앙값 같은 것은 안 됩니다.
저장본이 지나는 상태는 이렇게 오갑니다.
stateDiagram-v2
[*] --> 최신: 만들 때 질의를 돌려 채운다
최신 --> 낡음: 원본이 바뀐다
낡음 --> 낡음: 읽기는 그동안에도 답한다
낡음 --> 최신: 갱신이 저장본을 다시 채운다
눈여겨볼 곳은 가운데 화살표입니다. 낡은 동안에도 읽기는 막히지 않고 낡은 값을 그대로 내줍니다. 구체화 뷰가 읽기를 기다리게 하지 않는 까닭이 바로 이것이고, 조심해야 하는 까닭도 같습니다.
얼마나 낡아도 되는가
저장본이 원본보다 얼마나 뒤처져 있는지를 신선도라고 부릅니다. 갱신 주기가 한 시간이면 갱신이 돌기 직전에 읽는 값은 한 시간 전 것입니다.
그래서 구체화 뷰를 놓기 전에 읽는 쪽에 먼저 물어야 합니다. 이 화면은 얼마나 뒤처진 값을 받아도 되는가. 답이 "조금도 안 된다"이면 이 수단은 맞지 않습니다.
경영 보고용 집계는 대개 어제까지의 값이어도 뜻이 통합니다. 반면 재고 수량이나 잔액처럼 그 값을 보고 바로 결정을 내리는 화면은 뒤처진 값을 받으면 안 됩니다.
무엇을 내주고 무엇을 받나
| 받는 것 | 내주는 것 |
|---|---|
| 읽을 때 계산이 사라진다 | 결과를 담을 저장 공간이 한 벌 더 든다 |
| 원본 테이블이 읽기 부담에서 벗어난다 | 갱신이 도는 동안 계산 부담이 몰린다 |
| 복잡한 질의가 이름 하나로 줄어든다 | 갱신 사이에 낡은 값이 나간다 |
셋째 줄이 실무에서 자주 놓치는 대목입니다. 갱신은 공짜가 아니라 미룬 계산을 한꺼번에 치르는 것이라, 원본이 자주 바뀌면 갱신이 읽기보다 잦아지는 뒤집힘이 일어납니다. 그 지경이면 구체화 뷰가 부담을 줄인 것이 아니라 옮겨 놓기만 한 것입니다.
언제 쓰고 언제 안 쓰나
맞는 때는 읽기가 쓰기보다 훨씬 잦고, 그 읽기가 같은 계산을 되풀이할 때입니다. 집계·보고서·대시보드가 그런 모양이고, 여러 테이블을 잇는 조인이 깊게 겹칠 때도 그렇습니다.
안 맞는 때는 둘입니다. 읽을 때마다 최신이어야 하는 값이 하나이고, 원본이 쉴 새 없이 바뀌어 갱신이 따라잡지 못하는 때가 다른 하나입니다.
원래 금방 끝나는 질의에 붙이는 것도 안 맞습니다. 질의 자체가 인덱스 하나로 끝나는 값이면 저장본을 두고 갱신을 챙기는 수고가 버는 것보다 큽니다.
관련 항목
구체화 뷰와 같은 데이터베이스 안에 놓이는 대상
뷰 · 테이블 · 인덱스 · 릴레이션 · 스키마 · 임시 테이블
구체화 뷰를 만들고 갱신하는 명령
CREATE MATERIALIZED VIEW · REFRESH MATERIALIZED VIEW · CREATE VIEW · CREATE TABLE AS · CREATE INDEX
구체화 뷰가 감수하는 뒤처짐과 그 대가
신선도 · 낡은 데이터 · 증분 갱신 · 최종 일관성 · 툼스톤
구체화 뷰를 대신할 수 있는 다른 수단
캐싱 · 읽기 복제본 · 요약 테이블 · 배치 처리 · ETL
구체화 뷰를 골라 쓰는 질의 처리 단계
질의 최적화기 · 실행 계획 · 질의 재작성 · 조인 · 집계 함수
구체화 뷰가 쓰이는 분석 작업
다른 이름: materialized view · 실체화 뷰 · 구체화된 뷰