[DataBase] CS 스터디 11주차
트랜잭션이 무엇이고, ACID 원칙에 대해 설명해 주세요.
트랜잭션은 데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 논리적 단위를 말합니다. ACID 원칙은 트랜잭션의 안전성을 보장하기 위한 4가지 주요 특성을 말합니다.
- 원자성(Atomicity)
- 트랜잭션의 모든 연산이 완전히 수행되거나 전혀 수행되지 않아야 합니다.
- “All or Nothing” 원칙으로, 부분적 실행은 허용되지 않습니다.
- 일관성(Consistency)
- 트랜잭션 실행 전후로 데이터베이스가 일관된 상태를 유지해야 합니다.
- 정의된 규칙이나 제약조건을 위반하지 않아야 합니다.
- 격리성/고립성(Isolation)
- 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않아야 합니다.
- 각 트랜잭션은 다른 트랜잭션의 중간 결과를 볼 수 없습니다.
- 지속성/영속성(Durability)
- 성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 합니다.
- 시스템 장애가 발생하더라도 커밋된 트랜잭션의 결과는 보존되어야 합니다.
ACID 원칙 중, Durability를 DBMS는 어떻게 보장하나요?
지속성과 성능이 양립하도록 DBMS에서는 3가지의 구조를 사용합니다.
로그 선행 쓰기 (WAL, Write-Ahead-Log)
- 데이터베이스의 데이터 파일 변경을 직접 수행하지 않고, 로그로 변경 내용을 기술한 로그 레코드를 써서 동기화하는 구조
- MySQL에서는 이 로그를 InnoDB 로그라고 부릅니다.
- 장점
- 디스크에 연속해서 쓰기 때문에 무작위로 쓰는 것보다 성능이 좋습니다.
- 디스크에 쓰는 용량과 횟수를 줄일 수 있습니다.
- 데이터베이스 버퍼를 이용하여 데이터베이스 데이터 파일로의 변경을 효율 좋게 수행합니다.
데이터베이스 버퍼
커밋 시에는 WAL에 변경 내용을 쓰기 때문에, 데이터 파일의 변경 내용은 트랜잭션이 커밋될 때 동시에 동기화할 필요가 없습니다. 그렇다고 트랜잭션마다 버퍼를 취해 비동기 쓰기를 하면, 로그와 데이터 파일 간의 일관성을 유지하기 어렵습니다.
그래서 일반적인 DBMS에서는 데이터베이스 버퍼를 이용해 데이터 파일로의 입력을 단순화하고 있습니다. 이 덕분에 효율적으로 데이터의 일관성을 유지할 수 있게 됩니다.
- MySQL의 갱신 흐름
- 갱신 대상의 데이터를 포함한 페이지(버퍼나 캐시를 다루는 단위)가 버퍼 풀에 있는지를 확인하고, 없다면 데이터 파일로부터 읽어 들인다.
- 버퍼 풀의 해당 페이지에서 갱신을 수행한다.
- 2번의 갱신 내용이 커밋과 함께 로그에 기록된다. 버퍼 풀에 갱신되었지만, 아직 데이터 파일에 써지지 않은 페이지는 버퍼 풀 내에서 더티 페이지(일반적으로 메모리로 읽어서 갱신된 페이지를 가리킴)로 다룬다.
즉, WAL과 버퍼 풀에 갱신을 반영해가며 데이터 파일보다 앞질러가는 형태가 되며, 체크포인트에서 데이터 파일이 수정사항을 따라잡고 WAL과 버퍼풀이 선행해서 수정하기를 반복합니다.
크래시 복구
- 크래시가 발생하면 다음과 같은 상태가 됩니다.
- WAL: 마지막으로 커밋된 트랜잭션의 갱신 정보를 가진다.
- 데이터베이스 버퍼: 크래시로 내용이 전부 소실된다.
- 데이터베이스 파일: 최후 체크포인트까지의 갱신 정보를 가진다.
크래시 이후 MySQL 서버를 재시작하면 3번과 1번의 체크포인트 이후 갱신 정보를 사용해 데이터베이스 파일을 크래시 때까지 커밋된 최신 상태로 수정합니다. 이 동작을 롤 포워드(Roll-Forward)라고 합니다.
트랜잭션을 사용해 본 경험이 있나요? 어떤 경우에 사용할 수 있나요?
- 계좌 이체
- A 계좌에서 출금하고 B 계좌로 입금하는 과정을 하나의 트랜잭션으로 처리합니다.
- 두 작업 중 하나라도 실패하면 전체를 롤백하여 데이터 일관성을 유지합니다.
- 주문 처리
- 주문 정보 저장, 재고 감소, 결제 처리 등을 하나의 트랜잭션으로 묶습니다.
- 모든 단계가 성공해야 주문이 완료되도록 합니다.
- 복잡한 데이터 수정 작업
- 여러 테이블의 데이터를 동시에 수정해야 하는 경우 트랜잭션으로 처리합니다.
- 배치 작업
- 대량의 데이터를 처리하는 배치 작업에서 일정 단위로 트랜잭션을 적용합니다.
읽기에는 트랜잭션을 걸지 않아도 될까요?
읽기 작업에도 트랜잭션을 거는 것이 좋습니다.
- 일관성 있는 데이터 읽기
- 트랜잭션을 사용하면 데이터를 읽는 동안 일관된 스냅샷을 유지할 수 있습니다. 이는 특히 여러 테이블의 데이터를 조회할 때 중요합니다.
- 성능 최적화
@Transactional(readOnly = true)
를 사용하면 JPA의 변경 감지 기능을 비활성화하여 성능을 향상시킬 수 있습니다.
- 데이터베이스 부하 분산
- 읽기 전용 트랜잭션을 사용하면 데이터베이스 리플리케이션 환경에서 읽기 작업을 슬레이브 데이터베이스로 라우팅할 수 있습니다.
- OSIV(Open Session In View) 설정과의 연관성
- OSIV가 꺼져 있을 때,
@Transactional(readOnly=true)
이 없으면 지연 로딩 등의 문제가 발생할 수 있습니다.- OSIV가 켜져 있을 때
- 영속성 컨텍스트가 뷰 렌더링이 끝날 때까지 열려있어, 컨트롤러나 뷰에서도 지연 로딩이 가능합니다.
- 하지만 이는 데이터베이스 커넥션을 오래 물고 있어 성능 문제를 일으킬 수 있습니다.
- OSIV가 꺼져 있을 때
- 서비스 계층에서 트랜잭션이 종료되면 영속성 컨텍스트도 함께 닫힙니다.
- 이 경우
@Transactional(readOnly=true)
를 사용하지 않으면- 컨트롤러나 뷰에서 지연 로딩을 시도할 때 LazyInitializationException이 발생할 수 있습니다.
- 필요한 모든 데이터를 서비스 계층에서 미리 로딩해야 하는 번거로움이 생깁니다.
- OSIV가 켜져 있을 때
- 즉, OSIV가 꺼져 있을 때
@Transactional(readOnly=true)
를 사용하면- 서비스 계층에서 트랜잭션을 시작하고 영속성 컨텍스트를 유지할 수 있습니다.
- 이를 통해 컨트롤러나 뷰에서도 안전하게 지연 로딩을 사용할 수 있게 됩니다.
- 동시에 readOnly 설정으로 성능상의 이점도 얻을 수 있습니다.
- OSIV가 꺼져 있을 때,
트랜잭션 격리 레벨에 대해 설명해 주세요.
자신이 아닌 다른 트랜잭션의 영향 받는 것을 허용하는 4개의 단계를 ANSI라는 규격 단체에서 정의했습니다. ANSI가 정의하는 트랜잭션 격리 레벨은 다음과 같습니다.
- READ UNCOMMITTED (Level 0)
- 다른 트랜잭션의 커밋되지 않은 변경사항을 읽을 수 있음
- Dirty Read 문제 발생 가능
- READ COMMITTED (Level 1)
- 커밋된 데이터만 읽을 수 있음
- Non-Repeatable Read 문제 발생 가능
- 많은 DBMS의 기본 격리 수준
- REPEATABLE READ (Level 2)
- 트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회 가능
- Non-Repeatable Read 방지
- Phantom Read 문제 발생 가능
- MySQL의 기본 격리 수준
- SERIALIZABLE (Level 3)
- 완벽한 읽기 일관성 모드 제공
- 동시성이 크게 떨어져 성능 저하 발생
격리 수준의 완화(레벨이 낮아질수록 발생)에 따라 일어나는 현상은 다음과 같습니다.
모든 DBMS가 4개의 레벨을 모두 구현하고 있나요? 그렇지 않다면 그 이유는 무엇일까요?
그렇지 않습니다.
높은 격리 수준, 특히 SERIALIZABLE 수준은 구현이 매우 복잡합니다. 이는 동시성 제어와 잠금 메커니즘의 정교한 관리가 필요하기 때문입니다. 일부 DBMS는 이러한 복잡성을 피하기 위해 더 낮은 격리 수준만 구현하기도 합니다.
일부 격리 수준은 다른 수준에 비해 실제 사용 빈도가 낮을 수 있습니다. 예를 들어, READ UNCOMMITTED는 데이터 일관성 문제로 인해 실제 환경에서 거의 사용되지 않습니다.
결과적으로, 많은 DBMS가 모든 표준 격리 수준을 구현하지는 않습니다. 대신 가장 일반적으로 사용되는 수준(예: READ COMMITTED, REPEATABLE READ)을 중심으로 구현하거나, 자체적인 격리 수준을 구현합니다.
만약 MySQL을 사용하고 있다면, (InnoDB 기준) Undo 영역과 Redo 영역에 대해 설명해 주세요.
InnoDB에서 Undo 영역과 Redo 영역은 트랜잭션 처리와 데이터 복구에 중요한 역할을 하는 로깅 메커니즘입니다.
Undo 영역
- 트랜잭션 롤백을 지원합니다.
- 변경 전 데이터의 이미지를 저장하여 필요시 이전 상태로 되돌릴 수 있게 합니다.
- 트랜잭션 격리 수준을 구현하는 데 사용됩니다.
- 다중 버전 동시성 제어(MVCC)를 지원하여 읽기 트랜잭션과 쓰기 트랜잭션 간의 충돌을 방지합니다.
- 논리적 로깅 방식을 사용합니다.
- 이는 데이터베이스의 물리적 저장 구조 변경에 영향을 받지 않도록 합니다.
- Undo 로그 레코드는 변경된 레코드의 이전 값과 기본 키 정보를 포함합니다.
Redo 영역
- 데이터베이스 복구에 사용됩니다.
- 시스템 충돌 시 커밋된 트랜잭션의 변경사항을 재현하여 데이터 일관성을 유지합니다.
- WAL 방식을 사용하여 데이터 페이지가 업데이트되기 전에 Redo 로그에 변경사항을 기록합니다.
- 물리적 로깅 방식을 사용하여 데이터베이스 페이지의 변경사항을 기록합니다.
- Redo 로그 버퍼에 먼저 기록된 후, 주기적으로 또는 트랜잭션 커밋 시 디스크의 로그 파일에 기록됩니다.
그런데, 스토리지 엔진이 정확히 무엇을 하는 건가요?
스토리지 엔진은 데이터베이스 관리 시스템(DBMS)의 핵심 구성 요소로, 데이터의 물리적 저장, 검색, 수정을 담당합니다.
- 데이터 저장: 데이터를 디스크와 메모리에 효율적으로 저장합니다.
- CRUD 작업 수행: 데이터의 CRUD 작업을 처리합니다.
- 인덱스 관리: 데이터 검색 속도를 향상시키기 위한 인덱스를 생성하고 유지합니다.
- 트랜잭션 처리: 데이터의 일관성과 무결성을 보장하기 위한 트랜잭션을 관리합니다.
- 동시성 제어: 여러 사용자가 동시에 데이터에 접근할 때 발생할 수 있는 충돌을 관리합니다.
Leave a comment