[SeSAC] 트랜잭션 처리 전략 - Lock, 커밋/롤백, Audit Trail

j_wisdom_h·2026년 2월 27일

포트폴리오

목록 보기
4/6

트랜잭션 처리 전략 - Lock, 커밋/롤백

📌 배경(Why?)

입고(GR) 데이터는 재고·회계·후속 문서 흐름에 직접 연결되는 핵심 기준 데이터이기 때문에,
저장 시점의 작은 오류나 처리 충돌도 운영·재무 전반에 연쇄적인 영향을 미칠 수 있다.

이에 따라 다음과 같은 구조적 리스크를 통제할 필요가 있었다.

1) 부분 성공·부분 실패로 인한 트랜잭션 불일치

  • 한 번의 저장안에 여러 품목이 함께 처리되는 구조로
  • 일부 Item만 성공하고 일부는 실패하는 경우, 사용자 인식과 실제 반영 데이터 간 괴리가 발생

➡ 입고 문서와 재고 테이블 간 불일치, 운영자 재확인·조정 업무 증가

2) Lock 전략 미흡으로 인한 통제 불명확성

  • 동일 레코드에 대한 동시 수정 통제 기준이 명확하지 않을 경우
  • 누가 먼저 수정했는지, 왜 저장이 실패했는지 파악이 어려움

➡ 데이터 충돌 발생 가능성과 운영 부서의 시스템 신뢰도 저하의 문제 발생

📌 설계 의도(Why this design?)

입고(GR) 수정 및 저장 시점을 Logical Unit of Work (LUW) 정의하고,

동시성 제어와 Commit/Rollback 규칙을 명확히 설정함으로써

항상 일관된 처리 결과가 보장되도록 설계하였다.

1) 레코드 단위 Lock으로 ‘동시 수정’ 자체를 논리적으로 차단

입고 품목은 입고문서 번호(GRNUM) + 품목 번호(GRPOS) 조합으로 식별된다.

이에 해당 키를 기준으로 ENQUEUE/DEQUEUE Lock을 적용하여

동일 데이터에 대한 동시 수정이 발생하지 않도록 통제하였다.

  • 선행 사용자가 Lock을 선점하면 수정 권한을 보유
  • 후속 사용자는 조회만 가능하도록 제한하거나, 수정 시도 시 명확한 오류 메시지를 반환

이를 통해 “First User Wins” 원칙을 적용하고,

데이터 충돌을 애플리케이션 레벨이 아닌 트랜잭션 레벨에서 구조적으로 차단하였다.

“동시성 문제를 해결하는 가장 확실한 방법은 ‘동시에 같은 데이터를 못 건드리게 하는 것’이라고 판단했다.
동일 데이터에 대한 동시 수정 충돌을 SAP Lock(Enqueue) 메커니즘을 통해 구조적으로 방지하였다.

2) All or Nothing 트랜잭션 원칙

입고 저장 과정에서 일부 품목 또는 일부 테이블만 반영되고 나머지가 실패하는 경우,

GR 문서·PO 상태·재고 테이블·FI 문서 간 데이터 불일치가 발생할 가능성이 있었다.

다음 네 영역을 단일 처리 단위로 묶어 관리하였다.

  • GR 문서 생성
  • GR와 연계된 PO 상태 변경(완료 플래그)
  • GR 기준으로 반영되는 재고 테이블 수량/레코드 생성·수정
  • GR에 의해 발생하는 FI 문서(회계 전표) 생성

위 영역 중

어느 한 단계라도 오류가 발생하면 ROLLBACK WORK를 수행하고

모든 처리가 정상 완료된 경우에만 COMMIT WORK

를 실행하도록 설계하였다

즉,

“한 번의 저장은 GR·PO·재고·FI가 모두 성공했을 때만 유효하다”

라는 All-or-Nothing 트랜잭션 원칙을 적용하여,

문서·재고·회계 상태가 서로 어긋난 채로 남는 상황을 구조적으로 차단하였다.

📌 구현 방식(How?)

1) ENQUEUE/DEQUEUE 기반 동시성 제어 구현

① 입고 품목 단위 Lock 선점

입고 수정 저장이 시작될때,

변경 대상 품목을 기준으로 GRNUM(입고문서 번호) + GRPOS(품목 번호) 조합에 대해 Lock을 선점하도록 설계하였다.

  • 저장 로직 진입 시 각 품목별로 ENQUEUE 호출
  • Lock 선점 성공 시 → 해당 품목을 Validation 및 Update 대상으로 포함
  • Lock 선점 실패(foreign_lock) 시 → 즉시 저장을 중단하고 사용자에게 명확한 안내 메시지 제공

“해당 품목은 현재 다른 사용자에 의해 수정 중입니다.”

이를 통해 동일 품목에 대한 동시 수정 자체를 사전에 차단하고,

사용자가 저장 실패 원인을 명확히 인지할 수 있도록 하였다.

*&---------------------------------------------------------------------*
*&      Form  ENQUEUE_LOCK
*&---------------------------------------------------------------------*
*       Lock Enqueue
*----------------------------------------------------------------------*
FORM enqueue_lock_item .
  DATA lv_numc LIKE ztspgri-grpos.

  CLEAR gv_subrc.
  PERFORM get_selected_rows USING go_alvb.

  LOOP AT gt_selected INTO DATA(ls_selected).
    CLEAR lv_numc.
    lv_numc = ls_selected-row_id.

    CALL FUNCTION 'ENQUEUE_EZ_ZTSPGRI'
      EXPORTING
        mode_ztspgri   = 'E'
        mandt          = sy-mandt
        grnum          = gs_hmain-grnum
        grpos          = lv_numc
      EXCEPTIONS
        foreign_lock   = 1
        system_failure = 2
        OTHERS         = 3.
    IF sy-subrc NE 0.
      gv_subrc = sy-subrc.
      MESSAGE i012. " 해당 품목은 현재 다른 사용자에 의해 수정 중입니다.
      RETURN.
    ENDIF.
  ENDLOOP.
