ABAP

테이블 Buffering 3가지 — Full·Generic·Single 완전 비교 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 핵심 포인트

ABAP 애플리케이션의 응답 속도는 데이터베이스 왕복 횟수에 크게 좌우됩니다. 코드성 테이블이나 자주 조회되는 마스터 데이터를 매번 DB에서 읽어오면 네트워크 지연과 락 경합이 누적되어 트랜잭션 처리량이 떨어집니다. SAP NetWeaver Application Server는 이를 완화하기 위해 테이블 버퍼링(Table Buffering)이라는 인메모리 캐시 메커니즘을 제공하며, 일반적으로 SE11/SE13 트랜잭션에서 테이블별로 버퍼 종류를 지정합니다.

이 글에서는 ABAP Dictionary가 지원하는 세 가지 버퍼링 전략 — Full, Generic, Single Entry — 를 비교하고, 어떤 테이블에 어떤 전략이 적합한지 결정하는 기준을 다룹니다.

  • Full Buffering: 첫 접근 시 테이블 전체를 앱 서버 메모리에 적재 (예: T001, T005T)
  • Generic Buffering: 키 앞부분 N개 컬럼을 기준으로 부분 집합을 캐시 (예: T002T, 언어별 텍스트 테이블)
  • Single Entry Buffering: 조회된 단일 레코드만 캐시 (예: TSTC)
  • 버퍼링을 끄는 기준: 변경 빈도 높음, WHERE 절에 부등호/LIKE/BETWEEN/집계 함수 사용
  • SELECT SINGLE 또는 fully-qualified key를 사용하지 않으면 버퍼가 우회될 수 있다는 점 주의

사전에 알아두면 좋은 배경

ABAP의 데이터 접근은 일반적으로 다음 계층으로 구분됩니다. 애플리케이션 코드의 Open SQL → DBI(Database Interface) → DBSL(Database Specific Library) → 실제 RDBMS(HANA, Oracle 등). 테이블 버퍼는 DBI 계층에 속하며, 각 애플리케이션 서버 인스턴스의 공유 메모리에 위치합니다. 따라서 같은 시스템이라도 인스턴스가 여러 개라면 버퍼는 인스턴스별로 따로 존재합니다.

버퍼링을 이해하려면 Open SQL의 키 컬럼 처리 방식, MANDT(클라이언트) 필드의 의미, 그리고 변경 후 다른 앱 서버로 무효화 신호가 전파되는 데 걸리는 지연 시간(보통 1~2분 주기의 DDLOG 폴링)을 함께 알아두는 것이 좋습니다.

실습 환경 및 버전

본 글의 화면 동선과 명령은 다음 환경을 가정합니다.

  • SAP NetWeaver AS ABAP 7.50 이상 (S/4HANA 1909 / 2020 / 2022 호환)
  • ABAP Dictionary 트랜잭션: SE11 (테이블 정의), SE13 (기술 설정, Technical Settings)
  • 모니터링 트랜잭션: ST10 (버퍼 통계), ST02 (메모리 영역), DB05 (키 분포 분석)
  • 코드 예제는 ABAP for HANA(7.55+)의 인라인 선언과 New Open SQL을 사용
  • HANA의 결과 캐시(Statement Cache)와는 별개의 개념임을 유의

S/4HANA 환경에서도 테이블 버퍼는 여전히 유효합니다. HANA 자체가 인메모리 DB이지만, ABAP 앱 서버에서 DB로 가는 왕복 비용은 0이 아니기 때문에 빈번하게 읽히는 작은 테이블에는 버퍼링 효과가 일반적으로 크게 나타납니다.

핵심 개념과 동작 원리

버퍼링은 SE13의 Buffering allowed 옵션을 켜고 Buffering type을 선택하는 것으로 활성화됩니다. 종류별 동작은 다음과 같이 비유할 수 있습니다.

Full Buffering — 책 한 권 통째로 책상 위에 펴두기

첫 SELECT 시 테이블의 모든 행을 가져와 앱 서버 메모리에 통째로 올립니다. 이후 같은 테이블에 대한 모든 조회는 DB를 거치지 않고 메모리에서 해결됩니다. 행 수가 적고(보통 수천~수만 행 이내), 자주 읽히며, 거의 변하지 않는 코드성 테이블(T001 — 회사 코드, T005T — 국가 텍스트)에 적합합니다.

Generic Buffering — 챕터 단위로 불러오기

키 컬럼 중 앞에서 N개(generic key)를 기준으로 부분 집합을 단위 캐시합니다. 예를 들어 키가 (MANDT, SPRAS, KEY)인 텍스트 테이블에서 generic key 수를 2로 지정하면, 특정 클라이언트+언어 조합에 해당하는 모든 행이 하나의 캐시 블록으로 적재됩니다. 전체 테이블은 크지만, 보통 한 사용자가 한 번에 접근하는 부분 집합은 작은 경우(언어별 텍스트, 국가별 설정)에 유리합니다.

