티스토리 뷰

업무 이슈

Write 동시성 이슈 해소

JStack 2024. 7. 27. 20:40

이슈 발생

Jennifer 모니터링 중 리워드 발급 관련 API에서 지속적으로 에러가 발생하는 것을 발견하여 분석해보았다.

정확한 현상은 두 개 이상의 리워드 발급 요청이 동시에 들어오는 경우 DuplicatedKeyError가 발생하는 현상이었다.

 

원인 분석

리워드 이력 테이블의 PK는 대리키로 만들어진 bigint 타입의 컬럼인데 auto_increment가 적용되어 있지않은 상태였다.

insert 쿼리 시 서버 어플리케이션에서는 max+1 값을 조회해서 PK value로 넣어주는 방식으로 구현되어 있었다.

 

이 방식의 문제는 크게 두 가지가 있다.

첫 번째는 insert를 위해 select max(PK) 연산이 수행되어야 하기 때문에 최적화가 어렵다는 점이다.

두 번째는 동시에 들어온 요청은 동일한 max(PK) 값으로 insert할 것이기에 유일성을 보장하기위해 후에 들어온 요청은 실패 처리된다는 점이다.

요청 실패 케이스 다이어그램

 

이슈 해결을 위한 수단

1. auto_increment 적용 : 파티셔닝된 테이블은 정책상 PK 수정이 불가능하여 적용 불가

2. sequence 활용 : MySQL은 지원하지 않아서 사용 불가

3. select ... for update 구문 활용 : lock 점유하는 동안 응답 지연되기 때문에 사용 안함

4. 채번 테이블 : 사용

 

개선 작업 내용

우선 채번용 테이블을 신규 생성하였다.

이 때 채번 테이블의 PK에 auto_increment 초기값을 설정하였는데 이슈 테이블의 max(PK)보다 여유있게 큰 값으로 설정했다.

(예를 들어 이슈 테이블 max(PK)가 1,000이라면 채번 테이블의 초기값은 1,500 정도로 설정하는 식)

 

그리고 MySQL function을 신규 생성하였다.

해당 function은 채번용 테이블에 데이터를 insert하고 select last_insert_id() 값을 반환하는 식으로 구현하였다.

(어플리케이션 레벨에서 캡슐화하지 않는 점이 아쉽지만, 기존에 auto_increment 대신 적용된 방식을 고려하여 일관적으로 구현하기 위해 해당 방식으로 구현)

 

그리고 서버에서 발생시키던 select max(id)+1 쿼리를 select new_function()으로 수정해서 배포하였다.

 

결과

동시에 들어오는 요청에 대해 하루 약 200건 이상 발생하던 DuplicatedKeyError 발생하지 않는 것으로 확인되었다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함