이 글은 'Fundamentals of Database Engineering'에 관한 아래 유데미 강의를 보고, 공부한 내용을 정리하였습니다.
https://www.udemy.com/course/database-engines-crash-course
동시성 제어
공유 락 vs 배타 락
배타 락(Exclusive Lock)
- 특정 데이터 조각을 읽거나 업데이트하려 할 때, 보안 및 일관성을 위해 이 값을 읽으려는 사람에게 오류가 발생하도록 하려고 할 때 사용
- 연결된 유일한 사용자가 되는 것... 나만이 업데이트 가능
- 시스템의 일관성을 보장하기 위해 도입
공유 락(Shared Lock)
- 내가 데이터를 읽을 때, 아무도 수정하지 못하게 하고 싶을 때 사용... 이 값을 편집하려는 사람에게 오류가 발생
- 많은 사용자(각각의 연결)들이 각각의 다른 공유 락을 획득할 수 있음
- 배타 락을 획득하려면 해당 값에 공유 락이 획득되어 있어서는 안 됨(반대도 마찬가지)
- 락은 cost가 많이 들지만, 여러 상황을 제어해줄 수 있음
데드 락
- 두 프로세스나 두 클라이언트가 하나 이상의 리소스를 경쟁하는 경우에 발생
- 각각의 다른 프로세스가 특정 리소스의 잠금을 해제하기를 기다리는 상황
- DB 단에서 체크하고 트랜잭션을 실패시킴(데드 락에 먼저 진입한 트랜잭션이 실패)
2단계 잠금
- DB 잠금을 획득하고 단계별로 해제하는 개념
- 한 번 해제하면 다시 획득할 수 없음
- 이중 예약 문제 해결 가능
* 이중 예약 문제
- 영화표 등을 예약할 때, 두 사용자가 정확히 동일한 좌석을 정확히 동일한 ms에 예약하려고 하는 상황
- 2단계 잠금을 처리하지 않으면 생기는 문제
- 배타 락이 걸려있어도, 먼저 배타 락을 획득한 사람이 update 한 이후에 lock이 풀리기에 마지막으로 update 한 사람 것으로 갱신되는 문제 발생
방법 1
SELECT * FROM seats where id = 1 FOR UPDATE;
- 'FOR UPDATE' 를 이용하면 select와 동시에 튜플에 대해 배타 락을 획득할 수 있음
- 먼저 배타 락을 획득한 사람의 트랜잭션이 commit 된 이후에 배타 락이 풀린 튜플에 대해 동일 쿼리를 요청하더라도, select 단계에서 업데이트된 데이터를 획득
방법 2
UPDATE seats set isbooked = 1, name = 'testuser' WHERE id = 1 and isbooked=0;
- SELECT 후 UPDATE가 아니라, UPDATE 문 자체에서 이런식으로 해결을 시도할 수도 있지만, DB엔진의 종류와 설정에 영향을 받음.
- id와 isBooked가 둘 다 인덱스에 포함되어 있는지, 고립 수준이 어떻게 되어있는지... 에 따라서 이중 예약 문제를 해결 못할 수도 있음
- '방법 1' 이 더 명시적인 방법
SQL offset을 피해야 하는 이유
- offset 100 limit 10 일 경우, DB는 110개를 가져와서 100개를 논리적으로 drop 하는 방식으로 동작함... offset 값이 커지면 가져와야 하는 양이 많아져 느려질 수 있음
- offset을 순차적으로 실행하는 도중에 새로운 행이 insert되면 원치 않은 결과를 얻을 수 있음... 이전 페이지에서 보여줬던 데이터를 한 번 더 보여준다거나...
대안: 마지막에 읽어왔던 id를 저장하고 where 절을 이용해 가져오는 방법 where ${last_id}< id limit 10;
ㄴ 이렇게 하면 IO를 줄여 cost를 줄일 수 있음
DB 연결 풀링
- TCP 커넥션을 이용하여 사용 가능한 연결 풀을 생성하고, 여러 client가 이 연결 풀을 공유할 수 있는 패턴
- 하나의 GET 요청마다 커넥션을 새로 연결하는게 아닌, 이미 연결 풀을 만들어 놓고, 요청이 오면 연결 풀에서 랜덤으로 가져다 쓰는 것
- connection을 열고 받는 오버헤드가 없음
DB 복제
복제를 하는 이유?: 데이터 베이스의 신뢰성, 오류 허용성 및 접근성 향상 목적
마스터/스탠바이 복제
- 하나의 Leader DB가 테이블을 만들거나 DB를 변경하는 등의 DDL 작업을 수락하고 기록함
- 모든 쓰기 DML도 Leader DB에서 일어나고 수락함
- 하나 이상의 백업/스탠바이 노드 들은 마스터로부터 쓰기 작업을 받음(직접 하지 않음)
- 동시에 노드들이 쓰기 작업을 하지 않으니 conflict 날 일이 없음
- 마스터와 레플리카 사이에는 완전한 양방향 TCP 연결이 되어있음
- 읽기를 레플리카에 하더라도 최종적 일관성을 가짐 (단, 업데이트에 시간이 걸릴 수 있어서 감수할 수 있어야 함)
다중 마스터 복제
- 쓰기 작업을 확장하려는 목적
- 본질적으로 여러 개의 Master/Leader 노드가 쓰기 작업을 수락하지만, conflict를 막기 위한 많은 작업이 필요
동기 vs 비동기 복제
동기식 복제
- 클라이언트가 마스터에 쓰기를 요청할 때, 트랜잭션이 마스터에 기록될 때까지 기다린 후, 클라이언트가 unblock 되기 전까지 Backup/Standby 노드에 커밋하도록 강제해야 함
- Postgres에서는 N개의 standby가 있을 때, 몇 개까지 기다리고 unblock을 할지 옵션을 제공함
비동기식 복제
- 마스터에 작성하면 즉시 client와 연결 해제 후, 백그라운드에서 주기적으로 대기 복제본에 쓰는 프로세스
- 쓰기 속도는 빨라지는 장점이 있음
- 백그라운드에서 동기화되는 동안 CPU 소모는 많고, 속도가 느려질 수 있음
복제의 장단점
장점
- 수평적 확장성을 얻을 수 있음
- 지역 기반 쿼리 가능 - DB per region
단점
- 최종적 일관성(용인 가능한 상황이면 괜찮음)
- 느린 쓰기 (동기식일 때, 트랜잭션을 기다려야 하기에 클라이언트 입장에서 느림)
- 구현이 복잡함