📚 배운 것
DB 트랜잭션에 대하여
코드 스쿼드 호눅스의 마스터 시간에 DB 트랜잭션에 대해 배웠다. 무척 값진 시간이었다.
Jim Gray 소개
Jim Gray는 DB 발전에 큰 기여를 했다. 세계 최초의 관계형 데이터베이스인 System-R을 개발했다. 트랜잭션, 2 Phase Locking, Granularity Locking 개념을 제안했고 1992년 명저 "트랜잭션 처리: 개념과 기법(Transaction Processing: Concepts and Techniques)" 를 썼다.
트랜잭션의 성질
A: Atomicity(원자성) : all or nothing
C: Consistency
I: Isolation
D: Durability
트랜잭션이란
트랜잭션은 작업의 완전성을 보장한다. 여러 읽기/쓰기를 논리적으로 하나로 묶어 DB의 상태를 변경하는 단위라고 볼 수 있다.
논리적인 작업 단위가 처리 성공 단위이고, 처리하지 못한 경우 원상태로 복구해 작업 일부만 적용되는 현상을 막아준다. 데이터 정합성을 보장한다. 트랜잭션의 범위는 커넥션 기준으로 처리된다.
트랜잭션과 Serial Schedule
가장 쉽게 ACID 성징을 지원하는 방법은 한 번에 하나씩 트랜잭션 실행하게 만든다. 다시 말해 한 명의 유저가 DB를 사용한다면 전체 DB를 잠가 아무도 접근하지 못하게 한다. 하지만 성능은 떨어진다.
잠금(Lock) 이란
동시성을 제어하는 기능이다. 여러 커넥션이 동시에 동일한 자원을 요청한 경우 순서대로 한 시점에 하나의 커넥션만 자원을 변경할 수 있게 한다. MySQL의 예로 잠금은 MySQL 엔진 레벨(글로벌 락, 테이블 락, 네임드 락), 스토리지 엔진 레벨(레코드 락, 갭 락, 넥스트 키 락 ...)의 잠금이 있다. 다시 말해 Lock의 범위는 DB 전체를 Lock 하거나 테이블 단위, 레코드 단위로도 Lock 할 수 있다.
Lock 없이 트랜잭션이 가능할까?
가능하다. 만약 모든 커넥션이 다른 커넥션이 연결된 데이터에 접근하지 않는다고 가정하면 Lock 없이 트랜잭션을 할 수 있다.
Optimistic Concurrency Control(낙관적 동시성 제어) 방식으로 트랜잭션을 구성한다면, 처음엔 잠금 하지 않다가 동시성 접근을 감지한다면 문제가 발생할 것을 알고 Lock 할 수 있다. 반대로 Pessimistic Lock(비관적 락)은 트랜잭션이 발생한다고 가정하고 일단 락을 거는 방법이다.
Race Condition(경쟁 상태)
여러 클라이언트(커넥션)이 같은 데이터에 접근할 때 발생하는 문제다. 이 문제를 피하기 위해 가장 단순하게 트랜잭션을 순서대로 실행하여 동시성 접근 문제를 발생시키지 않으면 된다. 하지만 성능이 좋지 못하다. 멀티 유저를 지원하기 위해 발생할 수 있는 문제들을 해결해야 한다.
Transaction에서 발생할 수 있는 문제들
- Lost Update Problem : 두 개의 트랜잭션이 동시에 한 아이템의 데이터를 변경했을 때 발생하는 문제
- Dirty Read Problem : 한 트랜잭션에서 변경한 값을 다른 트랜잭션에서 읽을 때 발생하는 문제
- Non-repeatable Read Problem : 한 트랜잭션에서 같은 값을 두 번 읽었을 때 각각 다른 값이 읽어 발생하는 문제
- Phantom Read Problem : 주로 통계나 분석, aggregation function 등을 수행하는 쿼리에서 잘못된 값이 발생하는 문제
Transaction Isolation Level
트랜잭션의 격리 수준(isolation level)이란 여러 트랜잭션이 동시에 처리될 때 트랜 잭션을 격리하여 특정 트랜잭션이 다른 트랜잭션에 영향에서 변경하거나 조회하는 테이블을 볼 수 있게 허용할지 말지 결정하는 수준이다. 크게 4가지로 나뉜다.
- Read Uncommitted : 다른 트랜잭션에서 바꾼 값이 트랜잭션 중간에도 반영된다.
- Read Committed : 커밋된 아이템만 읽는다. 커밋되지 않은 값은 읽을 수 없다. 같은 트랜잭션에서는 최근의 스냅샷을 읽는다.
- Repeatable Read : MySQL의 기본 동작 모드로 첫 번째 읽기에 스냅샷을 생성하고 unique index, seconday index의 유무에 따라 잠금 여부가 달라진다.
- Serializable : MySQL에서는 모든 SELECT문에 락이 걸린다.
어떤 트랜잭션 격리수준을 사용해야 하는가?
정답이 없다.
현재 주어진 상황에 맞게 트랜잭션 격리 레벨을 사용해야 한다. DBMS가 지원하는 트랜잭션 격리 수준이 다를 것이고 비즈니스 모델마다 다를 것이다. 예를 들어 영화 티켓 예매하는 도메인이 있을 때 Repeatable Read로 한다면 중복 티켓(오버부킹)이 될 수 있다. 이런 경우 Read Uncommitted를 사용하는 것이 좋을 수 있다. 또한 통계 계산이 중요한 로직에는 Serializable이 좋을 수도 있다. 각 상황을 고려하여 트랜잭션 격리 수준을 선택하자.
JPA를 배우기까지
그 동안 코드 스쿼드에서 DB 접근 기술을 생(?) JDBC 부터 -> JDBC Template -> Data JDBC -> JPA 순으로 학습하고 프로젝트에 적용하고 있다.
JDBC를 사용하며 직접 쿼리를 작성할 수 있었다. 하지만 반복된 커넥션 관리와 매퍼 작성, 쿼리의 불편함을 느끼었다.
JDBC Template를 사용하며 JDBC의 반복 작업(커넥션 관리, 매퍼)을 보다 손쉽게 사용할 수 있었다. 하지만 테이블, 데이터 중심으로 개발하다 보니 객체지향적으로 구현하기 어려웠고 여전히 반복된 쿼리가 지속되었다.(단순 조회, 업데이트)
Data JDBC를 사용이 가장 재미있었다. DDD의 aggregate 개념을 도입하여 도메인 단위로 영속성을 관리할 수 있었다. JPA를 배우기 전에 Data JDBC를 배운 것은 행운이었다. 연관 관계에 대해 생각해볼 수 있었고 N+1 query의 문제점들을 알 수 있었다.
JPA : 드디어 완전(?)한 ORM이라고 할 수 있는 JPA를 공부하고 있다. 그동안 했던 기술들과는 다르게 편리한 기능이 많다. 객체와 관계형 DB 매핑과 영속성 컨텍스트를 중심으로 학습하려 한다. 편한 기술인 만큼 내부를 알기 어렵고 잘못 사용하면 성능이 나빠질 수 있다는 것을 잊지 말자. DB와 애플리케이션 사이에 영속성 컨텍스트 계층을 둔 것은 정말 놀라운 일이다.
👍 Keep
- 뽀모도로 1시간 8타임으로 집중 시간을 늘리고 있다.
🔥Problem
- 하나의 내용에 집중하다 보면 전체를 보지 못할 때가 있다.
🚒 Try
- 해당 기술을 왜 써야하는지 당위성을 점검하고 숲과 나무를 번갈아가며 보자.
- 한 가지만 파지 말고 컨텍스트 스위칭하며 공부하자(DB -> 네트워크 -> Spring 이런 식으로)
Reference
호눅스 마스터 클래스 : DB 트랜잭션
댓글