01. ACID

Last updated - 2024년 12월 06일 Edit Source

    유데미 Hussein Nasser님의 데이터베이스 엔지니어링 마스터 강의를 정리한 노트


    # ACID 소개

    ACID

    • 관계형 데이터베이스 시스템(RDB)의 중요한 속성

    Agenda

    • What is a Transaction?
    • Atomicity (원자성)
      • 왜 일관성을 보장하는데 원자성이 가장 중요할까?
    • Isolation (고립성)
      • 트랜잭션이 고립되어있다는 것은 무슨 의미일까?
      • 여러 트랜잭션을 동시에 시작하면 이 모든 것들이 내 데이터에 접근하고 변경을 하려하면?
      • 그들이 무엇을 볼 수 있는지, 보아서는 안되는 지?
      • 이 모든 것들이 일관성에 영향을 끼침
      • 일관성 이야기에 앞서서 원자성과 고립성을 먼저 이야기하는 것이 중요하다 생각하여 순서를 배치함
    • Consistency (일관성)
    • Durability (지속성, 영속성)
      • 트랜잭션을 커밋하거나 저장할 때 실제로 데이터가 비휘발성 저장소에 지속적으로 저장되어 내 데이터에 접근할 수 있어야 한다.
      • DB가 충돌하거나 전원이 꺼져도 내 데이터에 여전히 접근할 수 있어야 한다.



    # 트랜잭션이란?

    Transaction

    • 하나의 작업 단위
    • 하나의 작업 단위로 취급되는 SQL 쿼리들의 모음
    • 트랜잭션은 데이터를 변경할 수도, 읽기 전용일 수도 있음
    • 트랜잭션은 항상 시작됨
      • 만약, 우리가 트랜잭션을 시작하지 않으면 DB 쪽에서 우리 대신 시작함
      • 사용자가 정의한 트랜잭션일 수도, 시스템에 의해 암시적으로 정의한 트랜잭션일 수도 있음

    데이터는 구조화되어있고, 여러 테이블을 가지고 있기에 하나의 쿼리로 원하는 모든 작업을 수행하기에 어려움


    # 트랜잭션의 수명

    • Transaction BEGIN
    • Transaction COMMIT
    • Transaction ROLLBACK
    • Transaction unexpected ending = ROLLBACK (ex. crash)

    트랜잭션은 항상 여러 쿼리가 포함된 새로운 트랜잭션을 시작하겠다는 것을 DB에 알리는 BEGIN으로 시작

    • 작성한 쿼리를 모두 만족하는 트랜잭션이 지속성을 갖는 것은 아님
    • 트랜잭션의 수명 동안 내가 진행한 모든 변경사항을 커밋하여 디스크에 영구적으로 저장하라고 알려줘야함

    1000개의 쿼리를 실행하고 1000개의 변경을 커밋한다고 하자. 어떻게 DB를 구축할래?

    1. 모든 변경을 실제로 디스크에 모두 쓰는 경우
    2. 기다렸다가 메모리에 모은 다음, 커밋할 때 한꺼번에 디스크에 쓰는 경우

    첫 번째 방법은 커밋을 빠르게 할 것이고, 두 번째 방법은 커밋을 느리게 할 것이지만 디스크 I/O가 줄어들겠지. 모든 것에는 장단점이 있음


    예를 들어, 권장되지는 않지만 50,000개의 쿼리를 실행하다가 문제가 생겼다고 하자. 롤백해야겠지?

    • 중간에 진행되었던 변경사항을 커밋하지말고 모든 변경사항을 되돌려
    • 위의 1000개 쿼리 문제도 다시 생각해보자. 이 모든 시간동안 쿼리를 실행할 때 실제로 디스크에 영구적으로 저장되어있는지 확인하나요?
      • 만약, 그러한 작업을 한다면 모든 작업을 되돌려야 하는데 그것 또한 엄청나게 많은 작업…
    • 두 번째 방법처럼 메모리에 모으는 경우라면 실제 디스크에는 아무것도 쓰지 않았기에 롤백이 더 빨라질 것 → 그냥 메모리 비우면 되잖아 ~
    • 롤백을 위한 undo, redo 공간이 있으며 모든 변경사항에 대해 작동함

    예를 들어, 권장되지는 않지만 20,000개의 쿼리를 실행하다가 시스템이 다운되었다고 하자.

    • 롤백해야하는데 데이터베이스 시스템이 다운됐어. 어떻게 할래?
    • 데이터베이스가 다시 시작할 때, 이를 롤백해야함

    이러한 방법들을 실제 구현하는 것은 MySQL, Postgres, Oracle 등 각각 다르게 구현됨

    • 시스템 다운에 최적화 할 지, 커밋에 최적화 할 지 등등
    • Postgres는 커밋을 가장 빠르게 처리함
      • 트랜잭션 중에 실행된 모든 쿼리 변경사항을 영구적으로 저장하도록 시도하기 때문
      • Postgres는 많은 I/O를 수행하지만 커밋은 아주 빠름
      • 커밋 자체가 빠르기에 커밋 중에 시스템이 다운되는 상황이 발생할 가능성이 낮아짐

    # 트랜잭션의 본질

    • 트랜잭션은 데이터를 변경하고 수정하는데 사용됨
    • 하지만, 실제로는 읽기 전용 트랜잭션을 가지는 것도 좋음
      • DB에게 읽기 전용 트랜잭션이라고 알려주는 것
      • 읽기만 하려면 트랜잭션 없이 각 쿼리를 자체 트랜잭션으로 처리하면 안되나?
        • Consistency(일관성)을 위해 읽기만 하더라도 트랜잭션이 필요함

    예를 들어, 보고서를 생성하고 거래시간을 기준으로 일관된 스냅샷을 얻고자 하는 상황

    • 트랜잭션에서 제공하는 것은 작업해야할 이 그룹뿐만 아니라 트랜잭션이 시작한 시간의 스냅샷까지 얻어야함
    • 내가 읽는 모든것은 처음 시간을 기준으로 하기를 원함
    • 이것이 읽기 전용 트랜잭션의 힘인데, 무엇이든지 읽을 때 무언가가 동시 트랜잭션 때문에 바뀐다고 하더라도 우리는 신경쓸 필요가 없어짐 → Isoloation 파트에서 더 알아보자

    예시

    • 계좌의 잔액이 음수가 되는 것은 마이너스 통장과 같은 특별한 상황이 아니라면 의미 X
    • 즉, 음수인 데이터가 발생하면 일관성이 없는 데이터를 의미 



    # 원자성 (Atomicity)

    • 트랜잭션 내의 모든 쿼리는 성공해야함, 나눌 수 없는 원자처럼 취급함
    • 만약, 하나의 쿼리가 실패하면?
      • 잔고가 마이너스로 가거나, 중복된 키 항목이 생기거나, 잘못된 SQL 구문이 있거나 등
      • 같은 트랜잭션 내에 성공한 쿼리가 100개가 있다고 하더라도 하나의 실패한 트랜잭션으로 인해 즉시 롤백되어야함
    • 만약, 트랜잭션 실행 중에 데이터베이스가 다운된다면?
      • 커밋 이전에 다운된다면 실제로 트랜잭션을 커밋하지 않았음
      • 모든 성공한 쿼리와 트랜잭션은 롤백되어야함
      • 그런데, 데이터베이스가 다운되어 롤백을 못하잖아
        • 데이터베이스가 다시 시작되었을 때 데이터베이스는 실패가 있었다고 감지해야함

    결국, 데이터베이스마다 실제 구현이 달라서 트레이드오프를 고려하여 선택해야 함

    • 어떤 DB는 모든 트랜잭션을 커밋하기도 전에 모든 변경사항을 디스크에 기록함
      • 사용자가 커밋할 것이라고 낙관적으로 가정하는 것
      • 실제로, 커밋 당시에 어떤 작업도 추가적으로 진행하지 않고 그냥 “이 트랜잭션이 커밋되었다” 정도만 작성함
      • 쿼리의 속도는 느리지만 커밋의 속도는 빠름
    • 어떤 DB는 디스크에 기록하지 않고 메모리에 모아놨다가 한 방에 flush
      • 쿼리의 속도는 빠르지만 커밋의 속도는 느림
      • 롤백은 상대적으로 빠름
        • 디스크에 기록하지 않았고, 메모리만 비워버리면 되니까



    # 고립성 (Isolation)



    # 일관성 (Consistency)



    # 지속성 (Durability)



    # ACID 실전 예제



    # Phantom Reads



    # 읽기 방식 비교



    # 궁극적 일관성

    Comment