Single Entry Buffering — 한 줄씩 포스트잇

SELECT SINGLE으로 풀 키를 지정해 조회한 한 행만 캐시합니다. 키 공간이 매우 크고(수십만~수백만 행), 한 트랜잭션이 접근하는 행은 일부에 불과한 마스터/제어 테이블(TSTC 등)에 적합합니다. 누락된 행을 조회하는 경우도 "존재하지 않음"이라는 사실이 캐시될 수 있습니다.

버퍼 무효화와 동기화

INSERT/UPDATE/DELETE/MODIFY가 발생하면 해당 행을 포함하는 버퍼 항목이 무효화됩니다. 변경이 일어난 인스턴스는 즉시 자신의 버퍼를 비우지만, 다른 인스턴스는 DDLOG 테이블에 기록된 변경 로그를 주기적(기본 1~2분)으로 폴링해 동기화합니다. 이 지연 때문에 클러스터 환경에서는 잠깐 동안 인스턴스별로 다른 값이 보일 수 있습니다 — 변경 빈도가 높은 테이블을 버퍼링하면 안 되는 가장 큰 이유입니다.

실전 코드 — Full / Generic / Single Entry

1) Full Buffering 설정과 사용

SE13에서 테이블의 Technical Settings 화면을 열고 Buffering allowed but switched offBuffering switched on으로, Buffering type을 Fully buffered로 설정합니다. 저장 후 활성화하면 첫 SELECT 시 전체 행이 적재됩니다.

" Z_COUNTRY_TEXT 가 Full buffered 라고 가정
" 어떤 WHERE 조합이든 첫 호출 후엔 메모리에서 응답
DATA lt_country TYPE STANDARD TABLE OF t005t.

SELECT mandt land1 spras landx
  FROM t005t
  INTO TABLE @lt_country
  WHERE spras = @sy-langu.

" 같은 세션의 다음 호출은 DB hit 없음
SELECT SINGLE landx
  FROM t005t
  INTO @DATA(lv_name)
  WHERE land1 = 'KR'
    AND spras = @sy-langu.

2) Generic Buffering 설정과 사용

SE13에서 Generically buffered를 선택하고 Number of key fields를 입력합니다. 키가 (MANDT, SPRAS, OBJECT_ID)일 때 값 2를 지정하면 클라이언트+언어 단위로 캐시됩니다. 사용 코드에서는 반드시 generic key를 모두 WHERE 절에 포함시켜야 버퍼가 동작합니다.

" Generic key = 2 (MANDT, SPRAS) 가정
" generic key 모두 지정 → 버퍼 사용
SELECT object_id, text
  FROM zobj_text
  INTO TABLE @DATA(lt_text)
  WHERE spras = @sy-langu
    AND object_id IN @lr_objects.

" generic key 누락 → 버퍼 우회되어 DB 직행
SELECT object_id, text
  FROM zobj_text
  INTO TABLE @DATA(lt_text_bad)
  WHERE object_id = @lv_id.   " SPRAS 미지정

두 번째 쿼리는 같은 데이터를 노려도 generic key의 한쪽(SPRAS)이 빠져 있어 버퍼 조회 조건을 만족하지 못합니다. 이런 패턴이 반복되면 버퍼 적중률이 떨어지므로 ST10에서 hit ratio가 95% 미만이면 사용 패턴을 점검할 필요가 있습니다.

3) Single Entry Buffering과 로깅/예외 처리

Single entry는 SELECT SINGLE + 완전 키 지정과 짝을 이룹니다. 키 일부만 주면 버퍼를 우회합니다. 운영 환경에서는 호출 빈도와 실패율을 로그로 남기는 것이 좋습니다.

CLASS zcl_tcode_reader DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS read_tcode
      IMPORTING iv_tcode        TYPE tstc-tcode
      RETURNING VALUE(rs_tstc)  TYPE tstc
      RAISING   cx_sy_itab_line_not_found.
ENDCLASS.

CLASS zcl_tcode_reader IMPLEMENTATION.
  METHOD read_tcode.
    " TSTC는 Single entry buffered (표준)
    SELECT SINGLE *
      FROM tstc
      INTO @rs_tstc
      WHERE tcode = @iv_tcode.

    IF sy-subrc <> 0.
      " '없음'도 캐시되므로 동일 키 재조회 시 DB hit 없음
      MESSAGE i208(00) WITH |TCODE { iv_tcode } not found|.
      RAISE EXCEPTION TYPE cx_sy_itab_line_not_found.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

프로덕션 관점 — 성능 검증과 보안

운영 배포 전에는 단위 테스트로 캐시 동작을 검증하고, 권한 검사를 버퍼 조회 후에도 동일하게 적용해야 합니다. 캐시되었다고 권한 체크를 생략하면 데이터 노출 위험이 생깁니다.

