ABAP

CDS 분석 쿼리 매번 재계산 그만 — dataExtract #shorts #SAP #ABAP

▶ YouTube에서 보기

@Analytics.dataExtract 개요 및 배경

SAP S/4HANA에서 분석용 CDS View를 설계할 때 가장 자주 마주치는 병목은 "쿼리 응답이 초 단위로 튄다"는 것입니다. Fiori Analytical List Page(ALP)나 SAP Analytics Cloud(SAC) Live Connection으로 노출한 큐브 뷰가 수천만 건의 트랜잭션 테이블을 매번 집계하기 때문입니다. @Analytics.dataExtract 어노테이션 그룹은 이 문제를 어드밴스드 데이터 추출(Advanced Data Extract, ADE) 프레임워크로 넘겨, 미리 물리 테이블에 스냅샷을 만들고 델타 방식으로 갱신하도록 지시하는 지시자입니다.

이 글에서 확인할 내용은 다음과 같습니다.

  • ADE 프레임워크와 @Analytics.dataExtract.enabled가 어떤 관계인지
  • Cube / Query View에 어노테이션을 결합할 때의 실행 흐름
  • SalesOrder / PurchaseOrder 시나리오로 어노테이션 전후 성능 비교
  • Full Load vs. Delta 전략, 그리고 실무 체크리스트
  • DCL, Session Variable, Extract 스케줄이 뒤엉킬 때의 디버깅 패턴

사전에 익혀두면 좋은 지식

이 글은 advanced 난이도이므로, CDS View의 계층 구조(Basic → Composite → Consumption/Cube → Query)와 @Analytics.query: true 어노테이션 사용 경험이 있다고 가정합니다. 또한 ABAP Development Tools(ADT) 2023 이상, SAP BTP ABAP Environment 또는 S/4HANA 2022 FPS02 이상의 온프레미스 환경, 그리고 SAP HANA Cloud 또는 HANA 2.0 SPS07 이상의 DB 지식이 필요합니다. AMDP나 Table Function을 다뤄본 경험이 있다면 Materialization 개념을 더 빠르게 이해할 수 있습니다.

실습 환경과 준비물

다음 환경 기준으로 코드를 검증했습니다. 버전에 따라 지원 파라미터가 다를 수 있으므로 SAP Note와 릴리스별 CDS Annotation Reference를 함께 확인하는 것이 일반적으로 권장됩니다.

  • SAP S/4HANA 2023 온프레미스 또는 SAP BTP ABAP Environment 2025 release
  • SAP HANA Cloud QRC 2/2025 (Column Store 기준)
  • ABAP Development Tools 3.42 이상 (Eclipse 2024-03)
  • 권한: S_DEVELOP(DDLS, DCLS), S_RS_ADE(ADE Management), S_BTCH_JOB(스케줄러)
  • 확인용 트랜잭션/앱: RSDD_ADE (또는 Fiori 앱 "Manage Advanced Data Extract"), DBACOCKPIT, ST05

사전에 트랜잭션 테이블(예: ZSD_ORDER_ITEM)에 최소 500만 건 이상을 채워두면 어노테이션 적용 전후 차이를 명확히 관측할 수 있습니다.

CDS View에서 어노테이션이 동작하는 방식

일반적인 Analytical Query는 실행 시점에 Cube View의 SQL을 HANA로 위임합니다. Cube가 여러 개의 조인·유니온·통화 변환을 포함한다면, 매 요청마다 옵티마이저가 동일한 계산 그래프를 다시 만듭니다. 반면 @Analytics.dataExtract.enabled: true가 붙은 CDS View는 ADE 프레임워크가 "이 뷰의 결과를 물리 테이블로 저장할 수 있다"고 인식합니다.

비유하자면, 어노테이션 없이 사용하는 CDS Cube는 매번 "재료를 사서 요리하는 식당"이고, dataExtract를 활성화한 Cube는 "밀키트를 냉장고에 넣어두고 손님이 오면 데우기만 하는 식당"입니다. 재료(트랜잭션 데이터)가 바뀌면 델타 로직이 밀키트를 갱신합니다.

