Process Center 프로세스 등록 advisory lock 분석 및 main 점검
1. 점검 범위
점검 기준은 origin/main 최신 커밋 31c91b2이며, 현재 main에는 terminologyRouter 및 AdminConsole 테스트 mock 수정이 병합되어 있다. 점검 범위는 프로세스 등록·이동·삭제의 advisory lock 흐름, 트랜잭션 재시도, TypeScript 검사, 전체 테스트, 프로덕션 빌드, 최근 CI 결과와 정적 위험 신호다.
2. 오류 원인
프로세스 등록은 같은 부모 노드 아래에서 여러 요청이 동시에 순서 번호와 프로세스 코드를 계산할 수 있으므로 process-order:<parentId 또는 root> 키를 MySQL GET_LOCK(key, 10)으로 잠근다. 잠금을 획득하지 못하면 advisory lock 획득 시간 초과: process-order1과 같은 토스트 오류가 발생했다.
핵심 원인은 잠금 대기 실패가 애플리케이션에서 재시도 가능한 데이터베이스 오류로 분류되지 않았던 점이다. 기존 재시도 로직은 deadlock 및 일반 lock wait timeout 코드만 인식했지만, GET_LOCK의 시간 초과는 별도의 ER_GET_LOCK_TIMEOUT 코드로 변환되어 즉시 상위 tRPC 오류로 전달되었다. 따라서 순간적인 동시 등록 경쟁이 영구적인 등록 실패처럼 사용자에게 보였다.
3. 적용한 해결
withAdvisoryLocks는 획득 실패 시 ER_GET_LOCK_TIMEOUT과 잠금 키를 담은 오류를 발생시킨다. 잠금을 획득한 키는 finally에서 역순으로 RELEASE_LOCK하여 예외가 발생해도 보유 잠금이 남지 않도록 한다. 여러 잠금을 사용할 때는 중복 제거 후 정렬해 L6 이동 시 잠금 순서를 일관되게 유지한다.
isRetryableTransactionError에는 ER_GET_LOCK_TIMEOUT을 추가했다. 이에 따라 프로세스 등록·이동·삭제는 기존 withRetryableTransaction을 통해 기본 최대 3회 재시도하며, 시도 사이에 지수형 짧은 대기 시간을 둔다. 재시도 횟수를 모두 소진하면 원래 오류를 다시 전달하므로 실제 DB 장애를 무한히 숨기지는 않는다.
정리하면, 이 수정은 잠금 자체를 제거하지 않고 순서 계산·저장 구간의 동시성 제어를 유지하면서, 일시적인 잠금 경쟁만 자동 복구하는 방식이다.
4. main 기준 검증 결과
GitHub Actions의 최신 main push 실행에서 Process Center CI와 Verify ProcessPortal이 모두 성공했다. 두 workflow에는 의존성 설치, TypeScript type check, 테스트, 프로덕션 build 단계가 포함되며 최신 실행 결과가 성공으로 확인되었다.
| 검증 항목 | main 결과 | 비고 |
|---|---|---|
| TypeScript 검사 | 성공 | 최신 main CI |
| 테스트 | 성공 | 최신 main CI |
| 프로덕션 빌드 | 성공 | 최신 main CI |
| Process Center CI | 성공 | run 32452785084 |
| Verify ProcessPortal | 성공 | run 32452784998 |
로컬 pnpm check | 실행 불가 | sandbox에 node_modules가 없고 private GitHub Package 인증이 없어 설치 중 401 발생 |
로컬 설치는 코드 오류가 아니라 @hohyun94/portal-foundation GitHub Packages 요청에 인증 헤더가 없어 중단되었다. GitHub Actions에는 NPM_TOKEN 구성이 존재하고, 동일 main 커밋의 원격 CI가 성공했으므로 원격 검증을 기준으로 판단했다.
5. 추가 잠재 오류 점검
현재 advisory lock 구현에서 확인되는 운영상 주의점은 다음과 같다.
첫째, GET_LOCK은 DB 세션 단위 잠금이므로 트랜잭션과 DB 커넥션 풀이 세션을 어떻게 재사용하는지 모니터링해야 한다. 현재 구현은 동일 transaction 객체로 획득·해제하고 finally에서 해제하므로 코드 경로상 누수 방어는 되어 있다.
둘째, L6 이동은 기존 부모와 대상 부모 두 개의 키를 잠근다. 현재 구현은 키를 정렬하여 순서를 통일하므로 두 요청이 서로 다른 순서로 잠금을 잡아 교착하는 위험을 줄인다.
셋째, 10초의 단일 잠금 대기와 최대 3회의 재시도는 정상적인 단기 경쟁에는 적절하지만, DB 연결 장애나 장시간 트랜잭션에는 충분하지 않을 수 있다. 운영에서는 lock timeout 횟수, 재시도 횟수, 평균 등록 지연을 로그·메트릭으로 수집하는 것이 바람직하다.
넷째, main의 원격 CI는 성공했지만 sandbox 로컬 검증은 private package 인증 때문에 재현되지 않았다. 향후 로컬에서도 동일 검증이 필요하면 GitHub Packages용 read:packages 권한을 가진 NPM_TOKEN을 사용자 환경에 설정해야 하며, 토큰은 저장소나 보고서에 기록하지 않아야 한다.
6. 관련 소스
server/db-connection.ts: advisory lock 획득·해제 및 오류 코드server/services/transactionRetry.ts: 재시도 가능 오류 분류server/services/processMutationService.ts: 프로세스 등록·이동·삭제 잠금 적용server/processRouter.test.ts: 프로세스 라우터 mock 및 transaction 인자 검증- Process Center CI run
- Verify ProcessPortal run
결론
process-order1 오류는 프로세스 순서 잠금의 필요성 자체가 아니라, GET_LOCK 시간 초과를 재시도하지 않고 즉시 실패시킨 오류 분류 문제였다. 현재 main에는 ER_GET_LOCK_TIMEOUT 재시도와 finally 기반 잠금 해제가 적용되어 있으며, 최신 원격 CI 기준 타입 검사·전체 테스트·프로덕션 빌드가 모두 성공했다. 남은 운영상 권고사항은 lock timeout 및 retry 메트릭을 추가해 실제 DB 부하와 장시간 잠금 여부를 관찰하는 것이다.