데이터 웨어하우스·레이크·레이크하우스 차이: 매출 집계와 환불 처리


1장. 같은 9월 매출인데 어제는 5,500원, 오늘은 2,500원이다#

9월 매출 보고서를 다시 열었더니 숫자가 달라졌다고 하겠습니다.

어제:

9월 매출
5,500원

오늘:

9월 매출
2,500원

데이터가 망가진 것일까요?

반드시 그렇지는 않습니다.

10월에 9월 주문의 일부가 환불되었고, 보고서가 주문월 기준 최종 순매출을 보여주도록 설계되어 있다면 과거 9월 숫자가 바뀔 수 있습니다.

반대로 현금 흐름 보고서라면:

9월
+5,500원

10월
-3,000원

으로 보여줄 수도 있습니다.

같은 환불 사건을 사용하지만 지표의 정의가 다릅니다.

분석 시스템에서 가장 중요한 것은 숫자가 항상 고정되어 있는 것이 아닙니다.

왜 숫자가 바뀌었는지 설명할 수 있는가?

가 더 중요합니다.


2장. 분석 시스템을 설계하기 전에 한 행의 의미부터 정해야 한다#

다음 주문 데이터가 있다고 하겠습니다.

주문ID 주문시각 고객 상품 수량 단가
501 2026-09-01 09:00 7 연필 2 500
501 2026-09-01 09:00 7 공책 1 3,000
502 2026-09-02 11:00 8 연필 3 500

주문 501은 한 건입니다.

하지만 품목은 두 개입니다.

따라서 같은 데이터를 두 가지 관점으로 볼 수 있습니다.

주문 기준
→ 501, 502
→ 2건
주문 품목 기준
→ 3행

이 차이를 먼저 정하지 않으면 COUNT(*), SUM() 같은 기본 집계부터 틀릴 수 있습니다.


3장. 팩트의 그레인은 “한 행이 무엇인가”를 뜻한다#

분석 모델링에서 그레인은 팩트 테이블의 한 행이 무엇을 의미하는지를 정의합니다.

이번 예제에서는 다음과 같이 정하겠습니다.

팩트 한 행 = 주문 품목 한 줄

따라서 주문 501은 두 행입니다.

501 / 연필 / 2개

501 / 공책 / 1개

주문 502는 한 행입니다.

502 / 연필 / 3개

이 정의가 모든 집계의 출발점입니다.


4장. 품목 기준 매출을 직접 계산해 보자#

연필 두 개:

2 × 500
=
1,000원

공책 한 개:

1 × 3,000
=
3,000원

주문 501 합계:

1,000 + 3,000
=
4,000원

주문 502:

3 × 500
=
1,500원

전체:

4,000 + 1,500
=
5,500원

입니다.


5장. 주문 총액을 품목마다 반복 저장하면 합계가 부풀 수 있다#

주문 501의 총액 4,000원을 품목 행마다 넣었다고 하겠습니다.

주문ID 상품 주문총액
501 연필 4,000
501 공책 4,000

이 열을 단순 합산하면:

4,000 + 4,000
=
8,000원

입니다.

실제 주문 금액은 4,000원인데 두 배가 됐습니다.

문제는 SUM() 함수가 아닙니다.

행의 단위와 측정값의 단위가 맞지 않았던 것입니다.


6장. 주문 수를 COUNT 별표로 세면 품목 수가 될 수 있다#

이번 팩트의 그레인은 주문 품목입니다.

따라서:

SELECT COUNT(*)
FROM fact_order_item;

결과는:

3

입니다.

하지만 실제 주문 수는 2건입니다.

주문 수를 구하려면:

SELECT COUNT(DISTINCT order_id)
FROM fact_order_item;

처럼 그레인에 맞는 계산을 해야 합니다.


7장. 팩트와 차원을 분리하는 이유#

분석 데이터는 대체로 다음 두 종류로 나누어 생각할 수 있습니다.

팩트#

발생한 사건이나 측정값입니다.

예:

판매 수량

판매 단가

매출액

할인액

환불액

차원#

팩트를 분류하는 기준입니다.

예:

날짜

상품

고객

지역

채널

