연속 요청으로 인한 데드락 문제 해결하기

2024. 7. 24. 02:04·당장 프로젝트

 

문제 발견

현재 ‘당장’ 서비스를 운영하고 있는 중인데, 가끔 point_history 테이블에 대해 데드락 문제나 중복키 문제가 발생했다.

 

데드락 발생 로그

ERROR - Deadlock found when trying to get lock; try restarting transaction ... (생략)

 

중복키 발생 로그

ERROR - Duplicate entry '더블PKID' for key 'point_history.PRIMARY' ... (생략)

문제 원인

로그를 통해 point_history 테이블에 insert할 때 위의 두 문제가 발생함을 확인할 수 있었다.

 

 

발생원인

로그를 통해서 동시에 동일한 접속 요청이(같은 유저, 시간,분,초까지 동일한) 2번 있는 것을 확인했다. 말로만 듣던 따닥 (연속) 요청 문제였다.

우리 서비스는 하루에 1번이상 접속하면, 1일 1회 500 포인트를 적립할 수 있다.

그렇기에 접속시 유저 여부를 확인하고, 500 포인트를 적립한 후, 유저의 최근 로그인 일자를 업데이트하는 과정을 거친다.

 

접속 요청으로 처리해야하는 과정을 아래에 간단히 정리해봤다.

1. User 테이블에서 사용자 여부 확인 및 최근 접속일(updated_at) 날짜 조회
2. 만약 오늘 접속하지 않은 유저라면 500 포인트 적립 (Point_history 테이블 insert)
3. 유저의 최근 접속일(updated_at)을 업데이트한다. (User 테이블 update)

 

코드

	@Transactional
	public void addAccessPoint(String oauthId) {
		User user = userSearchService.findUserByOauthId(oauthId);
		if (!user.getAccessedAt().equals(LocalDate.now())) {
			addPointEvent(EarnPoint.ACCESS.getTitle(), user); // point_history 테이블 insert
			defaultOauthLoginService.updateUserAccessedAt(user); // user 테이블 update
		}
	}

 

 

테이블 ERD

 

여기서는 User 테이블과 Point_history 테이블만 참고하면 된다 !

  • User 테이블 : 사용자 정보를 저장
    • oauth_id : 사용자 ID  -  pk 
    • created_at : 사용자 가입 날짜
    • updated_at 컬럼 - 사용자가 최근 접속한 날짜 
    • ( 글에서는 updated_at == accessed_at 동일한 컬럼이다. ERD 구성에는 updated_at이라고 적어두고,, 실제로 테이블 생성할 때는 accessed_at이라고 만들어버림.. 바보몽총... 참고 부탁드립니다..)
  • Point_history 테이블 : 사용자의 포인트 사용, 적립 내역을 저장
    • oauth_id : User 테이블의 FK 값으로, 사용자의 ID -   pk (복합키)
    • created_at : 포인트 사용 및 적립 시간  -   pk (복합키)
    • product_name : 포인트 사용 및 적립 내용  -   pk (복합키)
    • chage_point : 사용 및 적립된 포인트
    • balance_point : 사용자의 총 포인트

(필요한 부분만 확인할 수 있도록 간략히 표현했다)

 

 

 

연속 요청 문제로 중복키 문제는 이해할 수 있지만, 데드락이 발생되는 것은 이해할 수 없어서 꽤나 고민하고 다른 경우의 문제도 찾아봤다 ㅜ

 

더보기

중복키 발생 원인

 

연속 요청 문제로 , 동일한 사용자가 똑같은 시간에 접속 요청을 보냈기 때문에, Point_history 테이블의 PK가 중복되어 중복키 문제가 발생

 

 

 

어떻게 데드락이 발생되는 걸까 ? 아래의 과정을 살펴보자 !!

 

접속 요청 처리 과정 중, point_history 테이블에 insert하고 User 테이블에 update하는 과정 중에 데드락이 발생하는 것이었다.

 

 

 

결론

트랜잭션 A의 Update 쿼리로 인해 User 테이블의 row에 대한 X 락 요청

→ 트랜잭션 B가 User 테이블(row)에 대한 S락을 갖고 있어서 거부 되었음

 (User의 PK인 oauth_id가 point_history 테이블의 복합키로 구성되어 있어, 참조 무결성 조건 때문에 User 테이블의 S락을 갖고 있음)

-> 트랜잭션 B의 S락 해제를 위해선 트랜잭션 A의 Point_history 테이블(row)에 대한 X락 회수 필요