내부 구조는 세 계층으로 나뉩니다.

  1. Source CDS View: 어노테이션이 붙는 뷰. 조인/필터/집계 로직을 담습니다.
  2. Persistency Table: ADE가 자동 생성하는 /1BS/* 접두어의 관리 테이블. 뷰 필드와 동일한 스키마를 가집니다.
  3. Extract Run: 스케줄러 또는 API로 트리거되는 배치. Full 또는 Delta 모드로 Persistency Table을 채웁니다.

Query View는 @Analytics.query: true로 정의하되, 그 소스가 Extract가 활성화된 Cube View라면 옵티마이저가 원본 조인 그래프 대신 Persistency Table을 스캔합니다. 결과적으로 SAC나 ALP 응답에서 파싱 비용, Materialization 비용이 사라집니다.

@Analytics.dataExtract 핵심 파라미터

어노테이션은 단일 boolean이 아니라 여러 sub-parameter로 구성됩니다. 자주 쓰는 것을 정리하면 다음과 같습니다.

  • enabled: 활성 여부. true여야 ADE에서 뷰가 노출됩니다.
  • delta.byElement.name: 델타 감지 필드(예: 마지막 변경 타임스탬프).
  • delta.byElement.maxDelayInSeconds: 델타 지연 허용치. 소스 시스템의 커밋 지연을 감안한 안전 버퍼입니다.
  • delta.byElement.detectDeletedRecords: 삭제 감지 여부. false가 기본이며, 삭제가 잦다면 별도 로직이 필요합니다.
  • defaultExtractionScheduling.scheduled: 자동 스케줄 여부.
  • defaultExtractionScheduling.intervalInMinutes: 실행 간격.

실전 1단계: SalesOrder Cube에 기본 적용

먼저 판매 오더 헤더·아이템을 조인한 Cube View에 최소한의 어노테이션을 붙여봅니다. 표준 I_SalesOrder가 아닌 자체 ZC_SALESORDER_CUBE를 만들었다고 가정합니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Analytical Cube'
@Analytics: { dataCategory: #CUBE }
@Analytics.dataExtract.enabled: true
@VDM.viewType: #COMPOSITE
define view entity ZC_SALESORDER_CUBE
  as select from zi_salesorder_item as Item
  association [0..1] to I_Customer as _Customer
    on $projection.SoldToParty = _Customer.Customer
{
  key Item.SalesOrder,
  key Item.SalesOrderItem,
      Item.SoldToParty,
      Item.Material,
      Item.SalesOrganization,
      Item.CreationDate,
      Item.LastChangeDateTime,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      Item.NetAmount,
      Item.TransactionCurrency,
      Item.OrderQuantity,
      _Customer
}

이 상태에서 활성화하면 ADE Management 앱에 ZC_SALESORDER_CUBE가 후보로 등록됩니다. Full Load를 한 번 실행하면 Persistency Table이 초기 채워지고, 이후 Query View가 이 테이블을 참조합니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Analytical Query'
@Analytics.query: true
@OData.publish: true
define view entity ZQ_SALESORDER_REPORT
  as select from ZC_SALESORDER_CUBE
{
      @AnalyticsDetails.query.axis: #ROWS
      SalesOrganization,
      @AnalyticsDetails.query.axis: #ROWS
      SoldToParty,
      @AnalyticsDetails.query.axis: #COLUMNS
      @DefaultAggregation: #SUM
      NetAmount,
      TransactionCurrency,
      @DefaultAggregation: #SUM
      OrderQuantity
}

실전 2단계: Delta 감지와 로깅 추가

실무에서는 Full Load만으로 부족합니다. 오더가 하루 수만 건씩 변경되면 델타 처리가 필수입니다. 아래는 LastChangeDateTime을 델타 기준으로 잡고, 삭제도 감지하도록 확장한 예입니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Cube with Delta'
@Analytics: { dataCategory: #CUBE }
@Analytics.dataExtract:
{
  enabled: true,
  delta.byElement:
  {
    name: 'LastChangeDateTime',
    maxDelayInSeconds: 900,
    detectDeletedRecords: true,
    ignoreDeletionAfterDays: 30
  },
  defaultExtractionScheduling:
  {
    scheduled: true,
    intervalInMinutes: 15
  }
}
@VDM.viewType: #COMPOSITE
define view entity ZC_SALESORDER_CUBE_V2
  as select from zi_salesorder_item as Item
{
  key Item.SalesOrder,
  key Item.SalesOrderItem,
      Item.SoldToParty,
      Item.Material,
      Item.SalesOrganization,
      cast(Item.CreationDate as abap.dats) as CreationDate,
      Item.LastChangeDateTime,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      Item.NetAmount,
      Item.TransactionCurrency,
      Item.OrderQuantity
}

이제 15분마다 델타 Extract가 실행됩니다. 운영 로그는 Manage Advanced Data Extract 앱의 Runs 탭 또는 CL_ADE_RUN_MANAGER API로 조회할 수 있습니다. 배치 실패 시 알림은 다음처럼 후처리 클래스에서 감지합니다.

DATA(lo_run) = cl_ade_run_manager=>get_instance( ).
DATA(lt_runs) = lo_run->get_last_runs(
  iv_view_name = 'ZC_SALESORDER_CUBE_V2'
  iv_max_count = 5 ).

LOOP AT lt_runs INTO DATA(ls_run) WHERE status = 'E'.
  cl_bali_message_setter=>create( )->set_severity( if_bali_constants=>c_severity_error )
    ->set_text( |Extract failed for run { ls_run-run_id } at { ls_run-end_ts }| ).
ENDLOOP.

실전 3단계: 프로덕션 튜닝과 보안

운영 환경에서는 다음 세 가지를 함께 설정하는 것이 일반적으로 권장됩니다.

  1. 파티셔닝 힌트: Persistency Table이 커지면 HANA Range Partitioning을 붙여 스캔 범위를 줄입니다. @AbapCatalog.dbHints로 힌트를 전달합니다.
  2. DCL 통합: Persistency Table에 대해서도 원본 뷰의 DCL이 적용되도록 @AccessControl.authorizationCheck: #CHECK를 유지하고, DCL은 REDIRECTED TO가 아닌 실제 컬럼 기준으로 작성합니다.
  3. 테스트 자동화: CL_CDS_TEST_ENVIRONMENT로 Cube View 결과와 Query View 결과가 일치하는지 회귀 테스트를 만듭니다.
CLASS ltc_salesorder_cube DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    CLASS-DATA env TYPE REF TO if_cds_test_environment.
    CLASS-METHODS class_setup.
    METHODS aggregation_matches FOR TESTING.
ENDCLASS.

CLASS ltc_salesorder_cube IMPLEMENTATION.
  METHOD class_setup.
    env = cl_cds_test_environment=>create(
      i_for_entity = 'ZC_SALESORDER_CUBE_V2' ).
  ENDMETHOD.

  METHOD aggregation_matches.
    env->insert_test_data( VALUE zi_salesorder_item_t(
      ( SalesOrder = '0001' SalesOrderItem = '10'
        SalesOrganization = '1710' NetAmount = 100
        TransactionCurrency = 'EUR' ) ) ).

    SELECT SUM( NetAmount ) FROM ZC_SALESORDER_CUBE_V2
      WHERE SalesOrganization = '1710'
      INTO @DATA(lv_total).

    cl_abap_unit_assert=>assert_equals(
      exp = 100 act = lv_total ).
  ENDMETHOD.
ENDCLASS.

쿼리 실행 계획 비교 — 어노테이션 전후

ST05 SQL Trace로 동일 쿼리를 두 번 잡아 보면 차이가 명확합니다. 어노테이션 이전에는 Union, Join, Currency Conversion이 실행 계획 상단에 나타나고, 계산 그래프가 매 요청마다 재구성됩니다. 500만 건 기준 응답이 4~6초 정도 걸리는 경우가 일반적입니다.

어노테이션 활성화 후에는 실행 계획이 Column Search on /1BS/ZC_SO_CUBE 단일 노드로 축약되며, 동일 쿼리가 200~400ms 수준으로 내려가는 사례가 보고됩니다. 다만 Extract 배치 자체는 15분마다 CPU를 사용하므로, 총 자원 소비 관점에서 트레이드오프를 검토해야 합니다.

복합 어노테이션 조합 전략

@Analytics.dataExtract는 단독으로 쓰기보다 다음 어노테이션과 함께 사용할 때 시너지가 큽니다.

  • @ObjectModel.usageType.dataClass: #TRANSACTIONAL — ADE가 트랜잭션 데이터임을 인식하도록 명시합니다.
  • @Analytics.internalName — Persistency Table 별칭을 커스텀 지정합니다.
  • @Consumption.filter — 초기 Full Load 범위를 조직 단위로 제한합니다.
  • @Semantics.systemDateTime.lastChangedAt: true — 델타 필드 의미를 명시합니다.

예를 들어 PurchaseOrder Cube에서는 PurchasingOrganization으로 파티션을 나눈 뒤, 조직별로 다른 Extract Job을 등록하면 락 경합을 줄일 수 있습니다.

실무 성능 측정 체크리스트

  1. Extract 시작 전 DBACOCKPIT > Table Size로 원본 테이블 크기 스냅샷을 남깁니다.
  2. Full Load 완료 후 Persistency Table 크기와 압축률을 비교합니다. 원본의 30~60% 수준이 일반적입니다.
  3. SAC/ALP에서 대표 쿼리 5개를 준비해 어노테이션 전후 응답 시간을 각 10회 측정, 중앙값을 사용합니다.
  4. Delta Run 로그에서 평균 처리 건수와 소요 시간을 확인해 intervalInMinutes가 적절한지 점검합니다.
  5. 야간 배치 시간대에 Extract를 몰아넣지 않았는지 스케줄 충돌을 체크합니다.

흔한 실수와 디버깅 패턴

Q1. Extract는 성공했는데 Query 결과가 원본과 다릅니다.
델타 필드가 실제 변경 시점을 반영하지 못하는 경우가 대부분입니다. LastChangeDateTime이 커밋 이후에 갱신된다면 maxDelayInSeconds를 늘려야 합니다. 또한 삭제 감지를 켜지 않으면 삭제된 오더가 Persistency Table에 남아 합계가 커지는 이슈가 자주 발생합니다.

Q2. DCL이 원본과 다르게 동작합니다.
Persistency Table은 물리 테이블이므로, 세션 변수 기반 DCL이 캐시된 결과에 그대로 적용되지 않을 수 있습니다. $session.user처럼 동적 값을 참조하는 DCL은 어노테이션 활성화 전 반드시 회귀 테스트를 수행해야 합니다.

Q3. 스케줄된 Delta Run이 자꾸 실패합니다.
Job 서버 그룹이 지정되지 않았거나, SBGRFCCONF의 bgRFC 대상이 비어 있는 경우가 흔합니다. SLG1의 오브젝트 ADE_RUN 로그를 먼저 확인하고, 그 다음 SM37에서 ADE_EXTRACT_* 잡의 스택 트레이스를 봅니다.

Q4. 어노테이션을 껐다 켰다 하면 Persistency Table은 어떻게 되나요?
비활성화 시 자동으로 Drop되지 않습니다. ADE Management 앱에서 명시적으로 "Unregister" 후 정리해야 하며, 그렇지 않으면 스토리지가 그대로 유지됩니다.

댓글 0

아직 댓글이 없습니다.