본문 바로가기
엑셈 경쟁력/DB 인사이드

DB 인사이드 | PostgreSQL Vacuum - 1. MVCC

by EXEM 2022. 4. 29.

📢 Vacuum Series의 첫 시작으로, PostgreSQL의 MVCC모델에 대해 알아보도록 하겠습니다. 본 문서에서는 ①MVCC동작 방식 및 예제를 설명한 후 ②해당 모델로 인해 발생 가능한 문제점들을 알아보도록 하겠습니다.

MVCC 동작 방식

MVCC 란?

MVCC(Multi Version Concurrency Control)“다중 버전 동시성 제어”의 줄임 말로, 과거부터 현재까지의 다양한 버전 데이터들에 대한 관리와 제공이 가능한지를 나타내는 DBMS 필수 기능 중 하나입니다. 즉 시시각각 변하고 있는 데이터 중 사용자가 조회를 시작한 시점의 정확한 데이터를 제공받을 수 있는지를 나타내는 것으로, 그 구현 방법은 각각의 DBMS마다 다르지만 목적만은 모두 동일하다고 할 수 있습니다.

PostgreSQL의 MVCC

익히 알려진 대로 Oracle은 Undo라는 공간을 이용하여 MVCC를 구현하였습니다. 하지만 PostgreSQL은 Undo 같은 공간을 사용하는 대신 아래와 같이 조금은 투박하고 간단한 방식을 사용하여 MVCC모델을 구현하였습니다.

📌 PostgreSQL의 MVCC 구현 방법
1. 테이블 내부에 변경 이전 버전(들)과 현재 버전의 데이터를 모두 위치시킵니다.
2. 각 레코드(row) 별로 4 Byte의 버전 정보(XID)를 두어 시점을 식별할 수 있도록 합니다.
3. 수행 시점의 Transaction ID와 레코드의 XID (XMIN) 비교를 통해 MVCC를 구현합니다.

XMIN / XMAX

MVCC 동작 방식을 이해하기에 앞서 XMIN과 XMAX에 대해 알아보도록 하겠습니다.

레코드 별로 존재하는 XMIN과 XMAX는 XID를 기록하기 위한 용도로 사용됩니다. 특히 XMIN은 Insert/Update를 수행한 XID를, XMAX는 Delete/Update를 수행한 XID를 기록하는 용도로 사용되는데, 데이터 조작(변경) 시 다음과 같이 동작합니다.

  • Insert 발생 시 해당 시점의 XID를 XMIN에 설정
  • Delete 발생 시 해당 시점의 XID를 삭제 대상 레코드의 XMAX에 설정
  • Update 발생 시 변경 전 레코드의 XMAX에 수행 시점의 XID를 설정하고(Delete와 동일), 변경 후 레코드는 새로 입력하며 XMIN에 Update시점의 XID를 설정
📌 XMAX값이 Null 인 레코드의 경우 가장 최신 버전의 레코드를 의미하며, XMAX 값이 설정된 레코드는 해당 시점에 과거 버전(삭제 및 변경)으로 변경되었음을 의미합니다.
💡 레코드, Row, Tuple과 같이 DBMS별로 사용하는 용어의 차이는 있지만 그 의미는 모두 동일합니다. 본 문서를 포함한 Vacuum Series에서는 레코드 및 Row를 혼용해 사용할 예정입니다.

PostgreSQL MVCC 예제

아래 예제를 통해 DML이 일어날 때의 페이지 변화를 살펴보고, PostgreSQL의 MVCC모델에서 사용자의 데이터 읽기 과정이 어떤 식으로 동작하는지 확인해보도록 하겠습니다.

페이지 내부의 변화

  1. XID=999 시점에 [A,10] 값을 Insert 했습니다. 이때 레코드의 [XMIN, XMAX] 값은 [999, Null]로 설정됩니다
  2. XID=1000 시점에 [B,20] 값을 Insert 했습니다. 이때 레코드의 [XMIN, XMAX] 값은 [1000, Null]로 설정됩니다
  3. 시간이 흘러 XID=2001 시점에 [A,10] 값을 [A,20]으로 Update 했습니다. 페이지 내부에서는 변경 전 레코드인 [A,10]의 XMAX 값을 Update 시점의 XID인 2001로 설정하여 해당 레코드가 과거 버전이 되었음을 표시합니다. 그리고 변경된 레코드 [A,20]를 새로 입력하여 XMIN값을 2001로 설정합니다.
