본문 바로가기

ComputerScience/Database 이론

[Database]10. 트랜잭션 개념과 회복 기법. 동시성 문제와 수행. 2PL 개념 부수기 +_+ | 직렬 가능성 스케줄

728x90

 

안녕하세요. CS지식 정리도 할 겸, 학교에서 배운 데이터베이스 개념 + "데이터베이스 개론-IT COOKBOOK" 의 책을 공부하며 알게 된 내용을 정리하려고 합니다.

 

지난 포스트"정규화 개념 정리"에 이어 이번 포스트는 트랜잭션 개념과 회복 기법.Concurrency에서 발생할 수 있는 문제, 제어에 관한 개념. 트랜잭션의 concurrent execution 중 serial schedule과 같은 연산 결과를 보장할 수 있는 직렬 가능성과 로킹, 2단계 로킹 기법에 대해 개념을 정리하려고 합니다.

 

주관적으로 작성된 글이기에 틀린 내용이 있을 수 있습니다.(댓글로 알려주시면 감사합니다 :)

1. 트랜잭션(Transaction)이란?

데이터베이스에서 수행되는 하나의 논리적인 작업, 연산집합(SQL문들의 모임) 또는 db 상태 변경 단위를 말합니다. SQL query의 작업들을 논리적으로 묶어 하나의 작업으로 처리하는 것을 의미합니다.

 

상태 변경을 하기 때문에 장애 발생 시 복구작업에도 용이합니다. concurrent control을 수행하기 위한 단위로도 사용됩니다. 트랜잭션의 주요 연산은 Commit, Rollback입니다.

Commit

트랜잭션이 수행이 성공했을 경우 Commit을 통해 트랜잭션 내 모든 변경 사항이 영구적으로 db에 적용되고 db는 변경 내용을 유지합니다. 이후 트랜잭션은 종료합니다.

Rollback 

트랜잭션이 수행 중 실패할 경우 등 트랜잭션의 취소를 나타내는 작업입니다. 트랜잭션 수행 중 오류가 발생되거나 트랜잭션을 명시적으로 취소할 경우 수행되었던 이전 모든 변경 사항이 rollback됩니다. 데이터베이스는 오류가 발생한 트랜잭션을 수행하기 전 상태로 복원됩니다.

오류나 장애

데이터가 잘못되었거나(트랜잭션 장애), system resource를 많이 사용했거나(트랜잭션 장애), 하드웨어 결함이거나(시스템 장애), 저장장치인 디스크 장치의 결함(미디어 장애)으로 발생될 수 있습니다.

 

Commit과 Rollback은 트랜잭션의 원자성, 지속성을 보장하기 위해 사용되는 메커니즘입니다. 트랜잭션 작업 안전하게 완료 or prev state로 되돌릴 수 있는 기능입니다. 트랜잭션은 ACID원칙을 준수합니다.

A(Atomicity) 원자성

트랜잭션은 "전부 성공" or "전부 실패"로 처리되야 합니다. 트랜 잭션 내 모든 task를 원자적인 단위로 처리합니다.

즉, 트랜잭션 내 모든 task가 전부 수행되거나 전부 수행되지 않아야 합니다.

C(Consistency) 일관성

트랜잭션은 db에 있는 일관성을 유지해야 합니다. 트랜잭션 수행 전과 후에 db는 일관된 상태여야 합니다. 예를들어 A가 B에게 5000원을 보낸다고 가정할 때 계좌이체 트랜잭션 전 후의 A와 B의 계좌 잔액 합계는 동일해야 합니다.

I(Isolation) 고립성

수행중인 트랜잭성은 완료되기 전까지 Isolated state여야 합니다. 중간 연산 결과에 다른 트랜잭션이 접근하면 안됩니다. 다시말해 각 트랜젝션은 다른 트랜잭션의 연산을 간섭하지 않아야 합니다.

D(Durability) 지속성

트랜잭션이 성공적으로 수행되면 그 결과는 영구적으로 db에 저장되야 합니다. 시스템 오류, 다른 장애 상황에서도 손실 없어야 합니다. 그래서 장애 발생 시 회복 기능이 필요합니다.

2. 회복

