서버가 여러 대로 분산되어 있을때.
서로가 살아있는지 보는걸 헬스체크
라고 하고, 내가 살아있다고 주기적으로 보내는 걸 하트비트
라고 함.
살았는지 죽었는지를 투표를 해서 결정하는데, 거기서 반반이 안나오게 하려고 분산된 서버는 보통 홀수개임.
안타깝게도 이 3개가 동시에 만족되기는 어렵다는 것이 네트워크 이론에서 증명되어 있음.
그래서 대부분의 경우는 Consistency(일관성)을 포기함. 대신 Eventual Consistency를 보장해 줌.
일부러 ACID(산) 반대로 BASE(염기). 슥 보고 넘어가
트랜잭션이 A, B, C 3개가 한 번에 하나씩 실행됐을 때, 가능한 결과들의 집합 ⇒ 3! 이하
임!
진짜로 한 번에 하나씩 실행한 게 아니라, 동시에 실행했는데, 한 번에 하나씩 실행한 것 처럼 결과가 나올때를 Serializable
이라고 함.
Lock
을 사용해한 사용자가 특정 리소스에 접근하는 동안 다른 사용자가 접근 못하도록 막는 것.
화장실 자물쇠가 가장 적절한 비유.
내가 싸는동안 다른 사람들이 못 싸게 막는 역할.
대신 열고 잠그는 동안 화장실 변기 리소스가 낭비됨.
낙관적 잠금이란 게 있음. 야산 화장실엔 잠금장치가 없음.
낮은 확률로 쓰고있는 데 누가 열어버릴 수 있지. 이것이 충돌!
Lock의 두 가지 방식?
낙관적 동시성 제어
, 높을 땐 비관적 잠금
을 쓰는 게 좋다.다음 3개의 문제는 DB 사용자가 해결해줘야 할 문제
P1, P2, P3 다 Isolation에 문제가 생긴거임. TIL(Transaction Isolation Level)
설정을 잘 하면 선택적으로 이걸 해결할 수 있다!
Isolation Level
로 나누어둠!Serializable로 통일하지 않고 왜 4개의 옵션을 다 주는걸까?
이 옵션은 세션별로 지정을 하는 거임.
SHOW VARIABLES LIKE 'tx_isolation'; -- 이렇게 확인할 수 있고
SET TRANSACTION ISOLATION LEVEL 레벨; -- 이렇게 지정해서 transaction 단위로 사용
START TRANSACTION;
-- 원하는 쿼리, 정해둔 TIL로 실행
COMMIT | ROLLBACK;
MYSQL 기준 가능한 레벨은 앞서 말했듯 다음 4가지
read-uncommitted
read-committed
repeatable-read
serializable
-- session A
set session transaction_isolation='read-uncommitted';
start transaction;
select * from sample;
update sample set money = 500 where nickname='abc'; -- B의 두 번째 select 전에 update
rollback;
-- session B
set session transaction_isolation='read-uncommitted';
start transaction;
select * from sample; -- 이 값이랑
select * from sample; -- A에서 업데이트 하고 다시 확인해보면 바뀌어있음
commit;
P1: Dirty Read Problem 문제도 해결이 안된 것을 확인할 수 있음.
ㅇㅋ 그럼 read-committed 레벨에서는 어떨까?
-- session A
set session transaction_isolation='read-committed';
start transaction;
select * from sample;
update sample set money = 9999999 where nickname='def'; -- B의 두 번째 select 전에 update
commit; -- B의 세 번째 select 전에 commit
-- session B
set session transaction_isolation='read-committed';
start transaction;
select * from sample; -- 이 값이랑
select * from sample; -- A에서 업데이트 하고 다시 확인해도 안바뀜! P1 문제 안생김
select * from sample; -- A에서 commit하면 바뀌어버림! P2 문제는 여전히 생김
commit;
read-committed는 P1문제는 해결되고 P2(,P3)문제는 여전히 있다는 것을 확인 수 있음.
참고로 repeatable-read는 global default임
-- session A
set session transaction_isolation='repeatable-read';
start transaction;
select * from sample;
update sample set money = 0 where nickname='ghi';
select * from sample; -- 트랜색션 안에서는 0원 됨
commit;
select * from sample; -- 0원 됨
-- session B
set session transaction_isolation='repeatable-read';
start transaction;
select * from sample;
select * from sample; -- A에서 update했어도 0원 안됨
select * from sample; -- A에서 커밋했어도 0원 안됨
commit;
select * from sample; -- 0원 됨
왜냐? repeatable-read 모드에서는 transaction이 끝날 때까지 다른 transaction의 영향을 안 받고 같은 값으로 유지가 되거든! P1, P2 문제 해결, P3는 아직 미해결!
이제 마지막으로 serializable
-- session A
set session transaction_isolation='serializable';
start transaction;
select * from sample;
update sample set money 100 where nickname='ghi'; -- B가 읽고 나서 쓰면 여기서 에러 뜸!
-- 왜냐? serializable에서는 읽기 lock이 걸리면 쓰기 lock 걸릴 수 없어서 쓰기 자체가 안됨
update sample set money 100 where nickname='ghi'; -- B transaction이 끝나고 쓰면 잘됨!
rollback;
-- session B
set session transaction_isolation='serializable';
start transaction;
select * from sample; -- A update 전에 한번 읽어줌
rollback; -- 얘 transaction을 종료해줌 그러면 읽기 lock 해제됨
P1, P2, P3 문제 모두 해결되지만 lock 때문에 동시에 요청하면 에러가 걸리는 경우가 많음.
shared lock, exclusive lock, 읽기 lock, 쓰기 lock 이 4개의 개념에 대해서 공부를 한 번 해보세요
필수필수!
[DB] Exclusive lock과 Shared lock의 차이
Shared Lock
(공유 잠금)Exclusive Lock
(배타적 잠금)-- session A
set session transaction_isolation='serializable';
start transaction;
select * from sample;
update sample set money=100 where nickname='kay'; -- B가 읽기 전에 쓰면 에러 안뜸!
select * from sample; -- 잘 반영됨
select * from sample; -- 계속 잘 됨 왜? session A가 락을 걸었으니까.
-- 쓰기 lock. 다른 말로는 exclusive lock을 걸었다.
commit;
-- session B
set session transaction_isolation='serializable';
start transaction;
select * from sample; -- A update 후에 읽으면 안됨!
-- 왜? Exclusive Lock(쓰기 Lock)이 걸려있으니까!
-- 30초가 지나서 timeout 걸림.
select * from sample; -- A가 commit 하자마자 잘 나옴. Lock이 풀렸으니 Shared Lock 걸 수 있음
commit;
위 예제는 Exclusive Lock을 먼저 거는 경우임.
어디다 써먹을까? 일반적인 경우의 transaction에서 쓸까요 안쓸까요?
잘 안쓰겠죠 쓰기가 한 번에 1개밖에 안돼요. 엄청 위험하죠. 에러도 많이 뜨고 ㅇ ㅇ
이렇게 돌아가는 이유는 MySQL의 InnoDB가 오라클과 같이 내부적으로 MVCC를 사용하기 때문임
어떻게 위와 같은 동시성 제어가 가능한가?
⇒ 트랜잭션마다 자기 자신의 사본(Multi Version)을 저장해두고 사용함. 이걸로 Isolation을 보장함.
GAP Lock
이라는 게 있어요. 굉장히 중요한거니까 공부해주세요.
이 어려운 걸 어떻게 정리했나 봤더니 Real MySQL 저자임 ㅋ ㅋ
Real MySQL은 참고로 많이 어려워 DBA를 위한 책
그치만 우리가 갈 회사엔 DBA가 없을 가능성이 높아요 ㅎ
그니까 배워야함
이 “분산 트랜잭션”이 인기가 있다가 없다가 다시 많아졌대
MSA(Micro Service Architecture)
때문임.
DT(Distributed Transaction)
= 분산 트랜잭션 어떻게 할건데? 이런 질문 무조건 들어옴.
보통 MSA로 분산된 서비스에 걸쳐서 비즈니스 처리를 수행하면,
비즈니스 정합성이나 데이터 일관성 보장에 대한 여러 문제가 생기는데,
대표적인 해결 방법이 분산된 서비스를 하나의 트랜잭션으로 묶는 것이라고 함.
어렵다.. 천천히 알아가자..
우대사항 다 안다고 쓰지 말고 한개만 잘 한다고 하고 파는 게 좋음.
백엔드 보면 React 우대가 젤 많아요. 그게 젤 좋음. React 잘한다고 쓰세요ㅋ
여러분 진행중인 프로젝트에 트랜잭션 신경 써야해요.
우리 서비스가 중요한지 안중요한지 보고 한번쯤 신경써보세요.
오늘은 여기까지~