앞으로의 목표 👍
- javascript 능력 및 고난도 알고리즘 풀이 능력
- Nest, Graphql등 최신 기술 스택 활용 능력
- 기초 미니프로젝트 포트폴리오
- 로그인, 결제기반 심화프로젝트 포트폴리오
- 배포를 위한 네트워크 및 CI/CD 배포자동화 능력
- 120% 백엔드 개발 지식
오늘부터 꾸준히 해야할 일 👍
- 영타실력 늘리기
- 단축키 사용 익숙해지기
- 코드리딩 실력 키우기
- 데일리 퀴즈
- 포트폴리오 작성
- 독스에 친숙해지기
- MDN 보는 연습하기
오늘의 수업 👍
📝 결제시 발생할 수 있는 트랜잭션 문제점
- 결제정보는 저장했는데 중간에 에러가 생겨 로직이 끝났다면 결제정보만 저장되고 사용자의 구매 누적금액은 최신화가 되지 않는다.
- 데이터가 꼬여서 데이터 오염이 발생한 것입니다. 이런 결제 상황에서 발생할 수 있는 문제를 해결하기 위해 ACID 트랜잭션을 사용한다.
- 서비스에서 가장 큰 문제 => 데이터의 오염
- 데이터의 오염보단 차라리 실패하는 것이 더 좋다.
📝 Transaction
- 처리되는 작업의 단위로, 데이터베이스에서의 Transaction 처리는 Business Logic 상 굉장히 중요한 기능이다.
- 서로 다른 트랜잭션들을 처리하는 도중 하나의 단위 트랜잭션에서 에러가 발생한다면 이전에 성공했던 트랜잭션들을 다시 rollback 시켜 데이터의 Consistency가 깨지지 않도록 해주는 것이다.
- 모두 성공했을 경우에는 commit을 통해 확정 지어주게 된다.
▷ DB의 Transaction Flow
-
서로 다른 Transaction을 부분적으로 처리합니다.
-
모든 Transaction이 정상적으로 완료되면 Commit 합니다.
-
만약 Transaction중 하나라도 비정상적으로 처리되면 rollback을 수행합니다.
📝 트랜잭션의 속성들(ACID)
- A(Atomicity) : 원자성 - 모두 성공할 것 아니면 모두 실패하게 만드는 것이다.(DB의 오염을 막기 위함)
- C(Consistency) : 일관성 - 똑같은 쿼리를 조회할 때마다 동일한 결과값이 나타나야하는 것이다.
- I(Isolation) : 격리성 - A 사람의 요청을 처리하는 동안 B사람의 요청은 잠시 기다리는 것이다.
- D(Durability) : 지속성 - 성공하여 commit이 되었으면 서버를 다시 켜도 그 데이터는 그대로 유지가 되어야 되는 것이다.
📝 Isolation-level
▷ Isolation-level의 정의
- Transaction의 격리 수준이라고 한다.
- 동시에 여러 트랜잭션이 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것이다.
▷ Isolation의 4단계
아래 단계로 내려갈수록 안전해지는 반면 성능이 느려진다.
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
📝 Isolation-level의 문제
▷ 1단계 : READ UNCOMMITTED
-
commit 되지 않는 데이터들을 조회할 수 있으나, 정합성에 문제가 많은 격리 수준이기 때문에 사용하지 않는 것을 권장한다.
-
Commit이 되지 않는 상태지만 Update된 값을 다른 트랜잭션에서 읽을 수 있다.
- Dirty Read (더러운 읽기)
- READ UNCOMMITTED는 문제는 DIRTY READ 현상이 발생 되는것 이다.
- 트랜잭션이 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있게 되는 현상을 의미한다.
▷ 2단계 : READ COMMITTED
- RDB(관계형 데이터베이스)에서 대부분 기본적으로 사용되고 있는 격리 수준이다.
- Dirty Read와 같은 현상은 발생하지 않는다.
- 실제 테이블 값을 가져오는 것이 아니라 Undo 영역에 백업된 레코드에서 값을 가져온다.
- NON REPEATABLE READ (반복 못하는 읽기)
-
한 트랜잭션 내에서 같은 쿼리를 두번 수행할 때, 그 사이에 다른 트랜잭션이 값을 수정 또는 삭제함으로써 두 쿼리가 상이하게 나타나는 현상이다. 즉 동일한 쿼리를 요청하면 매번 동일한 결과값이 나타나야 한다.
-
하지만 동일한 결과값이 나타나지 않을 때 나타나는 현상입니다.
-
트랜잭션-1이 Commit 한 이후 아직 끝나지 않는 트랜잭션-2가 다시 테이블 값을 읽으면 값이 변경됨을 알 수 있다.
-
하나의 트랜잭션내에서 똑같은 SELECT 쿼리를 실행했을 때는 항상 같은 결과를 가져와야 하는 REPEATABLE READ의 정합성에 어긋나게 된다.
-
이러한 문제는 주로 입금, 출금 처리가 진행되는 금전적인 처리에서 주로 발생하며 이러한 현상은 데이터의 정합성은 깨지고, 버그는 찾기 어려워 진다.
▷ 3단계 : REPEATABLE READ
- RDB(관계형 데이터베이스)에서 대부분 기본적으로 사용되고 있는 격리 수준이다.
- MySQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 된다.
- Undo 공간에 백업해두고 실제 레코드 값을 변경한다.
- 백업된 데이터는 불필요하다고 판단하는 시점에 주기적으로 삭제한다.
- Undo에 백업된 레코드가 많아지면 MySQL 서버의 처리 성능이 떨어질 수 있다.
이러한 변경방식은 MVCC(Multi Version Concurrency Control)라고 부른다.
- PHANTOM READ (귀신 읽기)
- REPEATABLE READ에 종종 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안 보였다가 하는 현상이 발생한다.
-
MySQL에서 자동으로 제거해 주기에 추가적으로 적용할 부분은 없다.
-
다음과 같은 PHANTOM READ 가 발생하지 않기 위해서 SERIALIZABLE 를 사용합니다.
-
성능 측면에서는 동시 처리성능이 가장 낮으며 가장 단순한 격리 수준이지만 가장 엄격하다.
-
데이터베이스에서는 거의 사용되지 않는다.
▷ 4단계 : SERIALIZABLE
- 데이터를 update 완료 하기 전에 다른 넘겨주면, 데이터의 오염이 일어나게 된다.
- 그렇기 때문에 데이터가 update 완료될 때 까지는 다른 데이터는 접근할 수 없도록 lock을 걸어 주셔야 합니다.
- PHANTOM READ 가 발생하지 않기 위해서 lock 걸 수 있는 SERIALIZABLE 가 있다.
- row-lock과 table-lock
▶ table-lock
const payment = await queryRunner.manager.find(Payment, {
lock: { mode: 'pessimistic_write' },
});
▶ row-lock
const payment = await queryRunner.manager.find(Payment, {
lock: { mode: 'pessimistic_write' },
where: { id: '0edb1d43-68d5-4225-a992-8b24b3c06972' },
});
▶ join
- join된 테이블의 해당하는 row에 lock이 걸린다
▷ pessimistic-read / pessimistic-write 의 잘못된 사용과 데드락
-
API-1에서는 User 테이블에 lock을 건 뒤, Board 테이블에 lock을 걸어주고 모든 것이 완료되면 commit이 되는 상황이다.
-
API-2에서는 Board 테이블에 먼저 lock을 걸고, Board가 끝나면 User 테이블에 lock을 걸어주고 모든것이 commit이 되는 상황이다.
-
이렇게 두 개의 비즈니스 로직이 존재하는데, 만약 두 비즈니스 로직이 동시에 실행된다면 어떻게 될까?
-
API-1에서 User 테이블에서 lock이 걸어지면서 처리를 완료하였다.
-
이와 동시에 API-2에서는 Board 테이블에서 lock이 걸어지면서 처리를 완료하였다.
-
그러면 비스니스-1에서는 User의 lock이 걸려서 잡혀있는 상태이며, 비즈니스-2에서는 Board의 lock이 걸려서 잡혀있는 상태가 된다.
-
즉, 비즈니스-1에서는 Board로 넘어갈 수 없으며, 비즈니스-2에서는 User로 넘어갈 수 없는 상황이 발생하는 것이다.
-
이러한 상황을 lock이 죽었다고 표현하여 Dead-Lock
이라고 부른다.
-
Dead-Lock
이 발생하지 않게 API 설계를 해야한다.
-
따라서, lock을 사용할 때는 모든 비즈니스 로직에 동일한 API 순서로 작성하는 것이 일반적인 원칙이다.
▷ update와 lock
- 트랜잭션 내에서 업데이트하면, 명시적으로 락을 걸지 않아도 row-lock이 걸린다.
- 락이 걸리지 않으면 데이터가 꼬일 수 있기 때문에 수정 단계에서는 자동적으로 해당 트랜잭션 내에서 row-lock이 걸린다.
- 그래서 명시적으로 lock을 걸어주지 않아도 데드락이 걸릴 수 있다.
▷ 커밋과 롤백
- 커밋을 안했으면 롤백이라도 해야한다.
- 왜냐하면 락이 걸려있으면 해제해야하고, 기록된 내용들을 삭제해야 디스크 용량 확보가 가능하다.
오늘의 마무리 👍
- 복습
- github 공부
- 블로그 포스팅
- 데일리 퀴즈
- 알고리즘 문제 풀기
항상 겸손한 자세로 배우면서 성장하자, 할 수 있다!! 💪
출처 : 코드캠프