장애 발생 시 데이터를 장애 발생 시점 이전으로 되돌려 데이터베이스의 일관성 유지가 필요합니다. 회복을 위해 사용되는 방법중 일반적으로 dump, log가 있습니다. 이 방법을 통해 데이터베이스의 복사본을 만드는 경우가 있습니다.

 

  • Dump: 데이터베이스 전체를 타 저장장치에 주기적 저장
  • Log: db에서 변경 연산이 실행될 경우마다 데이터를 변경 이전, 이후 값을 별도의 파일에 기록

회복 연산은 redo(재실행), undo(취소)가 있습니다. undo는 log를 통해 지금까지 변경된 연산 처리를 전부 취소 시킵니다. 즉, 이전 상태로 데이터베이스 복구하는 기능입니다.

회복 기법

  • 로그 회복기법(즉시 갱신 회복 기법, 지연 갱신 회복 기법)
  • 검사 시점 회복 기법
  • 미디어 회복 기법

로그 회복 기법

- 즉시 갱신 회복 기법

트랜잭션이 수행되는 동안에 db의 일관성을 유지하기 위해 사용되는 기법입니다. 트랜잭션의 갱신 연산(Insert, Delete, Update)이 수행되면 해당 변경 내용을 즉시 디스크에 반영해서 db를 일관된 상태로 유지합니다. 수행 중 장애 발생 시 상황에 따라 redo, undo 연산을 수행해야 합니다.

 

- 지연 갱신 회복 기법

즉시 갱신 회복 기법과 달리 트랜잭션의 갱신 연산이 수행될 때, 해당 변경 내용을 로그에 기록해둔 후 트랜잭션이 부분 완료 or 전체 완료된 후에 로그에 기록된 내용을 데이터베이스에 한 번 반영하는 방법입니다. 트랜잭션 수행 중 장애 발생 시 로그 내용만 버리면 되서 좋습니다: ]이 경우 undo 연산이 필요 없습니다. 로그 기록만 지우면 되기 때문입니다.

검사 시점 회복 기법

 

일정 시간 간격으로 검사 시점을 만듭니다. C1 시점 이전에 완료된 트랜잭션들은 commit을 통해 데이터베이스의 상태를 갱신했으며, 추가적인 redo작업 없이 앞으로 C1 이후의 시점에 장애가 발생해도 C1시점 이전에 완료된 트랜잭션은 회복 연산(redo, undo)를 하지 않아도 됩니다.

 

검사점 C2 시점에서는 t1, t2, t3 트랜잭션이 수행 완료 후 commit 된 것을 알 수 있고 C2시점 이후에 발생되는 장애에 대해서 t1, t2, t3은 회복연산을 하지 않아도됩니다.

 

장애 발생 시점 시 C2 이후 시점이기에 t1, t2, t3은 회복 연산을 하지 않고 장애 발생 전에 완료된 트랜잭션 중 검사점 대상이 아니었던 트랜잭션에 한해서 redo을 실행합니다. 만약 장애 발생시점에 실행중인 트랜잭션이 있다면 해당 트랜잭션은 undo 연산을 수행해야 합니다.

 

회복연산의 장점은 검사점을 기준으로 이미 완료된 트랜잭션에 대해서 회복작업을 수행하지 않아도 된다는 장점이 있습니다.

미디어 회복 기법

Db 내용을 주기적으로 dump 생성해서 안전한 저장 장치에 보관하며 로그 백업을 합니다. 이후 장애 발생 시 최근에 저장한 로그 정보를 사용해서 db를 복구하는 기법입니다. 로그 재생은 장애 발생 시 로그 파일을 사용해 db를 이전 상태로 되돌리는 과정이고, 로그 파일에 기록된 트랜잭션 변경 사항을 순차적으로 재생해 db를 회복합니다.

3. Concurrent problem, concurrent execution, concurrent control

동시성과 병렬성에 대해 정리했었는데 참고하면 좋을 것 같습니다 : ]

 

