ABAP

SM13 없이 놓치는 업데이트 실패 3가지 #shorts #SAP #ABAP

업데이트가 실패했는데 화면은 "성공"이라고 말하는 이유

ABAP에서 CALL FUNCTION ... IN UPDATE TASK로 등록한 업데이트 모듈이 실행 중에 예외로 죽으면, 정작 그 트랜잭션을 실행한 사용자 화면에는 아무 일도 일어나지 않습니다. 사용자는 성공 메시지를 보고 자리를 떠났는데 데이터베이스에는 아무것도 저장되지 않은 상태 — 운영 환경에서 가장 추적하기 어려운 유형의 장애입니다. 이 글은 물류센터 재고 실사 조정이라는 커스텀 시나리오를 놓고, 왜 이런 일이 구조적으로 발생하는지, 그리고 실패를 어떻게 감지하고 사용자에게 되돌려줄지를 단계별로 다룹니다.

  • V1/V2 업데이트 태스크가 다이얼로그 세션과 분리 실행되는 구조 이해
  • SY-SUBRC 체크가 왜 아무것도 보장하지 않는지 확인
  • COMMIT WORK AND WAIT로 V1 실패를 동기적으로 감지
  • SM13 + BAL 애플리케이션 로그 + 상태 테이블 기반 모니터링 체계 구축

미리 갖추면 좋은 배경

ABAP의 SAP LUW(Logical Unit of Work)와 DB LUW의 차이, 함수 모듈 속성 화면에서 "Update module" 체크가 의미하는 것, 그리고 COMMIT WORK / ROLLBACK WORK의 기본 동작을 알고 있으면 수월합니다. Open SQL과 함수 모듈 작성 경험은 필수이고, SM13·SM21 같은 Basis 모니터를 한 번이라도 열어봤다면 더 좋습니다.

검증 환경과 준비물

이 글의 예제는 SAP S/4HANA 2023 (ABAP Platform 7.58, 온프레미스/Private Cloud 에디션) 기준으로 작성했으며, 업데이트 태스크 메커니즘 자체는 클래식 NetWeaver 7.40 이상에서도 일반적으로 동일하게 동작합니다. 준비물은 다음과 같습니다.

  • 커스텀 테이블 2개: ZLGX_ADJ_HDR(재고 조정 문서 헤더), ZLGX_ADJ_ALERT(실패 알림 기록)
  • 업데이트 함수 모듈: Z_LGX_ADJ_POST (속성에서 "Update module – Start immediately" = V1 지정)
  • SM13(업데이트 레코드), SM14(업데이트 관리), SLG1(애플리케이션 로그) 조회 권한
  • 참고: SAP BTP ABAP Environment(Steampunk)에서는 클래식 업데이트 태스크 대신 RAP 저장 시퀀스를 쓰는 것이 권장되므로, 이 글은 온프레미스 클래식 ABAP 관점입니다.

동작 원리 — 업데이트 태스크는 "다른 사람"이 실행한다

택배 접수 창구를 떠올려 보세요. 창구 직원(다이얼로그 워크 프로세스)에게 송장을 접수하면 "접수 완료" 도장을 받고 돌아갑니다. 실제 배송은 나중에 배송 기사(UPD 워크 프로세스)가 수행하죠. 배송 중에 사고가 나도 접수 창구에는 아무 알림이 오지 않습니다 — 이미 손님은 떠났으니까요. 업데이트 태스크가 정확히 이 구조입니다.

CALL FUNCTION 'Z_...' IN UPDATE TASK는 함수를 실행하는 것이 아니라, 전달 파라미터를 로그 테이블(VBLOG 계열: VBHDR/VBMOD/VBDATA)에 기록만 합니다. 이후 COMMIT WORK가 실행되는 순간 업데이트 워크 프로세스가 이 레코드를 집어 별도의 DB LUW에서 실행합니다. 흐름을 정리하면 다음과 같습니다.

  1. 다이얼로그 WP: IN UPDATE TASK 호출 → 요청 등록 (SY-SUBRC = 0은 "등록 성공"일 뿐)
  2. 다이얼로그 WP: COMMIT WORK → 사용자에게 제어 반환, 성공 메시지 표시
  3. UPD WP: V1 모듈 순차 실행 → 전부 성공 시 커밋, 하나라도 실패 시 V1 전체 롤백
  4. UPD WP: V1 성공 시에만 V2 모듈 실행 (V2 실패는 이미 커밋된 V1을 되돌리지 못함)

3번 단계에서 업데이트 모듈이 MESSAGE ... TYPE 'A'를 내거나 미처리 예외로 덤프가 나면, 해당 레코드는 SM13에 오류 상태로 남고 SAP는 익스프레스 메시지로 통보를 시도합니다. 하지만 사용자가 이미 로그오프했거나 다른 화면에 있으면 사실상 아무도 모릅니다. E/W/I 타입 메시지는 업데이트 태스크 안에서 화면이 없으므로 의미 자체가 없고, 런타임이 이를 종료 상황으로 격상 처리합니다. 즉 업데이트 모듈 안에서 "사용자에게 메시지를 보여준다"는 개념은 존재하지 않습니다.

