ABAP

BOXED vs INCLUDE — ABAP 중첩 메모리 비교 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 핵심 체크리스트

ABAP에서 중첩 구조체를 다룰 때 모든 하위 컴포넌트는 부모 구조체와 동일한 연속 메모리 블록에 한꺼번에 할당됩니다. 이는 작은 구조에서는 문제가 없지만, 거대한 sub-structure를 자주 사용하지 않거나, 자기참조(self-referential) 트리·그래프 같은 재귀 타입을 정의해야 할 때 비효율과 컴파일 오류를 유발합니다. BOXED component는 NetWeaver 7.55(SAP_BASIS 755) 이후 도입된 기능으로, 하위 컴포넌트를 별도의 힙(heap) 영역에 분리하고 첫 접근 시점까지 메모리 할당을 지연시키는 메커니즘입니다.

  • 중첩 구조체에서 메모리 점유 패턴이 어떻게 달라지는지 이해
  • BEGIN OF BOXED 선언 문법과 lazy initialization 동작 확인
  • 자기참조 구조체(트리/링크드 리스트)에서 BOXED가 필수가 되는 이유 파악
  • 성능 측정·디버깅 시점에서의 주의사항 정리

읽기 전에 알아두면 좋은 것

ABAP의 데이터 타입 계층(elementary / structured / table / reference)과 TYPES BEGIN OF ... END OF 구문을 다뤄본 경험이 필요합니다. 또한 ABAP의 메모리 모델에서 deep structure(string, xstring, internal table)가 내부적으로 참조 기반으로 동작한다는 점, 그리고 NetWeaver 7.55 / ABAP Platform 1909 이후의 신규 키워드가 일부 구버전에서는 syntax error를 일으킨다는 사실을 알고 있으면 도움이 됩니다.

필요한 환경과 버전 정보

BOXED 키워드는 ABAP 릴리스 7.55(SAP NetWeaver AS ABAP 7.55 / S/4HANA 2020) 이상에서 지원되며, ABAP Cloud 및 Steampunk 환경에서도 사용할 수 있습니다. 구버전 시스템(7.54 이하)에서는 컴파일 단계에서 차단되므로 시스템 버전 확인이 우선입니다.

  • ABAP Platform 1909 / S/4HANA 2020 이상 (권장: 2022 FPS 이후)
  • ADT (ABAP Development Tools) 3.30 이상에서 구문 강조 지원
  • SE80은 일부 신규 키워드 자동완성이 누락될 수 있어 ADT 사용을 일반적으로 권장
  • 테스트용 패키지와 transport request 권한
  • 메모리 분석을 위한 트랜잭션 S_MEMORY_INSPECTOR 접근 권한

RAP(RESTful ABAP Programming Model) 환경에서 behavior implementation 내부에 BOXED를 정의할 수 있지만, CDS view 노출 필드로 직접 매핑되지는 않으므로 transient 데이터 보관용으로 사용하는 패턴이 일반적입니다.

핵심 개념: 메모리는 어디에, 언제 할당되는가

일반 중첩 구조체는 "한 권의 두꺼운 사전"에 비유할 수 있습니다. 표지부터 색인까지 모든 페이지가 물리적으로 붙어 있어, 책을 펴는 순간 전체 무게를 들어야 합니다. 반면 BOXED 컴포넌트는 "표지에 QR 코드가 인쇄된 얇은 팸플릿"과 같습니다. QR 코드를 스캔(첫 접근)하기 전까지 본문 페이지는 인쇄소에 머물고, 필요할 때 비로소 별도 책자로 받아보는 구조입니다.

내부 동작을 도식으로 정리하면 다음과 같습니다.

" 일반 중첩 구조
+-----------------------------------+
| order_id | items.count | items.total |   ← 한 덩어리(stack/work area)
+-----------------------------------+

" BOXED 컴포넌트
+----------------+        +-------------+
| header | [ptr] |  --->  | sub1 | sub2 |  (힙에 별도 할당)
+----------------+        +-------------+
                ↑ 첫 접근 전: ptr = initial, 힙 미할당

BOXED 컴포넌트는 내부적으로 참조 의미론(reference semantics)을 사용하지만, 외부 코드 입장에서는 일반 구조체처럼 값으로 접근할 수 있는 "투명한 참조"입니다. 이를 ABAP 문서에서는 boxed component with deep handling이라고 부르며, string·internal table과 동일한 카테고리의 deep 컴포넌트로 분류됩니다.

지연 초기화(lazy initialization)는 두 가지 효과를 만듭니다. 첫째, 대형 구조의 메모리 footprint를 실제 사용량에 비례하도록 줄입니다. 둘째, 자기참조 타입(예: 트리 노드가 자식 노드를 가리키는 구조)에서 "타입 정의 시점에 무한 크기 계산이 발생하는 모순"을 해소합니다. 일반 컴포넌트로 자기 자신을 포함시키면 컴파일러는 무한 재귀 크기를 계산하려다 실패하지만, BOXED는 참조 크기(8바이트 수준)만 차지하므로 재귀가 성립합니다.

