📖 개요: 이 글에서 다루는 것
같은 데이터, 같은 결과를 내는 Calculation View인데 하나는 0.4초, 다른 하나는 4초가 걸리는 경우가 있습니다. 원인은 대부분 조인을 Star Join 노드로 모델링했는지, 개별 Graphical Join(Join 노드)을 쌓아 올렸는지의 차이에서 나옵니다. 이 글은 SAP HANA(온프레미스 2.0 및 HANA Cloud 기준)에서 두 방식의 구조적 차이, 옵티마이저가 다르게 동작하는 이유, 그리고 선택 기준을 실전 예제로 정리합니다.
- Star Join 노드와 Join 노드의 내부 처리 방식 차이 이해
- 조인 프루닝(Join Pruning)이 성능을 좌우하는 원리 확인
- 카디널리티·Optimize Join Columns 설정으로 실행 계획 검증
- 스타 스키마 vs 복잡 변환 시나리오별 선택 기준 수립
📚 미리 알아두면 좋은 배경
Calculation View의 기본 노드(Projection, Aggregation, Join)를 만들어 본 경험, 스타 스키마(팩트/디멘전) 개념, 그리고 SQL의 조인 종류(Inner/Left Outer/Referential)에 대한 이해가 있으면 수월합니다. EXPLAIN PLAN이나 PlanViz를 한 번이라도 열어 봤다면 성능 검증 단계를 바로 따라올 수 있습니다.
🔧 사용한 버전
- SAP HANA Cloud 또는 SAP HANA 2.0 SPS05 이상 (XSA/HDI 컨테이너 기반 모델링 기준)
- 모델링 도구: SAP Business Application Studio(HANA Cloud) 또는 SAP Web IDE for SAP HANA
- HDI 컨테이너와 개발 권한, PlanViz 실행 권한
- 예제 도메인: 유통사 POS 판매 분석 — 팩트 테이블
TB_POS_SALES, 디멘전 테이블DIM_BRANCH(지점),DIM_ITEM(상품),DIM_FISCALDAY(영업일)
클래식 리포지토리(XS Classic) 환경에서도 개념은 동일하지만, 이 글의 예제는 HDI 기반 .hdbcalculationview 아티팩트를 기준으로 합니다.
💡 핵심 개념: 같은 조인, 다른 엔진 동작
비유하자면 Graphical Join은 "조인을 하나씩 계약서 쓰듯 순서대로 처리하는 방식"이고, Star Join은 "팩트를 중앙에 두고 디멘전 전체를 옵티마이저에게 한 번에 넘겨 알아서 최적화하게 하는 방식"입니다.
Graphical Join(Join 노드)은 두 입력을 받는 개별 노드입니다. 조인이 3개 필요하면 Join 노드를 3층으로 쌓아야 하고, 각 노드는 논리적으로 독립된 중간 결과를 만듭니다. 옵티마이저가 노드를 병합해 최적화하긴 하지만, 카디널리티 정보가 없으면 요청하지 않은 컬럼의 조인까지 전부 실행하는 경우가 생깁니다.
Star Join 노드는 CUBE 타입 Calculation View에서 "With Star Join" 옵션을 켰을 때 최상단에 생기는 특수 노드입니다. 중앙에 팩트 데이터(Data Foundation 역할)를 두고, 주변에 Dimension 타입 Calculation View들을 연결합니다. 구조 자체가 "이것은 스타 스키마다"라고 옵티마이저에 선언하는 것과 같아서 다음 최적화가 일반적으로 더 잘 작동합니다.
- 조인 프루닝: 쿼리가 특정 디멘전의 컬럼을 요청하지 않으면 해당 디멘전 조인을 실행 계획에서 통째로 제거
- 집계 후 조인(Aggregate before Join): 팩트를 먼저 집계해 행 수를 줄인 뒤 디멘전과 조인
- OLAP 스타일 처리: 과거 Analytic View가 하던 컬럼 엔진 중심의 집계 최적화 경로 활용
반면 Join 노드를 계단식으로 쌓은 모델은 조인 순서가 모델 구조에 묶이고, 프루닝은 카디널리티(n..1, 1..1) 설정 + Optimize Join Columns 플래그가 정확할 때만 동작합니다. 성능 차이의 본질은 "엔진이 다르다"가 아니라 옵티마이저에게 주는 정보량과 자유도의 차이입니다.
| 구분 | Star Join 노드 | Graphical Join 노드 |
|---|---|---|
| 구조 | 팩트 1개 + Dimension 뷰 N개 | 노드당 입력 2개, 계단식 적층 |
| 조인 프루닝 | 구조적으로 유도됨 | 카디널리티/플래그 설정 필수 |
| 조인 상대 | Dimension 타입 CV만 가능 | 테이블·뷰·CV 모두 가능 |
| 유연성 | 등가 조인 중심, 제약 있음 | 비등가·복합 변환 가능 |
💻 실전 예제 3단계
1단계 — Graphical Join으로 판매 분석 뷰 만들기
먼저 Join 노드 두 개를 쌓아 지점·상품을 붙이는 기본형입니다. CV_POS_SALES_JOIN.hdbcalculationview의 핵심 구조는 다음과 같습니다.
<calculationView xsi:type="Calculation:JoinView" id="Join_Branch"
joinType="leftOuter" cardinality="CN_1"
optimizeJoinColumns="true">
<input node="#Proj_Sales">
<viewAttribute name="BRANCH_ID"/>
<viewAttribute name="ITEM_ID"/>
<viewAttribute name="NET_AMOUNT"/>
</input>
<input node="#Proj_Branch">
<viewAttribute name="BRANCH_ID"/>
<viewAttribute name="REGION_NAME"/>
</input>
<joinAttribute name="BRANCH_ID"/>
</calculationView>
포인트는 두 가지입니다. cardinality="CN_1"(n..1)은 "팩트 한 행에 지점 한 행"임을 선언해 조인 생략 가능성을 열어 주고, optimizeJoinColumns="true"는 조인 컬럼이 결과에 필요 없을 때 컬럼 자체를 제거하도록 허용합니다. 같은 방식으로 Join_Item 노드를 위에 쌓아 DIM_ITEM을 연결하면 2단 조인 모델이 완성됩니다.
2단계 — Star Join으로 재설계하고 실행 계획 비교
이번엔 CUBE + Star Join 타입으로 CV_POS_SALES_STAR를 만듭니다. 먼저 DIM_BRANCH, DIM_ITEM, DIM_FISCALDAY를 각각 Dimension 타입 Calculation View(CV_DIM_BRANCH 등)로 감쌉니다. Star Join 노드는 팩트 프로젝션을 중앙 입력으로 받고 디멘전 뷰들을 조인합니다.
<calculationView xsi:type="Calculation:StarJoinView" id="StarJoin">
<input node="#Proj_Sales"/> <!-- 중앙 팩트 -->
<input node="CV_DIM_BRANCH">
<joinAttribute name="BRANCH_ID"/>
</input>
<input node="CV_DIM_ITEM">
<joinAttribute name="ITEM_ID"/>
</input>
<input node="CV_DIM_FISCALDAY">
<joinAttribute name="SALES_DATE"/>
</input>
</calculationView>
이제 상품 그룹별 매출만 조회하는 쿼리로 두 뷰의 실행 계획을 비교합니다. 지점·영업일 컬럼은 요청하지 않는 것이 핵심입니다.
EXPLAIN PLAN SET STATEMENT_NAME = 'STAR_TEST' FOR
SELECT "ITEM_GROUP", SUM("NET_AMOUNT") AS "AMT"
FROM "CV_POS_SALES_STAR"
GROUP BY "ITEM_GROUP";
SELECT OPERATOR_NAME, TABLE_NAME
FROM EXPLAIN_PLAN_TABLE
WHERE STATEMENT_NAME = 'STAR_TEST';
Star Join 버전에서는 결과에 DIM_BRANCH, DIM_FISCALDAY가 아예 나타나지 않아야 정상입니다(프루닝 성공). Graphical Join 버전은 카디널리티를 지정하지 않았다면 세 테이블이 모두 스캔 대상으로 남는 것을 확인할 수 있습니다. 느린 쿼리 추적에는 M_EXPENSIVE_STATEMENTS 뷰를 함께 활용하면 운영 중 로깅 근거를 남길 수 있습니다.
3단계 — 프로덕션 수준: 검증 자동화와 보안
운영 반영 전에는 프루닝이 실제로 유지되는지 회귀 검증을 자동화하는 것이 권장됩니다. 배포 파이프라인에서 아래와 같은 검증 SQL을 돌려 "프루닝 대상 테이블이 계획에 등장하면 실패" 규칙을 두는 방식입니다.
-- 프루닝 회귀 테스트: DIM_BRANCH가 계획에 남아 있으면 위반
SELECT COUNT(*) AS VIOLATION_CNT
FROM EXPLAIN_PLAN_TABLE
WHERE STATEMENT_NAME = 'STAR_TEST'
AND TABLE_NAME LIKE '%DIM_BRANCH%';
성능·보안 관점의 마무리 점검 목록입니다.
- Referential Join 주의: 정합성이 확실할 때만 사용. 팩트에 고아 키가 있으면 디멘전 컬럼 요청 여부에 따라 결과 건수가 달라질 수 있음
- 계산 컬럼 위치: 조인 컬럼을 참조하는 계산 컬럼을 Star Join 위에 두면 프루닝이 깨질 수 있으므로 최상단 노드로 이동
- 파티셔닝 정렬: 대용량
TB_POS_SALES는SALES_DATE기준 range 파티션으로 스캔 범위 축소 - 보안: 지점 단위 접근 제어는 SQL 기반 Analytic Privilege를 Star Join 뷰에 적용하고, Dimension 뷰에는 별도 권한이 새지 않는지 확인
- PlanViz: 병목 확정은 EXPLAIN PLAN이 아니라 PlanViz의 실제 실행 시간으로 판단
⚠️ 자주 만나는 함정
Q1. Star Join인데 조인 프루닝이 안 됩니다.
가장 흔한 원인 세 가지: (1) 디멘전이 Dimension 타입 CV가 아니라 일반 테이블/CUBE라서 Star Join 의미가 약해진 경우, (2) 쿼리 최상단 계산 컬럼이 디멘전 컬럼을 참조하는 경우, (3) Inner Join + 카디널리티 미설정으로 옵티마이저가 결과 건수 변화 가능성을 배제하지 못하는 경우입니다. Left Outer + n..1 조합이 일반적으로 프루닝에 가장 유리합니다.
Q2. Graphical Join이 오히려 빠른 경우도 있나요?
있습니다. 팩트-팩트 조인, 비등가 조인, 조인 전에 복잡한 필터·유니온이 필요한 변환 파이프라인은 Star Join 구조에 맞지 않으며, 억지로 Dimension 뷰로 감싸면 오히려 계층만 깊어집니다. 이런 케이스는 Join 노드에 카디널리티를 정확히 지정하는 편이 낫습니다.
Q3. Star Join 노드에 팩트 테이블을 두 개 넣을 수 있나요?
불가능합니다. 중앙 입력은 하나입니다. 다중 팩트가 필요하면 팩트별 CUBE를 만들고 상위에서 Union 또는 Join 노드로 결합하는 구조가 일반적입니다.
Q4. 카디널리티를 잘못 지정하면 어떻게 되나요?
실제로는 1..n인데 n..1로 선언하면 옵티마이저가 조인을 생략해 잘못된 집계 결과가 나올 수 있습니다. 카디널리티는 성능 힌트이기 전에 데이터 계약이므로, 지정 전
GROUP BY 키 HAVING COUNT(*) > 1로 반드시 검증하세요.
🚀 이어서 살펴볼 주제
이번 예제를 확장하려면 (1) Union Pruning과 Constant Mapping으로 다중 팩트 시나리오 최적화, (2) Time Dimension 생성기를 활용한 DIM_FISCALDAY 대체, (3) SQL Analyzer(HANA Cloud)로 노드별 실행 시간 프로파일링, (4) Calculation View의 Debug Query 모드로 프루닝 시각화를 이어서 다뤄 보길 권합니다. 입력 파라미터 푸시다운과 Star Join의 조합도 성능 튜닝에서 자주 만나는 주제입니다.
📚 함께 보면 좋은 자료
- SAP HANA 모델링 가이드 — Star Join 및 조인 프루닝 개념
- SAP HANA 성능 튜닝 자료 — 카디널리티와 실행 계획 분석
- SAP Community — HANA 모델링 성능 실무 사례
댓글 0
아직 댓글이 없습니다.