비정규화
Denormalization
개념
자주 함께 읽는 값을 여러 표에 일부러 중복해 두어, 읽을 때마다 표를 맞춰 보는 수고를 덜어내는 설계입니다.
등장 배경
데이터베이스 설계를 배울 때 가장 먼저 듣는 원칙은 같은 사실을 두 군데 적지 말라는 것입니다. 회사 이름을 주문 줄마다 적어 두면 상호가 바뀔 때 수만 줄을 고쳐야 하고, 한 줄이라도 빠뜨리면 같은 회사가 두 이름으로 남습니다. 그래서 정규화는 회사 이름을 회사 표에 한 번만 두고 주문 줄에는 번호를 적습니다.
문제는 읽을 때 드러납니다. 번호만 적어 두면 이름을 띄우려고 매번 회사 표를 다시 찾아가야 합니다. 흩어진 테이블을 키 값으로 맞춰 한 덩어리로 읽는 일을 조인이라고 하는데, 표가 둘일 때는 티가 안 나다가 예닐곱 개가 되면 달라집니다.
실제로는 이런 장면으로 나타났습니다. 문서 목록 50건을 띄우는 데 3초가 걸린다는 제보가 들어오는데, 열어 보면 문서 표 하나를 읽으려고 작성자 표와 요금제 표까지 다섯 개를 맞춰 보고 있습니다. 원칙대로 짠 결과인데도 화면은 느립니다.
비정규화는 이 원칙을 특정 지점에서만 깨는 선택입니다. 중복을 관리하는 비용이 매번 표를 맞추는 비용보다 싸다고 본 자리에만 씁니다.
작동 방식
원본 구조는 그대로 두고, 읽을 때마다 다른 표에서 가져오던 값을 읽는 쪽 표에 미리 복사해 둡니다. 그러면 그 질의는 자기 표만 읽고 끝납니다.
손대는 순서는 다섯 단계입니다.
- 느린 질의를 하나 고릅니다. 짐작이 아니라 기록에서 꺼냅니다. 목록 화면 하나가 전체 응답 시간의 절반을 먹으면 거기가 후보입니다.
- 그 질의가 다른 표에서 끌어오는 값 중 거의 바뀌지 않는 것을 고릅니다. 워크스페이스 이름은 1년에 한두 번 바뀌지만 문서 목록은 하루에 수만 번 읽힙니다.
- 그 값을 담을 칸을 읽는 쪽 표에 더합니다. 스키마를 고치는 일이라 이미 쌓인 행을 한 번 채워 넣는 작업이 따라붙습니다.
- 원본이 바뀔 때 사본도 같이 고치는 경로를 만듭니다. 워크스페이스 이름을 수정하는 코드가 문서 표의 사본 칸까지 고치게 합니다. 이 단계를 빼먹으면 화면에 옛 이름이 남습니다.
- 두 값이 어긋났는지 확인하는 장치를 둡니다. 밤마다 원본과 사본을 비교해 다른 행 수를 세어 두면 어긋난 날을 그날 압니다.
복사해 두는 값은 대개 두 모양 중 하나입니다. 하나는 다른 표의 값을 그대로 베껴 오는 것으로, 문서 행에 워크스페이스 이름을 같이 들고 가는 경우입니다. 다른 하나는 여러 행을 세거나 더한 결과를 미리 저장해 두는 것으로, 댓글이 2만 개 달린 문서라면 매번 2만 줄을 세는 대신 문서 행에서 숫자 한 칸만 읽습니다.
어느 쪽이든 원본은 남겨 둡니다. 사본이 어긋나도 원본에서 다시 계산해 채울 수 있습니다.
쓰이는 곳
읽는 횟수가 쓰는 횟수보다 크게 많은 자리에 씁니다. 대표적인 네 가지입니다.
- 목록과 대시보드 화면입니다. 한 화면에 수십 행을 띄우며 행마다 다른 표를 찾아가면 질의 수가 행 수만큼 불어납니다. 50건 목록이 질의 51번을 부르는 N+1 문제가 여기서 나오고, 필요한 값을 목록 표에 얹어 두면 질의 한 번으로 끝납니다.
- 미리 세어 두는 집계 숫자입니다. 좋아요 수나 팀 멤버 수처럼 화면마다 따라다니는 숫자는 볼 때마다 세지 않고 더하고 뺄 때만 고칩니다.
- 그 시점의 사실을 붙잡아 두는 기록입니다. 주문 표에 결제 당시 가격과 배송지를 그대로 적어 둡니다. 속도가 아니라 정확성 때문입니다. 상품 가격이 1만 원에서 1만 2천 원으로 올랐다고 지난달 영수증 금액까지 따라 오르면 안 됩니다.
- 분석용 저장소입니다. 데이터 웨어하우스는 처음부터 넓고 납작한 표를 만들어 둡니다. 표 열 개의 관계를 외우지 않아도 한 표만 훑으면 집계가 나오기 때문입니다.
반대로 쓰기가 잦은 값과 정확히 맞아야 하는 금액은 그대로 둡니다. 느리다는 이유만으로 바로 오지도 않습니다. 인덱스를 거는 쪽이 훨씬 싸고, 결과를 잠깐 저장해 두는 캐싱으로 풀리기도 합니다. 구조를 바꾸는 일이라 되돌리기가 번거로워 마지막에 꺼냅니다.
실무 용례
"이 목록 질의가 표를 일곱 개나 맞춰 봐요. 워크스페이스 이름만 문서 표에 같이 들고 있으면 세 개로 줄어듭니다."
구조를 바꿔 읽는 비용을 줄이자는 제안입니다.
"댓글 수는 매번 세지 말고 문서 행에 숫자로 들고 갑시다. 대신 댓글 지울 때 같이 빼는 걸 빠뜨리면 안 됩니다."
집계를 미리 저장하되 갱신 경로도 같이 만들자는 뜻입니다.
"여기는 일부러 중복해 둔 자리예요. 주문 시점 가격이라 원본이 바뀌어도 따라가면 안 됩니다."
실수로 남은 중복이 아니니 고치지 말라는 경고입니다.
페이지덱에서 이 말이 나온 건 관리자 화면이 느려졌을 때였습니다. 태오가 기록을 열어 보니 문서 목록 질의 하나가 평균 2.8초를 쓰고 있었고 대부분이 워크스페이스 표와 요금제 표를 맞춰 보는 시간이었습니다. 인덱스를 걸어 1.9초까지 줄였지만 거기까지였습니다.
그래서 태오가 문서 표에 워크스페이스 이름 칸을 하나 더하자고 제안했습니다. 지현이 물은 건 이름이 바뀌면 어디서 같이 고치느냐는 것이었습니다. 설정을 저장하는 코드가 문서 표까지 고치도록 하고 밤마다 두 값을 비교하는 점검을 붙이기로 정한 뒤 작업이 들어갔습니다. 목록은 0.4초가 됐고, 대신 이름을 고치는 코드에는 신경 쓸 자리가 하나 늘었습니다.
흔한 오해
비정규화를 정규화를 안 한 상태로 읽는 경우가 가장 흔합니다. 결과만 보면 둘 다 같은 값이 여러 군데 적혀 있으니 구분이 안 갑니다. 하지만 정규화를 거치지 않은 설계는 어디에 중복이 있는지 아무도 모르는 상태이고, 비정규화는 정규화된 구조에서 칸 하나를 골라 중복시킨 상태입니다. 둘을 같게 보면 정리되지 않은 설계에 비정규화라는 이름이 붙고, 값이 어긋났을 때 어디를 맞출지 찾을 근거가 사라집니다.
빨라지기만 한다고 읽는 것도 자주 보는 오해입니다. 읽기 시간이 눈에 띄게 줄어드니 이득만 있어 보입니다. 실제로는 쓰기 쪽이 그만큼 무거워집니다. 이름을 한 번 고치는 일이 문서 2만 건을 고치는 일이 되고, 그 사이에 오류가 나면 절반만 바뀐 상태가 남습니다. 읽기가 쓰기보다 100배 많은 화면이라면 남는 장사지만, 비율을 재 보지 않고 손대면 느린 화면 하나를 고치려다 저장 버튼이 느려집니다.
자주 헷갈리는 용어
| 용어 | 무엇을 하는가 | 비정규화와의 차이 |
|---|---|---|
| 정규화 | 같은 사실이 한 군데만 적히도록 표를 쪼갭니다 | 방향이 반대입니다. 정규화가 먼저 오고 비정규화는 그 결과에서 특정 자리만 되돌립니다. 정규화는 쓰기와 정확성을, 비정규화는 읽기 속도를 택합니다 |
같이 보면 좋은 페이지
마지막 업데이트 2026-09-16