지표 버전 관리 및 결재 승인 프로세스의 일관성을 보장하기 위한 멱등적 구조 설계
요약
지표 관리 기능에서 결재중 상태가 중복 생성되는 문제를 발견하고,
“사전 체크 API + 조건부 UPDATE 기반 멱등적 결재요청 API” 구조로 개선하여
지표 버전 관리·결재 프로세스·부서/임계치 정보의 일관성을 안정적으로 보장했어요.
1. 문제 정의
지표관리 화면은 한 지표에 대해 “등록 → 수정 → 결재요청 → 승인(새 버전 생성)”이라는 전체 수명주기를 담당해요.
그러나 다음과 같은 문제로 인해 버전 일관성·결재 상태 일관성·동시성 안전성이 깨질 위험이 있었어요.
기존 문제 상황
-
여러 사용자가 같은 지표를 동시에 수정할 수 있음
-
한 사용자가 결재요청을 한 뒤에도,
다른 사용자가 기존 화면 그대로 결재요청을 다시 시도하는 것이 가능함 -
이때 TASK 테이블에서
- 동일 지표(ANMLY_INDIC_ID)에 대해
ANMLY_INDIC_STAT_CD = '02'(결재중)상태의 작업본이 여러 건 생성되는 문제 발생
- 동일 지표(ANMLY_INDIC_ID)에 대해
-
결재버전기준 테이블(VER_DTL / THLD_DTL / MNG_DEPT_DTL)은
버전별 1개(또는 3개, N개) 행만 존재해야 하는 강한 일관성 규칙을 가짐
그러나 결재중 TASK가 여러 건 존재하면- 어떤 TASK가 실제 승인되어야 하는지 불명확해지고
- 승인 후 버전 증가(batch) 시
중복된 버전 생성, 부서 매핑 꼬임, 임계치 정보 충돌 등 심각한 데이터 불일치 위험 발생
즉,
“지표는 버전별로 1개만 존재해야 하고, 한 지표 한 버전에 대해 두개의 결재요청이 들어올 수는 없다.”
이 비즈니스 규칙을 기술적으로 완벽히 보장할 필요가 있었어요.
2. 원인 분석
(1) 화면 상태 기반 처리의 한계
지표관리 화면은 사용자가 지표를 선택하거나 버튼을 클릭할 경우, 다음과 같이 상태를 분기 별로 처리했어요.
- 내 작업본 존재 → 수정 모드
- 내 작업본 없음 → 최신 승인본 view 모드
- 등록 버튼 클릭 → 등록 모드 → 저장 후 작업본 생성
- 수정 버튼 클릭 → 수정 모드 → 수정 및 결재요청 가능
하지만 화면 상태(ScreenState.mode, ScreenState.isDirty)만으로
지표가 현재 결재 가능한지 여부를 판단했어요.
👉 실제 DB 상태(누가 어떤 지표를 결재중인지)는 전혀 확인하지 않았어요.
(2) 결재요청 API가 상태만 바꿈 (검증 없음)
기존 결재요청 API는:
shell작성중(01) → 결재중(02)
으로 변경하는 것만 수행하고,
이미 다른 사람이 결재 요청했는지에 대해서는 검증하지 않았어요.
👉 중복 결재중 TASK 생성 가능
(3) 사전 체크 API 부재
프론트에서는 결재요청 시 다음과 같은 정보가 필요하지만,
어디에서도 조회하고 있지 않았어요.
- 현재 지표에 작업본이 있는가?
- 그 작업본의 상태는 무엇인가? (작성중인지, 결재중인지)
- 누가 작업한 것인가?
결과적으로,
- A가 결재요청했어도
- B 화면에서는 여전히 “작성중”으로 보이고
- “결재요청” 버튼이 활성화된 상태 유지
3. 해결 전략 (멱등 + 일관성 보장 구조 설계)
전체 해결책은 **UX 레벨 사전 방어 + 서버 레벨 최종 방어(멱등성)**의 이중 구조로 설계했어요.
1) 사전 체크 API 신설 — /retrieve-appv-task
결재요청 버튼 클릭 시 항상 서버에 실제 상태를 확인하도록 새로운 API를 만들었다.
쿼리에서 다음 조건을 만족하는 작업본 최신 1건만 조회:
ANMLY_INDIC_ID동일- 상태가
작성중(01)이 아닌 모든 작업본 (02:결재중,03:승인완료등) - RN(ROW_NUMBER) = 1 (가장 최신)
이를 통해 프론트는 결재 요청 전에 사용자에게 다음과 같이 안내할 수 있게 되었어요.
“현재 이 지표는 ‘이병건’님에 의해 결재 요청된 상태입니다. 해당 결재요청을 진행할 수 없습니다.”
사용자는 현재 상태를 정확히 인지한 뒤 다음 행동을 할 수 있게 되었고,
잘못된 결재 요청 시도를 UI 단계에서 먼저 걸러낼 수 있게 되었어요.
2) 실제 결재요청 API에서 DB 조건부 UPDATE로 멱등성 보장
사전 체크는 어디까지나 UX용이고,
동시 클릭·여러 탭 등은 항상 존재하는 리스크이기 때문에,
핵심은 결재요청 API에 한 번 더 강력한 DB 제약을 넣는 것이에요.
조건부 UPDATE (핵심)
sqlUPDATE TB_EWS_ANMLY_INDIC_VER_TASK_DTL T SET ANMLY_INDIC_STAT_CD = '02' WHERE T.ANMLY_INDIC_CHG_TASK_ID = #{taskId} AND T.ANMLY_INDIC_STAT_CD = '01' AND NOT EXISTS ( SELECT 1 FROM TB_EWS_ANMLY_INDIC_VER_TASK_DTL X WHERE X.ANMLY_INDIC_ID = T.ANMLY_INDIC_ID AND X.ANMLY_INDIC_CHG_TASK_ID != T.ANMLY_INDIC_CHG_TASK_ID AND X.ANMLY_INDIC_STAT_CD IN ('02','03') )
- 먼저 들어온 결재요청만 작성중 → 결재중으로 변경
- 조건을 만족하지 않으면 updated=0 → 즉시 실패
- 즉, 두 요청이 동시에 들어와도 단 한 요청만 성공
이 패턴을 통해 지표결재 프로세스의 핵심 비즈니스 제약이 보장돼요.
“지표당 결재중 작업본은 단 하나만 존재해야 한다.”
이 구조 자체가 멱등성(Idempotent)을 만족하며,
동일 요청이 여러 번 반복되어도 DB 상태는 변하지 않아요.
3) 전체 화면 흐름도 비즈니스 규칙과 동기화
- 등록모드 → 최초 저장 → 사용여부는 ‘미사용’ 고정
- 사용하려면 → 수정모드 → 저장 → 결재요청
- 결재승인 시 → VER 테이블에 새 버전 Insert
- THLD_DTL, MNG_DEPT_DTL 등도 단일 버전 기준으로 정확히 1:N 구조 보장
- 승인 완료 시 → 마스터 테이블의 “적용중 버전”이 새 버전으로 업데이트
즉,
등록 ⇒ 수정 ⇒ 결재요청 ⇒ 승인 ⇒ 새 버전 반영
전 과정이 데이터 규칙 기반으로 자동 정렬되었어요.
4. 결과
1) 지표당 결재중 상태가 1건만 존재하는 구조 확립
중복 결재중 TASK 생성 문제 완전 해소.
2) 화면-DB 간 상태 불일치 해소
- 결재중/승인/작성중 여부가 항상 실제 DB 기준으로 판단됨
- 여러 사용자가 한 지표를 작업해도 프론트 상태만 믿는 문제 사라짐
3) 결재 프로세스의 UX 대폭 향상
사용자에게:
- “누가 결재 올렸는지”
- “현재 어떤 상태인지”
명확히 안내하므로 불필요한 혼란 제거.
✔ 데이터 무결성 보장
- THLD_DTL, MNG_DEPT_DTL, VER_DTL 등
버전 기준 테이블과 TASK 테이블 간의 일관성 유지 - 승인 시 버전 증가 및 마스터 버전 업데이트도 깨짐 없이 작동
✔ 멱등적 결재요청 API 완성
요청이 여러 번 발생해도 DB 상태는 정확히 한 번만 변경됨.