이전 글에서 트랜잭션 관리 요소인 ProcArray와 Wait Event에 대해 살펴보았습니다.
이어서 또 다른 트랜잭션 관리 요소인 MultiXact와 SubXact의 개념과 Wait Event에 대해 알아보겠습니다.
📢 MutiXact와 SubXact는 각각 Multitransaction, Subtransaction의 축약어로 본 문서에서는 설명 편의를 위해 축약어를 사용합니다.
MultiXact
MultiXact는 여러 트랜잭션이 동일한 Row(Tuple)에 대해 상호 호환 가능한 Mode로 동시에 Access야 할 때 사용하는 메커니즘입니다. 그리고 MultiXact ID는 해당 트랜잭션들을 대표하는 XID이며 대상 Row의 Header 영역에 기록됩니다.
대표적으로 외래키 제약이나 FOR SHARE가 사용될 때 MultiXact이 활용되며, 관련 정보는 multixact_offset *SLRU와 multixact_member SLRU에 기록됩니다.

- multixact_offset SLRU: MultiXact ID에 포함되는 XID들의 시작점이 되는 정보(multixact_member Entry) 저장
- multixact_member SLRU: 각 MultiXact ID에 포함된 실제 XID와 Lock Mode 정보 저장
위 정보들은 디스크의 /multixact_offset, /multixact_member에 저장되며, 각각 MultiXact Offset Buffer, MultiXact Member Buffer 영역에 Cache되어 사용됩니다.
여러 트랜잭션의 Access로 인해 MultiXact ID가 생성되고 Tuple Header에 기록되는 방식은 아래 글에서 테스트를 통해 확인할 수 있습니다.
PWI- Lock>Row-level Lock(2) (👉🏻 바로가기)
*SLRU(Simple Least-Recently-Used)
PostgreSQL의 SLRU는 트랜잭션에 대한 메타 정보를 추적하기 위한 모듈을 의미합니다. 즉, 트랜잭션 메타 정보를 저장하고 있는 메모리/디스크 영역을 말합니다.
메모리는 Shared Memory에, 디스크 파일은 $PGDATA 하위에 위치하며, 일반 데이터를 저장하는 Shared Buffer나 /base 영역과 구분되어 관리됩니다.
저장하는 메타 정보의 성격에 따라 여러 SLRU 항목으로 세분화되며, 주요 항목은 다음과 같습니다.

