1. 개요
SQL 기초 시리즈 마지막 편이다. 정규화는 배울 땐 규칙처럼 외우지만, 실무에서는 “언제 정규화를 깨도 되는가” 가 더 자주 부딪히는 질문이다. 정규화가 막아주는 문제와, 그걸 의도적으로 포기하는 반정규화, 그리고 최근 자주 보이는 JSONB 컬럼까지 정리한다.
2. 핵심 내용
2-1. 정규화는 왜 하나
정규화는 테이블을 쪼개서 같은 데이터가 여러 곳에 중복 저장되지 않게 만드는 작업이다. 목적은 성능이 아니라 갱신 이상(anomaly)을 막는 것이다.
한 테이블에 주문과 배송지 주소를 같이 넣었다고 하자. 같은 사용자가 열 번 주문하면 그 사용자의 주소가 열 번 중복 저장된다. 사용자가 이사를 가면 열 개 행을 전부 찾아서 고쳐야 한다. 하나라도 빠뜨리면 어떤 주문은 옛날 주소, 어떤 주문은 새 주소를 가진 채로 어긋난다. 정규화는 사용자 정보를 users 테이블 하나로 빼서 이 문제를 원천적으로 막는다.
간단히 말하면 1NF 는 한 칸에 값 하나만 넣는 것(원자값), 2NF 는 기본키 일부에만 의존하는 컬럼을 분리하는 것, 3NF 는 기본키가 아닌 컬럼끼리 서로 의존하는 관계를 분리하는 것이다. 실무에서는 “같은 값이 여러 행에 반복돼서 나오면 의심한다” 정도만 기억해도 3NF 근처까지는 자연스럽게 맞춰진다.
2-2. 반정규화 — 언제 규칙을 깨나
정규화를 철저히 지키면 조회할 때마다 여러 테이블을 JOIN 해야 한다. 테이블이 늘어날수록 조인 비용도 늘어난다. 조회가 압도적으로 많고 쓰기는 드문 데이터라면, 일부러 중복을 허용해서 조인을 줄이는 반정규화를 쓴다.
예를 들어 주문 테이블에 사용자 이름을 통계용으로 한 번 더 복사해 두면, 주문 목록을 보여줄 때 users 테이블까지 갈 필요가 없다. 대신 사용자가 이름을 바꾸면 과거 주문에 박힌 이름은 그대로 남는다. 이게 버그가 아니라 “주문 당시 이름을 남긴다” 는 의도라면 반정규화는 오히려 정답이다. 반정규화는 규칙을 어기는 게 아니라, 정합성과 조회 성능 사이에서 정합성 일부를 의도적으로 내주는 선택이다.
2-3. JSONB — 정규화하기 애매한 데이터를 위한 탈출구
컬럼으로 딱 떨어지지 않는 데이터도 있다. 언어별로 달라지는 텍스트, 지점마다 다른 설정값처럼 구조가 자주 바뀌거나 다국어처럼 축이 하나 더 붙는 데이터를 매번 컬럼이나 테이블로 정규화하면 스키마 변경이 잦아진다.
이럴 때 PostgreSQL 의 JSONB 컬럼을 쓰는 경우를 실제로 봤다. 다국어 사이트를 하나의 인스턴스로 묶었던 작업에서는 언어를 컬럼이 아니라 JSONB 안의 키로 뒀다. name: {"ko": "...", "en": "...", "zh": "..."} 형태로 넣으면 언어가 하나 늘어도 테이블 구조를 바꿀 필요가 없다(자세한 내용은 Chois Intl 1 - 두 브랜드 사이트를 인스턴스 하나로 참고). 지점별 안내 페이지를 다루던 다른 프로젝트에서는 “콘텐츠는 JSONB, 정책은 설정” 으로 나눠서, 지점이 늘거나 문구가 바뀌어도 코드를 안 건드리게 만들었다(The Seoul House 1 - Inquiry First 참고).
JSONB 는 정규화를 대체하는 게 아니라, 구조가 자주 바뀌거나 조회 조건으로 거의 안 쓰이는 부분을 정규화 대상에서 빼주는 도구에 가깝다. 검색이나 조인의 기준이 되는 데이터(외래키로 참조되는 값, 자주 필터링하는 값)는 여전히 정규화된 컬럼으로 두는 게 맞다. 아무 데나 JSONB 로 몰아넣으면 인덱스도 못 타고 타입 체크도 안 되는, 정규화 이전보다 더 다루기 힘든 테이블이 된다.
3. 마무리
요약
- 정규화의 목적은 성능이 아니라 중복 데이터로 인한 갱신 이상을 막는 것이다.
- 반정규화는 규칙 위반이 아니라 조회 성능을 위해 정합성 일부를 의도적으로 내주는 선택이다.
- JSONB 는 구조가 자주 바뀌거나 조인·검색 기준으로 쓰이지 않는 데이터를 정규화 대상에서 빼는 용도로 쓴다.
- 조인·필터링의 기준이 되는 데이터는 JSONB 로 몰아넣지 말고 여전히 정규화된 컬럼으로 둔다.
SQL 기초 시리즈는 여기서 끝난다. 실행 순서, 인덱스, 격리 수준, JOIN, 윈도우 함수, 호출 방식, 정규화까지 훑었으니, 다음에 낯선 쿼리를 만나면 이 일곱 편 중 하나는 힌트가 될 것이다.