ENDFORM.

② 트랜잭션 종료 시점의 Lock 해제 전략

입고 저장 로직이 종료될 때,

처리 대상 품목에 대해 DEQUEUE를 호출하여 Lock을 명확히 해제하도록 설계하였다.

  • 모든 Validation 및 DB Update가 정상 완료된 경우 → COMMIT WORK 이후 DEQUEUE 수행
  • 예외 또는 오류 발생 시 → ROLLBACK WORK 수행 후 반드시 DEQUEUE 실행

특히 예외 상황에서도 Lock이 해제되지 않고 남는 일이 없도록,

예외 처리 블록에서 Lock 해제를 강제하는 구조로 설계하였다.

이를 통해

  • Lock 잔존으로 인한 Dead Lock 발생 가능성 차단
  • 특정 사용자가 데이터를 장시간 점유하는 상황 방지
  • 동시 사용자 환경에서도 안정적인 운영 보장

하도록 하였다.

*&---------------------------------------------------------------------*
*&      Form  DEQUEUE_LOCK_ITEM
*&---------------------------------------------------------------------*
*       Lock Dequeue
*----------------------------------------------------------------------*
FORM dequeue_lock_item.
  DATA lv_numc LIKE ztspgri-grpos.

  LOOP AT gt_selected INTO DATA(ls_selected).
    CLEAR lv_numc.
    lv_numc = ls_selected-row_id.

    CALL FUNCTION 'DEQUEUE_EZ_ZTSPGRI'
      EXPORTING
        mode_ztspgri = 'E'
        mandt        = sy-mandt
        grnum        = gs_hmain-grnum
        grpos        = lv_numc.
  ENDLOOP.

  CLEAR gt_selected.
ENDFORM.

2) All or Nothing 트랜잭션 처리 구현 방식

‘한 번의 저장 = 한 번의 Commit 또는 Rollback’ 원칙을 실제로 적용하기 위해, 사용자가 GR을 저장하는 행위 전체를 하나의 업무 트랜잭션으로 보고 GR·재고·PO·FI가 함께 움직이도록 설계했다.

GR 문서 생성이 전체 트랜잭션의 시작점

  • 사용자가 저장을 누르면 먼저 GR 헤더를 생성한다.
  • GR 자체가 생성되지 않으면 → 재고 반영, PO 상태 변경, FI 생성 모두 수행되지 않도록 차단.
  • GR이 존재하지 않는데 재고나 회계가 움직이는 비정합 상황을 원천 방지했다.

모든 GR 품목·재고 반영을 ‘한 묶음’으로 처리

  • GR 헤더가 성공해야 그 다음에 각 GR Item 생성 + 재고 증가를 수행한다.
  • 어떤 품목이라도 입력 오류·재고 제약·Validation 실패가 발생하면 → 이번 저장 전체를 즉시 Rollback.
  • “몇 개 품목만 반영된 반쪽짜리 GR”이 남지 않도록 했다.

PO 품목·헤더 상태 자동 업데이트 (성공 시에만 진행)

  • 모든 GR Item + 재고 반영이 정상 완료된 경우에만 → PO Item 상태(진행/부분완료/완료) 갱신 → 잔여 진행 품목 수에 따라 PO 헤더 상태 자동 변경.
  • PO 상태 갱신 중 하나라도 실패하면 → GR·재고·PO 변경 전체를 Rollback.

→ “입고는 되었지만 PO는 예전 상태 그대로” 같은 문서 불일치가 발생하지 않도록 했다.


FI 문서까지 포함한 ‘단일 트랜잭션’으로 통합

  • FI 생성까지 성공해야만 최종 Commit.
  • FI에서 오류가 나면 → GR, 재고, PO 변경까지 모두 Rollback.
  • MM(재고)과 FI(회계)가 서로 다른 상태로 갈라지는 현상을 근본적으로 차단했다.

3) 생성/수정 이력 필드 기반 Audit Trail 확보

입고(GR)와 연계된 재고·PO·FI 전 구간에 대해,

모든 테이블에 생성자·생성일·생성시각 / 수정자·수정일·수정시각을 갖는공통 Audit 필드 구조를 표준 규칙으로 설계했다.

  • GR 생성 시점에는 해당 필드에 생성 정보를 일괄 세팅하고,
    이후 재처리·정정 등으로 데이터가 변경될 경우에는 수정 정보만 업데이트하도록 일관된 플로우를 정의했다.

이를 통해,

  • 누가, 언제, 어떤 업무 흐름으로 데이터를 생성·수정했는지를 명확하게 추적할 수 있고 단순 오류 분석뿐 아니라 감사(Audit), 내부통제, 운영 보고서 작성 시에도 신뢰도 높은 이력 기반을 제공할 수 있게 되었다.

생성/수정 이력은 단순히 “언제 바뀌었는지”를 남기는 용도를 넘어,

변경 주체(담당자)와 변경 타이밍을 기준으로 책임과 권한을 명확히 할 수 있는 근거 데이터로 활용된다.

이슈를 추적할 때, “어느 조직/사용자가 어떤 패턴으로 수정했는지”를 이력 기반으로 분석할 수 있어,
운영 정책 수립, 권한 조정, 교육 대상 선정 같은 후속 프로세스에도 직접적인 인사이트를 제공한다.

profile
안녕하세요! j_wisdom_h의 개발기록 블로그입니다.

0개의 댓글