“이 Unknown은 누가 넣은 건가요?”

DW 모델 리뷰 자리에서 꼭 나오는 질문이다.

상품 마스터 테이블을 열어보면 “Unknown"이라는 이름의 데이터가 들어 있다. 사원 테이블에도 있다. 기간계 시스템을 해온 사람이라면 당연히 의아하다. 마스터 테이블에 더미 데이터라니.

비슷한 질문이 뒤따른다. “주문실적 테이블에 시점담당사원이라는 컬럼은 뭔가요? 기간계 주문 테이블에는 없던 건데.” DW 모델을 처음 접한 사람에게는 이것도 낯설다.

두 모델의 차이를 키워드로 설명하는 자료는 많다. 비정규화, 스타스키마, 스노우플레이크. 검색하면 바로 나온다. 문제는 키워드만으로 “왜 이렇게 설계하는가"가 설명이 안 된다는 것이다. 목적부터 짚어야 한다.

기간계 모델은 트랜잭션을 지킨다

OLTP 데이터 모델의 목표는 명확하다. 빈번한 입력과 수정 과정에서 정합성을 깨뜨리지 않는 것.

이 목표가 모델의 생김새를 결정한다. 엔터티 사이의 관계가 엄격하다. 부서가 없으면 사원을 등록할 수 없고, 상품이 없으면 주문이 발생할 수 없다. 고객이 없는 주문도 존재하지 않는다. 모든 관계에는 선행 조건이 있고, 데이터가 발생하는 그 시점에 조건이 충족되어야 한다.

이걸 보장하기 위해 정규화를 한다. 중복을 줄이면 수정할 곳이 한 군데로 줄고, 정합성이 깨질 여지가 작아진다. 최상위 마스터(코드 테이블 같은)부터 순서대로 등록하고, 그 위에 트랜잭션 데이터를 쌓는다. 순서가 틀어지면 안 된다.

비유하면 이렇다. 할아버지가 있어야 아버지가 있고, 아버지가 있어야 아들이 있다. 존재 관계다. 사람이 있어야 사람의 행동이 기록된다. 행위 관계다. OLTP 모델은 이런 관계의 제약 조건을 빠짐없이 반영하는 데 집중한다.

DW 모델은 접근 경로를 설계한다

DW 데이터 모델은 다른 문제를 푼다. 모든 데이터를 빠짐없이 적재하고, 분석 대상에 접근하는 경로를 만드는 것이다.

“접근 경로"가 핵심이다. 주문실적이라는 분석 대상이 있다고 하자. 사원 기준으로도, 상품 기준으로도, 고객 기준으로도 들어갈 수 있어야 한다. 어느 경로로 가든 같은 결과가 나와야 하고, 성능도 비슷해야 한다. 스타스키마가 이 구조를 가장 직관적으로 표현한다.

        [사원]
          |
[상품] — 주문실적 — [고객]
          |
        [직업]

주문실적을 중심에 놓고, 접근 경로가 되는 차원 테이블이 주변을 둘러싸는 형태다.

OLTP 경험이 많은 사람이 이 모델을 보면 “비정규화한 OLTP"라고 오해하기 쉽다. ERD라는 도구가 같으니까 결과물도 같은 종류일 거라고 생각한다. 도구가 같을 뿐이다. 설계의 출발점이 다르다.

시점 데이터라는 낯선 개념

OLTP 주문 테이블에는 “담당사원” 컬럼이 있다. 현재 담당사원을 가리킨다. DW의 주문실적 테이블에는 시점담당사원 이 있다. 주문이 발생한 바로 그 시점의 담당사원이다.

왜 이런 게 필요한가. 상품 담당사원이 올해 A에서 B로 바뀌었다고 하자. OLTP에서는 현재 담당이 B다. 그걸로 끝이다. DW에서는 상황이 다르다. “작년 실적은 A 기준으로, 올해 실적은 B 기준으로 보고 싶다"는 요구가 자연스럽게 나온다. 상품담당사원이력이나 고객직업이력 같은 이력 데이터를 활용해서, 주문실적 적재 시점에 시점 데이터를 함께 만들어낸다.

OLTP에서 퇴사한 사원은 마스터에서 비활성화하면 그만이다. DW에서는 과거 시점에만 존재했던 사원도, 더 이상 유효하지 않은 직업 코드도 마스터 테이블에 전부 남겨야 한다. 과거 분석에 필요한 데이터가 빠지면 안 되니까.