팩트는 “무슨 일이 일어났는가”에 가깝고 차원은 “어떤 기준으로 나누어 볼 것인가”에 가깝습니다.


8장. 주문 분석용 스타 스키마를 그려보면#

erDiagram
    DIM_DATE ||--o{ FACT_ORDER_ITEM : date
    DIM_PRODUCT ||--o{ FACT_ORDER_ITEM : product
    DIM_CUSTOMER ||--o{ FACT_ORDER_ITEM : customer

    FACT_ORDER_ITEM {
        bigint order_id
        int item_no
        int date_key FK
        int product_key FK
        int customer_key FK
        int quantity
        decimal unit_price
        decimal gross_amount
    }

    DIM_DATE {
        int date_key PK
        date calendar_date
        int year
        int month
    }

    DIM_PRODUCT {
        int product_key PK
        string product_id
        string product_name
        string category
    }

    DIM_CUSTOMER {
        int customer_key PK
        string customer_id
        string region
    }

팩트 중심에 여러 차원이 직접 연결되어 있습니다.

이런 형태를 스타 스키마라고 부릅니다.


9장. 스타 스키마에서 중요한 것은 별 모양보다 조인 의미다#

스타 스키마라고 해서 그림만 별 모양이면 되는 것은 아닙니다.

더 중요한 질문은 다음입니다.

이 팩트는 어떤 시점의 상품 정보를 가리키는가?

고객의 현재 지역인가?

주문 당시 지역인가?

상품의 현재 분류인가?

판매 당시 분류인가?

차원 데이터를 어떤 시점 기준으로 연결하는지에 따라 같은 매출도 다른 범주에 들어갈 수 있습니다.


10장. 상품명이 바뀌면 과거 주문도 바뀌어 보여야 할까#

9월 1일 상품명:

연필

9월 3일 상품명 변경:

고급 연필

이제 9월 1일 주문 보고서를 다시 조회합니다.

정책은 두 가지가 가능합니다.

현재 상품명 기준#

9월 1일 판매
→ 고급 연필

거래 당시 상품명 기준#

9월 1일 판매
→ 연필

둘 다 상황에 따라 맞을 수 있습니다.

중요한 것은 어느 정책을 사용하는지 명시하는 것입니다.


11장. 상품의 업무 ID와 분석 차원의 키는 다를 수 있다#

원본 상품 ID:

P100

은 그대로 유지될 수 있습니다.

하지만 분석 차원에서는 상품 이력이 바뀔 때마다 새로운 차원 행을 만들 수 있습니다.

예:

product_key product_id 이름 유효 시작 유효 종료
101 P100 연필 2026-01-01 2026-09-03
205 P100 고급 연필 2026-09-03 이후

product_id는 동일하지만 product_key는 다릅니다.

이처럼 분석용 대체키를 사용할 수 있습니다.


12장. 과거 팩트는 당시 차원 버전을 가리킬 수 있다#

9월 1일 주문은:

product_key = 101

을 가리킵니다.

9월 5일 주문은:

product_key = 205

를 가리킵니다.

그러면 과거 보고서를 다시 조회해도 당시 상품명이 유지됩니다.

9월 1일
→ 연필

9월 5일
→ 고급 연필

입니다.


13장. 상품ID만으로 차원의 모든 버전과 조인하면 매출이 중복될 수 있다#

팩트:

product_id = P100
매출 = 1,000

차원:

P100 / 연필

P100 / 고급 연필

두 행이 있습니다.

단순히:

ON fact.product_id = dim.product_id

로 조인하면 팩트 한 행이 차원 두 행과 모두 연결될 수 있습니다.

결과:

1,000
1,000

이 됩니다.

매출이 2,000으로 부풀 수 있습니다.


14장. 차원 이력은 레이블 문제이면서 조인 카디널리티 문제다#

차원 이력을 잘못 연결하면 단순히 상품 이름이 잘못 표시되는 데서 끝나지 않습니다.

팩트 행 자체가 여러 번 복제될 수 있습니다.

따라서 다음 둘을 모두 확인해야 합니다.

올바른 상품 버전을 보여주는가?

팩트가 정확히 한 차원 버전에만 연결되는가?

이 점이 매우 중요합니다.


15장. 데이터 웨어하우스는 무엇을 위한 저장소인가#

데이터 웨어하우스는 여러 업무 시스템의 데이터를 분석 목적으로 통합하고 정리하는 구조입니다.

대표적으로 다음 요구를 다룹니다.

매출 분석

고객 분석

상품 분석

기간 비교

경영 지표

이력 분석

운영 DB처럼 개별 주문 처리 속도에 초점을 맞추기보다 일관된 분석 기준과 대규모 집계에 초점을 둡니다.


16장. 전통적인 데이터 웨어하우스의 특징#

데이터 웨어하우스는 전통적으로 다음 성질로 많이 설명됩니다.

주제 지향

통합

시간성

비휘발성

그러나 “비휘발성”을:

DW는 절대 UPDATE하지 않는다

로 이해하면 지나치게 단순합니다.

실제 운영에서는:

정정 데이터

차원 이력

재처리

늦게 도착한 데이터

오류 수정

등이 존재합니다.


17장. 데이터 마트는 특정 질문과 사용자를 위한 분석 영역이다#

전체 회사 DW가 있다고 하겠습니다.

그 안에서 영업팀이 자주 사용하는 데이터만 별도로 구성할 수 있습니다.

예:

일별 매출

상품별 매출

영업지역별 매출

목표 대비 실적

이런 영역을 데이터 마트로 구성할 수 있습니다.

flowchart LR
    S["업무 시스템"] --> DW["Data Warehouse"]
    DW --> M1["영업 마트"]
    DW --> M2["재무 마트"]
    DW --> M3["고객 마트"]

18장. 마트는 단순히 작은 DW라는 뜻만은 아니다#

영업팀은 주문 발생일 기준 매출이 중요할 수 있습니다.

재무팀은 결제·환불의 현금 흐름 시점이 더 중요할 수 있습니다.

같은 원천 데이터라도 소비자의 질문이 다릅니다.

따라서 마트에서는:

지표 정의

집계 주기

시간 기준

권한

도 함께 달라질 수 있습니다.


19장. 데이터 레이크는 다양한 원형 데이터를 보존하기 좋다#

업무 시스템에서 들어오는 원천 데이터가 다양하다고 하겠습니다.

CSV

JSON

애플리케이션 로그

이미지

Parquet

IoT 파일

이벤트 로그

모든 데이터를 처음부터 관계형 DW 구조로 만들 필요는 없습니다.

데이터 레이크는 이런 다양한 원천 데이터를 비교적 원형에 가깝게 보존하는 저장 계층으로 활용할 수 있습니다.


20장. 데이터 레이크가 “스키마가 전혀 없는 곳”이라는 설명은 부정확하다#

JSON 파일을 그대로 저장한다고 하겠습니다.

그래도 최소한 다음은 알아야 합니다.

어디서 왔는가?

언제 수집됐는가?

어떤 버전인가?

필드 의미는 무엇인가?

개인정보가 들어 있는가?

즉 파일 형식이 자유롭다고 데이터 계약까지 없어지는 것은 아닙니다.


21장. 원시 레이크에는 수신 시각도 남겨 두는 것이 좋다#

주문 발생 시각:

2026-09-01 09:00

하지만 데이터가 레이크에 도착한 시각:

2026-09-01 09:07

이라고 하겠습니다.

두 시각은 다릅니다.

event_time
→ 주문 발생 시각

ingested_at
→ 분석 플랫폼 수신 시각

늦게 도착한 데이터나 재처리 문제를 분석하려면 둘 다 필요할 수 있습니다.


22장. 분석 데이터는 원시·정제·서비스 계층으로 나눌 수 있다#

한 가지 예시를 들면:

flowchart LR
    R["Raw<br/>원본 파일"] --> C["Clean<br/>정제·표준화"]
    C --> D["DW<br/>팩트·차원"]
    D --> M["Mart<br/>보고서용 집계"]

원시 계층에서는 원본을 최대한 보존합니다.

정제 계층에서는:

형식 통일

자료형 변환

중복 제거

유효성 검사

를 수행합니다.

DW에서는 분석 기준을 통합합니다.

마트에서는 소비자별 결과를 만듭니다.


23장. ETL과 ELT는 변환 시점의 차이다#

ETL:

Extract
↓
Transform
↓
Load

즉 적재 전에 변환합니다.

ELT:

Extract
↓
Load
↓
Transform

원본을 먼저 적재하고 분석 플랫폼 안에서 변환합니다.

둘 중 하나가 항상 더 좋은 것은 아닙니다.


24장. 원본 보존과 개인정보 요구가 충돌할 수도 있다#

“레이크에는 무조건 원본을 그대로 보존하자”고 정했다고 하겠습니다.

그런데 원천 데이터에 불필요한 민감 정보가 포함되어 있습니다.

정책상 해당 정보를 분석 플랫폼에 적재해서는 안 된다면 수집 전에 제거하거나 마스킹해야 할 수 있습니다.

따라서:

Raw
=
아무 가공도 하지 않은 데이터

라고 기계적으로 정의하면 안 됩니다.


25장. 독립적인 SQL 예제로 월 매출을 계산해 보자#

이번에는 별도의 실습 데이터를 사용하겠습니다.

WITH item (
    order_id,
    item_no,
    order_date,
    quantity,
    unit_price
) AS (
    VALUES
        ('O1', 1, '2026-09-01', 2, 10000),
        ('O1', 2, '2026-09-01', 1, 5000),
        ('O2', 1, '2026-09-02', 1, 10000)
)
SELECT
    substr(order_date, 1, 7) AS month,
    SUM(quantity * unit_price) AS gross_amount,
    COUNT(*) AS item_rows,
    COUNT(DISTINCT order_id) AS orders
FROM item
GROUP BY substr(order_date, 1, 7);

결과는:

2026-09
총매출 = 35,000
품목 행 = 3
주문 수 = 2

입니다.


26장. 여기에서도 그레인이 모든 숫자를 결정한다#

O1은 품목이 두 개입니다.

A × 2
B × 1

O2는 한 개입니다.

팩트 행:

3개

주문:

2건

총매출:

35,000원

각 숫자는 서로 다른 계산 기준을 가집니다.


27장. 주문 헤더 금액을 품목과 조인해 합산하면 문제가 생길 수 있다#

O1 헤더:

order_total = 25,000

품목은 두 행입니다.

조인 결과:

O1 / item1 / 25,000

O1 / item2 / 25,000

이 상태에서:

SUM(order_total)

을 하면 O1의 금액이 두 번 들어갑니다.

분석 SQL 오류의 상당수는 이런 그레인 혼합에서 발생합니다.


28장. 이제 늦게 도착한 환불을 넣어 보자#

9월 1일 주문 501에서 공책 3,000원을 환불합니다.

환불 확정 시각:

2026-10-04

입니다.

이제 보고서에서 선택해야 합니다.

환불을 9월 매출에 반영할 것인가?

10월 환불로 기록할 것인가?

이것은 기술 문제가 아니라 지표 정의 문제입니다.


29장. 주문월 기준 최종 순매출#

원래 9월 매출:

5,500원

3,000원 환불:

-3,000원

최종 9월 순매출:

2,500원

으로 재계산할 수 있습니다.

이 방식에서는 과거 보고서가 바뀝니다.


30장. 환불 발생월 기준 현금 흐름#

다른 보고서는 이렇게 보여줄 수 있습니다.

9월:

판매
+5,500원

10월:

환불
-3,000원

이 경우 9월 원매출은 그대로 남습니다.

월별 숫자가 다르지만 잘못된 것은 아닙니다.

지표의 목적이 다릅니다.


31장. 두 방식의 차이를 표로 보면#

보고서 9월 10월
주문월 기준 최종 순매출 2,500 0
발생월 기준 현금 흐름 5,500 -3,000

같은 환불 한 건을 서로 다른 시간 축에서 본 결과입니다.


32장. 가장 위험한 것은 두 방식을 동시에 적용하는 것이다#

9월 값을:

5,500
→ 2,500

으로 이미 정정했습니다.

그런데 10월에서도 다시:

-3,000

을 같은 누적 순매출에서 차감했다고 하겠습니다.

전체 기간 누적값에서 환불을 두 번 반영할 수 있습니다.

원매출
5,500

1차 차감
-3,000

2차 차감
-3,000

결과
-500

실제 최종 순매출은 2,500원입니다.


33장. 환불 사실을 삭제보다 별도 사건으로 저장할 수도 있다#

원래 매출 행:

+3,000

을 지우는 대신:

환불 사건
-3,000

을 추가할 수 있습니다.

장점은 거래 이력을 보존하기 쉽다는 것입니다.

판매 발생

환불 발생

두 사건이 모두 남습니다.


34장. 환불 팩트도 그레인을 명확히 해야 한다#

환불 한 행이 무엇인지 정해야 합니다.

가능한 예:

주문 전체 환불 한 건

주문 품목 환불 한 건

환불 결제 트랜잭션 한 건

부분 환불이 가능한 서비스라면 주문 전체 단위 하나만으로는 부족할 수 있습니다.


35장. 발생 시각과 정정 시각을 함께 저장하면 보고서 변화가 설명된다#

다음 필드를 생각할 수 있습니다.

ordered_at

refunded_at

loaded_at

corrected_at

이렇게 하면:

왜 10월 5일에 다시 만든 9월 보고서가 전날과 달라졌는가?

라는 질문에 답하기 쉬워집니다.


36장. 데이터 웨어하우스에서 중요한 것은 재현 가능성이다#

9월 30일에 발행한 보고서:

5,500원

10월 10일에 재계산한 9월 최종 순매출:

2,500원

둘 다 필요할 수 있습니다.

따라서 분석 시스템은 상황에 따라:

당시 보고서

현재 재계산 보고서

를 구분할 수 있어야 합니다.


37장. 레이크하우스는 파일 기반 분석 저장소에 테이블 관리 기능을 더한다#

데이터 레이크에 Parquet 같은 파일이 쌓여 있다고 하겠습니다.

단순 파일만 관리하면 다음 작업이 복잡할 수 있습니다.

동시 쓰기

스키마 변경

정정

삭제

일관된 스냅샷 조회

레이크하우스 접근은 파일 저장소 위에 테이블 메타데이터와 스냅샷 관리 같은 기능을 더해 이런 문제를 다루려 합니다.


38장. 레이크하우스의 핵심은 “레이크+DW를 무조건 하나로 합쳤다”보다 더 구체적으로 봐야 한다#

대표적인 테이블 형식으로는 다음과 같은 기술이 있습니다.

Apache Iceberg

Delta Lake

Apache Hudi

구현과 생태계는 서로 다릅니다.

공통적으로는 파일 기반 분석 테이블에서 다음과 같은 문제를 다루는 데 초점을 둘 수 있습니다.

스냅샷

스키마 진화

파티션 진화

동시 갱신

증분 처리

39장. 시간 여행은 과거 보고서를 재현하는 데 유용할 수 있다#

예를 들어 스냅샷이 다음과 같습니다.

S1
초기 적재

S2
9월 2일 주문 반영

S3
주문 502 단가 정정

S2의 합계:

5,500원

S3에서 단가가 수정되어:

5,800원

이 되었다고 하겠습니다.

스냅샷을 유지하고 있다면 과거 S2 상태를 조회해 당시 보고서를 다시 확인할 수 있습니다.


40장. 시간 여행이 무한 과거 보존을 의미하지는 않는다#

오래된 스냅샷과 참조 파일을 정리했다고 하겠습니다.

그렇다면 예전 스냅샷 메타데이터가 있더라도 필요한 데이터 파일이 이미 삭제되어 조회할 수 없을 수 있습니다.

따라서:

time travel 지원
=
영구 보존

은 아닙니다.

실제 보존 기간을 별도로 설계해야 합니다.


41장. 레이크하우스가 지표 정의를 대신 결정해 주지는 않는다#

레이크하우스가 스냅샷을 잘 관리한다고 하겠습니다.

그래도 여전히 다음 질문은 사람이 정해야 합니다.

환불을 어느 월에 반영할 것인가?

상품 분류는 당시 기준인가 현재 기준인가?

주문 수는 어떤 상태를 포함하는가?

취소 주문은 총매출에 포함하는가?

저장 기술이 좋아져도 지표 정의 문제는 사라지지 않습니다.


42장. 데이터 레이크와 레이크하우스의 차이를 지나치게 단순화하지 말자#

다음처럼 외우면 부족합니다.

데이터 레이크
→ 파일 저장

레이크하우스
→ ACID 가능한 레이크

실제로는 제품과 엔진, 테이블 포맷, 기능 버전에 따라 지원 범위가 다릅니다.

확인할 것은 다음입니다.

어떤 엔진이 읽고 쓸 수 있는가?

동시 쓰기는 어떻게 처리되는가?

스냅샷 보존은 어떻게 되는가?

스키마 변경을 어떻게 다루는가?

43장. DW와 레이크하우스도 반드시 경쟁 관계인 것은 아니다#

조직에 따라:

원시 데이터
→ 레이크

정제 데이터
→ 레이크하우스 테이블

핵심 재무 지표
→ DW

부서 보고서
→ 마트

처럼 함께 사용할 수 있습니다.

아키텍처의 이름보다 각 계층의 책임을 명확하게 나누는 것이 중요합니다.


44장. 분석 플랫폼 전체 흐름을 그려보면#

flowchart LR
    A["주문·결제·상품 시스템"] --> B["수집"]
    B --> C["Raw Lake"]
    C --> D["정제·표준화"]
    D --> E["DW / Lakehouse Table"]
    E --> F["매출 Mart"]
    F --> G["BI 보고서"]

각 단계에서 다른 검증이 필요합니다.


45장. 원시 계층에서는 원본 재현 가능성을 본다#

확인할 내용:

원본 파일이 존재하는가?

수신 시각이 있는가?

스키마 버전을 아는가?

같은 파일이 두 번 들어왔는가?

원천에서 잘못된 데이터를 놓치면 이후 모든 계층에서 문제가 반복됩니다.


46장. 정제 계층에서는 형식과 중복을 검사한다#

예:

order_id 비어 있음

quantity 음수

currency 형식 오류

같은 order_id + item_no 중복

등을 확인합니다.

특히 재처리 시 같은 품목이 두 번 들어가면 매출이 두 배가 될 수 있습니다.


47장. 중복 적재를 막을 자연키 또는 이벤트 키가 필요하다#

이번 팩트의 한 행은 주문 품목입니다.

따라서 자연스럽게:

order_id

item_no

조합을 품목 식별 기준으로 사용할 수 있습니다.

재적재할 때:

501 / 1

이 이미 존재하면 새 행으로 추가할지, 갱신할지 정책을 정해야 합니다.


48장. 배치를 다시 돌릴 때 매출이 두 배가 되는 문제#

첫 적재:

9월 매출
5,500

배치 실패 메시지를 보고 같은 파일을 다시 적재했습니다.

중복 검사가 없다면:

5,500
+
5,500
=
11,000

이 될 수 있습니다.

재처리 가능한 파이프라인에서는 멱등성이 중요합니다.


49장. 환불 ID도 중복 처리 기준이 필요하다#

환불 이벤트:

refund_id = R100

order_id = 501

item_no = 2

amount = 3000

가 있습니다.

같은 환불 이벤트가 다시 들어왔다고 하겠습니다.

단순히 매번:

-3,000

을 추가하면 두 번 차감됩니다.

따라서 refund_id 같은 식별자를 이용해 중복 반영을 방지할 수 있습니다.


50장. 분석 지표에는 포함·제외 조건까지 포함해야 한다#

“매출”이라고만 하면 부족합니다.

다음 주문 상태가 있다고 하겠습니다.

결제대기

결제완료

배송중

배송완료

취소

환불

어떤 상태를 매출로 볼지 정해야 합니다.

예:

총매출
→ 결제완료 이후 금액

순매출
→ 총매출 - 확정 환불액

같은 정의가 필요합니다.


51장. 주문월·결제월·배송월은 서로 다른 시간 축이다#

주문:

9월 30일 23:58

결제:

10월 1일 00:01

배송:

10월 2일

어느 월 매출일까요?

업무 지표에 따라 달라질 수 있습니다.

주문 기준
→ 9월

결제 기준
→ 10월

배송 기준
→ 10월

날짜 컬럼 하나만 보고 결정하면 안 됩니다.


52장. 시간대까지 명확히 해야 월 경계가 흔들리지 않는다#

저장 시각이 UTC라고 하겠습니다.

2026-09-30 15:30 UTC

한국 시간으로는:

2026-10-01 00:30 KST

입니다.

UTC 월로 집계하면 9월입니다.

한국 영업일 기준이면 10월입니다.

따라서:

저장 시간대

업무 시간대

보고서 시간대

를 구분해야 합니다.


53장. 데이터 마트에서는 지표 이름만 보고 계산하지 않게 만들어야 한다#

monthly_sales라는 컬럼 하나만 있으면 다음을 알기 어렵습니다.

총매출인가?

환불 차감 후인가?

주문월 기준인가?

결제월 기준인가?

부가세 포함인가?

따라서 지표 정의와 메타데이터가 필요합니다.


54장. 좋은 분석 데이터는 숫자의 계보를 추적할 수 있다#

BI 화면에 다음 숫자가 있습니다.

9월 순매출
2,500원

이 숫자를 다음 단계로 추적할 수 있어야 합니다.

flowchart TD
    R["보고서 2,500원"] --> M["매출 Mart"]
    M --> F["Fact Order Item / Refund"]
    F --> C["정제 데이터"]
    C --> S["원본 주문·환불"]

어떤 원천 데이터와 변환 규칙 때문에 2,500원이 되었는지 설명할 수 있어야 합니다.


55장. 숫자가 바뀌면 변경 원인을 한 줄로 연결할 수 있어야 한다#

예를 들어:

보고서
9월 순매출 5,500 → 2,500

원인:

10월 4일

refund_id R100

order 501

item 2

-3,000원

처리:

10월 4일 11:20

9월 구매월 기준 순매출 재계산

처럼 연결할 수 있으면 분석 신뢰도가 높아집니다.


56장. 과거 숫자를 절대 바꾸지 않는 것이 항상 좋은 것도 아니다#

결제 금액이 잘못 들어갔습니다.

실제
6,000원

적재
60,000원

오류가 확인됐습니다.

과거 보고서를 절대 변경하지 않는 정책이라면 잘못된 60,000원을 계속 유지하게 됩니다.

따라서 분석 시스템에서는:

당시 발표값

현재 정정값

을 구분해 관리하는 것이 더 유용할 수 있습니다.


57장. 오타 수정과 업무 사건 변경도 구분해야 한다#

상품명이:

고급연필

인데 실수로:

고급연필ㄹ

로 저장됐다고 하겠습니다.

이런 오타 수정은 과거 보고서에도 새 이름을 보여줘도 문제가 없을 수 있습니다.

반면 상품 분류가 실제로 9월 10일부터 변경됐다면:

9월 9일 판매
→ 기존 분류

9월 10일 이후
→ 새 분류

로 이력을 남기는 것이 중요할 수 있습니다.

모든 차원 변경을 같은 방식으로 처리할 필요는 없습니다.


58장. 데이터 플랫폼을 고를 때 확인해야 할 질문#

  1. 분석 팩트의 한 행은 무엇인가?
  2. 합산 가능한 측정값은 무엇인가?
  3. 주문·품목·결제·환불 중 어느 사건을 저장하는가?
  4. 주문 수를 어떤 그레인으로 계산하는가?
  5. 상품·고객 속성은 현재값인가 당시값인가?
  6. 늦게 도착한 데이터는 과거 보고서를 수정하는가?
  7. 환불은 원주문 월과 환불 월 중 어디에 반영하는가?
  8. 중복 적재를 어떤 키로 막는가?
  9. 원본 파일을 얼마나 오래 보존하는가?
  10. 과거 보고서를 어느 스냅샷으로 재현할 수 있는가?
  11. BI 숫자에서 원본까지 계보를 추적할 수 있는가?
  12. 개인정보를 어느 단계에서 제거하는가?
  13. 데이터 레이크와 DW의 역할이 겹치지 않는가?
  14. 레이크하우스 스냅샷 보존 기간은 충분한가?
  15. 재처리해도 같은 결과가 나오는가?

59장. DW·마트·레이크·레이크하우스를 다시 비교하면#

구조 중심 역할 주문 예제
데이터 레이크 다양한 원본 보존 주문 JSON·CSV·이벤트 원본
데이터 웨어하우스 통합된 분석 팩트·차원 주문품목 팩트·상품·고객 차원
데이터 마트 특정 조직·질문의 결과 영업 월매출·상품별 실적
레이크하우스 파일 기반 분석 테이블 관리 강화 원본·정제 데이터를 스냅샷 가능한 테이블로 관리

서로 완전히 배타적인 구조가 아닙니다.


60장. 저장 기술보다 먼저 지표의 의미를 통일해야 한다#

다음 상황을 생각해 보겠습니다.

DW:

9월 매출
2,500

레이크하우스:

9월 매출
5,500

마트:

9월 매출
8,000

저장 기술이 달라서 숫자가 달라진 것처럼 보일 수 있습니다.

하지만 실제 원인은:

DW
→ 환불 차감

레이크하우스
→ 환불 미반영

마트
→ 주문 총액 중복 합산

일 수 있습니다.

기술보다 지표 정의와 그레인을 먼저 맞춰야 합니다.


61장. 분석 시스템에서 가장 먼저 작성해야 할 표#

항목 정의 예
팩트 그레인 주문 품목 한 행
주문 수 distinct order_id
총매출 quantity × unit_price
순매출 총매출 - 확정 환불
매출 기준일 주문일
환불 보고 기준 구매월 정정
상품 분류 거래 당시 분류
고객 지역 주문 당시 지역
시간대 Asia/Seoul
중복 키 order_id + item_no
환불 중복 키 refund_id

이 표가 있으면 시스템과 도구가 달라져도 같은 기준을 유지하기 쉽습니다.


62장. 핵심 정리#

데이터 웨어하우스·데이터 마트·데이터 레이크·레이크하우스를 이해할 때 가장 먼저 구분해야 할 것은 제품 이름이 아닙니다.

분석 데이터 한 행이 무엇을 뜻하는가?

입니다.

이번 예제에서는:

팩트 한 행
=
주문 품목 하나

로 정했습니다.

그래서:

COUNT(*)
→ 품목 수

COUNT(DISTINCT order_id)
→ 주문 수

SUM(quantity × unit_price)
→ 품목 기준 매출

이라는 의미가 만들어집니다.

이 그레인을 무시하고 주문 헤더 총액을 품목 행마다 반복해 합치면 같은 주문을 여러 번 계산하게 됩니다.

데이터 웨어하우스는 분석을 위한 팩트·차원·이력과 공통 지표를 통합하는 데 초점을 둡니다.

데이터 마트는 특정 조직과 질문에 맞는 분석 결과를 제공합니다.

데이터 레이크는 다양한 원천과 파일 형식을 보존하는 데 유용합니다.

레이크하우스는 파일 기반 분석 데이터에 스냅샷·스키마 진화·동시 변경 관리 같은 테이블 기능을 더하는 접근입니다.

하지만 어떤 구조를 사용해도 다음 문제는 자동으로 해결되지 않습니다.

환불을 어느 월에 반영할 것인가?

상품 분류는 현재 기준인가 과거 기준인가?

중복 재적재를 어떻게 막을 것인가?

보고서가 바뀌었을 때 과거 결과를 어떻게 재현할 것인가?

특히 늦은 환불에서는 두 지표를 구분해야 합니다.

주문월 기준 최종 순매출
→ 과거 월을 정정

환불 발생월 기준 현금 흐름
→ 환불 월에 마이너스 기록

둘 다 필요할 수 있습니다.

하지만 같은 누적 지표에서 두 방식을 동시에 적용하면 환불을 두 번 차감할 수 있습니다.

분석 시스템의 신뢰성은 숫자가 절대 바뀌지 않는 데서 나오지 않습니다.

어떤 원천 사건과 변환 규칙, 차원 버전, 환불 정책 때문에 숫자가 바뀌었는지를 재현할 수 있는가?

여기에 달려 있습니다.

좋은 분석 플랫폼은 단순히 데이터를 많이 모으는 곳이 아닙니다.

보고서 한 칸의 숫자를 원본 사건까지 거슬러 올라가 설명할 수 있는 구조입니다.

이 페이지의 목차