단계별 실전 예제 — 재고 실사 조정 문서 전기

1단계 — 기본 구조, 그리고 조용한 실패 재현

V1 업데이트 모듈부터 만듭니다. 함수 빌더 속성에서 Processing Type을 Update module(Start immediately)로 지정합니다.

FUNCTION z_lgx_adj_post.
*"  IMPORTING VALUE(is_hdr) TYPE zlgx_adj_hdr
  INSERT zlgx_adj_hdr FROM @is_hdr.
  IF sy-subrc <> 0.
    " 업데이트 태스크 내부: 이 메시지는 사용자 화면에 절대 닿지 않는다.
    " 레코드는 SM13에 'Error' 상태로 남고 V1 전체가 롤백된다.
    MESSAGE a001(zlgx_msg) WITH is_hdr-adj_docno.
  ENDIF.
ENDFUNCTION.

호출부는 이렇게 생겼습니다. 문제가 되는 지점에 주석을 달았습니다.

DATA(ls_hdr) = VALUE zlgx_adj_hdr(
  adj_docno = lv_docno  whs_id = 'WH07'
  matnr     = lv_matnr  diff_qty = lv_diff ).

CALL FUNCTION 'Z_LGX_ADJ_POST'
  IN UPDATE TASK
  EXPORTING is_hdr = ls_hdr.
" 여기서 sy-subrc = 0 은 '로그 테이블에 등록됐다'는 뜻일 뿐,
" 실제 INSERT의 성공과는 아무 관계가 없다.

COMMIT WORK.
MESSAGE s010(zlgx_msg). " 실패해도 사용자는 이 성공 메시지를 본다

중복 문서번호를 강제로 넣어 실행해 보면 화면은 멀쩡히 성공 메시지를 띄우지만, SM13을 열면 해당 레코드가 오류 상태로 남아 있고 테이블에는 아무것도 없습니다. 이것이 이 글이 다루는 문제의 원형입니다.

2단계 — 실무 보강: AND WAIT 감지 + BAL 로그

COMMIT WORK AND WAIT는 V1 업데이트가 끝날 때까지 다이얼로그를 대기시키고, V1이 실패하면 SY-SUBRC를 0이 아닌 값으로 돌려줍니다. 실패를 감지하면 SLG1에서 조회 가능한 애플리케이션 로그를 남기고 사용자에게 오류를 알립니다.

CALL FUNCTION 'Z_LGX_ADJ_POST'
  IN UPDATE TASK
  EXPORTING is_hdr = ls_hdr.

COMMIT WORK AND WAIT.   " V1 완료까지 동기 대기

IF sy-subrc <> 0.
  " ---- V1 실패 감지: 애플리케이션 로그 기록 ----
  DATA(ls_log) = VALUE bal_s_log(
    object    = 'ZLGX'  subobject = 'ADJ_POST'
    extnumber = ls_hdr-adj_docno ).
  DATA lv_handle TYPE balloghndl.

  CALL FUNCTION 'BAL_LOG_CREATE'
    EXPORTING i_s_log      = ls_log
    IMPORTING e_log_handle = lv_handle.

  CALL FUNCTION 'BAL_LOG_MSG_ADD'
    EXPORTING
      i_log_handle = lv_handle
      i_s_msg      = VALUE bal_s_msg(
        msgty = 'E' msgid = 'ZLGX_MSG' msgno = '020'
        msgv1 = ls_hdr-adj_docno msgv2 = ls_hdr-whs_id ).

  CALL FUNCTION 'BAL_DB_SAVE'
    EXPORTING i_save_all = abap_true.
  COMMIT WORK. " 로그 저장 확정 (새 LUW)

  MESSAGE e021(zlgx_msg) WITH ls_hdr-adj_docno.
ENDIF.

주의할 점 두 가지입니다. 첫째, AND WAIT는 V1까지만 기다립니다. V2 모듈의 실패는 여기서 잡히지 않습니다. 둘째, 다이얼로그가 업데이트 완료를 기다리므로 응답 시간이 늘어납니다. 사용자가 결과를 즉시 확인해야 하는 단건 처리에는 적합하지만, 대량 배치에서 건건이 쓰면 처리량이 떨어지므로 일반적으로 선별 적용이 권장됩니다.

3단계 — 프로덕션: 상태 플래그 + 모니터 잡으로 촘촘하게

AND WAIT를 못 쓰는 대량 처리나, V2 실패까지 잡아야 하는 경우에는 "요청 접수 흔적"을 다이얼로그 LUW에서 직접 남기는 패턴이 효과적입니다. 다이얼로그 WP의 직접 DB 변경은 COMMIT WORK 시점에 즉시 커밋되고, 업데이트 태스크의 실패는 업데이트 LUW만 롤백하므로 접수 흔적은 살아남습니다. 이 비대칭을 역이용하는 것입니다.

" [다이얼로그] 접수 마커를 직접 INSERT (status = 'P'ending)
INSERT zlgx_adj_alert FROM @( VALUE #(
  adj_docno = ls_hdr-adj_docno
  status    = 'P'
  req_user  = sy-uname
  req_ts    = utclong_current( ) ) ).