실전 코드 1단계 — 일반 중첩 구조의 한계 확인

먼저 BOXED 없이 중첩 구조를 정의하고, 사용하지 않는 sub-structure가 어떻게 메모리를 점유하는지 살펴봅니다.

REPORT zdemo_nested_normal.

TYPES: BEGIN OF ty_order,
         order_id TYPE vbeln,
         BEGIN OF items,
           count TYPE i,
           total TYPE p LENGTH 13 DECIMALS 2,
           note  TYPE c LENGTH 4000,   " 거대한 필드
         END OF items,
       END OF ty_order.

DATA: lt_orders TYPE STANDARD TABLE OF ty_order WITH EMPTY KEY.

DO 10000 TIMES.
  APPEND VALUE #( order_id = |{ sy-index ALPHA = IN }| ) TO lt_orders.
ENDDO.

WRITE: / |Rows: { lines( lt_orders ) }|.
" items.note 를 한 번도 쓰지 않았지만 행마다 4000 byte 가 잡혀 있음

여기서 items.note를 한 번도 사용하지 않았음에도 행마다 약 4KB가 선점됩니다. 10,000건이면 40MB가 헛되이 소모되는 셈입니다. S_MEMORY_INSPECTOR로 스냅샷을 떠보면 lt_orders 영역이 그대로 부풀어 있는 것을 확인할 수 있습니다.

실전 코드 2단계 — BOXED로 lazy init 적용 및 로깅

같은 구조를 BOXED로 바꾸고, 첫 접근 전/후의 동작 차이를 명시적으로 다뤄봅니다.

REPORT zdemo_nested_boxed.

TYPES: BEGIN OF ty_order,
         order_id TYPE vbeln,
         BEGIN OF BOXED items,
           count TYPE i,
           total TYPE p LENGTH 13 DECIMALS 2,
           note  TYPE c LENGTH 4000,
         END OF items,
       END OF ty_order.

DATA: lt_orders TYPE STANDARD TABLE OF ty_order WITH EMPTY KEY,
      lo_log    TYPE REF TO if_xo_logger.

TRY.
    DO 10000 TIMES.
      APPEND VALUE #( order_id = |{ sy-index ALPHA = IN }| ) TO lt_orders.
    ENDDO.

    " 100건만 실제로 items 에 접근 → 그 100건만 힙에 할당됨
    LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<fs_ord>) TO 100.
      <fs_ord>-items-count = sy-tabix.
      <fs_ord>-items-total = sy-tabix * '1.25'.
    ENDLOOP.

  CATCH cx_root INTO DATA(lx_err).
    cl_demo_output=>display( lx_err->get_text( ) ).
ENDTRY.

" BOXED 컴포넌트가 initial 상태인지 확인
DATA(lv_initial_count) = 0.
LOOP AT lt_orders ASSIGNING <fs_ord>.
  IF <fs_ord>-items IS INITIAL.
    lv_initial_count = lv_initial_count + 1.
  ENDIF.
ENDLOOP.

WRITE: / |Initial(미할당) 행 수: { lv_initial_count }|.

위 코드에서 9,900건은 items에 한 번도 접근하지 않았으므로 힙 영역이 만들어지지 않습니다. 메모리 점유는 참조 슬롯(행당 약 8바이트)만 추가될 뿐이라, 전체 footprint가 극적으로 줄어듭니다. IS INITIAL로 미할당 여부를 점검할 수 있다는 점도 BOXED만의 특징입니다. 일반 구조는 모든 필드가 초기값이어도 메모리는 이미 잡혀 있기 때문입니다.

실전 코드 3단계 — 자기참조 트리와 단위 테스트

이진 트리 노드처럼 자기 자신을 포함해야 하는 타입은 BOXED 없이는 정의 자체가 불가능합니다. 아래는 명령 트리(command tree)를 BOXED로 구현하고 ABAP Unit으로 검증하는 예입니다.

CLASS zcl_cmd_tree DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_node,
             value TYPE string,
             left  TYPE REF TO data,    " 일반 참조 대체 가능
             right TYPE REF TO data,
           END OF ty_node.

    " BOXED 를 직접 사용하는 값-기반 트리
    TYPES: BEGIN OF ty_vnode,
             value TYPE string,
             BEGIN OF BOXED left,
               dummy TYPE c LENGTH 1,   " placeholder
             END OF left,
           END OF ty_vnode.

    METHODS insert IMPORTING iv_value TYPE string.
    METHODS to_list RETURNING VALUE(rt_values) TYPE string_table.

  PRIVATE SECTION.
    DATA: mt_nodes TYPE STANDARD TABLE OF ty_node WITH EMPTY KEY.
