REDO와 UNDO를 배워야 하는 이유
트랜잭션이 가지고 있어야 하는 특성에 'ACID'라는 것이 있다. 이 특성을 구현하기 위해서 REDO와 UNDO는 빠질 수 없으므로 이들을 배울 필요가 있다. REDO와 UNDO에 대한 설명에 들어가기에 앞서 ACID 특성이 대체 무엇인지를 먼저 살펴보자.
A(Atomicity) 원자성
트랜잭션에 포함된 데이터의 변경은 '전부 OK'이거나 '모두 NG'라는 'all or nothing'을 말한다. DBMS는 수행 중인 트랜잭션에서 데이터를 일부만 변경하고 나머지는 수행하지 않은 채 커밋할 수 없다.예를 들어, 어떤 트랜잭션에서 A 계좌에서 출금하고 B 계좌에 입금했을 때 '출금은 기록됐지만 입금은 기록되지 않았다'는 상황이 발생하면 곤란하다. 원자성은 이런 일이 일어나서는 안된다는 의미이며, 트랜잭션은 더 이상 분리할 수 없는 데이터 변경을 위한 최소 단위이다.
원자성이 지켜지지 않는다면?
트랜잭션이 여러 체크포인트로 나누어져 부분 실행이 되면 추후 Abort가 발생하여도 복구하기 어려워질 수 있다. A계좌는 업데이트 되었는데, B 계좌는 업데이트 되지 않았다. 그런데 A 계좌가 다른 C 계좌랑도 트랜잭션을 한 기록이 있으면? 일이 복잡해진다.
C(Consistency) 일관성
트랜잭션에 의해 데이터 간의 일관성이 어긋나서는 안 된다는 의미이다. 특히 사용자 뷰 관점에서 그렇다. 일관성이 어긋나는 예로는 '고객 개인의 데이터는 변경되었는데, 고객 전체의 통계 데이터는 변경되지 않았다'와 같은 것들이 있다.
- 단일 DB에서는 DB에서 제공하는 cascades, triggers, fk constraints 등을 준수해야 하고 일관된 뷰를 제공해야 한다고 이해하면 좋을 듯 싶다.
- 단일 DB에서 Consistency는 사용자가 트랜잭션 내에 이벤트를 어떻게 구성하냐도 중요하다고 생각한다.
- 분산 시스템에서는 Safety, 블록체인에서는 Finality라는 개념으로 사용되는데, 합의된 결과를 모든 시스템에서 일관되게 제공해야 한다는 의미를 지녀서 보다 명확한 뜻을 가지고 있다.
이러한 특징은 DB를 예측가능하게 만들기 때문에 보다 사용자가 안정적이고 신뢰할 수 있게 만든다.
I(Isolation) 고립성
트랜잭션끼리는 고립(분리)되고 독립되어 있다는 의미이다. 어떤 트랜잭션을 단독으로 실행했거나 다른 트랜잭션과 동시에 실행했더라도 결과는 같아야(다른 트랜잭션을 의식할 필요가 없다) 한다는 것이다. 고립성을 지키기 위해서 synchronization(동기화) 기술을 사용하며, 이는 다양한 잠금(Lock)과 동시성 제어 메커니즘을 통해 이뤄진다.
고립성이 지켜지지 않는다면?
트랜잭션 내에 이벤트가 실행되는 데에 다른 트랜잭션이 영향을 끼치게 되면 다양한 비정상적인 상황이 만들어질 수 있다.
어느정도로 고립할 것인가?
대부분 상용 DBMS는 아래 레벨을 준수하여 사용하고 있다.
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
D(Durability) 지속성
성공적으로 수행된 트랜잭션은 영원히 반영되는 것을 말한다. 그래서 커밋한 트랜잭션은 장애가 발생하더라도 데이터는 반드시 복구되어야 한다.
지속성이 지켜지지 않는다면?
커밋된 데이터임에도 DB 시스템 장애로 인해 커밋되지 않는다면 사용자에게 혼란을 초래할 수 있다. 또한 DB 신뢰성이 무너지게 된다.
ACID 필요성
ACID 속성은 데이터베이스 시스템의 신뢰성과 무결성을 위한 기본 요소이다. 이 속성은 트랜잭션이 안정적으로 처리되도록 보장하며, 동시에 액세스하는 복잡한 다중 사용자 환경에서도 데이터베이스를 일관되고 안정적이며 예측 가능한 상태로 유지한다. 이러한 속성은 데이터 무결성을 유지하고, 오류를 효과적으로 처리하며, 데이터베이스 시스템의 신뢰성을 보장하는 데 매우 중요하다.
- 트랜잭션 단위로 변경(또는 롤백)되어야 한다. (A)
- 어중간한 데이터 변경은 안 된다. (C)
- 다른 트랜잭션과 동시에 실행하든 단독으로 실행하든 결과는 같아야 한다. (I)
- 장비가 꺼지더라도 커밋한 데이터는 복구할 수 있어야 한다. (D)
지속성을 구현하기 위해서
우선은 데이터베이스의 중요한 특징인 '커밋한 데이터를 지키는 특성(지속성)'을 어떤 식으로 구현하였는지 생각해보자. 지속성을 구현하기 위해서는 커밋한 데이터를 그 즉시 디스크에 기록하면 될 것 같아 보인다.
하지만 여기서 생각해야 하는 부분은 첫머리를 찾는 시간이 디스크 I/O의 대부분을 차지하고 있다는 디스크의 특성이다. 첫머리를 찾는 시간이 이 방법으로는 대량의 데이터를 변경하면 커밋하는 데 너무 많은 시간이 걸린다.
그래서 상용 RDBMS 대부분은 로그(변경 로그)를 채용하여 성능과 지속성을 양립시킬 수 있게 하였다. Mysql이나 오라클도 마찬가지로 REDO 로그(WAL이라고도 함)를 통해서 성능과 지속성을 확보했다. 어떻게 REDO 로그에 의해서 성능이 확보되느냐 하면, REDO 로그에 데이터를 한꺼번에 기록하는 것으로 I/O의 횟수가 줄어들고, 시퀀셜 액세스를 사용하여 I/O에 소모되는 시간을 줄였기 때문이다. 또한, I/O 크기는 커지지만, 첫머리를 찾아가는 작업의 횟수는 변하지 않으므로 I/O 시간이 지연되지 않는다.
REDO와 UNDO의 개념
REDO와 UNDO 개념의 이해를 쉽게하기 위해 '가상 세계'와 '시간의 신'을 예로 들어 생각해보자.
이 가상 세계에서는 필요에 따라 시간을 되돌릴 수 있다. 또한, 가상 세계 안에 문제가 발생하여 정보가 손상되면 시간의 신이 가상 세계를 다시 과거로 되돌려야 하는 경우도 있다. 이 가상 세계 안의 '시간의 신'에게 필요한 정보는 무엇일까?
우선은 가상 세계가 유실되었을 때 최신 상태까지 복구하기(시간을 흐르게 하기) 위한 정보가 필요하다. 몇 가지 방법이 있겠지만, '어떤 시점의 가상 세계 정보 + 가상 세계의 변경 정보(누가 무엇을 했다)'가 있다면 최신 상태까지 복구할 수 있을 것이다.
예를 들어, 어제부터 오늘까지 시간을 흐르게 하려면 누가 무엇을 했는지와 같은 정보를 사용해서 어제의 가상 세계 정보를 오늘의 정보로 바꾸어 가는 것으로 구현할 수 있다.
'누가 무엇을 했는지'와 같은 정보만으로는 현재의 상태에서 과거의 상태로 시간을 되돌릴 수 없다. 예를 들어, '오늘 A군이 학교에 갔다'와 같은 정보만으로는 A군이 학교에 가기 전에 어디에 있었는지 알 수 없으므로 학교에 가기 전의 시간으로 되돌릴 수 없다. 이를 위해서는 '어떻게 해야 과거의 상태로 돌아갈 수 있는가?'에 대한 정보도 필요하다.
- 이 가상 세계가 데이터베이스에 해당한다.
- '누군가가 무엇을 했다는 정보'가 REDO 로그이다.
- '어떻게 하면 과거의 상태로 돌아갈 수 있는지에 관한 정보'가 UNDO 정보이다.
- REDO 로그를 사용해서 과거의 데이터를 최신 데이터 쪽으로 흐르게 하는 것을 '롤 포워드(roll-forward)'라고 한다.
- 반대로, UNDO 정보를 사용해서 변경을 취소(과거의 상태로 되돌린다)하는 것을 '롤백(rollback)'이라고 한다.
REDO 구조
데이터의 변경은 캐시에서 이루어진다. 그때 REDO 로그(변경 이력 데이터)라고 불리는 로그 데이터가 생성된다. 여기서 주목했으면 하는 것이 블록의 데이터가 이 시점(커밋되지 않은 시점)에 변경된다는 점이다. REDO 로그를 커밋이 발생하기 전에 디스크에 기록하는 방식으로 여러 상용 DBMS(Oracle, MySQL, PostgreSQL 등)들은 지속성(D)을 구현해냈다. 단, 앞에서도 설명했듯이 성능에 단점이 있으므로 커밋하는 시점에 연동해서 데이터 블록을 기록하려고 하지는 않는다.
그러면 REDO 로그의 구조는 어떻게 되어 있을까?
- REDO 로그용 메모리로서 REDO 로그 버퍼가 공유 메모리에 존재한다. REDO 로그를 디스크에 REDO 로그 파일로 기록하는 것은 LGWR이라고 불리는 프로세스가 수행한다.
- 또한, REDO 로그 파일은 개수가 한정(일반적으론 한 세트에 3개)되어 있으며, 크기도 제한되어 있으므로 REDO 로그를 계속 보관하고 있을 수는 없다.
- 그래서 아카이브 REDO 로그 파일이라는 오랫동안 REDO 로그를 보관해두기 위한 파일이 존재한다.
- REDO 로그 파일은 REDO 로그의 일시적인 보관 창고이며, 아카이브 REDO 로그 파일이 오랜 시간 보관할 수 있는 본격적인 보관 창고이다.
- 다만, 데이터베이스의 백업을 받으면 백업을 시작한 시점 이전에 만들어진 아카이브 REDO 로그는 필요하지 않는다.
REDO 로그 파일은 매우 중요한 파일이므로 반드시 다중화해야 한다. 일반적으로 REDO 로그 그룹을 여러 개의 세트로 만들고, 그룹 안에 멤버(REDO 로그 파일)를 추가한다. 다중화하면 '그룹이 늘어난다'고 생각할 수 있지만, 실제로는 그룹이 아닌 '멤버'가 늘어나기 때문에 예상했던 내용과 다른 형태의 다중화가 될 수 있으므로 신경을 써야 한다.
- 서버 프로세스는 커밋했을 때 LGWR 프로세스에 REDO 로그를 기록하도록 요청한다.
- 요청을 받은 LGWR 프로세스는 REDO 로그를 REDO 로그 파일에 기록한다.
- 기록이 끝나면 LGWR 프로세스가 서버 프로세스에 기록이 끝났다고 통보한다.
- 그 후에 서버 프로세스는 커밋이 끝난 것을 오라클 클라이언트에게 통보한다.
REDO 장점
병렬 처리를 가능케 하고 높은 처리량을 실현
기본적으로 여러 개의 서버 프로세스는 데이터를 동시에 변경할 수 있다.(단, 같은 데이터는 제외). REDO 로그를 기록하는 데에서도 LGWR은 여러 서버 프로세스의 REDO 로그를 한꺼번에 기록하기 때문에 높은 처리량을 구현할 수 있다.
응답 시간(response time)을 중시
커밋할 때 블록을 디스크에 기록하지 않고 REDO 로그에 기록하는 것으로 빠른 커밋을 구현할 수 있다.
커밋(commit)한 데이터는 지킴
장비에 장애가 발생하여 DBWR이 데이터를 기록할 틈도 없었다고 가정하더라도 그 후에 REDO 로그와 데이터 파일에 남아 있는 오래된 데이터를 사용해서 데이터를 복구(롤 포워드)할 수 있다.
UNDO 구조
- 데이터가 변경되면 UNDO 정보가 생성되며, 생성된 UNDO 정보는 세그먼트에 보관된다. 세그먼트에 보관된다는 점으로 인해 UNDO 정보가 테이블스페이스들 중 어딘가에 보관된다는 사실을 알 수 있다.
- UNDO 정보가 보관되는 테이블스페이스를 UNDO 테이블스페이스라고 부른다. UNDO 테이블스페이스에는 여러 개의 UNDO 세그먼트가 생성된다. 기본적으로 트랜잭션과 UNDO 세그먼트가 일대일로 대응하기 때문이다.
- UNDO 세그먼트는 링 버퍼(ring buffer)이다. 링 버퍼는 조금 지나면 데이터가 덮어쓰이는 버퍼이지만, 커밋하지 않은 데이터는 덮어써지지 않는다. 덮어쓰지 못하고 UNDO 세그먼트가 가득 차면 UNDO 세그먼트가 커진다.
undo_retention이라는 파라미터 등으로 UNDO 정보의 유지 시간을 설정할 수 있다. UNDO 정보를 커밋한 이후에도 일정 시간 유지하고 싶을 때 유용한 파라미터이다.
정리
설명한 내용을 정리하면 다음과 같다.
- REDO는 오래된 데이터를 최신 데이터로 만들기 위해 존재한다.
- UNDO는 최신 데이터를 오래된 데이터로 만들기 위해 존재한다.
- 읽기 일관성을 위해 UNDO를 사용한다.
- 장비에 장애가 발생하거나 인스턴스가 비정상 종료했을 때는 REDO와 UNDO를 사용해서 데이터를 복구하고, 커밋하지 않은 데이터의 롤백을 수행한다.
Ref
'Dot Database > Concept' 카테고리의 다른 글
Database Index 탐구: 디스크 I/O, 랜덤 액세스 (0) | 2022.05.22 |
---|---|
[DB] 캐싱과 캐싱 전략에 대해 알아보자 (0) | 2022.04.13 |
[DB] 대기와 Lock에 대해 알아보자 (0) | 2021.10.24 |
[DB] RDB 데이터 구조에 대해 알아보자 (0) | 2021.10.24 |
DB 동작과정을 이해해보자 (애플리케이션과 DB 커넥션) (0) | 2021.10.14 |