트랜잭션 격리 수준 알아보기
240901 TIL | 중복 작성 403? 409?
트랜잭션 격리 수준 알아보기
🗃️ 트랜잭션 격리 수준 알아보기
리뷰가 생성되면 맛집의 평점을 업데이트 하는 게 요구사항이었는데, 이 두 작업을 하나의 트랜잭션으로 묶으려고 하다 공부한 내용이다.
READ UNCOMMITTED
- 가장 낮은 격리 수준이다.
- 커밋되지 않은 데이터에도 접근이 가능해 데이터 정합성 문제가 발생할 수 있지만 가장 빠르다.
- 일부 행이 제대로 조회되지 않더라도 괜찮은 거대한 양의 데이터를 어림잡아 집계하는 데에 쓰일 수 있다.
READ COMMITTED
- 가장 많이 사용되는 격리 수준으로, PostgreSQL의 기본 격리 수준이다.
- 커밋 완료된 데이터에 대해서만 조회가 가능하다.
- 한 트랜잭션이 접근한 행을 다른 트랜잭션이 수정할 수 있다.
REPEATABLE READ
- MySQL의 기본 격리 수준이다.
- MVCC를 이용해 한 트랜잭션 내에서 항상 동일한 결과를 보장한다.
- 한 트랜잭션이 접근한 행을 다른 트랜잭션이 수정할 수 없도록 막는다.
- 새로운 레코드가 추가되는 경우엔 수정을 막지 않아 유령 읽기가 생길 수 있다.
MVCC(Multi-Version Concurrency Control, 다중버전 동시성 제어)
- 일반적인 RDBMS는 변경 전의 레코드를 undo 공간에 백업해 두어서 변경 전과 후의 데이터가 모두 존재하게 된다.
- 동일한 레코드에 대해 여러 버전의 데이터가 존재한다고 하여 이를 MVCC라고 한다.
유령 읽기(Phantom Read)
- 한 트랜잭션 내에서 동일한 쿼리를 보냈을 때 조회 결과가 다른 경우. 즉, 동일한 조회 쿼리를 보냈지만 처음과 다르게 없던 데이터가 보이거나 있었던 데이터가 사라지는 것을 의미한다.
- 이를 방지하려면 쓰기 방지(Write Lock, Exclusive Lock)을 걸어야 한다.
SERIALIZABLE
- 가장 엄격한 격리 수준이다.
- 여러 트랜잭션이 동일한 레코드에 동시 접근할 수 없어 데이터 부정합 문제가 생기지 않는다.
- 트랜잭션이 순차적으로 처리되어야 하므로 동시 처리 성능이 매우 떨어지고, 교착 상태(데드락)이 일어날 확률이 높다.
그래서 어떻게 썼나요?
안 썼다. 푸하하.
- 위에 썼듯이
리뷰 생성
과맛집 평점 업데이트
이 두 작업을 하나의 트랜잭션으로 묶으려고 했다. - 트랜잭션에서 한 작업이 실패하면 그 트랜잭션 내의 모든 작업이 취소된다. (= 롤백. 트랜잭션 내에서 명시적으로 커밋하지 않았다는 전제 하에 트랜잭션 시작 시점으로 돌아간다.)
- 따라서, 한 트랜잭션으로 묶었을 때,
평점 업데이트
가 실패하면리뷰 생성
도 취소된다. 이렇게 생각하니 둘은 논리적으로 같은 맥락에 있을 뿐 꼭 같이 처리되어야 하는 작업이 아니란 걸 깨달았다. - 또한 완전히 즉각적인 평점 반영이 필요한가? 라는 의문도 함께 들어 평점 업데이트 코드엔
await
키워드를 붙이지 않았다.- 트랜잭션을 공부하다보니 DB 부하에 대한 관점으로 생각하게 되어 이런 결정을 했는데, 유의미한 영향이 있는지는 사실 잘 모르겠다..ㅎ
참고
🤔 중복 작성 403? 409?
문제
1
2
3
4
5
6
7
8
// 사용자가 이미 해당 맛집에 리뷰를 작성했는지 확인하고, 이미 작성했다면 에러를 발생시킵니다.
private async isAlreadyReviewed(memberId: string, restaurantId: string): Promise<void> {
const review = await this.reviewRepository.findOneBy({ memberId, restaurantId });
if (review !== null) {
throw new ConflictException('already reviewed');
}
}
- 요구사항엔 없었지만 논리적으로 있어야 할 것 같아 위와 같은 예외처리를 추가했다.
- 그러면서 이 HTTP Status Code를
403
으로 해야할지409
로 해야할지 고민이었다.
해결
403
(Forbidden)의 예로는 관리자 권한이 없는 사용자가 관리자 페이지에 접근하려는 경우, 권한과 관련된 경우409
(Conflict)의 예로는 이미 존재하는 아이디로 가입하려는 경우, 클라이언트에서 해결 가능한 경우- 이미 리뷰를 작성한 경우, 리소스 충돌에 대한 예외처리이기 때문에
409
로 결정했다. - 근데 지금 보니까 함수명이 약간 거슬린다…
참고
This post is licensed under CC BY 4.0 by the author.