" 1) ST05 SQL Trace로 두 번째 SELECT가 'Buffer' 로 표시되는지 확인
" 2) ST10에서 hit ratio / invalidations 모니터링
" 3) AUTHORITY-CHECK 는 버퍼 조회 결과에도 동일하게 적용

AUTHORITY-CHECK OBJECT 'S_TCODE'
  ID 'TCD' FIELD rs_tstc-tcode.
IF sy-subrc <> 0.
  RAISE EXCEPTION TYPE cx_sy_authorization_error.
ENDIF.

아주 큰 결과 집합을 LOOP로 반복 SELECT 하는 패턴은 버퍼링 이전에 알고리즘 개선(FOR ALL ENTRIES, JOIN, CDS View)을 먼저 검토하는 편이 일반적으로 효과가 큽니다.

실전 패턴 — 버퍼링 선택 가이드

아래 매트릭스는 SE13 설정 결정을 도와줍니다. 절대 기준은 아니며, 실제 워크로드를 ST10/ST05로 측정해 조정하는 것을 권장합니다.

테이블 크기 변경 빈도 주요 접근 패턴 권장 전략
~1MB / 수천 행 매우 낮음 (일 단위) 다양한 WHERE, 코드 변환 Full
수십 MB / 수만~수십만 행 낮음 키 앞부분으로 그룹 조회 (언어/회사/플랜트별) Generic (N=1~3)
크지만 한 트랜잭션은 일부만 낮음~중간 풀 키 SELECT SINGLE 위주 Single Entry
큼 + 변경 빈번 높음 (분/초 단위) 트랜잭션 데이터 버퍼링 끔
아무 크기 - WHERE에 LIKE/BETWEEN/집계 함수 다수 버퍼링 끔

generic key 개수를 정할 때는 DB05로 키 분포를 분석해 "한 묶음의 평균 행 수"가 수십~수백 정도가 되도록 잡는 것이 일반적으로 안정적입니다. 묶음이 너무 크면 Full과 비슷한 메모리 부담이, 너무 작으면 캐시 항목 수가 폭증해 관리 비용이 커집니다.

흔한 실수와 트러블슈팅

Q1. SELECT … BYPASSING BUFFER를 무심코 남발했어요

BYPASSING BUFFER 절은 버퍼를 우회해 항상 DB로 직행합니다. 최신 값이 반드시 필요한 일부 검증 로직에만 제한적으로 사용하고, 보고서나 일반 조회에 일괄 적용해 둔 코드가 있다면 제거 대상입니다. ST05에서 같은 테이블이 반복적으로 DB hit으로 잡힌다면 의심해볼 만합니다.

Q2. 클러스터 환경에서 인스턴스마다 값이 다르게 보입니다

변경 동기화가 DDLOG 폴링 주기(기본 1~2분) 만큼 지연되기 때문입니다. 변경 빈도가 높거나 즉각적 일관성이 필요한 테이블이라면 버퍼링을 끄는 것이 안전합니다. 임시 회피로는 BYPASSING BUFFER 또는 변경 후 RESET BUFFER 호출이 있으나, 잦은 호출은 오히려 캐시 효율을 떨어뜨립니다.

Q3. SUM/COUNT/GROUP BY 쿼리가 버퍼를 안 쓰는 것 같아요

집계 함수, DISTINCT, ORDER BY (PRIMARY KEY 외), 서브쿼리, JOIN이 포함된 SELECT는 테이블 버퍼를 사용하지 않습니다. 즉, 캐시 대상이 아닌 패턴입니다. 집계가 필요하다면 결과를 ABAP 내부 테이블에서 직접 집계하거나, 별도의 집계용 CDS View를 만들고 그쪽을 캐시 전략에 맞게 사용하는 편이 일반적으로 낫습니다.

Q4. ST10에서 hit ratio가 낮습니다

대개 두 가지 원인입니다. (1) generic key 누락 — WHERE 절에 generic key를 모두 명시했는지 확인, (2) 메모리 부족으로 인한 displacement — ST02에서 TABL/TABLP 영역의 free space와 swaps를 점검합니다. swaps가 많다면 프로파일 파라미터(rsdb/ntab/entrycount, zcsa/table_buffer_area 등)를 BASIS 팀과 함께 재조정합니다.

다음 단계 / 관련 주제

테이블 버퍼링은 ABAP 성능 튜닝의 한 축일 뿐입니다. 함께 익혀두면 좋은 주제는 다음과 같습니다.

  • CDS View와 HANA의 Column Store / Result Cache 활용
  • SHMA(Shared Memory Area) / SHARED BUFFER / SHARED MEMORY OBJECTS
  • SAT, ST05, ST12를 활용한 성능 트레이스 해석
  • FOR ALL ENTRIES vs JOIN vs Subquery 선택 가이드
  • S/4HANA에서 SELECT * 지양과 필드 리스트 최소화 원칙

참고 자료

댓글 0

아직 댓글이 없습니다.