SLRU 명 | 디렉토리 | 저장 정보 |
transaction | pg_xact | 트랜잭션의 Commit/Abort 상태 정보 |
subtransaction | pg_subtrans | SubXact과 부모 transaction Mapping 정보 |
multixact_offset | pg_multixact/offsets | 특정 MultiXact을 구성하는 member들의 시작 위치(offset) 정보 |
multixact_member | pg_multixact/members | MultiXact을 구성하는 실제 member(XID 및 권한) 정보 |
notify | pg_notify | LISTEN/NOTIFY 메커니즘에서 발생한 알람(notification) 메시지 |
serializable | pg_serial | Serializable 격리 수준에서 트랜잭션 간 충돌을 감지하고 직렬화 순서를 보장하기 위한 상태 정보 |
commit_timestamp | pg_commit_ts | 각 트랜잭션의 Commit 시간 정보(timestamp)(타임라인 기반 질의나 replication conflict 분석에 활용) |
SLRU Buffer는 전체 크기가 수 MB 정도로 Shared Buffer 대비 매우 작아, 트랜잭션 메타 정보에 대한 I/O가 많아지면 성능 저하의 원인이 될 수 있습니다. PostgreSQL 17부터는 각 SLRU Buffer 크기를 개별 파라미터(transaction_buffers, subtransaction_buffers 등)로 조정할 수 있습니다.
Wait Event
이번에는 MultiXact이 활용되는 과정에서 발생할 수 있는 Wait Event에 대해서 알아보겠습니다.
이들은 offset과 member SLRU를 탐색하는 과정에서 발생하는 Wait Event와 탐색 후 I/O작업을 수행하는 과정에서 발생하는 Wait Event로 분류할 수 있습니다.
LWLock:MultiXactOffsetSLRU, LWLock:MultiXactMemberSLRU
LWLock:MultiXactOffsetSLRU, LWLock:MultiXactMemberSLRU는 MultiXact 정보를 확인을 위해 해당 정보가 Cache된 Buffer에 Access하는 과정(검색/할당)에서 발생하는 Wait Event 입니다.
LWLock:MultiXactOffsetBuffer, LWLock:MultiXactMemberBuffer
LWLock:MultiXactOffsetBuffer, LWLock:MultiXactMemberBuffer는 MultiXact 관련 Buffer에 실제 I/O 작업을 하는 과정에서 발생하는 Wait Event 입니다.
주요 발생 원인
위에서 설명한 Wait Event들은 모두 MultiXact SLRU(multixact_offset SLRU, multixact_member SLRU) Access 상황과 관련되므로 이들에 대한 경합이 주요 발생 원인이 됩니다.
- 동일 행에 대한 과도한 Share Lock
- 여러 Session이 같은 Row에 SELECT FOR SHARE 또는 외래키 제약 조건 검증을 동시에 수행하면, 해당 Row의 xmax에 MultiXact ID가 기록되어야 합니다. 이때 MultiXact ID 정보를 확인하기 위해MultiXact Offset SLRU와 MultiXact Member SLRU에 Access해야 하며, 필요한 정보가 메모리에 없을 경우 디스크 I/O가 발생합니다.
Session이 많아질수록 SLRU에 대한 접근 경합과 I/O가 증가하여 Wait Event 발생 빈도가 높아질 수 있습니다.
- 오래된 MultiXact Entry
- Vacuum이 제대로 수행되지 않으면 오래된 MultiXact Entry가 SLRU 영역에 남아있게 되어, 새로운 MultiXact 생성 시 메모리 공간 부족으로 인해 디스크 I/O가 발생하여 Wait Event 발생이 증가합니다.
- 특히 외래키 제약이 있는 테이블에서 UPDATE/DELETE 작업을 수행할 때, 참조 무결성 검증을 위해 관련된 Row들에 대한 MultiXact 정보를 확인해야 합니다. 이 과정에서 오래된 Entry로 인해 SLRU 공간이 부족하여 Wait Event가 빈번하게 나타날 수 있습니다.
해결 방안
- Application 쿼리 패턴 최적화
- 쿼리 패턴을 조정하여 동일 Row에 대한 Share Lock(Lock Mode가 Access Share, Row Share)이 과도하게 집중되지 않도록 합니다.
- 불필요한 SELECT FOR SHARE 사용을 줄이고 외래키 검증이 집중되는 패턴을 분산시킵니다.
Lock Mode에 대한 자세한 내용은 아래의 글에서 다룹니다.
PWI - Lock>Row-level Lock(1) (👉🏻 바로가기)
- Vacuum 관리
- 불필요한 MultiXact가 정리될 수 있도록 정기적인 Vacuum을 수행하거나 vacuum_multixact_freeze_min_age 파라미터를 조정합니다.
- 오래 지속되는 트랜잭션 관련 Row들은 Vacuum 대상에서 제외되므로 Long Transaction를 모니터링하여 관리합니다.
SubXact
SubXact은 하나의 트랜잭션 내부에서 SAVEPOINT나 예외 처리(EXCEPTION)를 통해 생성되는 하위 트랜잭션을 관리하는 메커니즘으로, SubXact ID는 상위 트랜잭션에 종속된 별개의 XID 입니다. 이 메커니즘을 통해 전체 트랜잭션을 롤백하지 않고 특정 작업만 부분적으로 롤백하거나 재시도할 수 있으며, PostgreSQL은 이러한 상위↔하위 트랜잭션간 관계를 SLRU 모듈로 관리합니다.

