ABAP

내부 테이블 메모리 — 딥 구조체 넣으면 큰일 #shorts #SAP #ABAP

▶ YouTube에서 보기

딥 구조체가 메모리에서 어떻게 동작하는가

ABAP 내부 테이블의 한 라인이 또 다른 내부 테이블이나 STRING을 필드로 가지는 구조를 딥 구조체(Deep Structure)라고 부릅니다. 평범하게 보이지만 메모리 관점에서는 라인마다 별도의 힙(heap) 블록을 잡기 때문에, 수만 라인을 다루는 배치 프로그램에서 메모리 폭발의 주범이 됩니다.

CHAR, INT4, DEC 같은 기본형은 라인 안에 직접 저장됩니다. 그런데 라인의 필드 중 하나라도 STRING, XSTRING, 내부 테이블, REF TO 같은 가변 크기 타입이면 그 필드는 라인 안에 "포인터"만 저장되고, 실제 데이터는 힙 영역에 별도로 할당됩니다.

비유하자면 플랫 구조체는 우체국 사서함처럼 정해진 칸 안에 모든 내용이 들어가는 반면, 딥 구조체는 사서함 안에 "창고 열쇠"가 들어 있고 실제 짐은 별도 창고(힙)에 쌓이는 형태입니다. 사서함을 비워도 창고는 누군가 명시적으로 정리하지 않으면 그대로 남습니다.

결과적으로 내부 테이블 라인 1개의 "라인 크기"는 작아 보여도, 라인마다 STRING/내부 테이블이 추가로 차지하는 힙은 별도이며 DESCRIBE TABLE은 이 부분을 집계하지 않습니다. 한도(보통 2GB 전후)에 도달하면 TSV_TNEW_PAGE_ALLOC_FAILED 덤프가 발생합니다.

Flat 구조 vs Deep 구조 메모리 비교

REPORT z_deep_struct_demo.

" 플랫 구조체 - 모든 필드가 고정 길이
TYPES: BEGIN OF ty_order_flat,
         vbeln  TYPE vbeln_va,
         posnr  TYPE posnr_va,
         matnr  TYPE matnr,
         kwmeng TYPE kwmeng,
         netwr  TYPE netwr_ap,
         text40 TYPE c LENGTH 40,
       END OF ty_order_flat,
       tt_order_flat TYPE STANDARD TABLE OF ty_order_flat
                     WITH EMPTY KEY.

" 딥 구조체 - STRING과 중첩 테이블 포함
TYPES: BEGIN OF ty_order_deep,
         vbeln          TYPE vbeln_va,
         posnr          TYPE posnr_va,
         matnr          TYPE matnr,
         kwmeng         TYPE kwmeng,
         netwr          TYPE netwr_ap,
         long_text      TYPE string,           " 가변 길이 -> 힙
         schedule_lines TYPE STANDARD TABLE    " 내부 테이블 -> 힙
                        OF vbep WITH EMPTY KEY,
       END OF ty_order_deep,
       tt_order_deep TYPE STANDARD TABLE OF ty_order_deep
                     WITH EMPTY KEY.

lt_flat는 라인당 약 80바이트 내외로 일정하게 메모리를 잡습니다. 반면 lt_deep는 라인마다 STRING과 중첩 테이블이 힙에 별도 공간을 잡아 실제 사용량이 수십 배 차이날 수 있습니다.

실전 예제 1단계: 딥 구조체 선언과 데이터 로딩

DATA(lv_mem_start) = cl_abap_memory_utilities=>get_total_used_size( ).

DATA: lt_deep TYPE tt_order_deep.

DO 100000 TIMES.
  APPEND INITIAL LINE TO lt_deep ASSIGNING FIELD-SYMBOL().
  -vbeln = sy-index.

  " 라인마다 평균 2KB짜리 스트링 (힙 별도)
  -long_text = repeat( val = 'A' occ = 2000 ).

  " 라인마다 중첩 테이블 20건 (힙 별도)
  DO 20 TIMES.
    APPEND INITIAL LINE TO -schedule_lines
           ASSIGNING FIELD-SYMBOL().
    -vbeln = -vbeln.
    -etenr = sy-index.
  ENDDO.
