스타 스키마와 스노플레이크 스키마
빠른 분석을 위한 데이터 모델링을 배웁니다
스타 스키마와 스노플레이크 스키마은(는) CoddyKit의 무료 SQL Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 SQL Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
데이터 웨어하우스 스키마란 무엇인가
트랜잭션(OLTP) 데이터베이스에서는 중복을 피하기 위해 데이터를 정규화합니다. 데이터 웨어하우스에서는 쿼리 속도를 위해 저장 공간을 늘리는 대신 의도적으로 비정규화하는 경우가 많습니다. 웨어하우스 테이블을 구성하는 대표적인 두 패턴은 스타 스키마와 스노플레이크 스키마입니다.
두 패턴 모두 중앙의 팩트 테이블을 차원 테이블이 둘러싸는 구조입니다. 차이점은 차원을 얼마나 정규화하는지에 있습니다.
팩트 테이블과 차원 테이블
팩트 테이블은 판매, 클릭, 배송과 같은 측정 가능한 이벤트를 저장합니다. 행이 많고 숫자 측정값과 차원을 가리키는 외래 키를 포함합니다.
차원 테이블은 각 이벤트의 맥락, 즉 누가, 무엇을, 언제, 어디서 발생했는지를 설명합니다. 차원 테이블은 행 수는 적지만 설명 열이 더 풍부합니다.
CREATE TABLE fact_sales (
sale_id SERIAL PRIMARY KEY,
date_key INT NOT NULL,
product_key INT NOT NULL,
customer_key INT NOT NULL,
store_key INT NOT NULL,
quantity INT NOT NULL,
revenue NUMERIC(12, 2) NOT NULL
);
CREATE TABLE dim_product (
product_key SERIAL PRIMARY KEY,
product_name VARCHAR(200),
category VARCHAR(100),
brand VARCHAR(100),
unit_price NUMERIC(10, 2)
);스타 스키마
스타 스키마에서는 모든 차원 테이블이 팩트 테이블에 직접 연결됩니다. 관계를 종이에 그려 보면 별처럼 보입니다. 팩트 테이블이 중심이고 차원 테이블이 꼭짓점입니다.
차원 테이블은 완전히 비정규화되어 있습니다. 일부 속성이 여러 행에서 반복되더라도 모든 설명 속성이 하나의 테이블에 저장됩니다.
-- Star schema: all product info in one flat dimension table
CREATE TABLE dim_product (
product_key SERIAL PRIMARY KEY,
product_name VARCHAR(200),
category_name VARCHAR(100), -- denormalized
subcategory VARCHAR(100), -- denormalized
brand_name VARCHAR(100), -- denormalized
brand_country VARCHAR(100), -- denormalized
unit_price NUMERIC(10, 2)
);
CREATE TABLE dim_date (
date_key INT PRIMARY KEY, -- e.g. 20240315
full_date DATE,
year INT,
quarter INT,
month INT,
month_name VARCHAR(20),
week INT,
day_of_week VARCHAR(10)
);스타 스키마 쿼리
평탄한 차원 테이블 덕분에 쿼리가 단순해집니다. 팩트 테이블을 하나 이상의 차원과 조인한 다음 집계하면 됩니다. 정규화된 테이블 연결 고리를 거치는 추가 조인이 필요하지 않습니다.
스타 스키마가 빠른 분석 쿼리를 제공하는 이유가 바로 여기에 있습니다. 조인 그래프가 얕기 때문입니다.
SELECT
d.year,
d.quarter,
p.category_name,
SUM(f.revenue) AS total_revenue,
SUM(f.quantity) AS units_sold
FROM fact_sales f
JOIN dim_date d ON d.date_key = f.date_key
JOIN dim_product p ON p.product_key = f.product_key
WHERE d.year = 2024
GROUP BY d.year, d.quarter, p.category_name
ORDER BY d.quarter, total_revenue DESC;스노플레이크 스키마
스노플레이크 스키마는 차원 테이블을 하위 차원으로 다시 나누어 더 정규화합니다. 예를 들어 dim_product 안에 category_name과 brand_name을 저장하는 대신, 별도의 dim_category 및 dim_brand 테이블을 만듭니다.
그 결과로 만들어지는 다이어그램은 서로 관련된 테이블이 가지처럼 뻗어 나가는 눈송이 모양입니다.
-- Snowflake schema: product dimension is normalized
CREATE TABLE dim_brand (
brand_key SERIAL PRIMARY KEY,
brand_name VARCHAR(100),
brand_country VARCHAR(100)
);
CREATE TABLE dim_category (
category_key SERIAL PRIMARY KEY,
category_name VARCHAR(100),
subcategory VARCHAR(100)
);
CREATE TABLE dim_product (
product_key SERIAL PRIMARY KEY,
product_name VARCHAR(200),
category_key INT REFERENCES dim_category(category_key),
brand_key INT REFERENCES dim_brand(brand_key),
unit_price NUMERIC(10, 2)
);스노플레이크 스키마 쿼리
스노플레이크 스키마를 쿼리하려면 테이블에 나뉘어 저장된 차원 데이터를 다시 조합하기 위해 더 많은 조인이 필요합니다. 쿼리 최적화기는 추가된 계층을 탐색해야 하므로 스타 스키마보다 지연 시간이 늘어날 수 있습니다.
하지만 정규화된 차원은 더 작고 일관됩니다. dim_brand의 한 행에서 브랜드 이름을 갱신하면 모든 곳에 자동으로 적용됩니다.
SELECT
d.year,
c.category_name,
b.brand_name,
SUM(f.revenue) AS total_revenue
FROM fact_sales f
JOIN dim_date d ON d.date_key = f.date_key
JOIN dim_product p ON p.product_key = f.product_key
JOIN dim_category c ON c.category_key = p.category_key
JOIN dim_brand b ON b.brand_key = p.brand_key
WHERE d.year = 2024
GROUP BY d.year, c.category_name, b.brand_name
ORDER BY total_revenue DESC;대리 키와 자연 키 비교
차원 테이블은 일반적으로 대리 키를 사용합니다. 대리 키는 원천 시스템의 자연 키가 아니라 웨어하우스가 생성하는 정수입니다(예: SERIAL).
대리 키는 원천 시스템이 변경되어도 안정적이고, 대규모 팩트 테이블에서 저장 공간을 적게 차지하며, 이력을 추적해야 하는 서서히 변하는 차원을 지원합니다.
-- Surrogate key (product_key) vs natural key (sku)
INSERT INTO dim_product (product_name, category_key, brand_key, unit_price)
VALUES ('Wireless Headphones', 3, 7, 89.99);
-- product_key is assigned by SERIAL -- the natural key (SKU) lives elsewhere
-- Natural key would be:
-- INSERT INTO dim_product (sku, product_name, ...)
-- VALUES ('WH-1000XM5', 'Wireless Headphones', ...);
-- Risky: SKU can be reused or reassigned by the source system날짜 차원
날짜 차원은 특별합니다. 거의 항상 존재하며 일반적으로 여러 해에 해당하는 날짜가 미리 채워져 있습니다. 파생 속성(연도, 분기, 월 이름, 회계 기간, 공휴일 여부)을 차원 테이블에 저장하면 쿼리 실행 시 이를 다시 계산하지 않아도 됩니다.
-- Populate dim_date for one year using generate_series
INSERT INTO dim_date (date_key, full_date, year, quarter, month, month_name, week, day_of_week)
SELECT
TO_CHAR(d, 'YYYYMMDD')::INT AS date_key,
d AS full_date,
EXTRACT(YEAR FROM d)::INT AS year,
EXTRACT(QUARTER FROM d)::INT AS quarter,
EXTRACT(MONTH FROM d)::INT AS month,
TO_CHAR(d, 'Month') AS month_name,
EXTRACT(WEEK FROM d)::INT AS week,
TO_CHAR(d, 'Day') AS day_of_week
FROM generate_series('2024-01-01'::DATE, '2024-12-31'::DATE, '1 day') AS d;서서히 변하는 차원(SCD 유형 2)
고객이 도시를 옮기거나 제품의 카테고리가 바뀌면 어떻게 될까요? 이력을 추적해야 합니다. SCD 유형 2는 변경될 때마다 새 차원 행을 삽입하고 이전 행에는 종료 날짜를 설정하여 닫습니다. 팩트 테이블 행은 여전히 이전 차원 키를 가리키므로 이력이 정확하게 보존됩니다.
-- SCD Type 2 customer dimension
CREATE TABLE dim_customer (
customer_key SERIAL PRIMARY KEY,
customer_id INT NOT NULL,
customer_name VARCHAR(200),
city VARCHAR(100),
country VARCHAR(100),
valid_from DATE NOT NULL,
valid_to DATE,
is_current BOOLEAN DEFAULT TRUE
);
-- When a customer moves, close old row and insert new one:
UPDATE dim_customer
SET valid_to = CURRENT_DATE - 1, is_current = FALSE
WHERE customer_id = 42 AND is_current = TRUE;
INSERT INTO dim_customer (customer_id, customer_name, city, country, valid_from, is_current)
VALUES (42, 'Alice Muller', 'Berlin', 'Germany', CURRENT_DATE, TRUE);스타와 스노플레이크 — 장단점
어느 스키마가 항상 더 좋은 것은 아닙니다. 우선순위에 따라 선택하세요:
- 스타 — 조인 수가 적고, 쿼리가 빠르며, ETL이 단순하지만 저장 비용이 높습니다. Tableau, Power BI 같은 읽기 중심 분석 도구에 적합합니다.
- 스노플레이크 — 정규화된 차원, 적은 중복, 쉬운 차원 갱신을 제공하지만 조인이 더 많습니다. 차원이 크거나 여러 팩트 테이블에서 공유될 때 더 적합합니다.
-- Checking how much storage the denormalized category column costs
-- in a large dim_product (star schema) vs a separate dim_category (snowflake)
SELECT
COUNT(*) AS total_products,
COUNT(DISTINCT category_name) AS unique_categories,
pg_size_pretty(
SUM(pg_column_size(category_name))
) AS category_storage
FROM dim_product;갤럭시 스키마(팩트 콘스텔레이션)
데이터 웨어하우스에 차원 테이블을 공유하는 여러 팩트 테이블이 있으면 그 결과를 갤럭시 스키마(또는 팩트 콘스텔레이션)라고 합니다. 예를 들어 소매 데이터 웨어하우스에는 매출과 반품을 위한 별도의 팩트 테이블이 있고, 두 테이블 모두 동일한 dim_product와 dim_date를 참조할 수 있습니다.
공유 차원은 일관된 필터링을 보장하고 팩트 간 비교를 간단하게 만듭니다.
CREATE TABLE fact_returns (
return_id SERIAL PRIMARY KEY,
date_key INT NOT NULL REFERENCES dim_date(date_key),
product_key INT NOT NULL REFERENCES dim_product(product_key),
customer_key INT NOT NULL,
quantity INT NOT NULL,
refund_amount NUMERIC(12, 2) NOT NULL
);
-- Cross-fact query: net revenue = sales - refunds
SELECT
d.year,
d.month,
SUM(s.revenue) AS gross_revenue,
SUM(r.refund_amount) AS total_refunds,
SUM(s.revenue) - COALESCE(SUM(r.refund_amount), 0) AS net_revenue
FROM dim_date d
LEFT JOIN fact_sales s ON s.date_key = d.date_key
LEFT JOIN fact_returns r ON r.date_key = d.date_key
WHERE d.year = 2024
GROUP BY d.year, d.month
ORDER BY d.month;스타 스키마와 스노플레이크 스키마
스타 스키마와 스노플레이크 스키마에 대한 이해도를 확인해 보세요.
레슨 요약
이 레슨에서는 데이터 웨어하우스의 두 가지 기본 설계 패턴을 살펴보았습니다:
- 스타 스키마 — 평면적이고 비정규화된 차원 테이블이 중앙의 팩트 테이블을 둘러싼 구조입니다. 조인이 적고 쿼리가 빠르며 저장 공간은 약간 더 많이 필요합니다.
- 스노플레이크 스키마 — 차원 테이블을 하위 차원으로 더 정규화한 구조입니다. 중복이 적고 갱신하기 쉽지만 조인이 더 많이 필요합니다.
- 팩트 테이블은 측정 가능한 이벤트를 저장하고, 차원 테이블은 누가, 무엇을, 언제, 어디서 했는지에 대한 맥락을 제공합니다.
- 대리 키는 이력의 정확성을 지키고 웨어하우스를 원천 시스템의 변경 사항과 분리합니다.
- SCD 유형 2는 기존 행을 덮어쓰지 않고 유효 날짜가 포함된 새 행을 추가하여 차원 이력을 추적합니다.
- 여러 팩트 테이블이 차원을 공유하면 설계는 갤럭시(팩트 콘스텔레이션) 스키마가 됩니다.
단순성과 속도가 중요하면 스타를, 차원이 크거나 자주 갱신되거나 여러 팩트 테이블에서 공유되면 스노플레이크를 선택하세요.
자주 묻는 질문
“스타 스키마와 스노플레이크 스키마” 강의는 무료인가요?
네 — “스타 스키마와 스노플레이크 스키마” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 SQL Academy 강의 전체를 잠금 해제할 수 있습니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“스타 스키마와 스노플레이크 스키마”에서 뭘 배우나요?
빠른 분석을 위한 데이터 모델링을 배웁니다 브라우저에서 직접 실행하는 실습 코드로 SQL Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
SQL Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 SQL Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“스타 스키마와 스노플레이크 스키마” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 SQL Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 SQL Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- OLTP와 OLAP 비교
- 팩트 테이블과 차원 테이블
- 스타 스키마와 스노플레이크 스키마
- 분석 쿼리 작성