📌 PostgreSQL에서 레코드의 변경 작업(Update)은 내부적으로 삭제 표시(Delete) → 변경 값 입력(Insert)의 동작으로 이루어집니다.

 

조회시점에 따른 추출 Data의 변화

이처럼 데이터 조작 시 레코드 별 버전 정보를 XMIN과 XMAX로 관리하는 이유는, 해당 값과 조회 시점의 Transaction ID (이하 XID) 비교를 통해 특정 시점의 데이터를 추출하기 위함입니다.

예를 들어 XID=2000인 시점에 시작되어 오래 수행되는 Select SQL이 있고, 거의 동시에 시작한 Update(XID=2001) SQL이 있다고 가정을 해보겠습니다. Select SQL이 실제 레코드를 찾아간 순간은 이미 Update가 완료된 후입니다.

  1. [A,10] 레코드는 조회시점보다 과거(XMIN=999)에 입력되었고 미래 시점(XMAX=2001)에 삭제될 예정임을 알 수 있으므로 조회 대상에 포함됩니다.
  2. 두 번째 레코드인 [B,20]은 변경된 이력이 없으며(XMAX=Null) 입력된 시점(XMIN=1000)이 조회 시작 시점인 2000보다 과거이므로 조회 대상에 포함됩니다.
  3. 마지막 레코드인 [A,20]의 경우 조회시점(2000)보다 미래(XMIN=2001)에 입력되었으므로 대상에 포함되지 않습니다.

 

해당 모델의 문제점

이와 같이 PostgreSQL의 MVCC모델은 레코드 별 XID 비교를 통해 비교적 쉽게 구현할 수 있다는 장점은 있지만 반대로 몇 가지 치명적인 문제의 원인이 되기도 했습니다.

대표적인 문제로는 아래 내용과 같이 공간 사용의 비효율, 그리고 부족한 Transaction ID 등이 있습니다. PostgreSQL은 이러한 문제점을 보완하기 위해 VM, Data Freezing, Age 등의 개념을 만들어냈으며, 궁극적으로 Vacuum을 이용하여 이러한 일련의 문제를 해결하고자 했습니다.

테이블 내부에 변경 이전 버전(들)과 현재 버전의 데이터를 모두 위치시킵니다.
각 레코드(row) 별로 4 Byte 버전 정보(XID)를 두어 시점을 식별할 수 있도록 합니다.

문제점 1. 공간 사용의 비효율

  • Update, Delete가 빈번한 테이블에 대해서는 각 데이터의 이전 버전을 모두 저장해야 하므로 공간 비효율이 커지며 스캔 범위가 늘어나는 부작용이 발생할 수 있습니다.
  • 또한 Oracle처럼 Block 단위가 아닌 레코드 단위의 XID 저장 방식은 페이지 내부에 대한 공간 비효율을 야기합니다.

문제점 2. XID 부족 현상

  • 레코드 별 XID가 4 Byte라는 점의 또 다른 문제점으로는 43억 정도의 Transaction밖에 표현할 수 없다는 것입니다.
  • 이로 인해 43억 XID를 Newer/Older 구조로 나누어 순환식으로 사용하며(모듈로 연산), 오래된 XID의 경우 주기적으로 특수 값(Frozen XID) 세팅을 통해 언제나 가장 오래된 데이터임을 표시해야 합니다.

이후 문서에서는 XID, Age, Data Freezing, VM등에 대한 내용을 차례대로 알아본 후, Vacuum의 동작원리에 대해 설명하도록 하겠습니다.

 

기획 및 글 | 기술기획팀

이미지 제작 | 디자인그룹 이민석

 

 

 

댓글