- 결제 완료 => 2. frontend에서 결제된 데이터를 요청 받음 => 3. backend에서 결제정보 저장과, 동시에 사용자가 결제한 금액을 누적 결제에 더해서 컬럼에 최신화 시켜줌
이 때, 발생할 수 있는 문제점
결제정보는 저장했는데 중간에 에러가 생겨 로직이 끝났을 때, 결제정보만 저장하고 사용자의 구매 누적금액은 최신화가 되지 않는 경우가 생길 수 있다. 이런 상황을 데이터가 꼬여서
데이터 오염
이 발생했다고 한다.pointTransaction.service.ts
결과
해결방법
ACID Transaction
을 사용한다
: 처리되는 작업의 단위
데이터베이스에서의 Transaction 처리는 Business Logic 상 굉장히 중요한 기능서로 다른 트랜잭션들을 처리하는 도중 하나의 단위 트랜잭션에서 에러가 발생한다면, 이전에 성공했던 트랜잭션들을 다시 rollback 시켜 데이터의 일관성이 깨지지 않도록 해준다.
모두 성공 했을 경우에 commit을 통해 확정 지어준다.Transaction Process
TypeOrm Transaction을 처리하기 위한 다양한 전략
@Transactional
데코레이터를 사용하여 해당 Method 위에서 간편하게 처리- Callback Style로 처리
QueryRunner
를 통해 Transaction 수행 => NestJS에서 강력 추천
Transaction의 Commit과 Rollback을 수동으로 제어 가능하며, Unit Testing(단위 테스트)를 보다 쉽게 진행할 수 있다(즉, Jest를 통한 Testing시 Mocking을 좀 더 쉽게 할 수 있음)
Point Transaction API rollback
pointTransaction.service.ts
결과
: Transaction을 정의하는 4가지 속성을 가리키는 약어
A(Atomicity)
: 원자성
안전성 보장을 위해 가져야 할 성질 중의 하나.
트랜잭셕과 관련된 작업들이 부분적으로 실행되다가 중단되지 않는 것을 보장하는 능력(= 모두 성공 or 모두 실패 => DB오염을 막아줌)C(Consistency)
: 일관성
트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지
=> 똑같은 쿼리를 조회할 때마다 동일한 결과값이 나타나야하는 것(수정과 삭제로 인해 결과값이 달라지는 것은 당연)I(Isolation)
: 격리성
트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장D(Durability)
: 지속성
성공적으로 수행된 트랜잭션에 대한 로그가 남아야하는 성질
런타임 오류나 시스템 오류가 발생하더라도, 해당 기록은 영구적이어야 하는 것
=> 성공하여 commit이 되었으면 서버를 다시 켜도 그 데이터는 그대로 유지되어야 한다.
: 동시에 여러 트랜잭션이 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것
Isolation 단계
아래 단계로 내려갈수록 안전해지지만, 성능은 느려짐
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
Isolation-level에 따른 문제점
출처
1단계 : READ UNCOMMITTED
각 트랜잭션에서의 변경 내용이commit
,rollback
여부에 상관없이 다른 트랜잭션에서 값을 읽을 수 있다.
즉, commit 되지 않는 데이터들까지 조회할 수 있으나, 정합성에 문제가 많은 격리 수준이기 때문에 사용하지 않는 것을 권장
밑에 그림과 같이 commit 되지 않는 상태지만 update된 값을 다른 트랜잭션에서 읽을 수 있다.
- READ UNCOMMITTED의 문제점(Dirty Read)
: 트랜잭션이 작업이 완료되지 않았는데도, 다른 트랜잭션에서 볼 수 있게 되는 현상2단계 : READ COMMITTED
RDB(관계형 데이터베이스)에서 대부분 기본적으로 사용되고 있는 격리 수준
Dirty Read와 같은 현상은 발생하지 않는다.
실제 테이블 값을 가져오는 것이 아닌, Undo 영역에 백업된 레코드에서 값을 가져옴
- READ COMMITTED의 문제점(NON REPEATABLE READ)
한 트랜잭션 내에서 같은 쿼리를 두번 수행할 때, 그 사이에 다른 트랜잭션이 값을 수정 or 삭제함으로써 두 쿼리가 다르게 나타나는 현상
즉, 동일한 쿼리를 요청하면 매번 동일한 결과 값이 나타나야하는데 그렇지 않을 때 나타나는 현상이다.
트랜잭션-1
이 Commit한 이후 아직 끝나지 않는트랜잭션-2
가 다시 테이블 값을 읽으면 값이 변경됨을 알 수 있다.- 하나의 트랜잭션내에서 똑같은 SELECT 쿼리를 실행했을 때는 항상 같은 결과를 가져와야 하는
REPEATABLE READ
의 정합성에 어긋난다.- 이러한 문제는 주로 입금, 출금 처리가 진행되는 금전적인 처리에서 주로 발생한다.
데이터의 정합성은 깨지고, 버그는 찾기 어려워 진다.3단계 : REPEATABLE READ
RDB(관계형 데이터베이스)에서 대부분 기본적으로 사용되고 있는 격리 수준
MYSQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 한다.
Undo 공간에 백업해두고 실제 레코드 값을 변경
- 백업된 데이터는 불필요하다고 판단하는 시점에 주기적으로 삭제
- Undo에 백업된 레코드가 많아지면 MySQL 서버의 처리 성능이 떨어질 수 있다.
이러한 변경방식은 MVCC(Multi Version Concurrency Control)이라고 한다.
- REPEATABLE READ의 문제점(PHANTOM READ)
REPEATABLE READ에 종종 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안 보였다가 하는 현상이 발생하는 것
이를 방지하기 위해서는 쓰기 잠금(lock)을 걸어야한다.4단계 : SERIALIZABLE
PHANTOM READ
가 발생하지않도록 사용
성능 측면에서는 동시 처리성능이 가장 낮으며 가장 단순한 격리 수준이지만 가장 엄격하다.
데이터베이스에서는 거의 사용되지 않는다.
- Dead-Lock
: 두 개 이상의 프로세스나 스레드가 서로 자원을 얻지 못해서 다음 처리를 하지 못하는 상태
즉, 무한히 다음 자원을 기다리게 되는 상태를 말한다.
시스템적으로 한정된 자원을 여러 곳에서 사용하려고 할 때 발생한다.
데드락이 일어나는 경우프로세스1과 2가 자원1, 2를 모두 얻어야 한다고 가정
t1 : 프로세스1이 자원1을 얻음 / 프로세스2가 자원2를 얻음
t2 : 프로세스1은 자원2를 기다림 / 프로세스2는 자원1을 기다림
서로 원하는 자원이 상대방에 할당되어 있어서 두 프로세스는 무한정 wait 상태에 빠져버리는 건데 이것이 데드락이라고 한다.데드락이 발생하지 않게하려면 모든 비즈니스 로직에 동일한 API 순서로 작성하는 것이 일반적인 원칙이다.
데드락 출처
Read-Uncommitted로 발생하는 Dirty-Read
파일 구조
payment.entity.ts
payment.module.ts
payment.resolver.ts
payment.service.ts
app.module.ts
결과
5초 뒤엔 rollback이 실행되기 때문에, 5초 안에 fetchPayments 요청을 하면 데이터가 정상적으로 조회됨
DB에는 데이터가 들어가지 못하는데, 5초 안에 fetch를 요청할 시 데이터가 조회되는 상황
=> 이러한 데이터 오염은 결제, 송금 시 큰 문제가 발생될 수 있다.
그렇게 때문에 commit 되지 않은 데이터는 읽을 수 없어야 한다.5초가 지나서 요청하면 빈 배열 반환
Read-Committed로 발생하는 Non-Repeatable-Read
payment.service.ts
결과
payment 테이블의 전체 데이터 조회
1초에 한번씩 데이터 조회(위에 로직에서 1000 설정)
데이터 추가
Serializable 단계에서 적용 가능한 Pessimistic-Write
payment.service.ts
결과
5초 뒤에 COMMIT 로그가 나옴
COMMIT 로그가 나오기 전에 fetchPayments 요청을 다시 보내면 대기를 함
하지만 5초가 지나서 COMMIT 후 LOCK이 풀리고 난 뒤에 요청을 하면 정상적으로 응답 받음
pointTransaction.service.ts
결과
Lock을 걸어줌으로써 Transaction이 끝날 때까지는 해당 유저 외 아무도 접근할 수 없는 형태로 만들어 줌
Login을 해서 토큰을 받아옴
payment.html