1. 개요 및 이 글에서 얻어갈 것
ABAP Open SQL 또는 ABAP SQL에는 BYPASSING BUFFER라는 특수한 힌트가 있습니다. 이름 그대로 SAP 애플리케이션 서버에 존재하는 테이블 버퍼를 우회하고 직접 데이터베이스로 SELECT를 던지는 기능입니다. 처음 본 개발자는 "그러면 항상 정확한 최신 데이터가 나오니까 안전한 거 아닌가?"라고 오해하기 쉬운데, 실제 운영 환경에서는 오히려 오래된 데이터 반환, 성능 저하, 락 충돌이라는 세 가지 부작용을 일으키는 안티 패턴으로 다뤄지는 경우가 많습니다.
이 글에서는 다음을 다룹니다.
- SAP 애플리케이션 서버 테이블 버퍼와 DB 버퍼가 무엇이고,
BYPASSING BUFFER가 정확히 무엇을 우회하는지 - 이 힌트를 무분별하게 쓰면 발생하는 3대 부작용을 코드 레벨에서 단계별로 재현
COMMIT WORK,REFRESH BUFFER를 활용한 올바른 대체 패턴- 진단/단발성 시나리오와 ABAP Unit 테스트에서 합리적으로 사용하는 응용 패턴
대상 환경은 SAP NetWeaver AS ABAP 7.5x 이상, S/4HANA(ABAP Platform 2021/2023, 2025년 기준 Cloud Edition 포함)이며, 단일 코드 컨벤션은 7.40 SP08+에서 도입된 인라인 선언 기반으로 작성합니다.
2. 핵심 개념 — SAP DB 버퍼와 BYPASSING BUFFER 동작 원리
SAP ABAP 스택에서 "버퍼"라는 용어는 두 층위로 쓰입니다. 헷갈리기 쉬우니 먼저 정리합니다.
- SAP 애플리케이션 서버 테이블 버퍼 — 각 애플리케이션 서버 인스턴스의 메모리에 자주 읽히는 마스터/커스터마이징 테이블의 레코드를 캐싱합니다. DDIC(SE11)에서 테이블의 Technical Settings에 정의된 버퍼링 종류(완전 / 일반 영역 / 단일 레코드)에 따라 동작합니다.
- 데이터베이스 버퍼 캐시 — HANA, Oracle, DB2 등 실제 DBMS가 가지는 버퍼 풀입니다. 한 번 읽힌 페이지를 메모리에 유지해 디스크 I/O를 줄입니다.
ABAP SQL에 붙는 BYPASSING BUFFER는 첫 번째 층인 애플리케이션 서버 테이블 버퍼를 우회하라는 지시입니다. 즉, "이 SELECT만은 캐시에 있어도 무시하고 DB까지 직접 다녀와라"라는 의미입니다. DB 자체의 버퍼 캐시는 우회하지 않습니다.
비유하자면 회사 인트라넷에 캐싱된 사내 전화번호부(애플리케이션 서버 버퍼)를 무시하고, 본사 인사팀(DB)에 직접 전화해 매번 새로 물어보는 행위와 비슷합니다. 가끔 필요할 수 있지만, 사내 모든 직원이 항상 그렇게 한다면 인사팀 회선이 과부하되고, 그렇다고 더 "정확"한 답을 받는다는 보장도 없습니다(같은 트랜잭션에서 아직 COMMIT 되지 않은 데이터는 어차피 볼 수 없기 때문).
"동작 원리 도식
ABAP 프로그램
│
│ SELECT ... FROM tab.
▼
SAP App Server 테이블 버퍼 ──(HIT)──▶ 메모리에서 즉시 반환
│
│ (MISS 또는 BYPASSING BUFFER)
▼
Database 인터페이스(DBI)
│
▼
DBMS 버퍼 캐시 → 디스크
3. 1단계: 부작용 1 — 오래된 데이터(Stale Data) 반환
"버퍼를 우회하면 항상 최신 데이터를 본다"는 표현은 절반만 맞습니다. BYPASSING BUFFER는 다른 워크 프로세스가 COMMIT한 결과는 DB에서 직접 가져오므로 최신처럼 보입니다. 그러나 같은 트랜잭션 내에서 본인이 UPDATE 후 아직 COMMIT 하지 않은 변경은 일반적으로 DB까지 내려가지 않았기 때문에, 의도와 달리 "오래된" 값을 반환합니다.
" 1단계 예제 - stale data 시나리오
DATA(lv_carrid) = 'LH'.
" 같은 LUW 안에서 UPDATE
UPDATE scarr SET currcode = 'USD'
WHERE carrid = lv_carrid.
" COMMIT WORK 없이 BYPASSING BUFFER 로 다시 조회
SELECT SINGLE currcode
FROM scarr
BYPASSING BUFFER
INTO @DATA(lv_currcode)
WHERE carrid = @lv_carrid.
" lv_currcode 는 'USD'가 아닌 이전 값일 수 있음
" - SAP LUW 관점에서 UPDATE 는 아직 DB로 전송되지 않았을 수 있고
" - 전송됐더라도 isolation level 에 따라 자기 자신은 보일 수 있으나
" 다른 워크 프로세스에서 호출된 함수 모듈에서는 보이지 않음
WRITE: / 'currcode after update =', lv_currcode.
핵심은 BYPASSING BUFFER가 "트랜잭션 무결성"을 보장하는 도구가 아니라는 점입니다. 정합성이 필요하다면 COMMIT WORK로 DB에 변경을 확정한 뒤 조회하거나, 같은 LUW 내에서는 ABAP 측 내부 테이블로 변경분을 트래킹해야 합니다.
4. 2단계: 부작용 2 — 성능 저하 (버퍼 캐시 미사용)
두 번째 부작용은 운영 환경에서 가장 자주 문제 되는 항목입니다. 버퍼링이 설정된 테이블(특히 커스터마이징 테이블 T로 시작하는 다수의 테이블)은 호출 빈도가 높습니다. 매 SELECT마다 BYPASSING BUFFER가 붙어있으면 모든 호출이 DBI를 거쳐 DB까지 내려갑니다.
" 2단계 예제 - 루프 안에서 BYPASSING BUFFER 사용 (안티 패턴)
LOOP AT lt_docs INTO DATA(ls_doc).
SELECT SINGLE land1
FROM t005
BYPASSING BUFFER " 매 반복마다 DB I/O 발생
INTO @DATA(lv_land)
WHERE land1 = @ls_doc-country.
" ... 후속 로직
ENDLOOP.
위 코드를 ST05(SQL Trace)에서 보면, 동일 키에 대한 SELECT가 반복적으로 DB까지 내려가는 현상이 그대로 드러납니다. 정상이라면 첫 번째 SELECT가 애플리케이션 서버 버퍼에 적재되고, 두 번째부터는 마이크로초 단위에 끝납니다.
더 나은 대체는 두 가지입니다.
- 버퍼링을 신뢰하고
BYPASSING BUFFER를 제거 - 키가 분산되어 캐시 효율이 낮다면 FOR ALL ENTRIES 또는 CDS view + JOIN으로 단일 트립 SQL 작성
" 2단계 개선 예제 - 한 번의 SQL 로 대체
SELECT land1
FROM t005
FOR ALL ENTRIES IN @lt_docs
WHERE land1 = @lt_docs-country
INTO TABLE @DATA(lt_country).
LOOP AT lt_docs INTO DATA(ls_doc).
READ TABLE lt_country
WITH KEY land1 = ls_doc-country
TRANSPORTING NO FIELDS
BINARY SEARCH.
" ...
ENDLOOP.
5. 3단계: 부작용 3 — DB 락 충돌과 런타임 오류
세 번째 부작용은 상대적으로 덜 알려져 있지만 야간 배치에서 종종 발견됩니다. BYPASSING BUFFER가 붙은 SELECT는 DBI를 거쳐 DB까지 내려가기 때문에, DB 측 트랜잭션과 동시에 실행될 때 락 대기, deadlock, snapshot 충돌이 발생할 가능성이 높아집니다.
특히 HANA가 아닌 일부 전통적 RDBMS에서, 같은 테이블을 여러 워크 프로세스가 UPDATE/INSERT 하는 동안 다른 프로세스가 BYPASSING BUFFER로 SELECT 하면, 락 호환성 조합에 따라 다음과 같은 ABAP 런타임 오류로 이어질 수 있습니다.
DBIF_RSQL_SQL_ERROR— SQL 실행 중 DB 에러DBIF_RSQL_LOCK_TIMEOUT— 락 대기 타임아웃SAPSQL_ARRAY_INSERT_DUPREC등 동시성 관련 예외
" 3단계 예제 - 락 타임아웃 가능성 있는 패턴
TRY.
SELECT *
FROM zorder_header
BYPASSING BUFFER
INTO TABLE @DATA(lt_order)
WHERE status = 'OPEN'.
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
" DBIF_RSQL_LOCK_TIMEOUT 등이 cx_sy_open_sql_db 로 전파될 수 있음
MESSAGE lx_db->get_text( ) TYPE 'E'.
ENDTRY.
해결의 출발점은 "그 SELECT가 정말로 버퍼를 우회해야 하는가?"를 다시 묻는 것입니다. 대부분의 경우 답은 "아니오"이고, 단순히 힌트를 제거하기만 해도 락 경합이 줄어듭니다.
6. 4단계: 올바른 대체 패턴 (COMMIT WORK + REFRESH BUFFER)
최신 데이터를 보장하면서 버퍼 이점을 포기하지 않는 대안은 다음과 같이 정리됩니다.
- 패턴 A — COMMIT WORK 이후 조회: 자신이 변경한 데이터를 다시 읽어야 한다면 우선 LUW를 닫습니다. 단, COMMIT 이후 일관성/롤백 가능성에 대한 책임은 호출자에게 옮겨갑니다.
- 패턴 B — 명시적 버퍼 무효화: 외부 시스템(IDoc, RFC, BAPI)이 같은 테이블을 갱신했을 가능성이 있다면
$TAB또는DB FUNCTION호출, 혹은 ABAP의REFRESH BUFFER명령으로 해당 인스턴스의 버퍼만 즉시 무효화합니다. - 패턴 C — 버퍼링 종류 재설계: 데이터가 매우 자주 변하는데 버퍼링이 켜져 있다면 애초에 SE11에서 버퍼링 정책을 재검토하는 편이 안전합니다.
" 패턴 A: COMMIT WORK 후 조회
UPDATE zprice SET amount = @lv_new_amount
WHERE matnr = @lv_matnr.
COMMIT WORK AND WAIT. " DB 반영 보장 + UPDATE TASK 완료 대기
SELECT SINGLE amount
FROM zprice
INTO @DATA(lv_current)
WHERE matnr = @lv_matnr.
" 패턴 B: 명시적 버퍼 무효화
" 현재 애플리케이션 서버의 zprice 버퍼만 비우기
$TAB. " SE16 등에서의 단축 명령 - 프로그램에서는 아래 구문 사용
REFRESH BUFFER zprice.
SELECT SINGLE amount
FROM zprice
INTO @DATA(lv_amount)
WHERE matnr = @lv_matnr.
운영 관점에서 강조할 점은 REFRESH BUFFER가 현재 애플리케이션 서버 인스턴스의 버퍼만 비운다는 것입니다. 다중 인스턴스 환경에서 모든 노드의 버퍼를 동기화하고 싶다면, 표준 메커니즘인 DDLOG(버퍼 동기화 테이블)와 rdisp/bufrefmode 파라미터 설정에 의존하는 편이 권장됩니다. 즉, 일반적으로 ABAP 코드 내에서 강제 무효화를 남용하기보다는 시스템 파라미터를 통한 표준 동기화 주기에 맞추는 것이 안전합니다.
7. 자주 겪는 함정과 FAQ
Q1. 버퍼링된 테이블에 무조건 BYPASSING BUFFER를 붙이면 안 되나요?
일반적으로 안 됩니다. SAP 표준 가이드라인에서도 운영 코드에서의 상시 사용은 권장되지 않습니다. 대신 데이터의 신선도가 결정적으로 중요한 단일 지점에 한해 사용하고, 호출 빈도가 낮다는 것을 ST05로 사전에 검증하세요.
Q2. SELECT FOR UPDATE 와 BYPASSING BUFFER 의 차이는?
FOR UPDATE는 DB 락을 거는 잠금 의도가 있는 SELECT이고, BYPASSING BUFFER는 단순히 애플리케이션 서버 캐시를 우회할 뿐 락과 무관합니다. 두 옵션은 서로 다른 목적이며 동시에 쓸 수도 있지만, 락 경합 가능성이 누적되므로 신중해야 합니다.
Q3. HANA에서는 어차피 메모리 DB이니 BYPASSING BUFFER가 무해하지 않나요?
HANA의 메모리 특성과 관계없이, 우회되는 것은 애플리케이션 서버 버퍼입니다. 매 호출마다 ABAP DBI → HANA 왕복이 발생하므로 컨텍스트 전환과 네트워크 호출 비용은 그대로 누적됩니다. 결과적으로 HANA 환경에서도 권장되지 않는 것은 동일합니다.
Q4. CDS 뷰에는 BYPASSING BUFFER가 어떻게 적용되나요?
CDS 뷰 정의 자체에는 버퍼링 옵션이 별도로 존재합니다(@AbapCatalog.buffering.status, @AbapCatalog.buffering.type). 일반적으로 CDS는 HANA 위에서 푸시다운되도록 설계되므로 BYPASSING BUFFER를 강제하기보다는 CDS 단계에서 버퍼링 정책을 명시적으로 끄는 편이 일관성 있는 접근입니다.
8. 응용 패턴 — 진단 용도와 ABAP Unit 테스트에서의 활용
그렇다면 BYPASSING BUFFER는 절대 쓰면 안 될까요? 그렇지는 않습니다. 다음 시나리오에서는 합리적으로 활용됩니다.
- 진단/조사용 일회성 프로그램 — 운영 이슈 분석 중 "버퍼된 값과 DB 실제 값이 다른가?"를 확인할 때 임시 보고서에 사용
- 데이터 마이그레이션 후 검증 보고서 — 마이그레이션 직후 DB 반영 상태를 즉시 확인하는 단발성 스크립트
- ABAP Unit 테스트 — 테스트 더블이 어려운 경우, 테스트 사전 조건/검증 단계에서 버퍼 영향을 배제하기 위해 사용
" 응용 예제 - ABAP Unit 테스트에서 사전조건 검증
CLASS ltc_price_update DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS setup.
METHODS update_should_persist FOR TESTING.
ENDCLASS.
CLASS ltc_price_update IMPLEMENTATION.
METHOD setup.
" 테스트 시작 전 버퍼 상태와 무관하게 DB 실제 값을 읽기 위함
SELECT SINGLE amount
FROM zprice
BYPASSING BUFFER
INTO @DATA(lv_baseline)
WHERE matnr = 'TEST-0001'.
ENDMETHOD.
METHOD update_should_persist.
" System Under Test 호출
zcl_price_service=>set_amount(
iv_matnr = 'TEST-0001'
iv_amount = 100 ).
COMMIT WORK AND WAIT.
SELECT SINGLE amount
FROM zprice
BYPASSING BUFFER
INTO @DATA(lv_after)
WHERE matnr = 'TEST-0001'.
cl_abap_unit_assert=>assert_equals(
exp = 100
act = lv_after ).
ENDMETHOD.
ENDCLASS.
이런 테스트 코드에서의 사용은 운영 트래픽에 영향이 없고, "DB에 실제로 반영되었는가"라는 의도를 명시적으로 표현하기 때문에 가독성 측면에서도 정당화됩니다. 다만 가능하다면 테스트 더블(test double) 또는 SAP가 제공하는 CDS 테스트 프레임워크를 활용해 DB 의존을 줄이는 방향이 더 권장됩니다.
정리하면, BYPASSING BUFFER는 "최신 데이터 보장 마법봉"이 아니라 버퍼 메커니즘을 의도적으로 무력화하는 도구입니다. 운영 코드에서는 기본적으로 사용하지 않고, 일관성이 필요한 지점에서는 COMMIT WORK + 표준 버퍼 동기화에 의존하며, 정말 필요할 때만 진단/테스트 용도로 한정해 사용하는 것이 안전한 선택입니다.
댓글 0
아직 댓글이 없습니다.