본문 바로가기

IT & AI

PostgreSQL MVCC의 비용, 다른 DB는 어디로 옮길까요?

728x90

PostgreSQL MVCC의 비용, 다른 DB는 어디로 옮길까요?

IT & AI 뉴스 썸네일
IT & AI 뉴스 썸네일

PostgreSQL의 MVCC를 두고 쓰기 증폭과 테이블 팽창, VACUUM 부담을 단점으로 꼽는 일이 많아요. 이런 비용은 실제로 존재해요. 다만 다른 데이터베이스도 과거 행 버전을 보관하고 정리해야 하며, 저장 위치와 정리 주체가 다를 뿐이에요. 1

핵심 요약

구분설계 선택운영에서 드러나는 비용
PostgreSQL과거 행 버전을 테이블 heap에 남겨요dead tuple, 테이블 팽창, VACUUM, XID 관리가 필요해요
Oracle·InnoDB이전 값을 undo 영역에 저장해요오래된 읽기는 undo 탐색 비용을 치르고, 큰 롤백은 오래 걸릴 수 있어요
SQL Server과거 버전을 version store에 둬요오래 열린 스냅샷이 tempdb나 PVS 사용량을 늘릴 수 있어요
MongoDB·WiredTiger캐시와 history store에 버전을 보관해요오래된 읽기가 캐시 압박과 지연으로 이어질 수 있어요
LSM 계열타임스탬프가 붙은 키를 누적해요오래된 버전과 tombstone을 compaction이 정리해요

비교 기준은 "MVCC 문제가 있나요?"보다 "과거 버전을 어디에 두고, 누가 언제 치우나요?"에 가까워요. 읽기와 쓰기가 서로 막히지 않게 만들려면 어느 엔진이든 여러 버전을 일정 기간 유지해야 해요. 2

1. PostgreSQL의 눈에 보이는 비용과 다른 엔진의 숨은 청구서

PostgreSQL은 UPDATE 때 기존 행을 덮어쓰지 않아요. 새 행 버전을 heap에 추가하고, 기존 버전은 나중에 VACUUM이 정리해요. 인덱스가 행의 물리 위치인 `ctid`를 가리키기 때문에 새 버전이 다른 위치에 생기면 변경하지 않은 열의 인덱스도 새 항목이 필요할 수 있어요.

728x90

원문 실험에서는 100만 행 가운데 `last_seen` 10만 건을 수정했어요. 기본 키만 둔 테이블은 약 36MB의 WAL을 만들었고, 보조 인덱스 4개가 있는 테이블은 약 69MB를 만들었어요. 같은 논리 변경이라도 인덱스 구조와 행 배치에 따라 기록량이 달라진다는 사례예요. PostgreSQL의 HOT 업데이트는 인덱스 열이 바뀌지 않고 새 버전이 같은 페이지에 들어갈 때 인덱스 갱신을 줄여요. 다만 페이지에 여유 공간이 있어야 해서 `fillfactor`와 실제 HOT 비율을 함께 봐야 해요. 2

테이블 팽창도 같은 설계에서 나와요. 원문의 별도 실험에서는 100만 행 전체를 갱신한 뒤 롤백했더니 89MB였던 테이블이 178MB가 됐고, dead tuple 100만 개가 남았어요. 롤백 자체는 0.124ms에 끝났어요. 변경 내용을 하나씩 되돌리지 않고 새 버전을 무효로 만들 수 있어서 롤백은 빠르지만, 남은 공간 정리는 VACUUM이 맡아요.

오래 열린 트랜잭션은 정리를 더 어렵게 만들어요. 과거 스냅샷에 행 버전이 필요하면 VACUUM이 그 버전을 지울 수 없어요. 운영 환경에서는 `idle_in_transaction_session_timeout`과 `statement_timeout`으로 오래 열린 세션을 제한하고, 오래된 replication slot과 `xmin` 지연도 함께 확인하는 편이 안전해요.

Oracle과 InnoDB는 이전 값을 별도 undo 구조에 보관해요. 테이블에는 최신 행을 두기 쉬운 대신, 오래된 스냅샷을 읽을 때 undo chain을 따라가야 해요. 큰 변경을 취소할 때는 undo를 역으로 적용해야 해서 롤백 시간이 길어질 수 있어요. Oracle은 필요한 undo가 이미 재사용됐다면 `snapshot too old` 오류를 낼 수 있고, InnoDB는 purge가 늦어지면서 history list와 undo 공간이 커질 수 있어요.

SQL Server의 스냅샷 격리는 version store를 사용해요. 과거 버전이 heap 대신 tempdb 또는 Persistent Version Store에 쌓여요. MongoDB의 WiredTiger는 메모리 캐시와 history store를 활용해요. CockroachDB 같은 LSM 기반 엔진은 논리 키와 커밋 시각을 함께 저장하고 compaction으로 오래된 버전을 정리해요. PostgreSQL의 VACUUM이 사라져도 버전 보존과 정리 비용까지 사라지는 것은 아니에요. 비용을 쓰는 쪽, 읽는 쪽, 임시 저장소, 캐시, compactor 가운데 어디서 감당할지 달라져요. 1

운영할 때 확인할 항목

  • `pg_stat_all_tables`에서 dead tuple 증가 속도와 autovacuum 실행 간격을 확인해요.
  • `pg_stat_user_tables`의 HOT 업데이트 비율을 보고 `fillfactor` 조정이 필요한지 판단해요.
  • 오래 열린 트랜잭션과 idle in transaction 세션을 제한해요.
  • replication slot이 오래된 WAL과 `xmin`을 붙잡고 있지 않은지 확인해요.
  • XID age와 freeze 진행 상황을 관찰해 wraparound 방지 작업이 밀리지 않게 해요.
이 항목은 PostgreSQL을 피해야 한다는 체크리스트가 아니에요. 업데이트가 많고 인덱스가 많은 워크로드에서는 WAL과 bloat 비용이 커질 수 있어요. 반대로 빠른 롤백과 오래된 reader를 쉽게 취소하지 않는 특성이 중요한 시스템에는 PostgreSQL의 선택이 유리할 수 있어요.

왜 중요한가요

데이터베이스를 고를 때 기능 이름만 비교하면 운영 비용이 빠져요. 같은 MVCC라도 PostgreSQL은 heap과 VACUUM에서 비용이 보이고, InnoDB는 undo와 purge에서 비용이 보여요. SQL Server는 version store, WiredTiger는 캐시, LSM 엔진은 compaction 상태를 확인해야 해요. 2

장애 대응 기준도 엔진마다 달라져요. PostgreSQL에서 오래 열린 스냅샷은 dead tuple 정리를 늦출 수 있어요. Oracle은 오래된 스냅샷 읽기를 실패시킬 수 있고, SQL Server는 공유 tempdb에 압력을 줄 수 있어요. 따라서 벤치마크에는 정상 처리량뿐 아니라 장시간 읽기, 대량 업데이트 취소, 정리 작업 지연 같은 실패 조건도 넣는 편이 좋아요.

PostgreSQL 운영자는 VACUUM을 불필요한 뒷정리로 보기보다 버전 수명주기를 담당하는 핵심 작업으로 다뤄야 해요. 업데이트 속도, autovacuum 처리 속도, 가장 오래된 스냅샷의 수명을 함께 보면 테이블 팽창과 XID 위험을 더 일찍 발견할 수 있어요.

참고 자료

  1. PostgreSQL의 MVCC는 나쁘다. 다른 DB도 마찬가지다 — GeekNews
  2. PostgreSQL's MVCC is bad. So is everyone else's. — boringSQL
728x90