본문은 [웹 프로그래머를 위한 데이터베이스를 지탱하는 기술]를 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.
1. 트랜잭션의 중요성 이해하기
1) 트랜잭션의 의미
- 트랜잭션(Transaction)이란 데이터베이스의 내용을 접근하거나 변경하는 논리적 프로그램 단위를 의미한다
- 마지막까지 처리를 마치고 결과를 확정시키는 것을 커밋(Commit), 커밋하지 않고 모든 작업을 원래대로 되돌리는 것을 롤백(Rollback)이라고 한다.
- 트랜잭션의 존재로 인해 애플리케이션 측면에서 데이터 무결성과 관련된 에러 핸들링을 크게 줄일 수 있다
2) SQL문 레벨에서의 롤백
- 한 개의 SQL문이라고 하더라도 내부에서는 복수의 작업을 수행하므로 반드시 트랜잭션은 존재해야 한다
- 단일 SQL문을 통해 여러 레코드가 한꺼번에 갱신되는 경우에 트랜잭션이 없다면 일부만 갱신될 수 있다.
- 한 개의 레코드만 갱신되는 경우에도 인덱스도 함께 갱신될 수 있다
- 만약 인덱스가 없더라도 갱신 대상이 여러 열로 되어 있고 각각의 물리적 저장 위치가 다르면 여러 차례의 갱신이 수행될 수 있다
3) 무정지성 확보하기
- 트랜잭션은 무결성 뿐만 아니라 무정지성의 향상에도 기여 한다
- 즉, OS 장애 등의 서버 장애가 발생하여 그로부터 데이터베이스를 재기동한 때에 장애 직전까지의 커밋 결과를 손실하지 않고 마치는 것이 가능하다
- 데이터베이스는 이를 위해 REDO 로그를 이용한다
- 여기에서는 MySQL의 InnoDB를 예로 들어 그 구조를 간단하게 설명하고자 한다
REDO 로그의 역할
- InnoDB에서는 트랜잭션을 커밋하면 그때마다 LSN(Long Sequence Number)이라는 시퀀스 번호가 증가하고, 그 번호와 갱신 대상의 블록의 정보(갱신할 곳의 블록 ID 및 갱신할 곳의 위치 및 값)를 REDO로그 파일에 쓴다
- 한편, 열 값과 인덱스를 갖는 데이터 파일에는 커밋을 할 때마다 기록을 하는 것이 아니라 캐시 영역에 보관해 두고 정기적으로 디스크에 기록하는 동작을 한다
- 이렇듯 정기적으로 기록하는 처리를 체크 포인트라고 한다
- 이러한 처리에 따라 REDO 로그 파일이 최신 커밋 정보를 가지고 있는 반면, 본체의 데이터 파일은 오래된 데이터를 가지고 있게 된다
- 그래도 캐시 영역에 최신 데이터가 있으므로 애플리케이션에서 보면 최신 데이터를 보고 쓸 수 있어 모순된 상태가 되지 않는다
- 서버 장애 등의 이유로 데이터베이스가 멈춰서 재기동을 하는 경우엔 캐시 영역의 데이터가 없어졌으므로 데이터 파일과 READ 로그에 기록되어 있는 데이터가 올바르다
- 그러나 데이터 파일은 이전 데이터밖에 남지 않았고, 최신 데이터를 읽지 못하므로 모순된 결과가 되어 버린다
- 이러한 장애 상황에서는 REDO 로그의 내용을 데이터 파일에 적용시켜 나감으로써 데이터 파일과 REDO 로그의 LSN을 일치시키는 작업을 수행한다
- 이 작업을 충돌 복구(Clash Receovery)라고 한다
- 복구 시에는 적용을 시작할 가장 오래된 LSN을 특정한 뒤 그 위치에서부터 REDO 로그의 내용을 순서대로 대응해 나가면 된다
이중 기록의 비용
- 데이터 파일과 REDO 로그 파일 두 종 류의 파일을 갖는 방식은 트랜잭션을 지원하는 데이터베이스의 표준적인 아키텍처다
- 이 방식은 1회 갱신에 대한 그 자체의 갱신 정보를 REDO 로그에 동기적으로 기록하면서 나중에 데이터 파일에도 기록하는식의 두 번의 기록이 발생하게 된다
- 기록량이 많아지더라도 그 속도에는 큰 영향이 없는데, 이는 REDO 로그 파일이 순차적 기록하는 특징이 있기 때문이다
- 반대로 레코드나 인덱스로의 기록은 랜덤 기록(Random Write)이다
- 구체적으로는 우선 기록 대상의 영역(블록)을 메모리로 읽어들이고 그것을 갱신한 후 디스크에 기록하는 흐름이다
- 대상 블록이 메모리에 없으면 디스크에서 읽어 온다라는 식의 처리(랜덤 읽기)도 발생한다
6.2 잠금 메커니즘에 의한 배타 제어
다수의 사용자가 이용하는 데이터베이스에서는 동일한 레코드에 대해서 동시에 액세스가 발생할 가능성이 있다. 데이터베이스에서는 모순된 상태가 되지 않도록 배타 제어를 해야 한다. 만약 배타 제어가 잘못 이루어진다면 그림 6-3과 같이 일관성이 망가진 상태가 될 수 있다.
이를 위한 매커니즘이 바로 잠금(Lock)이다. 먼저 갱신할 것이 잠금을 확보하고, 다른 트랜잭션에서의 동일한 레코드에 대한 갱신을 방지하는 동작을 한다.
1) 잠금의 범위
- 과거 MyISAM과 같은 RDBMS에서는 테이블에 대해 배타적 잠금을 걸었다
- 그러나 InnoDB 등 현대적인 RDBMS에서 확보하고 있는 잠금의 범위는 레코드다
- 또한, Oracle이나 InnoDB의 경우 기록을 하고 있는 동안에도 커밋 완료된 데이터를 읽을 수 있다
2) 잠금 기간
- 잠금은 트랜잭션의 종료(커밋 또는 롤백) 시까지 유지한다
- 잠금이 트랜잭션의 종료까지 유지되지 않으면 서로 다른 트랜잭션간에 침범이 가능해져서 결국 일관성이 깨지게 된다 (그림 6-4 참고)
- 잠금 메커니즘의 단점은 동일한 레코드에 대한 갱신이 동시에 한 개의 클라이언트밖에 할 수 없다는 점에 있다
- 이에 여러 클라이언트에서 갱신할 수 있도록 잠금을 걸지 않고 배타 제어를 실시하는 락 프리 알고리즘과 같은 개념들이 등장하게 되었다
6.3 복제 및 트랜잭션
5장에서 설명한 대로 복제 구성(그림5-1)을 만들 경우 트랜잭션 기능은 더욱 중요성을 갖게된다.
원자성을 갖는 복제의 중요성
- 복제 구성에 있어서 슬레이브는 마스터에서 전송되어 온 업데이트성 쿼리를 실행하는 역할을 한다
- 이에 더하여 "마스터에서 보낸 업데이트성 쿼리 중에서 어디까지를 실행했는지"라는 정보도 관리해야 한다
- 이 정보들은 원자성 있게 (동일 트랜잭션에서) 갱신해야 한다
- MySQL(InnoDB)에서는 원자성 있는 갱신이 가능하다 (그림 6-5 참고)
- 그림 6-5에서 마스터는 트랜잭션이 커밋될 때마다 그 갱신 SQL문을 바이너리 로그에 기록한다
- 슬레이브에서는 바이너리 로그의 내용을 실행하여 나가지만, "현재 어디까지 실행했는지"를 관리하기 위해 실행을 마친 바이너리 로그의 위치 정보를 관리하는 InnoDB 테이블을 제공한다
- 그리고 갱신 SQL문의 실행과 위치 정보 갱신을 동일한 트랜잭션에서 실시한다
- 따라서 만약 갱신 작업 도중에 슬레이브가 크래쉬해도 재기동하면 마지막 커밋 시점의 상태를 복원할 수 있다
'책 > 웹 프로그래머를 위한 데이터베이스를 지탱하는 기술' 카테고리의 다른 글
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 5장: 데이터베이스는 어떤 때에 크래쉬되는가? (0) | 2022.12.11 |
---|---|
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 4장: SQL 문의 특징과 이를 잘 다루는 법 (0) | 2022.12.06 |
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 3장: 테이블 설계와 릴레이션 (0) | 2022.11.29 |
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 2장: 인덱스로 고속 액세스 실현하기 (0) | 2022.11.11 |
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 1장: 데이터베이스가 없으면 무엇이 곤란한가? (0) | 2022.11.09 |