데이터베이스는 한 사람만 이용하는게 아니라 여러 사람이 동시에 query를 요청할 수 있습니다. 데이터동시에 접근할 때 read를 할 경우 문제가 거의 발생하지 않지만 write를 할 경우에 concurrent problem이 발생하게 됩니다. 여러 사용자가 데이터를 동시 수정 or 사용자가 데이터를 수정하는 동안 다른 사용자가 동일한 데이터를 읽을 때도 concurrent problem이 발생합니다. 

여담,,,,, | 동시성, mutable state, concurrent problem의 간단 개념 

사실 데이터베이스 뿐 아니라 동시성 문제(concurrent problem)는 OperatingSystem에서도 발생하고, db, multi thread가 가능한 프로그램 등 여러 사용자가 동시 접근(read, write)할 때 발생되는 문제입니다.

어느 것에 대한 접근인지 what이 빠졌네요. 일반적으로 프로그램에서는 "mutable state!" 변경 가능한 상태를 가지는 변수, 데이터 구조, 객체 등에 대한 동시 접근일 때 concurrent problem이 발생됩니다.

 

동시 접근을 할 수 있다는 것은 동시에 수행(Concurrent execution)이 가능하다는 뜻 입니다. 평소에 무심코 작성했던 데이터 처리 함수들이 동시에 접근되거나 수행된다면 동시성 문제가 발생될 가능성이 아주 큽니다. 

 

Concurrent problem의 예로 race condition(경쟁조건: 예기치 못한 결과), deadlock(데드락: 서로가 대기) etc...

 

Concurrent problem을 해결하기 위해서는 concurrent 한 환경임에도 불구하고 shared resuroce를 접근할 때, 각각의 task는 serial execution으로 처리를 해야 합니다. 또는 공유 자원에 접근을 할 때 isolation state로 처리하는게 좋습니다. 이와 같은 작업을 concurrent control(동시성 제어)이라고 부릅니다. 대표적으로 Locking algorithm, semaphore 등 상호 배제 관련된 기능이나 알고리즘을 통해 제어를 하면 그나마 concurrent problem을 방지할 수 있습니다.

Concurrent problem의 예

계좌 출금을 예로들때, 기본적으로 계좌를 출금할 때의 과정은 아래와 같습니다.

1. 첫 사용자가 잔액 확인 후 츨금 진행

2. 두번째 사용자도 첫 사용자와 같은 시간에 계좌 잔액 확인

3. 첫 사용자가 출금하는 동안 두번째 사용자는 대기 상태

4. 첫 사용자 출금 완료잔액 갱신

5. 두번째 사용자가 잔액 확인 후 출금 시도

6. 잔액이 이미 첫번째 사용자에 의해 갱신되어서 출금 실패

일반적으로 동시에 동일 계좌에서 출금을 시도한다면 둘 중 한 사용자만 출금이 성공적으로 수행되는 concurrent contorl이 적용될 수 있습니다.

 

만약 위와 같이 concurrent control을 적용하지 않게 된다면? ( 정말 간단하게 3,6이 없는 경우 )

한 계좌에 100,000이 있을 때, 첫 사용자, 두 번째 사용자 동시에 잔액 확인 (둘다 계좌에 100,000확인)후 출금 진행을 한다면 둘이 동시에  100,000을 접근했을 후 차감을 하는데

사용자1 사용자2
A계좌 = A 계좌 - 100,000. // 이때 A계좌는 100,000 A 계좌 = A계좌 - 100,000 // 이때 A계좌는 100,000

결국 A계좌는 두번 0으로 갱신되며 나간 돈은 200,000이 될 수도 있습니다.. 그래서 동시에 접근할 때 concurrent control이 상당히 중요합니다.

4. 데이터베이스 트랜잭션 concurrent problem

데이터베이스는 여러 개의 트랜잭션을 동시에 수행할 수 있습니다. 여러 트랜잭션이 인터리빙(끼어들며) 번갈아 수행됩니다. 당연히 shared resource(데이터)를 access할 때 concurrent problem이 발생됩니다. 동시 접근이 된다면 트랜잭션의 수행을 반드시 제어 해야 문제가 발생되지 않습니다.

 