CALL FUNCTION 'Z_LGX_ADJ_POST'
  IN UPDATE TASK EXPORTING is_hdr = ls_hdr.

" 성공 시 마커를 'C'ompleted로 바꾸는 V2 모듈도 함께 등록.
" V2는 V1이 전부 성공한 뒤에만 실행되므로 훌륭한 성공 신호다.
CALL FUNCTION 'Z_LGX_ADJ_CONFIRM_V2'   " V2 update module
  IN UPDATE TASK EXPORTING iv_docno = ls_hdr-adj_docno.

COMMIT WORK.

업데이트가 죽으면 마커가 'P'로 영원히 남습니다. 주기 배치 잡이 이를 수거해 알림을 발송합니다.

REPORT zlgx_adj_monitor.
" 10분 이상 'P' 상태로 방치된 접수 건 = 업데이트 실패 의심
SELECT * FROM zlgx_adj_alert
  WHERE status = 'P'
    AND req_ts < @( utclong_add( val = utclong_current( )
                                 seconds = -600 ) )
  INTO TABLE @DATA(lt_stale).

LOOP AT lt_stale INTO DATA(ls_a).
  " 상세 원인은 SM13(VBHDR/VBERROR)에서 확인하도록 문서번호와
  " 사용자·시각을 담아 담당자에게 통보 (메일/워크플로 등)
  UPDATE zlgx_adj_alert SET status = 'F', noti_sent = @abap_true
    WHERE adj_docno = @ls_a-adj_docno.
ENDLOOP.
COMMIT WORK.

프로덕션 관점 마무리 체크리스트입니다.

  • 성능: 접수 마커 테이블은 status + req_ts 보조 인덱스를 두고, 완료 건('C')은 주기적으로 아카이브
  • 테스트: 등록 로직을 별도 클래스(ZCL_LGX_ADJ_SVC)로 분리하고 함수 호출을 인터페이스 뒤로 감추면 ABAP Unit에서 테스트 더블로 실패 경로까지 검증 가능
  • 보안/운영: SM13 재처리·삭제 권한은 운영 담당자로 제한하고, 오류 레코드 방치 여부를 SM14와 함께 정기 점검

자주 겪는 문제와 해결

Q1. IN UPDATE TASK 호출 직후 SY-SUBRC = 0이면 성공 아닌가요?

아닙니다. 그 시점에는 함수가 한 줄도 실행되지 않았습니다. 파라미터가 로그 테이블에 직렬화되어 저장됐다는 뜻일 뿐이며, 실제 실행은 COMMIT WORK 이후 UPD 워크 프로세스에서 일어납니다. 실행 결과를 알려면 COMMIT WORK AND WAIT(V1 한정) 또는 3단계의 상태 마커 패턴이 필요합니다.

Q2. 업데이트가 죽었는데 익스프레스 메시지도 안 왔습니다.

익스프레스 통보는 사용자가 여전히 로그온 상태일 때만 의미가 있고, 시스템 설정이나 실패 유형에 따라 도착하지 않는 경우가 흔합니다. 통보를 설계의 전제로 삼지 말고, SM13 오류 레코드 확인과 자체 알림 체계를 기본으로 두는 편이 권장됩니다.

Q3. SM13에서 오류 레코드가 사라졌습니다.

재처리 후 성공했거나, 재편성 잡(예: 오래된 업데이트 레코드 삭제 배치)이 지웠을 가능성이 큽니다. 그래서 SM13만으로는 이력 추적이 어렵고, BAL 로그나 커스텀 알림 테이블처럼 애플리케이션이 소유하는 기록이 별도로 필요합니다.

Q4. V2 모듈이 실패하면 V1 데이터도 롤백되나요?

아니요. V2는 V1 커밋 이후에 실행되므로 V1 결과는 이미 확정입니다. V2 실패는 통계·후속 처리 누락 형태로 남으니, V2에 핵심 정합성 로직을 두지 않는 것이 일반적으로 안전합니다.

Q5. 업데이트 모듈 안에서 BAL 로그를 쓰면 안 되나요?

실패하는 그 LUW 안에서 쓴 로그는 함께 롤백됩니다. 로그는 실패의 영향권 밖(호출자 측 새 LUW, 또는 별도 커넥션)에서 남겨야 살아남습니다.

여기서 더 나아가려면

같은 문제의식을 확장할 수 있는 주제들입니다. 트랜잭션 경계와 재시도를 프레임워크가 관리해 주는 bgRFC/tRFC/qRFC, 실패 큐 모니터인 SM58·SBGRFCMON, 그리고 RAP(ABAP RESTful Application Programming Model)의 저장 시퀀스가 클래식 업데이트 태스크를 어떻게 대체하는지 비교해 보면 좋습니다. BAL 로그를 깊게 파려면 로그 오브젝트 설계와 SLG1 필터 전략, 보존 기간 관리까지 이어집니다.

댓글 0

아직 댓글이 없습니다.