ENDDO.

DATA(lv_mem_end) = cl_abap_memory_utilities=>get_total_used_size( ).
WRITE: / 'Deep table memory delta (bytes):',
         lv_mem_end - lv_mem_start.

DESCRIBE TABLE의 함정 — 왜 집계가 안 되는가

" 함정: DESCRIBE TABLE은 라인 메타데이터만 본다
DESCRIBE TABLE lt_deep LINES DATA(lv_lines).
DATA(lv_line_size) = cl_abap_typedescr=>describe_by_data(
                       lt_deep )->length.

" lv_line_size 는 작게 나오지만 실제 메모리는 수백 MB
" DESCRIBE TABLE 결과만 믿으면 안심했다가 덤프 맞음

" 올바른 측정: cl_abap_memory_utilities
DATA(lv_real_mem) = cl_abap_memory_utilities=>get_total_used_size( ).
WRITE: / 'Actual heap usage:', lv_real_mem.

이 시점에 ST02/SM04로 워크 프로세스를 보면 PRIV 모드로 전환된 상태일 가능성이 큽니다. 라이브 운영 시스템에서는 절대 그대로 실행하지 말고 테스트 클라이언트에서 검증하세요.

실전 예제 2단계: 힙 메모리 폭발 시나리오

현업에서 자주 만나는 패턴 — 헤더-아이템-스케줄라인을 하나의 거대한 딥 구조체로 빌드하고 마지막에 ALV로 출력하거나 RFC로 반환하는 흐름입니다.

METHOD build_full_order_data.
  " 잘못된 패턴: 전체 데이터를 한 번에 딥 구조체로 빌드
  SELECT vbeln, posnr, matnr, kwmeng
    FROM vbap
    WHERE vbeln IN @s_vbeln
    INTO TABLE @DATA(lt_items).

  LOOP AT lt_items INTO DATA(ls_item).
    APPEND INITIAL LINE TO me->mt_orders ASSIGNING FIELD-SYMBOL().
    -vbeln = ls_item-vbeln.
    " 라인마다 긴 텍스트 로딩 -> 힙 폭발
    -long_text = get_long_text( ls_item-vbeln ).
    " 라인마다 중첩 테이블 로딩 -> 힙 폭발
    -schedule_lines = get_schedule( ls_item-vbeln ).
  ENDLOOP.

  " 100만 건 처리 시 TSV_TNEW_PAGE_ALLOC_FAILED 발생
ENDMETHOD.

실전 예제 3단계: FREE로 명시적 해제하는 패턴

CONSTANTS c_chunk_size TYPE i VALUE 5000.
DATA: lt_chunk TYPE tt_order_deep,
      lv_offset TYPE i VALUE 0.

DO.
  " 1) 청크 단위로만 메모리에 적재
  SELECT FROM vbap
    FIELDS vbeln, posnr, matnr, kwmeng, netwr
    WHERE vbeln IN @s_vbeln
    ORDER BY vbeln, posnr
    INTO TABLE @DATA(lt_items)
    OFFSET @lv_offset
    UP TO @c_chunk_size ROWS.

  IF sy-subrc <> 0 OR lt_items IS INITIAL.
    EXIT.
  ENDIF.

  " 2) 처리
  LOOP AT lt_items ASSIGNING FIELD-SYMBOL().
    APPEND INITIAL LINE TO lt_chunk ASSIGNING FIELD-SYMBOL().
    -vbeln     = -vbeln.
    -long_text = get_long_text( -vbeln ).

    LOOP AT -schedule_lines ASSIGNING FIELD-SYMBOL().
      " 처리 완료된 중첩 라인 즉시 삭제
    ENDLOOP.
    FREE -schedule_lines.  " 중첩 테이블 즉시 해제
  ENDLOOP.

  " 3) 전송 후 FREE
  send_to_rfc( lt_chunk ).
  FREE lt_chunk.   " 힙까지 완전 해제
  FREE lt_items.

  lv_offset = lv_offset + c_chunk_size.
