드래프트 테이블은 왜 끝없이 커지는가
RAP(ABAP RESTful Application Programming Model)로 설비 점검 요청 관리 앱을 운영하던 한 현장 팀의 사례를 보면 문제의 본질이 잘 드러납니다. 설비 점검 요청(EqpInspReq) 화면에서 현장 엔지니어가 입력을 시작하면 즉시 드래프트 인스턴스가 생성되고, 저장하지 않고 화면을 닫거나 브라우저 탭을 그냥 꺼버리는 일이 하루에도 수백 건씩 발생했습니다. 몇 달 뒤 데이터베이스 모니터링 팀이 드래프트 테이블(ZEQPINSPREQ_D)의 용량이 운영 테이블(ZEQPINSPREQ)보다 더 커졌다는 사실을 발견했습니다.
이 현상의 핵심 원인은 세 가지입니다. 첫째, 드래프트는 활성 데이터와 달리 "임시 작업 공간"일 뿐이므로 사용자가 명시적으로 활성화(Activate)하거나 폐기(Discard)하지 않으면 영구히 남아 있습니다. 둘째, RAP 프레임워크 자체는 드래프트를 자동으로 삭제하는 백그라운드 메커니즘을 기본 내장하지 않으며, 정리 작업은 개발팀이 별도로 설계하고 스케줄링해야 하는 책임 영역입니다. 셋째, 드래프트 만료 기준(예: N일 이상 미변경)을 애초에 정의하지 않은 채 운영에 들어가면, 담당자가 퇴사하거나 업무가 중단된 요청 건들이 기약 없이 쌓이게 됩니다.
이 예제를 이해하기 위해 필요한 배경 지식
이 글은 RAP 비헤이비어 정의(Behavior Definition)와 draft 활성화 구문을 이미 작성해 본 경험이 있는 개발자를 대상으로 합니다. CDS 뷰 엔티티, 매니지드 비헤이비어 구현 클래스, EML(Entity Manipulation Language) 구문에 대한 기본 이해가 필요하며, 트랜잭션 SE80/ADT(ABAP Development Tools)에서 BDEF를 생성해 본 경험이 있으면 내용을 따라가기 수월합니다. 드래프트가 무엇인지 처음 접하는 경우, 먼저 draft 활성화와 Edit/Resume 액션의 기본 동작부터 익힌 뒤 이 글을 읽는 것을 권장합니다.
실습 환경과 준비물
이 예제는 SAP S/4HANA 2023 On-Premise(ABAP 플랫폼 기반) 및 SAP BTP ABAP Environment 양쪽 모두에서 재현 가능한 내용을 기준으로 작성했습니다. ADT(Eclipse 기반 ABAP Development Tools) 최신 버전, 패키지 생성 권한, 그리고 백그라운드 잡 스케줄링 권한(SM36 또는 BTP Job Scheduling Service 접근 권한)이 필요합니다. 예제에 등장하는 CDS 인터페이스 뷰 ZI_EQPINSPREQ, 프로젝션 뷰 ZC_EQPINSPREQ, 비헤이비어 정의 zi_eqpinspreq, 비헤이비어 풀 zbp_i_eqpinspreq, 드래프트 테이블 ZEQPINSPREQ_D는 모두 이 글을 위해 새로 설계한 가상의 설비 점검 요청 관리 시나리오입니다. 실제 구현 시에는 프로젝트의 네이밍 규칙에 맞춰 Z/Y 접두사를 조정하면 됩니다.
드래프트 테이블 동작 원리를 비유로 이해하기
드래프트 테이블을 "공유 사무실의 임시 작업대"에 비유하면 이해가 쉽습니다. 직원(사용자)이 서류(비즈니스 데이터)를 작성하려고 할 때, 정식 캐비닛(활성 테이블)에 바로 넣지 않고 먼저 개인 작업대(드래프트 테이블)를 하나 배정받아 거기서 초안을 씁니다. 작업이 끝나면 "결재 완료" 버튼을 눌러 캐비닛으로 옮기거나(Activate), "작업 취소" 버튼을 눌러 작업대를 치웁니다(Discard). 문제는 많은 사용자가 작업대만 배정받고 결재도, 취소도 하지 않은 채 자리를 떠나버린다는 점입니다. 사무실 관리자가 "일정 기간 방치된 작업대는 정기적으로 치운다"는 규칙을 세우지 않으면, 사무실(데이터베이스)은 비어 있는 작업대로 가득 차게 됩니다.
RAP 런타임 관점에서 드래프트 테이블의 각 행은 비즈니스 데이터 필드 외에 관리 정보(DraftUUID, CreationDateTime, LastChangedDateTime, InProcessByUser 등)를 함께 저장합니다. 이 관리 정보가 바로 "누가 언제 마지막으로 이 작업대를 건드렸는가"를 알려주는 단서이며, 정리 로직은 이 타임스탬프를 기준으로 "일정 기간 이상 손대지 않은 드래프트"를 찾아내는 방식으로 설계됩니다. 중요한 점은 이 판단과 삭제 실행을 자동으로 해주는 범용 백그라운드 기능이 기본으로 켜져 있지 않다는 것이며, 애플리케이션 팀이 비즈니스 요구에 맞는 만료 기준을 정의하고 주기적으로 실행되는 정리 로직을 직접 구현·스케줄링해야 한다는 점입니다.
또한 드래프트는 단순히 "미완성 데이터"가 아니라 락(Lock) 및 권한 체크와 연동되어 있다는 사실도 함께 기억해야 합니다. 방치된 드래프트가 많아지면 저장공간 문제뿐 아니라, 동일 인스턴스를 여러 드래프트가 참조하면서 발생하는 락 경합, 그리고 목록 조회 시 활성 데이터와 드래프트 데이터를 조인하는 쿼리의 성능 저하로도 이어집니다.
단계별 실전 코드로 보는 드래프트 관리
1단계: 기본 드래프트 활성화와 만료 안내 설정
먼저 설비 점검 요청 엔티티에 드래프트를 활성화하는 비헤이비어 정의부터 살펴봅니다. 아래는 표준 RAP 드래프트 구문을 사용한 기본 골격입니다.
managed implementation in class zbp_i_eqpinspreq unique;
strict(2);
define behavior for ZI_EQPINSPREQ alias EqpInspReq
persistent table zeqpinspreq
draft table zeqpinspreq_d
lock master
authorization master( instance )
etag master LastChangedAt
{
create;
update;
delete;
field ( readonly ) InspReqId;
draft action Edit;
draft action Activate;
draft action Discard;
draft action Resume;
draft determine action Prepare;
mapping for zeqpinspreq
{
InspReqId = inspection_req_id;
EquipmentId = equipment_id;
Status = status;
LastChangedAt = last_changed_at;
}
}
여기서 주목할 부분은 draft table zeqpinspreq_d 구문입니다. 이 한 줄이 선언되는 순간 프레임워크는 활성 테이블과 동일한 구조에 드래프트 관리 필드를 추가한 전용 테이블을 사용하도록 요구합니다. 문제는 이 테이블이 "얼마나 오래 보관할지"에 대한 정보를 BDEF 레벨에서 강제하지 않는다는 점입니다. 따라서 프로젝션 뷰 단에서 Fiori Elements 사용자에게 만료 경고를 보여주는 어노테이션을 함께 설정하는 것이 실무에서 자주 쓰이는 패턴입니다.
@ObjectModel.draft.draftExpirationIntervalInDays: 14
@Semantics.draft.enabled: true
define view entity ZC_EQPINSPREQ
as projection on ZI_EQPINSPREQ
{
key InspReqId,
EquipmentId,
Status,
LastChangedAt
}
이 어노테이션은 사용자 화면에 "이 작업 복사본은 N일 후 삭제될 수 있습니다"라는 안내를 노출하는 용도로 많이 활용되지만, 실제 삭제 동작을 자동으로 수행해주지는 않습니다. 즉 UI 안내와 실제 데이터 삭제는 별개의 메커니즘이라는 점을 반드시 구분해야 합니다.
2단계: 정리 잡 구현과 오류 처리, 로깅
실제 삭제를 수행하려면 드래프트 관리 데이터를 조회해 만료 기준을 넘긴 인스턴스를 찾아 Discard 액션을 호출하는 로직을 직접 작성해야 합니다. 아래는 이러한 정리 리포트의 핵심 구조 예시입니다.
CLASS zcl_eqpinspreq_draft_cleanup DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS run_cleanup
IMPORTING
iv_expiration_days TYPE i DEFAULT 14
RETURNING
VALUE(rv_deleted_count) TYPE i.
ENDCLASS.
CLASS zcl_eqpinspreq_draft_cleanup IMPLEMENTATION.
METHOD run_cleanup.
DATA(lv_threshold) = cl_abap_context_info=>get_system_date( ) - iv_expiration_days.
SELECT FROM zeqpinspreq_d
FIELDS inspection_req_id, last_changed_at, in_process_by_user
WHERE last_changed_at < @lv_threshold
INTO TABLE @DATA(lt_expired_drafts).
IF lt_expired_drafts IS INITIAL.
RETURN.
ENDIF.
LOOP AT lt_expired_drafts INTO DATA(ls_draft).
MODIFY ENTITIES OF ZI_EQPINSPREQ
ENTITY EqpInspReq
EXECUTE discard
FROM VALUE #( ( InspReqId = ls_draft-inspection_req_id ) )
MAPPED DATA(ls_mapped)
FAILED DATA(ls_failed)
REPORTED DATA(ls_reported).
IF ls_failed IS NOT INITIAL.
" 실패 건은 애플리케이션 로그(BAL)로 기록하여 추적 가능하게 유지
MESSAGE ID 'ZEQPINSPREQ' TYPE 'W' NUMBER '001'
WITH ls_draft-inspection_req_id
INTO DATA(lv_dummy).
ELSE.
rv_deleted_count = rv_deleted_count + 1.
ENDIF.
ENDLOOP.
COMMIT ENTITIES.
ENDMETHOD.
ENDCLASS.
이 구현에서 중요한 세 가지 실무 포인트가 있습니다. 먼저 last_changed_at 기준으로 임계값을 계산해 "일정 기간 손대지 않은 드래프트"만 대상으로 삼는다는 점, 둘째 Discard 호출 결과 중 FAILED가 발생한 건은 조용히 무시하지 않고 애플리케이션 로그(BAL, 트랜잭션 SLG1 조회 가능)로 남겨 추후 원인을 추적할 수 있게 한다는 점, 셋째 COMMIT ENTITIES를 통해 EML 변경 내용을 실제로 데이터베이스에 반영한다는 점입니다. 만약 이 커밋을 빠뜨리면 정리 로직이 정상 실행된 것처럼 보여도 실제로는 아무것도 삭제되지 않는 흔한 실수로 이어집니다.
3단계: 프로덕션 적용 — 스케줄링, 성능, 테스트, 보안
운영 환경에서는 이 정리 로직을 단발성으로 실행하지 않고 주기적인 백그라운드 잡으로 등록해야 합니다. On-Premise 환경이라면 SM36에서 매일 새벽 시간대에 실행되는 잡을 등록하고, BTP ABAP Environment에서는 Job Scheduling Service를 통해 동일한 역할을 수행할 수 있습니다. 다만 몇 가지 프로덕션 고려사항이 추가로 필요합니다.
성능 측면에서는 드래프트 건수가 수만 건 이상으로 누적된 상태에서 첫 정리 잡을 돌릴 경우 한 번에 전체를 처리하려 하면 롱 러닝 트랜잭션으로 인한 락 타임아웃이 발생할 수 있습니다. 따라서 PACKAGE SIZE를 지정해 일정 건수 단위로 끊어 COMMIT하는 방식이 권장됩니다.
SELECT FROM zeqpinspreq_d
FIELDS inspection_req_id
WHERE last_changed_at < @lv_threshold
INTO TABLE @DATA(lt_batch)
PACKAGE SIZE 500.
" 500건 단위로 discard 호출 후 COMMIT ENTITIES 반복
ENDSELECT.
테스트 측면에서는 ABAP 유닛 테스트 더블(Local Test Double)을 활용해 실제 테이블을 건드리지 않고 정리 로직의 임계값 계산과 FAILED 처리 분기를 검증하는 것이 일반적입니다. 또한 보안 측면에서는 정리 잡이 배치 사용자(RFC/배치 전용 기술 사용자)로 실행되더라도 각 드래프트 소유자의 권한 컨텍스트를 우회해서는 안 되므로, authorization master 설정이 인스턴스 단위로 유지되는지 반드시 재확인해야 합니다. 다른 사용자의 드래프트를 임의 삭제하는 로직이라면, 정리 잡 전용 기술 사용자에게 명시적으로 범위를 한정한 권한을 부여하고 일반 업무 사용자와 분리된 역할로 운용하는 것이 바람직합니다.
자주 발생하는 실수와 점검 포인트
- 정리 잡 자체를 스케줄링하지 않음: 코드를 구현해두고 실제 SM36/Job Scheduling Service 등록을 누락하는 경우가 가장 흔합니다. 배포 체크리스트에 잡 등록 여부를 반드시 포함해야 합니다.
- 만료 기준일을 너무 짧게 설정: 7일 미만으로 설정하면 주말 사이 작업을 이어가려던 사용자의 드래프트가 사라져 업무 불만으로 이어질 수 있습니다. 업무 주기(주간 보고, 월간 점검 등)를 고려해 기준을 정해야 합니다.
- COMMIT ENTITIES 누락: EML 변경이 메모리상에만 남고 실제 반영되지 않아 "정리 잡은 도는데 테이블은 안 줄어든다"는 혼란을 유발합니다.
- FAILED 결과를 무시: 락이 걸려 있거나 활성화 진행 중인 드래프트를 강제로 discard 시도하면 실패하는데, 이를 로그로 남기지 않으면 특정 레코드가 영구히 쌓이는 원인을 찾기 어려워집니다.
- Q: 드래프트 테이블 용량은 어떻게 모니터링하나요? A: 드래프트 테이블에 대해 주기적으로 건수와 최소/최대 last_changed_at 값을 집계하는 모니터링 쿼리를 CCMS나 자체 대시보드에 연동하는 방식이 실무에서 널리 쓰입니다.
- Q: 사용자가 작업 중인 드래프트까지 삭제되면 어떡하나요? A: 임계값을 넉넉히 잡고, 삭제 전 알림(이메일/인앱 메시지)을 보내는 2단계 정리 전략을 함께 설계하는 것이 안전합니다.
- Q: 활성 데이터가 없는 신규 생성 드래프트와 기존 데이터 수정 드래프트를 다르게 다뤄야 하나요? A: 네, 신규 생성 드래프트는 폐기해도 데이터 손실 영향이 적지만, 기존 레코드 수정 드래프트는 폐기 시 활성 데이터가 그대로 유지되므로 영향도가 다릅니다. 이 둘을 구분해 임계값을 다르게 가져가는 설계도 가능합니다.
이어서 학습하면 좋은 주제
드래프트 정리 로직을 안정적으로 운영하게 되었다면, 다음으로는 락(Lock) 및 ETag 충돌 처리 심화, Fiori Elements에서 드래프트 만료 안내 메시지를 커스터마이징하는 방법, 그리고 BTP Job Scheduling Service와 Business Event를 연동해 정리 결과를 실시간 알림으로 전달하는 구조를 살펴보는 것을 권장합니다. 또한 대규모 운영 환경이라면 드래프트 정리와 별도로 활성 테이블 자체의 아카이빙 전략(Data Archiving)도 함께 설계해 전체 데이터 생애주기를 관리하는 것이 바람직합니다.
더 살펴보면 좋은 자료
- help.sap.com - ABAP RESTful Application Programming Model, 드래프트 개념 개요
- help.sap.com - 비헤이비어 정의(Behavior Definition) 구문 레퍼런스
- help.sap.com - EML(Entity Manipulation Language) 사용법
- SAP Community - ABAP RESTful Application Programming Model 토픽 모음
- SAP Developers - RAP 관련 학습 자료 모음
- SAP Blogs - RAP 드래프트 처리 실전 경험 공유 태그
댓글 0
아직 댓글이 없습니다.