Unknown이 존재하는 이유

DW 프로젝트에서 흔한 상황이 하나 있다. 과거 10년치 주문실적을 분석하려는데, 상품 마스터 관리가 부실해서 최근 상품만 남아 있다. 주문실적에는 상품ID가 찍혀 있는데 상품 테이블에는 해당 ID가 없다.

OLTP였으면 이런 일 자체가 안 일어난다. 상품이 없으면 주문이 생길 수 없도록 설계했으니까. DW는 입장이 다르다. 이미 발생한 과거 데이터를 있는 그대로 적재해야 한다.

이때 선택지가 몇 가지 있다.

  • 주문실적의 상품ID를 Unknown에 해당하는 ID로 바꾸거나
  • 분석용 상품ID 컬럼을 하나 더 두어 이중 관리하거나
  • 매핑 안 되는 상품ID를 적재 시점에 상품 마스터에 먼저 추가하고, 나머지 속성은 NULL이나 대체값으로 채우거나

어떤 방식이든 한 가지는 공통이다. 상품 마스터에 Unknown 이라는 기준 데이터를 미리 넣어 둔다는 것. 해당 상품의 담당사원도 알 수 없으니 사원 테이블에도 Unknown을 넣는다. 엔터티 간 관계를 형식적으로 충족시키되, 데이터가 발생한 시점이 아니라 적재하는 시점에 인위적으로 맞추는 방식이다.

OLTP 모델러가 보면 불편할 수 있다. 인위적인 더미 데이터로 관계를 맞추다니. DW의 목적을 생각하면 합리적인 판단이다. 분석 대상 데이터를 빠뜨리지 않으면서, 어떤 접근 경로로 들어가든 일관된 구조가 유지되어야 하니까.

관계를 맞추는 시점이 다르다

정리하면 이렇다.

OLTP 는 데이터가 발생하는 시점에 관계 조건을 충족시킨다. 부서 없이 사원을 등록할 수 없고, 고객 없이 주문을 넣을 수 없다. 관계를 어기면 데이터 자체가 들어가지 않는다.

DW 는 데이터를 적재하는 시점에 관계를 맞춘다. 원본에 누락이 있으면 Unknown으로 채운다. 과거 시점 데이터가 필요하면 이력에서 끌어와서 만든다. 적재 과정에서 일정한 개입을 통해 관계를 맞추는 방식이다.

OLTPDW
목적트랜잭션 처리, 정합성 보장분석 데이터 적재, 접근 경로 설계
관계 충족 시점데이터 발생 시점데이터 적재 시점
누락 데이터허용하지 않음Unknown으로 처리
이력 관리현재 상태 중심시점 데이터 생성
설계 방향정규화 (중복 최소화)접근 경로 중심 (분석 편의)

이 차이를 알고 나면 DW 모델에서 “왜 이렇게 했지?“라는 의문이 상당 부분 풀린다.

클라우드 시대에도 같은 이야기

이전 글 에서 클라우드 DW의 물리적 제약 변화를 다뤘다. 스토리지가 싸졌고, 컬럼나 스토리지 덕에 조인 패턴이 달라졌고, ELT 패러다임으로 전환됐다.

물리적 제약은 바뀌었지만 OLTP와 DW의 목적 차이는 여전하다. BigQuery를 쓰든 Synapse를 쓰든, 분석 데이터에 대한 접근 경로를 설계해야 하는 건 마찬가지다. Unknown 레코드가 필요한 상황도, 시점 데이터를 관리해야 하는 요건도 인프라가 바뀐다고 없어지지 않는다.

달라진 게 있다면 이력 관리를 더 적극적으로 할 수 있게 됐다는 것 정도다. 스토리지 부담이 줄어서 SCD Type 2 방식으로 차원 이력을 쌓아도 부담이 덜하다. SCD 유형별 설계 방식은 시리즈 뒤쪽에서 다룬다.

다음 글에서는 ERD 표기법 차이를 짚어본다. 같은 관계를 그려놓아도 Crow’s Foot이냐 IDEF1X이냐에 따라 해석이 달라진다. 표기법을 모르면 같은 모델을 보고도 서로 다른 이야기를 하게 된다.