ENDDO.

" CLEAR vs REFRESH vs FREE
" CLEAR   lt_deep -> 라인 0건, 단 관리 페이지는 유지
" REFRESH lt_deep -> CLEAR와 유사, 페이지 유지 가능
" FREE    lt_deep -> 라인 + 힙(STRING/중첩 테이블) 모두 해제

SAT(SE30 후속)로 측정해보면 동일 데이터 처리에서 메모리 피크가 70~90% 감소하는 사례가 일반적으로 보고됩니다.

성능 측정과 메모리 모니터링 방법

딥 구조체 메모리를 측정하는 올바른 방법은 다음과 같습니다.

  • cl_abap_memory_utilities=>get_total_used_size(): 현재 워크 프로세스 힙 전체 사용량 반환. before/after 차이로 딥 구조체 실제 사용량 측정 가능
  • SAT (SE30 후속): 런타임 분석에서 메모리 할당 trace 확인
  • RSMEMORY 트랜잭션: 힙 메모리 사용 현황 상세 분석
  • ST02: 워크 프로세스별 메모리 현황, PRIV 전환 여부 확인

딥 구조체 사용 기준과 설계 가이드

딥 구조체가 필요한 경우와 피해야 하는 경우를 명확히 구분하는 것이 중요합니다. 필요한 경우: RFC/BTP API 전용 DTO, 화면에 직접 바인딩되는 소규모 데이터(수백 건 이하), JSON 직렬화가 필요한 복잡한 계층 구조. 피해야 하는 경우: 배치 처리 수만 건 이상, 반복 루프 안에서 생성되는 중간 구조체, 프로그램 전역 변수로 유지되는 캐시.

S/4HANA에서는 가능하면 ALV 대신 Fiori Elements와 CDS로 화면단까지 위임하는 설계가 ABAP 메모리 압박을 근본적으로 줄입니다. 코드 푸시다운 원칙에 따라 집계와 필터를 데이터베이스단에서 처리하면 ABAP로 올라오는 데이터 자체가 줄어듭니다.

흔한 실수와 트러블슈팅

  • Q1. CLEAR로 비웠는데 ST22 덤프가 계속 발생합니다. CLEAR/REFRESH는 라인을 비우지만 STRING/중첩 테이블이 차지한 힙은 즉시 회수되지 않을 수 있습니다. FREE 또는 변수 자체의 스코프 종료가 필요합니다.
  • Q2. DESCRIBE TABLE로 메모리 점검했더니 작다고 나오는데요. DESCRIBE TABLE은 LINES, OCCURS, KIND만 반환합니다. 딥 필드가 힙에 잡은 양은 포함하지 않습니다. 실제 측정은 cl_abap_memory_utilities=>get_total_used_size() 또는 RSMEMORY를 사용해야 합니다.
  • Q3. 함수 모듈 EXPORTING으로 딥 테이블을 반환하면 두 배로 잡히나요? EXPORTING은 값 전달 시 복사가 발생합니다. 큰 딥 테이블은 CHANGING으로 전달하거나 ASSIGNING으로 처리해 복사를 피하세요.
  • Q4. SELECT INTO TABLE에서 STRING 필드를 직접 받으면 안 되나요? 텍스트 테이블을 조인해 STRING으로 받는 패턴은 라인마다 힙을 잡는 대표 케이스입니다. 키만 가져오고 텍스트는 청크 단위로 별도 조회하는 분리 패턴을 권장합니다.

댓글 0

아직 댓글이 없습니다.