ENDCLASS.

CLASS zcl_cmd_tree IMPLEMENTATION.
  METHOD insert.
    APPEND VALUE #( value = iv_value ) TO mt_nodes.
  ENDMETHOD.

  METHOD to_list.
    LOOP AT mt_nodes ASSIGNING FIELD-SYMBOL(<fs>).
      APPEND <fs>-value TO rt_values.
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

"=== Unit Test ==========================================
CLASS ltc_tree DEFINITION FOR TESTING DURATION SHORT
                          RISK LEVEL HARMLESS FINAL.
  PRIVATE SECTION.
    DATA mo_cut TYPE REF TO zcl_cmd_tree.
    METHODS:
      setup,
      insert_and_count FOR TESTING.
ENDCLASS.

CLASS ltc_tree IMPLEMENTATION.
  METHOD setup.
    mo_cut = NEW #( ).
  ENDMETHOD.

  METHOD insert_and_count.
    mo_cut->insert( 'A' ).
    mo_cut->insert( 'B' ).
    cl_abap_unit_assert=>assert_equals(
      act = lines( mo_cut->to_list( ) )
      exp = 2 ).
  ENDMETHOD.
ENDCLASS.

프로덕션 환경에서는 다음 사항을 함께 챙기는 것이 일반적으로 권장됩니다. (1) BOXED 컴포넌트는 직렬화(CALL TRANSFORMATION, JSON 변환) 시 일반 구조와 동일하게 직렬화되지만, initial 상태와 빈 구조의 출력이 미묘하게 다를 수 있어 결과를 단위 테스트로 고정하는 것이 안전합니다. (2) Authority check가 걸린 필드를 BOXED 안에 두면 권한 점검 로직이 첫 접근 시점에 실행되도록 조정해야 합니다. (3) Shared memory object에 BOXED를 보관할 때는 SAP_BASIS 패치 레벨에 따라 동작 차이가 있을 수 있으므로 SAP Note를 사전 확인하는 패턴이 권장됩니다.

흔히 만나는 함정과 FAQ

BOXED는 강력하지만, 잘못된 가정으로 도입하면 오히려 성능이 나빠지거나 디버깅이 어려워집니다.

  • Q1. BOXED 컴포넌트는 항상 메모리를 절약하나요? — 아닙니다. 모든 행에서 sub-structure를 무조건 사용한다면 BOXED는 참조 관리 오버헤드만 추가합니다. 일반적으로 접근 비율이 30% 이하일 때 효과가 큽니다.
  • Q2. IS INITIAL과 빈 구조는 같은 의미인가요? — 다릅니다. BOXED가 한 번이라도 할당되면, 모든 필드가 초기값이라도 IS NOT INITIAL이 됩니다. 이를 혼동하면 "지웠는데 왜 메모리가 안 줄지?" 같은 이슈가 발생합니다. 명시적으로 CLEAR 후 다시 unbox 해야 하며, ABAP은 자동 deallocation을 보장하지 않습니다.
  • Q3. ABAP Cloud / RAP에서 사용 가능한가요? — 사용 가능하지만, released API 시그니처에 BOXED를 직접 노출하면 클라이언트 호환성이 떨어질 수 있습니다. 내부 helper class의 private/protected 영역에서 활용하는 패턴이 안전합니다.
  • Q4. 디버거에서 보이지 않는 필드가 있어요. — 미할당 BOXED 컴포넌트는 ADT 디버거에서 "not yet boxed"로 표기되며, 변수 트리에서 자동 확장되지 않습니다. 우클릭 후 강제 확장하면 빈 구조를 만들어 메모리가 잡힐 수 있으므로 주의가 필요합니다.
  • Q5. ALV 표시에 사용해도 되나요? — ALV는 모든 행의 sub-structure를 순회하며 렌더링하므로 BOXED의 lazy 이점이 사라집니다. 화면 표시용은 일반 구조를, 백엔드 임시 데이터는 BOXED를 쓰는 분리 설계가 일반적으로 권장됩니다.

다음에 살펴보면 좋은 주제

BOXED를 익혔다면 자연스럽게 다음 주제로 확장됩니다.

  • Generic data referencesREF TO data와 RTTI/RTTC를 활용한 동적 타입 처리
  • Shared Objects — 세션 간 공유되는 메모리 영역 설계, BOXED 호환성
  • ABAP Channels / AMC — 메시지 페이로드에서 deep 구조 직렬화 패턴
  • RAP Managed/Unmanaged BO — transient 필드 설계와 메모리 풋프린트 최적화
  • HANA-side computation — ABAP 레이어 메모리를 줄이고 CDS/AMDP로 위임하는 결정 기준

참고 자료

댓글 0

아직 댓글이 없습니다.