왜 "느린 CDS 뷰"는 원인 찾기가 더 어려운가
ABAP CDS 뷰는 로직을 데이터베이스로 내려보내는 코드 푸시다운(Code Pushdown)의 핵심 도구지만, 바로 그 특성 때문에 성능 문제가 생기면 원인이 ABAP 레이어가 아닌 HANA 내부 실행 과정에 숨어버립니다. 이 글은 해결책 나열이 아니라 "느려진 원인을 어떤 순서로, 어떤 도구로 좁혀 들어가는가"라는 진단 방법론에 초점을 맞춥니다. 이 글을 끝까지 읽으면 다음을 할 수 있게 됩니다.
- ST05 SQL 트레이스로 CDS 뷰가 실제 실행한 SQL 문장을 확보한다
- 실행 계획(Explain Plan)과 PlanViz로 병목 연산자를 특정한다
- SAT(ABAP 프로파일러)로 DB 시간과 ABAP 시간을 분리해 판단한다
- SQLM/SWLT로 운영 시스템에서 상시 모니터링 체계를 만든다
읽기 전에 갖추면 좋은 배경
Open SQL과 조인·집계의 기본 문법, ADT(ABAP Development Tools)에서 CDS 뷰를 열고 활성화해 본 경험, 그리고 트랜잭션 코드를 실행할 수 있는 개발 시스템 접근 권한이 있으면 충분합니다. HANA 내부 구조 지식은 없어도 따라올 수 있도록 필요한 부분만 본문에서 설명합니다.
진단 환경과 도구 준비
이 글의 예제는 SAP S/4HANA 2023(ABAP Platform 2023, 7.58 계열) 온프레미스 기준으로 작성했으며, 대부분의 진단 절차는 7.50 이상이면 동일하게 적용됩니다. SAP BTP ABAP Environment(ABAP Cloud)에서는 GUI 트랜잭션 대신 ADT의 SQL Trace/Profiling 뷰와 SQL Monitor 앱을 사용한다는 차이만 있습니다. 준비물은 다음과 같습니다.
- ADT(Eclipse) — CDS 소스 확인, Dependency Analyzer, SQL 콘솔
- ST05 — SQL 트레이스 및 Explain Plan 진입점
- SAT — ABAP 런타임 프로파일러 (구 SE30의 후속 도구)
- SQLM / SWLT — 운영 부하 기반 SQL 모니터와 워크로드 분석
- 진단 권한: 일반적으로 S_ADMI_FCD, S_DEVELOP 등 트레이스 관련 권한이 필요하므로 Basis 팀과 사전 협의를 권장합니다
CDS 성능 문제의 발생 원리 — 어디서 느려지는가
CDS 뷰의 성능 저하를 이해하려면 한 가지 사실이 출발점입니다. CDS 뷰는 데이터베이스에 물화(materialize)된 결과가 아니라, 실행 시점마다 다시 계산되는 SQL 뷰라는 점입니다. 뷰를 조회하는 순간 HANA 옵티마이저가 뷰 정의 전체를 펼쳐서(unfold) 하나의 실행 계획을 만듭니다.
비유하자면 CDS 뷰 스택은 겹겹이 쌓인 반투명 필름과 같습니다. 위에서 보면 한 장의 그림처럼 보이지만, 실제로는 매번 아래층 필름까지 전부 겹쳐서 다시 그려내는 구조입니다. 필름이 5장, 10장 쌓이고 각 층마다 조인·CASE·통화 환산이 들어가면, 최상위에서 필드 3개만 조회해도 전체 층의 계산 비용을 지불하게 될 수 있습니다. 일반적으로 성능 저하는 다음 네 가지 지점에서 발생합니다.
- 뷰 스태킹 깊이 — 하위 뷰가 많을수록 옵티마이저의 계획 수립이 복잡해지고, 중간 층의 DISTINCT·UNION·집계가 필드 프루닝(불필요한 필드 제거)을 막습니다.
- 조인 폭발 — 카디널리티 명시 없이 LEFT OUTER JOIN을 중첩하면 중간 결과 집합이 기하급수로 커집니다.
- 계산 필드의 조기 평가 — WHERE 조건이 계산 필드에 걸리면 인덱스나 파티션 프루닝이 무력화됩니다.
- 접근 제어(DCL) 오버헤드 — 뷰 자체는 빨라도 DCL이 붙는 순간 권한 조건이 SQL에 병합되어 계획이 달라집니다. "SQL 콘솔에서는 빠른데 앱에서는 느린" 전형적 원인입니다.
따라서 진단의 핵심 질문은 "어느 층의 어떤 연산자가 시간을 소비하는가"이고, 이를 밝히는 도구가 트레이스와 실행 계획입니다.
단계별 실전 진단 — 문제 재현부터 병목 특정까지
1단계 — 문제 상황 재현과 ST05 트레이스 확보
가상의 시나리오로 시작합니다. 물류팀의 출고 지연 현황 앱이 느리다는 보고가 들어왔고, 백엔드에는 다음과 같은 CDS 뷰가 있습니다. 커스텀 테이블 ztb_shpevent(출고 이벤트 로그, 약 8천만 건)와 ztb_carrier(운송사 마스터)를 조인합니다.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '출고 지연 현황'
define view entity ZI_DelayedShipment
as select from ztb_shpevent as Event
left outer join ztb_carrier as Carrier
on Event.carrier_id = Carrier.carrier_id
{
key Event.shipment_id as ShipmentId,
Event.plant_code as PlantCode,
Carrier.carrier_name as CarrierName,
-- 계산 필드: 지연 일수
dats_days_between( Event.planned_date,
Event.actual_date ) as DelayDays,
case
when dats_days_between( Event.planned_date,
Event.actual_date ) > 3
then 'X' else ''
end as IsCritical
}
이 뷰를 소비하는 ABAP 코드가 계산 필드 IsCritical로 필터링합니다.
SELECT shipmentid, plantcode, carriername, delaydays
FROM zi_delayedshipment
WHERE iscritical = 'X'
AND plantcode = @lv_plant
INTO TABLE @DATA(lt_delayed)
UP TO 200 ROWS.
이제 ST05를 엽니다. 절차는 다음과 같습니다.
- ST05 → SQL Trace 체크 → 문제 사용자 ID로 필터 설정 후 Activate Trace with Filter
- 앱 또는 리포트를 한 번 실행해 문제를 재현
- 즉시 트레이스 비활성화 → Display Trace
트레이스 목록에서 실행 시간(Duration) 내림차순으로 정렬하면, CDS 뷰 조회가 SQL 뷰 이름(예: ZIDELAYEDSHPMT 형태의 sqlViewName 또는 뷰 엔티티의 내부 이름)으로 나타납니다. 여기서 확인할 세 가지 수치가 진단의 출발점입니다. 실행 횟수(Executions), 건당 시간, 페치된 레코드 수. 건당 500ms인데 루프 안에서 40번 실행되고 있다면 문제는 뷰가 아니라 호출 패턴이고, 1회 실행에 20초라면 뷰 내부로 들어가야 합니다.
2단계 — 실행 계획과 SAT로 병목 층 특정
1회 실행 자체가 느린 경우, ST05 트레이스 라인을 선택하고 Explain을 실행해 HANA 실행 계획을 봅니다. 이때 읽는 요령이 있습니다.
- Estimated Rows와 실제 처리 행 수의 괴리 — 옵티마이저가 1만 건으로 예측했는데 실제 3천만 건이 흐른 연산자가 있다면 그 지점이 병목 후보입니다.
- Filter 연산자의 위치 — 위 예제처럼 계산 필드(
IsCritical)에 WHERE를 걸면, 필터가 테이블 스캔 이후 CASE 평가 다음 단계에 나타납니다. 즉 8천만 건 전체에 대해 날짜 계산을 수행한 뒤에야 걸러내는 계획입니다. 반면plant_code같은 원본 컬럼 조건은 스캔 단계로 푸시다운됩니다. - Join 타입 — 대량 데이터에서 Nested Loop Join이 보이면 카디널리티 추정 실패 신호인 경우가 많습니다.
더 시각적인 분석이 필요하면 HANA PlanViz(ADT SQL 콘솔에서 해당 SQL을 붙여 넣고 실행 계획 시각화, 또는 ST05의 계획 표시에서 분기)를 사용해 연산자별 실제 소요 시간(Inclusive/Exclusive Time)을 확인합니다.
한편 "DB가 느린 게 맞는가"부터 검증하려면 SAT가 유용합니다. SAT에서 측정 배리언트를 만들어 대상 프로그램을 실행하면 시간 분포가 ABAP / Database / System으로 나뉘어 나옵니다. 실무에서는 다음처럼 재현 지점을 좁히기 위한 로깅을 함께 심어두면 트레이스 시점을 정확히 맞출 수 있습니다.
DATA(lo_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(lv_start) = lo_timer->get_runtime( ).
SELECT shipmentid, plantcode, carriername, delaydays
FROM zi_delayedshipment
WHERE iscritical = 'X'
AND plantcode = @lv_plant
INTO TABLE @DATA(lt_delayed)
UP TO 200 ROWS.
DATA(lv_micro) = lo_timer->get_runtime( ) - lv_start.
IF lv_micro > 2000000. " 2초 초과 시에만 기록
" 애플리케이션 로그(BAL)로 경고 기록 — 운영 추적용
MESSAGE w001(zshp_log) WITH lv_micro INTO DATA(lv_msg).
" cl_bali_log 등으로 저장 (생략)
ENDIF.
SAT 결과에서 DB 비중이 90% 이상이면 CDS/HANA 쪽 조사를 계속하고, ABAP 비중이 높다면 내부 테이블 처리나 루프 구조를 먼저 의심해야 합니다. 이 분기 판단을 건너뛰고 뷰부터 고치는 것이 현장에서 가장 흔한 시간 낭비입니다.
3단계 — 운영 환경 상시 진단 체계: SQLM, SWLT, 그리고 검증
개발 시스템에서 재현되지 않는 문제는 운영 데이터 분포에서만 나타납니다. 이때는 재현 시도 대신 SQLM(SQL Monitor)을 운영에 활성화합니다. SQLM은 트레이스와 달리 오버헤드가 매우 낮게 설계되어 있어 일반적으로 운영 상시 가동이 권장되며, 모든 SQL 실행을 호출 지점(엔트리 포인트) 기준으로 집계합니다.
- SQLM에서 Activate → 전체 서버, 기간 지정(예: 1~2주)
- 수집 후 총 DB 시간 기준 상위 목록에서 해당 CDS 뷰의 SQL 뷰 이름 검색
- 엔트리 포인트별로 실행 횟수·평균 시간·행 수를 비교 — "어떤 앱/배치가 이 뷰를 가장 비싸게 쓰는가"를 확인
SWLT(SQL Performance Tuning Worklist)는 SQLM의 런타임 데이터와 ATC/코드 인스펙터의 정적 검사 결과를 결합해 "실제로 부하가 크면서 코드 품질 결함도 있는" 지점을 우선순위화해 줍니다. 튜닝 대상 선정 회의에서 감이 아닌 데이터로 근거를 제시할 수 있게 됩니다.
원인이 특정되면 수정은 보통 "필터 가능한 원본 필드 노출 + 계산의 지연 평가"로 향합니다. 위 예제라면 계산 필드 필터를 소비 계층 파라미터로 대체하는 식입니다.
define view entity ZI_DelayedShipment_V2
with parameters
p_min_delay : abap.int4
as select from ztb_shpevent as Event
association [0..1] to ZI_CarrierBasic as _Carrier
on $projection.CarrierId = _Carrier.CarrierId
{
key Event.shipment_id as ShipmentId,
Event.plant_code as PlantCode,
Event.carrier_id as CarrierId,
dats_days_between( Event.planned_date,
Event.actual_date ) as DelayDays,
_Carrier
}
where
dats_days_between( Event.planned_date, Event.actual_date )
>= $parameters.p_min_delay
조인을 연관(association)으로 바꿔 실제 사용 시점에만 조인이 실행되도록 했고, 임계값을 파라미터로 받아 옵티마이저가 조건을 더 일찍 적용할 여지를 만들었습니다. 중요한 것은 수정 후 반드시 1~2단계의 동일한 측정을 반복해 전/후 수치를 비교하는 것입니다. Explain Plan의 처리 행 수 감소, ST05 건당 시간 개선을 기록으로 남기고, DCL이 있다면 권한이 좁은 테스트 사용자로도 측정해 접근 제어 병합 후의 계획까지 확인해야 검증이 완결됩니다. 보안 관점에서 트레이스 파일에는 실제 데이터 값이 노출될 수 있으므로 운영 트레이스 결과의 외부 공유는 피하는 것이 일반적입니다.
진단 과정에서 자주 빠지는 함정
Q1. SQL 콘솔에서는 0.3초인데 애플리케이션에서는 15초가 걸립니다. 왜인가요?
가장 흔한 원인은 세 가지입니다. 첫째, SQL 콘솔은 기본적으로 접근 제어(DCL)를 우회하므로 권한 조건이 병합된 실제 계획과 다릅니다. 둘째, 콘솔은 상위 일부 행만 가져오지만 애플리케이션은 전체를 페치할 수 있습니다. 셋째, 애플리케이션이 정렬·페이징($top/$skip + $orderby)을 추가하면 계획이 완전히 달라집니다. 반드시 ST05로 실제 실행된 문장을 확보해 비교하세요.
Q2. ST05 트레이스에 CDS 뷰 이름이 안 보입니다.
트레이스에는 CDS 엔티티 이름이 아니라 데이터베이스에 생성된 SQL 뷰 이름이 기록됩니다. ADT에서 뷰를 열어 매핑되는 DB 오브젝트 이름을 확인하거나, 뷰 엔티티라면 엔티티 이름 자체가 DB 뷰명으로 쓰이는 점을 참고하세요. 또한 테이블 버퍼에서 응답된 조회는 DB까지 가지 않으므로 트레이스 활성화 시 버퍼 트레이스도 함께 켜는 것이 안전합니다.
Q3. 첫 실행만 느리고 두 번째부터는 빠릅니다. 튜닝이 필요한가요?
첫 실행 지연은 SQL 계획 캐시 미스(계획 수립 비용)나 컬럼 로드 때문일 수 있습니다. 뷰 스택이 깊을수록 계획 수립 자체가 오래 걸리므로, 매번 다른 조건으로 호출되어 캐시 재사용이 안 되는 패턴이라면 스택 단순화가 유효한 개선입니다. 측정 시에는 항상 첫 실행과 반복 실행을 구분해 기록하세요.
Q4. SQLM을 운영에 켜면 성능에 영향이 없나요?
일반적으로 오버헤드가 수 퍼센트 미만으로 낮아 상시 가동이 권장되지만, 수집 기간과 대상 서버를 명시적으로 지정하고 Basis 팀과 합의 후 활성화하는 운영 절차를 갖추는 편이 좋습니다.
이 진단 흐름을 몸에 익히면
진단 역량이 잡혔다면 다음 주제로 확장해 보세요. HANA PlanViz의 타임라인 분석으로 병렬화 병목 읽기, CDS 뷰 엔티티의 어노테이션 기반 성능 힌트(@Consumption.filter 설계와 필터 푸시다운), 대용량 집계를 위한 CDS 계층 설계 패턴(기본 뷰/컴포지트 뷰/소비 뷰 분리), 그리고 ATC 성능 검사 배리언트를 CI 파이프라인에 넣어 성능 결함을 릴리스 전에 차단하는 방법이 자연스러운 다음 여정입니다. ABAP Cloud 환경이라면 ADT의 SQL 트레이스 뷰와 Fiori 기반 SQL Monitor 앱 사용법도 함께 익혀두길 권장합니다.
핵심 정리 — 다음에 같은 문제를 만나면
- 먼저 ST05로 실제 실행된 SQL과 실행 횟수·건당 시간·페치 행 수를 확보한다 — 반복 호출 문제인지 단일 실행 문제인지부터 구분
- 단일 실행이 느리면 Explain Plan/PlanViz로 Estimated Rows와 실제 행 수 괴리, Filter 위치, Join 타입을 확인
- SAT로 DB 시간과 ABAP 시간 비중을 나눠 어느 레이어를 고칠지 먼저 결정
- 운영에서만 재현되면 SQLM/SWLT로 상시 모니터링 후 엔트리 포인트 기준으로 비교
- 수정 후에는 반드시 같은 측정을 재실행해 전/후 수치로 검증 — DCL이 있다면 권한이 좁은 사용자로도 재측정
댓글 0
아직 댓글이 없습니다.