→ 트랜잭션 A는 Update 쿼리가 실행되지 않았음으로 커밋 불가 → X락 회수 불가

 

 

 데드락 발생

 

즉 트랜잭션 B의 User 테이블(row)의 S락 때문에, 트랜잭션 A가  User 테이블(row)의 X락을 갖지 못해 데드락이 발생한 것.

 

여기서 포인트! ✨

기존에 S락 또는 X락을 갖는 트랜잭션이 존재할 경우, 다른 트랜잭션은 X락을 갖지 못한다.

 

락 호환성

 

 

락 상태 조회 (데드락 걸리기 전)

SELECT * FROM performance_schema.data_locks;

 

문제 해결

데드락을 해결하는데는 아래와 같이 3가지의 방법이 있다.

  1. 데드락 발생 조건을 고려해서 테이블을 구성하여 예방한다.
  2. 데드락 발생 가능성을 검사해 데드락을 회피한다. (은행원 알고리즘)
  3. 데드락 발생 시, 해결하여 회복한다. (프로세스 종료 및 선점 )

이중에 나는 예방하기로 했다.

애초에 User 테이블은 여러 테이블에 외래키로 참조되는 경우가 많은데, 불필요한 Update 쿼리가 있다는 것은 좋지 않다. 그래서 이 기회에 User 테이블과 Point 와 관련된 테이블 구성을 수정하기로 했다.

 

 

 

테이블 수정

기존 ERD

 

개선된 ERD

  • UserAccess 테이블을 따로 두어, 접속 날짜를 따로 관리합니다.
    • accessed_at 필드의 빈번한 update 및 insert 시, User 테이블의 X락을 방지합니다. (S락이 걸림)
    • User의 접속 기록을 따로 관리하여 더 다양한 이벤트를 시도할 수 있습니다.
  • PointHistory 테이블의 PK는 대리키를 두고, 복합키였던 필드 조합들을 유니크 인덱스로 중복 여부를 관리합니다.
    • 복합키 대신 Long 타입의 대리키를 PK로 지정함으로써 복잡한 연관관계 결합도를 낮춥니다.
    • 또한 PK 인덱스 크기가 비교적 작아지며 효율적인 검색 성능을 기대할 수 있습니다.

동일하게 테스트 해봤을 때, 성공적으로 데드락을 예방할 수 있었다. 다만 중복키 문제가 일어나긴 하지만, 연속 요청 문제가 발생되어도 중복으로 저장되지 않는다.

 

느낀점

이번 문제 개선 과정을 통해 트랜잭션과 락의 원리 및 호환성에 대해 깊이 이해하게 되었습니다. 또한 데드락을 해결할 수 있는 다양한 방법과 DB 설계의 중요성에 대해 깨달았습니다. 그동안 데이터 무결성을 위해 복합키를 자주 사용했지만, 이제는 대리키와 유니크 인덱스를 활용하여 보다 유연하게 무결성을 유지할 수 있다는 것을 알게 되었습니다.

 

'당장 프로젝트' 카테고리의 다른 글

전체 사용자 포인트 조회 쿼리 개선하기  (0) 2024.07.30
'당장 프로젝트' 카테고리의 다른 글
  • 전체 사용자 포인트 조회 쿼리 개선하기
gani+
gani+
꾸준히 기록할 수 있는 사람이 되자 !
  • gani+
    Gani_Dev :)
    gani+
  • 전체
    오늘
    어제
    • 분류 전체보기 (43)
      • 당장 프로젝트 (2)
        • 트러블슈팅 (0)
      • 댕댕어디가 프로젝트 (11)
        • 트러블슈팅 (3)
        • MSA (8)
      • 개발일지 (2)
      • BOOK (12)
        • SQL 레벨업 (10)
      • 프로젝트 (0)
      • ELK (5)
      • 알고리즘 (9)
      • CS (2)
        • 디자인패턴 (2)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    소마
    이것이 코딩 테스트다
    백준
    최단경로
    백준4963
    섬의개수
    다이나믹프로그래밍
    플로이드워셔
    4963
    후기
    9095
    해쉬
    이것이코딩테스트다
    14기
    4673
    SWMaestro14
    완전탐색
    SW마에스트로
    알고리즘
    정렬
    dfs
    순차탐색
    이진탐색
    다익스트라
    DP
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
gani+
연속 요청으로 인한 데드락 문제 해결하기
상단으로

티스토리툴바