읽는 동안 값이 바뀌어도 이전 모습을 보는 MVCC
MVCC는 데이터의 여러 버전을 관리하여 동시 접근을 제어하는 방식이다. 독자는 자신에게 보이는 버전을 선택하고, 작성자는 새 버전을 만든다. 하나의 최신 값을 모두가 직접 공유하는 방식과 달리, 같은 행에서도 서로 다른 시점의 일관된 관찰을 제공할 수 있다.
MVCC는 스냅샷에 맞는 버전을 선택하며 필요한 이전 버전은 정리하지 않는다.
값뿐 아니라 보이는 조건을 저장한다
잔액이 100인 행을 다른 트랜잭션이 70으로 바꾼다고 하자. MVCC에서는 기존 버전과 새 버전을 구분하고, 어떤 트랜잭션이 만들었는지와 언제 가시성이 달라지는지 판단할 정보를 유지한다. 변경이 아직 커밋되지 않았다면 다른 독자가 그 새 버전을 사용할 수 있는지는 격리와 가시성 규칙에 달려 있다.
오래된 스냅샷을 사용하는 독자는 기존 100을 계속 읽을 수 있고, 새 커밋 이후의 스냅샷을 사용하는 독자는 70을 읽을 수 있다. 이는 모두가 같은 순간의 물리적 메모리 값을 읽는 모델과 다르다. 스냅샷은 단순한 벽시계 시간 하나로만 구현되는 것이 아니라 트랜잭션 상태 등 시스템의 정보와 연결된다.
스냅샷의 유지 범위도 중요하다. 문장마다 새 스냅샷을 얻는 수준과 트랜잭션 동안 같은 스냅샷을 유지하는 수준에서는 반복 조회 결과가 달라질 수 있다. MVCC라는 이름이 있다는 사실만으로 모든 조회가 항상 이전과 같은 결과를 갖는다고 말할 수 없다.
독자와 작성자가 덜 기다리는 이유
이전 버전을 사용할 수 있으면 독자가 읽는 동안 작성자가 새 버전을 만드는 방식으로 진행할 수 있다. 많은 환경에서 일반적인 읽기와 쓰기의 직접적인 대기를 줄이는 데 도움이 된다. 다만 모든 형태의 잠금이나 충돌이 사라지는 것은 아니다. 두 작성자가 같은 행을 수정하거나 스키마를 바꾸는 상황에는 별도 제어가 필요하다.
또한 여러 버전을 읽는다는 사실 자체가 직렬 가능성을 뜻하지 않는다. 서로 다른 행을 수정하는 작업들이 같은 과거 상태에 근거해 의사결정을 하여 여러 행의 규칙을 깨는 쓰기 편향이 가능하다. 강한 격리를 제공하려면 의존성 추적, 잠금이나 검증 같은 추가 조건이 필요할 수 있다.
오래된 버전을 무한히 남길 수는 없으므로 정리 작업이 중요하다. 더는 어떤 독자에게도 필요하지 않은 버전은 회수할 수 있다. 하지만 오랫동안 열린 트랜잭션이나 복구·복제의 특정 요구가 있으면 정리 가능한 범위를 제한할 수 있다. PostgreSQL의 VACUUM은 이런 버전 정리와 공간 재사용에 관련된 중요한 유지 관리다.
빠른 읽기에도 관리 비용이 남는다
행을 자주 수정하면 여러 버전과 관련 메타데이터가 공간을 사용한다. 오래된 버전이 많이 남으면 테이블이나 인덱스의 부피와 접근 비용에 영향을 줄 수 있다. 정리 속도, 쓰기 패턴과 장기 트랜잭션을 함께 관리해야 한다. 버전 관리가 있으니 삭제와 수정은 비용이 없다는 생각은 맞지 않는다.
인덱스를 통해 후보를 찾은 뒤에도 그 버전이 현재 스냅샷에 보이는지 확인해야 한다. 논리적인 삭제는 어떤 독자에게는 여전히 보이는 이전 버전을 남길 수 있다. 행이 디스크에 존재한다는 물리적 사실과 질의 결과에 보인다는 논리적 사실은 서로 다르다.
MVCC를 관찰할 때는 트랜잭션 시작과 스냅샷 획득 시점, 커밋, 실제 읽기 결과를 함께 기록하면 좋다. 시스템이 언제 새 스냅샷을 얻는지와 어떤 버전을 회수할 수 있는지를 설명할 수 있어야 한다. 핵심은 과거를 무조건 보존하는 것이 아니라 동시 작업에 필요한 과거를 관리하면서 올바른 가시성과 자원 사용을 유지하는 데 있다.