데이터베이스의 트랜잭션 concurrent problem은 일반적으로 아래와 같습니다.

 

  • 갱신 분실(lost update): 한 트랜잭션이 수행한 결과를 타 트랜잭션이 덮어 써 이전 트랜잭션 수행 무효화
  • 모순성(inconsistency): 일관성 없는 상태의 데이터를 가져와 연산-> 모순 발생
  • 연쇄 복귀(casecading rollback): 장애 발생 후 연속적으로 rollback해야 하지만 commit을 완료 트랜잭션이 존재해서 해당 트랜잭션은 rollback할 수 없는 문제
  • Dirty read: 한 트랜잭션이 다른 트랜잭션이 수정 중인 데이터를 읽는 현상. 즉, 아직 커밋되지 않은 변경사항을 다른 트랜잭션이 읽을 수 있는 문제

트랜잭션 스케줄

데이터베이스에서 여러 트랜잭션이 동시 실행되는 순서, 연산 작업을 의미합니다. 트랜잭션 스케줄은 db에서 동시 수행되는 여러 트랜잭션을 스케줄링해서 데이터의 무결성, 일관성을 유지 + 동시성 문제를 해결하기 위해 사용됩니다.

트랜잭션 스케줄 유형

  • 직렬 스케줄: 인터리빙 방식 이용하지 않음. serial하게 트랜잭션을 수행
  • 비 직렬 스케줄: 인터리빙 방식 이용. 여러 트랜잭션 동시 수행
  • 직렬 가능 스케줄: 비 직렬 스케줄이지만 직렬 스케줄 처럼 정확한 결과 생성

직렬 스케줄(serial schedule)

다양한 직렬 스케줄 경우 마다 최종 결과가 다를 수 있지만 직렬 스케줄을 통한 트랜잭션 각각의 수행 결과는 모순이 없습니다. serial execution에서는 공유자원을, 데이터를 동시에 접근하는 게 아니기 때문에 동시성 문제가 발생되지 않습니다.

비 직렬 스케줄(nonserial schedule)

예전 concurrency에 대한 설명 중 그린 그림입니다. 초록색을 인터리빙이라 생각하면 됩니다. 노란, 분홍, 파랑 트랜잭션이 수행되는데 인터리빙 방식으로 각각의 트랜잭션이 번갈아 수행됩니다. 트랜잭션이 아직 끝난 상황이 아닌 도중에 다른 트랜잭션이 수행됨으로 위에서 말한 트랜잭션 concurrent problem이 발생 가능성이 높습니다. 즉 트랜잭션의 올바른 수행 결과를 보장받기 어렵습니다.

직렬 가능 스케줄(serializable schedule)

그럼에도 concurrent execution을 하는 이유는 작업 처리 속도가 높아지기 때문에 비직렬 스케줄이지만 serial(직렬) schedule처럼 트랜잭션의 수행 결과가 모순이 없고 보장될 수 있도록 하는 스케줄이 직렬 가능 스케줄입니다. 일반적으로 로킹 기법을 사용합니다.

 

여담,,

예전에 concurrency를 공부하면서 locking algorithm을 공부했었는데, 그냥 공유 자원 접근할 때 락 걸고 task 완료하면 락 해제하는 방식은 완벽하게 concurrent problem을 해결할 수 없다는 것을 이번 db 수업을 통해 알게 되었습니다,,,, 아래서 나오겠지만 Locking algorithm을 사용할 때, Two-Phase Locking (2PL)이 일반적으로 사용되니다.(대박.....)

직렬 가능 스케줄의 로킹(locking) 기법

직렬 가능 스케줄을 하기 위해서 각각의 트랜잭션에 대해 concurrent control(제어)를 해야 합니다. 대표적으로 로킹 기법을 사용합니다.

 

한 트랜잭션이 공유 자원이나 데이터에 접근한다면 그 트랜잭션이 연산을 끝낼 때 까지 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 상호 배제하는 기법 입니다.

 

locking의 연산은 lock, unlock 두 가지가 있습니다.

 

  • lock: 트랜잭션이 데이터에 대한 독점권을 요청합니다. 이 경우 다른 트랜잭션은 해당 데이터를 읽거나 수정할 수 없습니다.
  • unlock: 독점권을 반환합니다. 다른 트랜잭션은 해당 데이터에 접근할 수 있게 됩니다.

 

locking 범위를 크게하면 할수록 serial execution에 가까워집니다.