SubXact의 특징은 다음과 같습니다.
- 각 SubXact은 자신이 속한 상위 트랜잭션(부모 트랜잭션)을 가지며, 부모 트랜잭션이 Commit될 때 모든 하위 SubXact도 함께 Commit됩니다.
- SubXact 정보는 디스크의 pg_subtrans에 저장되며, 필요에 따라 SubXact Buffer에 Cache되어 사용됩니다.
- pg_subtrans에는 각 SubXact의 부모 트랜잭션 ID가 저장되어 계층 구조를 보존합니다.
- 최상위 트랜잭션은 부모가 없어 InvalidTransactionId를 가지며 pg_subtrans에 기록되지 않습니다.
- 읽기 전용 SubXact은 pg_subtrans에 기록되지 않습니다.
- SubXact이 과도하게 사용되면 SubXact Cache에 부하가 발생하여 SLRU overflow 문제가 발생할 수 있습니다.
Wait Event
SubXact과 관련된 Wait Event도 마찬가지로 이들이 저장된 SLRU Access 경합에 의해 발생합니다.
LWLock:SubtransSLRU
LWLock:SubtransSLRU는 SubXact 정보 확인을 위해 SubXact Buffer Access를 대기할 때 발생합니다.
LWLock:SubtransBuffer
LWLock:SubtransBuffer는 SubXact Buffer Access 과정에서 I/O 작업을 대기할 때 발생합니다.
해당 Wait Event들은 PostgreSQL 12 이전까지 LWLock:SubtransControlLock로 통합되어 사용되었습니다.
주요 발생 원인
SubXact 관련 Wait Event들은 모두 SubXact SLRU(pg_subtrans) Access에 대한 부하 상황이 주요 원인이 됩니다.
- SubXact이 대량으로 생성될 때
- 다수의 Session이 동시에 대량의 SubXact을 생성하면 SubXact Buffer에 대한 Access가 집중되면서 Wait Event 발생이 증가할 수 있습니다.
- Session 양이 많으면 Buffer 공간 부족으로 디스크(pg_subtrans) I/O 작업도 늘어나 부하가 늘게 됩니다.
- Long 트랜잭션 안에서 SAVEPOINT가 반복될 때
- 장시간 수행되는 트랜잭션에서 SAVEPOINT가 반복 호출되면, Row 가시성 판단을 위해 오래된 SubXact의 부모 트랜잭션까지 추적해야 합니다. 이 과정에서 Buffer Access와 디스크 I/O가 증가하여 Wait Event 발생이 증가할 수 있습니다.
하위 트랜잭션이 발급받은 XID도 전역(공유) XID 카운터를 통해 이뤄집니다. 때문에 발급 대상(상/하위)과 상관없이 모든 트랜잭션은 XID를 증가시키는 원인이 됩니다.
이러한 이유로 무분별한 SubXact의 사용은 빠르게 XID 값을 소진 시키며, 나아가 XID Wraparound 방지를 위한 Vacuum의 부담이 가중 될 수 있습니다.
해결방안
- SubXact 사용 최소화
- 불필요한 SubXact 사용을 줄이고, 가능한 한 짧은 트랜잭션 내에서만 사용합니다.
- Loop 내 SubXact을 통한 검증 로직을 Temporary Table로 대체합니다.
- Exception 처리를 위한 SubXact을 Application 레벨에서 처리하는 방법을 검토합니다.
- 동시 세션 수 관리
- Connection Pool을 통해 적절한 Session 수를 유지하여 동시 SubXact 생성을 제한합니다.
PostgreSQL 17부터는 pg_stat_slru 뷰가 제공되어 SLRU 유형별 부하를 확인할 수 있습니다.
blks_read, blks_written 값이 클수록 디스크 접근이 더 자주 발생했음을 의미하므로, 트랜잭션 로직 개선을 위한 단서로 활용할 수 있습니다.
마무리
- MultiXact은 여러 트랜잭션이 동시에 하나의 Row를 접근할 때 이들을 대표하는 XID이며, SubXact은 하나의 트랜잭션 내부에서 SAVEPOINT나 예외 처리를 통해 생성되는 하위 트랜잭션이다.
- MultiXact, SubXact 관련 Wait Event는 모두 해당 SLRU에 대한 Access 부하가 증가할 경우 발생한다.
- MultiXact 관련 Wait Event(LWLock:MultiXactOffsetBuffer, LWLock:MultiXactMemberBuffer, LWLock:MultiXactOffsetSLRU, LWLock:MultiXactMemberSLRU)s는 과도한 Share Lock 사용을 줄이거나 Vacuum 관리를 통해 개선될 수 있다.
- SubXact 관련 Wait Event(LWLock:SubtransBuffer, LWLock:SubtransSLRU)를 줄이기 위해서는 불필요한 SAVEPOINT나 EXCEPTION 처리를 줄이는 것이 중요하다.
함께 보면 좋은 아티클
