안녕하세요!
동시성 문제를 저의 상황에 맞게 해결했던 경험을 공유하려고 합니다.
상황은 이렇습니다.
제가 운영 중이던 서비스는 시퀀스가 단순히 번호를 매기는 의미가 아닌
비즈니스적으로 의미 있는 번호로 사용되어야 했습니다.
그래서 프로세스 중간중간에 예외로 인해 시퀀스가 버려지는 일이 발생하지 않아야 했기 때문에
RDBMS의 시퀀스를 사용하지 않고, 직접 시퀀스 테이블을 만들어 시퀀스의 순서가 보장되도록 설계가 되었습니다.
하지만 이러한 설계에는 문제가 있었습니다.
발생한 에러를 보겠습니다.
Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: 무결성 제약 조건(CHOICODE.SEQ_PK)에 위배됩니다
무결성 제약 조건 위배는 PK와 같은 제약조건에 중복된 값이 INSERT 또는 UPDATE 될 때 발생되는 에러입니다.
이렇게 시퀀스 테이블은 순서가 보장될 수 있는 장점이 있지만,
왕왕 동시성 문제에서 볼 수 있는 레이스 컨디션(Race Condition)으로 인해
사용자의 요청을 처리하지 못하는 단점이 있었습니다.
레이스 컨디션을 어떻게 해결해야 할까?
먼저 아키텍처는 이렇습니다.
그림과 같이 서버를 2대 구축해 사용하는 분산 환경으로 구성되어 있습니다.
먼저 동시성 문제 하면 Synchronized, DB Lock, Redis 등등 다양한 해결 방법이 있었지만,
결론부터 말씀드리면 앞서 나열한 방법이 아닌 PL/SQL의 GOTO로 해결을 하게 되었습니다.
그 이유는 다음과 같습니다.
1. 분산 환경에서는 Syncronized를 사용할 수 없다.
: 서버가 1대라면 문제없지만 2대 이상부터는 레이스 컨디션을 막을 수 없다. (+성능 이슈도 있다)
2. 동시 접속자 평균 30명대 서비스에서 레이스 컨디션이 발생할 확률이 있으나 크진 않다.
: Redis 환경을 설정해 얻는 이익에 비해 설계 및 관리로 인해 발생하는 비용이 더 크다.
(더 좋은 아키텍처 설계도 중요하지만, 서비스 상황에 맞지 않는 과한 설계는 오히려 비용이 증가할 수 있다)
3. DB Lock 사용 시 데드락에 걸렸을 때를 추가로 고려해야 함. (낙관적 락은 그렇지 않지만)
4. 프로시저를 사용하고 있기 때문에 GOTO를 적극 활용하기로 결정. (낙관적 락과 결이 비슷)
GOTO 적용
GOTO 사용 방법은 간단합니다.
GOTO를 지정하고 GOTO를 만나 이동될 블록을 지정하면 끝입니다.
💡 프로시저 순서는 다음과 같습니다.
1) 시퀀스 테이블에서 시퀀스를 얻는다.
2) 얻은 시퀀스를 시퀀스 테이블에 INSERT 한다.
3-성공 시) 완료.
3-예외 시) INSERT시점에 DUP_VAL_ON_INDEX 예외가 발생하면 다시 1~2번 과정을 반복해 성공할 때까지 진행한다.
결과
GOTO 적용 전
: 동시성 문제로 인해 TEST_USER2가 요청한 것은 예외 발생 (INSERT 실패)
GOTO 적용 후
: 정상적으로 TEST_USER2 요청까지 INSERT 완료
'Database' 카테고리의 다른 글
[H2] JPA User 엔티티, 테이블 drop 에러 (0) | 2023.08.20 |
---|---|
우왕좌왕 좌충우돌 Slow query 개선 경험기 (0) | 2023.08.18 |
[DB지식] MyBatis 사용 시 <!CDATA[ ... ]]> 사용 이유? (0) | 2021.11.01 |
[DB] 인덱스(Index) 사용 예시 (생성, 조회, 삭제, 리빌드) (0) | 2021.09.17 |
[DB] 데이터베이스(DB) 인덱스(Index) 란 무엇인가? (0) | 2021.09.13 |
댓글