locing을 적용했음에도 여전히 concurrent problem 발생하는 경우

문제1. 데이터베이스 X = 3000, Y = 3000인 데이터가 있습니다.

트랜잭션 t1의 경우에는 X = X + 1000, Y = Y + 1000 연산 수행, 트랜잭션 t2의 경우 X = X * 0.5, Y = Y * 0.5 연산 되는 두 개의 트랜잭션 t1, t2가 있습니다.

 

Serial schedule에서의 트랜잭션 수행 결과는 t1이 먼저 수행되는지, 아니면 t2가 먼저 수행되는지에 따른 결과는 다를 수 있습니다. 하지만 모든 스케줄의 결과는 전부 일관성 있는 연산입니다. 지금은 트랜잭션의 수행 순서t1부터 수행한다고 가정하겠습니다. 이 경우 serial schedule에서의 결과는 X = 2000, Y = 2000이 됩니다.

 

이제 비직렬 스케줄에 로킹 기법을 적용한 직렬 가능성 스케줄의 예를 보겠습니다. 이 경우에도 트랜잭션의 시작 순서는 t1부터라고 정의하겠습니다.

 

 

위 그림에서 read는 데이터베이스에서 데이터를 읽어오는 연산, write는 commit 이후 데이터베이스에 영구 저장하는 작업입니다.

 

동시 접근에 대한 제어를 하기 위해 locking algorithm을 사용했습니다. 트랜잭션 t1의 X 연산이 너무 빨리 unlock을 수행시키기 때문에 트랜잭션 t2는 일관성 없는 X, Y에 접근하게 되고 이상한 결과가 됩니다. 

 

지금 문제는 트랜잭션 t1에서 X는 수행됬지만 아직 Y는 수행이 되지 않았습니다. 위 그림에서는 순수하게 특정 데이터를 읽기 전에 lock을 했지만 이렇게 lock을 활용할 경우 동시성 문제의 완벽한 제어는 보장되지 않습니다. 이 문제를 해결하기 위해 2단계 로킹 기법을 적용해야 합니다.

Two-Pahse Locking(2PL). 2단계 로킹 기법 적용하기! | Shared lock, Exclusive lock

2PL(2단계 로킹 기법)은 확장단계, 축소단계가 있습니다.

 

그 전에 간단히 알면 좋을 두 개념이 있습니다.

 

  • Shared lock:  해당 데이터에 read는 수행 but write수행 불가. 그 대신 다른 트랜잭션도 shared lock 실행 동시 가능
  • Exclusive lock: read, write 둘 다 실행 가능. 이때 타 트랜잭션은 shared lock, exclusive lock 수행 불가

 

1. 확장단계(Growing Phase): 트랜잭션이 로킹을 요청하는 단계입니다. 필요한 데이터 읽기 위해 shared lock 요청하고 데이터 수정하기 위해 exclusive lock를 요청할 수 있습니다. 이 단계에서 unlock은 할 수 없습니다. lock할 때는 lock만!!

2. 축소단계(Shrinkin Pahse): 트랜잭션이 로킹을 해제하는 단계입니다. 이 때 더 이상 새로운 lock 요청 불가. 이미 지정했던 lock를 unlock 요청 가능합니다.

 

이 두 단계를 통해 mutable state에 대해 isolated state를 유지할 수 있습니다. 이렇게 되면 트랜잭션 간 일관성 있는 데이터 접근이 가능합니다.

문제1에 2PL 적용

사실 데이터를 읽을 때, shared lock을 통해서 여러 트랜잭션과 함께 데이터를 읽을 수 있지만, 한 트랜잭션에서 shared lock(x)를 선언하고 적용중인 경우 바로 exclusive lock(x)를 사용할 수는 없습니다. shared lock이 해제된 후에 exclusive lock을 사용할 수 있습니다... 그래서 아래의 그림에서 lock을 사용한다는 것은 거의 exclusive lock을 사용한다는 느낌입니다.

 

 

약간의 리뷰 입니다.

 

 

 

긴 글 읽어주셔서 감사합니다.

틀린 부분 발견 시 댓글로 남겨주시면 정말 감사합니다.

728x90