목차
- Transaction
- MySQL
- Index
- Replication
- 현업에서 FK를 피하는 이유
- QueryDSL [사용하는 이유, 장단점]
트랜잭션
트랜잭션은 데이터베이스의 상태를 변화시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위이다.
특징
Atomicity
- 트랜잭션의 연산들은 데이터베이스의 모두 반영되거나 전혀 반영되지 않아야 한다.
- 트랜잭션 내의 모든 연산들은 완벽히 수행되어야 하며, 모두가 완벽히 수행되지 않고 어느 하나라도 오류가 생기면 트랜잭션 전부가 취소되어야 한다.
Consistency
- 트랜잭션이 그 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태를 유지해야한다.
- 시스템이 가지고 있는 고정요소는 트랜잭션 수행 전이나 후나 상태가 같아야 한다.
예시 1) 기본 키, 외래 키 제약과 같은 명시적인 무결성 제약 조건들이 보존된다.
예시 2) 자금 이체에서 두 계좌 잔고의 합은 이체 전후가 같아야한다는 비명시적인 일관성 조건들도 보존된다.
Isolation
- 둘 이상의 트랜잭션이 동시에 병행 실행되는 경우 어느 하나의 트랜잭션 실행중에 다른 트랜잭션 연산이 끼어들 수 없다.
- 수행중인 트랜잭션은 완전히 종료될 때까지 다른 트랜잭션이 수행 결과를 참조할 수 없다.
Duration
- 성공적으로 완료된 트랜잭션의 결과는 시스템이 고장나더라도 영구적으로 반영되어야 한다.
예시) 트랜잭션을 로깅하여 복구 시에 사용할 수 있다.
Spring Transaction
주요 기능
- Transaction 동기화
- Transaction 추상화
- AOP를 이용한 Transaction 분리
Transaction 동기화
트랜잭션을 시작하기 위해 만든 Connection 객체를 특별한 저장소에 보관하면서,
호출되는 DAO의 메소드에서 앞서 저장된 Connection을 가져다 사용하는 기법
즉, 하나의 트랜잭션을 동기화 방식을 통해 이 사이에 실행된 메소드들은 동기화된 트랜잭션을 사용하게 하는 기법.
TransactionSynchronizationManager
- UserService는 트랜잭션을 생성한다
- 트랜잭션을 트랜잭션 동기화 저장소에 저장해두고, Connection의 setAutoCommit(false)를 호출해 트랜잭션을 시작시킨다.
- 첫번째 update 메소드가 호출된다.
- update 메소드 내부에서 이용하는 JdbcTemplate 메소드에서 가장 먼저 트랜잭션 동기화 저장소에 현재 시작된 트랜잭션을 가진 Connection 객체가 존재하는지 확인하다.
- 존재한다면 Connection을 가져온다.
- 존재하지 않는다면 새로운 Connection을 생성한다.
- 가져온 Connection을 이용하여 PreparedStatement를 만들어 수정 SQL을 실행한다.
- 트랜잭션 동기화 저장소에서 DB 커넥션을 가져왔을 때는 JdbcTemplate은 Connection을 닫지 않은 채로 작업을 마친다.
- 트랜잭션 안에서 첫번째 DB 작업을 마치고 트랜잭션은 진행중인 채로 저장소에 저장되어 있다.
6-11. 두 번째 update 메소드가 호출되면 위와 같은 작업이 반복된다.
12. 트랜잭션이 정상적으로 끝났으면 UserService에서 Connection commit으로 트랜잭션을 완료시킨다.
13. 트랜잭션 저장소가 더이상 Connection 객체를 저장해두지 않도록 이를 제거한다.
- 어느 작업 중에서라도 예외 상황이 발생하면 rollback을 호출하고 트랜잭션을 종료할 수 있다.
- 롤백인 수행된 경우에도 저장소에서 Connection을 제거해주어야 한다.
- 트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 객체를 저장하고 관리하기 때문에 다중 사용자를 처리하는 멀티스레드 환경에서도 충돌이 일어나지 않는다.
Transaction 추상화
Spring은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공하고 있다.
이를 이용함으로써 애플리케이션에 각 기술마다(JDBC, JPA, Hibernate 등) 종속적인 코드를 이용하지 않고도 일관되게 트랜잭션을 처리할 수 있도록 해주고 있다.
Spring이 제공하는 트랜잭션 경계 설정을 위한 추상 인터페이스는 PlatformTransactionManager이다.
예를 들어 JDBC의 로컬 트랜잭션을 이용한다면 DatasourceTxManager를 이용하면 된다.
이 추상화를 통해 기술과 무관하게 트랜잭션을 공유하고, 커밋하고 롤백할 수 있다.
public Object invoke(MethodInvoation invoation) throws Throwable {
TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
Object ret = invoation.proceed();
this.transactionManager.commit(status);
return ret;
} catch (Exception e) {
this.transactionManager.rollback(status);
throw e
}
}
하지만 위와 같은 트랜잭션 관리 코드들이 비지니스 로직 코드와 결합되어 2가지 책임을 갖고 있다. Spring에서는 AOP를 이용해 이러한 트랜잭션 부분을 핵심 비지니스 로직과 분리하였다.
AOP를 이용한 Transaction 분리
위의 코드는 여러 책임을 가질 뿐만 아니라 서로 성격도 다르고 주고받는 것도 없으므로 분리하는 것이 적합하다.
하지만 위의 코드를 어떻게 분리할 것인지에 대한 고민을 해야 한다. 흔히 떠올릴 수 있는 방법으로는 내부 메소드로 추출하거나 DI로 합성을 이용해 해결하거나 상속을 이용할 수 있을 것이다.
하지만 위의 어떠한 방법을 이용하여도 트랜잭션을 담당하는 기술 코드를 완전히 분리시키는 것이 불가능하였다. 그래서 Spring에서는 마치 트랜잭션 코드와 같은 부가 기능 코드가 존재하지 않는 것 처럼 보이기 위해 해당 로직을 클래스 밖으로 빼내서 별도의 모듈로 만드는 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)를 고안 및 적용하게 되었고, 이를 적용한 트랜잭션 어노테이션(@Transactional)을 지원하게 되었다.
Spring의 DefaultTransactionDefinition이 구현하고 있는 TransactionDefinition 인터페이스는 트랜잭션의 동작방식에 영향을 줄 수 있는 네 가지 속성을 정의하고 있다. 해당 4가지 속성은 트랜잭션을 세부적으로 이용할 수 있게 도와주며, @Transactional 어노테이션에도 공통적으로 적용할 수 있다.
Propagation(전파 옵션)
REQUIRED
- 부모 트랜잭션 내에서 실행하여 부모 트랜잭션이 없는 경우 새로운 트랜잭션을 생성한다.
REQUIRED_NEW
- 부모 트랜잭션을 무시하고 새로운 트랜잭션을 생성한다.
SUPPORT
- 부모 트랜잭션 내에서 실행하면 부모 트랜잭션이 없는 경우 트랜잭션 없이 실행한다.
MANDATORY
- 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없는 경우 예외가 발생한다.
NOT_SUPPORT
- 트랜잭션 없이 실행하며 부모 트랜잭션이 있는 경우 일시 정지한다.
NEVER
- 트랜잭션 없이 실행하며 부모 트랜잭션이 있는 경우 예외가 발생한다.
NESTED
- 부모 트랜잭션 내에서 실행할 때, 별개로 커밋되거나 롤백될 수 있다.
- 부모 트랜잭션이 없는 경우 새로운 트랜잭션을 생성한다.
Isolation Level(고립 수준)
READ UNCOMMITED
- 가장 낮은 수준의 고립 수준
- 각 트랜잭션에서의 변경 내용이 COMMIT이나 ROLLBACK 여부에 상관 없이 다른 트랜잭션에서 값을 읽을 수 있다.
- S-Lock을 걸지않고 X-Lock만 건다.
- 따라서 Dirty Read가 일어날 수 있다.
Dirty Read 문제
트랜잭션 A에서 커밋되지 않은 데이터를 트랜잭션 B가 읽어와서 사용 했을때,
트랜잭션이 A가 롤백된다면 트랜잭션 B는 잘못된 데이터에 대한 작업을 수행하게 된다.
READ COMMITED
- 커밋된 데이터를 읽는다.
- 실제 테이블 값을 가져오는 것이 아니라 Undo 영역에 백업된 레코드에서 값을 가져온다.
- Unrepeatable read가 일어날 수 있다.
Unrepeatable Read 문제
- Read 작업을 반복적으로 하는 트랜잭션 도중에 데이터의 내용이 commit 된다면 일관성이 깨질 수 있다.
Repeatable Read
- MySQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 된다.
- Undo 공간에 백업해두고 실제 레코드 값을 변경한다
- 이러한 변경방식은 MVCC(Multi Version Concurrency Control)라고 부른다.
Phantom Read 문제
- 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안 보였다가 하는 현상
- 이를 방지하기 위해서는 쓰기 잠금을 걸어야 한다.
Serializable
- Phantom Read 까지 고려한 고립 수준
- 내부의 구현 방식은 DB마다 다르다.
제한시간
트랜잭션을 수행하는 제한시간을 설정할 수 있다. 제한시간의 설정은 트랜잭션을 직접 시작하는 PROPAGATION_REQUIRED나 PROPAGATION_REQUIRES_NEW의 경우에 사용해야만 의미가 있다.
읽기 전용
읽기전용으로 설정해두면 트랜잭션 내에서 데이터를 조작하는 시도를 막아줄 수 있을 뿐만 아니라 데이터 액세스 기술에 따라 성능이 향상될 수 있다.
DB Lock
Shared Lock(S-Lock)
- 리소스를 다른 사용자가 동시에 읽을 수 있게 하되 변경은 불가능하게 하는 것
- 어떤 자원에 S-Lock이 동시에 여러개 적용될 수 있다.
- 어떤 자원에 S-Lock이 하나라도 걸려있으면 Exclusive Lock을 걸 수 없다.
Exclusive Lock(X-Lock)
- 어떤 트랜잭션에서 데이터를 insert/update를 할 때 해당 트랜잭션이 완료될 때까지 해당 테이블 혹은 레코드를 다른 트랜잭션이 읽거나 쓰지 못하게 lock을 걸고 트랜잭션을 진행시키는 것
- X-Lock이 걸리면 S-Lock을 걸 수 없다.
- X-Lock에 걸린 테이블, 레코드 등의 자원에 대한 다른 트랜잭션이 X-Lock을 걸 수 없다.
Mysql
Replication
필요한 요소
- Master 에서의 변경을 기록하기 위한 Binary Log
- Binary Log 를 읽어서 Slave 쪽으로 데이터를 전송하기 위한 Master Thread
- Slave 에서 데이터를 수신하여 Relay Log 에 기록하기 위한 I/O Thread
- Relay Log 를 읽어서 해당 데이터를 Slave 에 Apply(적용)하기 위한 SQL Thread
동작 원리
1. 클라이언트(Application)에서 Commit 을 수행한다.
2. Connection Thead 는 스토리지 엔진에게 해당 트랜잭션에 대한 Prepare(Commit 준비)를 수행한다.
3. Commit 을 수행하기 전에 먼저 Binary Log 에 변경사항을 기록한다.
4. 스토리지 엔진에게 트랜잭션 Commit 을 수행한다.
5. Master Thread 는 시간에 구애받지 않고(비동기적으로) Binary Log 를 읽어서 Slave 로 전송한다.
6. Slave 의 I/O Thread 는 Master 로부터 수신한 변경 데이터를 Relay Log 에 기록한다. (기록하는 방식은 Master 의 Binary Log 와 동일하다)
7. Slave 의 SQL Thread 는 Relay Log 에 기록된 변경 데이터를 읽어서 스토리지 엔진에 적용한다.
Index
https://mangkyu.tistory.com/96
현업에서 FK를 피하는 이유
- 잦은 ERD 변경시 FK 설정으로 인한 로직 강제화
- 운영하면서 잘못들어간 데이터를 직접 조작하거나 대량으로 밀어넣거나 등등의 이유
FK로 인한 성능 저하
- 인덱스 유지 비용
- 부모 테이블에 대한 룩업 작업
QueryDsl
장점
- 문자가 아닌 코드로 쿼리를 작성함으로써, 컴파일 시점에 문법 오류를 쉽게 확인할 수 있다.
- 자동 완성 등 IDE의 도움을 받을 수 있다.
- 동적인 쿼리 작성이 편리하다.
- 쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용할 수 있다.
Querydsl 의 JPA 쿼리
- Querydsl은 JPQL과 Criteria 쿼리를 모두 대체할 수 있다.
- Querydsl은 Criteria 쿼리의 동적인 특징과 JPQL의 표현력을 타입에 안전한 방법으로 제공한다.
- type-safe한 query 작성 및 Criteria 보다 보기 편한 query를 만드는 것이 목적이다.
질문
공통
- 운영 환경에서 트랜잭션 전파옵션을 REQUIRED_NEW 로 지정했을때 달라지는 점 또는 문제점이 있을까요?
에어
- Replication으로 책임을 나누어 줄 때 트랜잭션이 read-only 인지 어떻게 확인하셨나요?
파피
- Replication Slave에 대한 연결이 끊어진 경우 복구 방법?
참조