Database

[Database] MVCC

mxruhxn 2024. 10. 7. 00:01
728x90
반응형

MVCC 등장 배경

LOCK 기반 Concurrency Control은 다음과 같은 단점이 있었다.

  • read - read 경우에는 허용하지만, 그 외의 경우에는 모두 허용하지 않아서 대기 시간이 오래 걸린다
  • 동시 처리량이 줄어들어 성능 이슈

=> 이를 해결하기 위해 등장한 것이 MVCC

MVCC를 사용하면 write-write의 경우를 제외하고는 모두 block 없이 동시 처리를 허용한다

MVCC 개념

  • 데이터를 읽을 때 특정 시점 기준으로 가장 최근에 commit된 데이터를 읽는다.
    • 특정 시점 기준은 isolation level에 따라 다르다.
    • 이렇게 특정 시점 기준으로 commit된 데이터를 읽는 방식을 MySQL에서는 Consistent read라고 한다.
  • 데이터 변화(write) 이력을 관리한다
    • 따라서 추가적으로 저장공간을 많이 쓰게 된다 - 단점
    • 데이터 변경 사항을 데이터베이스에 반영하는 것이 아니라 snapshot에만 반영하기 때문에, write–read를 block하지 않을 수 있는 것이다.
  • read와 write는 서로를 block 하지 않는다.
    • lock 기반 concurrency control 구현에 비해 성능면에서 더 큰 장점이 있다.

Isolation Level에 따른 동작

  • READ COMMITTED: read하는 시간을 기준으로 그전에 commit 된 데이터를 읽는다.
    • 이러한 동작은 MySQL이나 PostgreSQL 모두 동일하다.
  • REPEATABLE READ: 트랜잭션 시작 시간을 기준으로 그전에 commit 된 데이터를 읽는다.
    • tx 시작 시간 기준이라고 말했지만, RDBMS마다 조금씩 다를 수 있다.
    • 하지만, MySQL과 PostgreSQL에서는 서로 동일하게 동작한다
  • SERIALIAZABLE
    • REPEATABLE READ와 동일한 결과가 나온다.
    • MySQL의 경우: MVCC로 동작하기 보다는 LOCK으로 동작한다.
    • PostgreSQL의 경우: SSI(Serializable Snapshot Isolation) 기법이 적용된 MVCC로 동작한다.
  • READ UNCOMMITTED
    • MVCC는 committed된 데이터를 읽기 때문에 이 level에서는 보통 MVCC가 적용되지 않는다
    • MySQL의 경우: MVCC가 적용되는 level을 read committed나 repeatable read라고 한다.
    • PostgreSQL의 경우: read uncommitted level이 존재하지만, read committed level 처럼 동작한다.

Lost Update 문제와 해결

PostgreSQL에서의 해결

동시 접근하는 트랜잭션들의 Isolation Level을 모두 REPEATABLE READ 수준으로 설정하면 LOST UPDATE 문제를 해결할 수 있다.

  • First-Commiter-Win 전략을 취하므로 커밋 이후의 트랜잭션은 롤백된다 -> 재시도하면 성공

MySQL에서의 해결

  • PostgreSQL과는 다르게 REPEATABLE READ 수준을 적용하더라도 어느 한쪽이 커밋됐다고 하더라도 다른 한쪽이 실패하지 않고 쭉 진행된다. 즉, 여전히 LOST UPDATE가 존재하게 된다.
  • MySQL에서는 LOST UPDATE 문제를 해결하기 위해 값을 읽을 때 추가적으로 lock을 주어야 한다 -> SELECT ... FOR UPDATE 구문
  • Locking Read 시 가장 최근에 커밋된 데이터를 읽는다
    ⇒ MySQL에서는 lost update 방지를 위해서는 repeatable read만으로는 해결이 안되기에 locking read를 사용해주어야 한다. (물론, serializable을 사용하면 해결된다.)
  • Locking Read
    • FOR UPDATE: write-lock(exclusive lock)을 획득한다.
    • FOR SHARE: read-lock(shared lock)을 획득한다.

REPEATABLE READ에서 Write Skew 문제

PostgreSQL에서의 해결

Locking Read를 사용하면 해결할 수 있다. 하지만, MySQL과 동작 방식이 다르다.

  • 한쪽이 Locking Read를 통해 획득한 락을 커밋을 통해 반환하고, 다른 한쪽이 락을 획득한다면 "같은 데이터에 먼저 update한 트랜잭션이 커밋되면 나중 트랜잭션은 롤백된다"는 규칙이 여기서도 적용된다.
  • 결국 한쪽은 롤백되므로 재시도 해주어야 한다.
  • 이는 PostgreSQL에서 REPEATABLE READ 수준에서 for update 혹은 for share 구문을 사용했을 때의 경우이다.

MySQL에서의 해결

Locking Read를 사용하면 해결할 수 있다.

SERIALIZABLE (두 RDBMS의 차이)

REPEATABLE READ와 locking read를 사용하지 않고 아예 serializable level로 isolation level을 올려버리는 방법도 있다.

하지만, 각 DBMS의 동작방식에 약간의 차이가 있다.

MySQL의 경우

- repeatable read와 유사
- tx의 **모든 평범한 `select` 문은 암묵적으로 `select … for share` 처럼 동작한다.**
    - 때문에, `MVCC`라기 보다는 lock으로 동작한다고 많이들 얘기함.
    - `for update`를 하게 되면 성능 이슈가 있을 수 있으므로 for share를 쓰는 듯.

PostgreSQL의 경우

  • SSI(serializable snapshot isolation)로 구현
    • 여전히 MVCC로 동작하면서도 모든 이상현상을 막아줌
    • 자세한건 따로 알아보자..
    • first-commiter winner 방식
728x90
반응형