지표 버전 관리 및 결재 승인 프로세스의 일관성을 보장하기 위한 멱등적 구조 설계
요약
지표 관리 기능에서 결재중 상태가 중복 생성되는 문제를 발견하고,
“사전 체크 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 상태는 정확히 한 번만 변경됨.