지난 게시글에 이어서 lock에 대해 학습해 보겠습니다.
동시성 제어와 데이터 정합성을 위해 자원 접근 제어 메커니즘입니다.
PostgreSQL의 lock은 다음과 같은 종류가 있습니다.
또한, 각 lock 종류들은 내부에 lock mode를 통해 한번 더 lock을 제어합니다.
Table-level Lock: 테이블 단위의 잠금Row-level Lock: 특정 row에 대한 잠금 (MVCC 기반)Advisory Lock: 사용자 정의 잠금Lightweight Lock(LWLock): PostgreSQL 내부 자료구조 용SpinLock: 내부 구현 수준에서 사용테이블 수준의 Lock은 우리가 흔히 아는 그 테이블에 Lock을 설정하는 것입니다.
각 수준별로 충돌하는 lock이 다릅니다.
AccessShareLock)SELECTACCESS EXCLUSIVERowShareLock)SELECT FOR UPDATE, SELECT FOR SHAREEXCLUSIVE, ACCESS EXCLUSIVE그리고 FOR UPDATE나 FOR SHARE로 선택되지 않은 다른 참조된 테이블들에 대해서는 ACCESS SHARE 락을 추가로 획득합니다.
RowExclusiveLock)INSERT, UPDATE, DELETESHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE, ACCESS EXCLUSIVEShareUpdateExclusiveLock)VACUUM, ANALYZE, CREATE INDEX CONCURRENTLY, 일부 ALTER TABLESHARE, ROW EXCLUSIVE, 등)ShareLock)CREATE INDEX (동시성 없이)ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, EXCLUSIVE, 등ExclusiveLock)REFRESH MATERIALIZED VIEW CONCURRENTLYROW SHARE 제외)AccessExclusiveLock)DROP TABLE, TRUNCATE, VACUUM FULL, CLUSTER, 기본 LOCK TABLE| 요청 락 모드 | 기존 락 모드 → | ACCESS SHARE | ROW SHARE | ROW EXCL. | SHARE UPDATE EXCL. | SHARE | SHARE ROW EXCL. | EXCL. | ACCESS EXCL. |
|---|---|---|---|---|---|---|---|---|---|
| ACCESS SHARE | ❌ | ||||||||
| ROW SHARE | ❌ | ❌ | |||||||
| ROW EXCL. | ❌ | ❌ | ❌ | ❌ | |||||
| SHARE UPDATE EXCL. | ❌ | ❌ | ❌ | ❌ | ❌ | ||||
| SHARE | ❌ | ❌ | ❌ | ❌ | ❌ | ||||
| SHARE ROW EXCL. | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | |||
| EXCL. | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | |
| ACCESS EXCL. | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
PostgreSQL은 기본적으로 row-level lock을 사용하며, 동시성 제어와 무결성 유지를 위해 매우 중요합니다.
row-level lock은 데이터를 조회하는 select 에 영향을 주지 않지만, 동일한 행에 대한 쓰기 작업은 차단합니다.
또한, 동일 트랜잭션 내의 서브트랜잭션 간에는 충돌되는 락을 동시에 가질 수 있습니다.
SELECT ... FOR UPDATE, DELETE, PK/UNIQUE 컬럼을 변경하는 UPDATEFOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATEUPDATE (PK/UNIQUE 컬럼 변경이 없는 경우), SELECT ... FOR NO KEY UPDATEFOR UPDATE, FOR NO KEY UPDATE, FOR SHARESELECT ... FOR SHAREFOR UPDATE, FOR NO KEY UPDATE, FOR SHARE외래키 FK 검사, SELECT ... FOR KEY SHAREFOR UPDATE만 충돌요청한 락 ↓ / 기존 락 → FOR KEY SHARE FOR SHARE FOR NO KEY UPDATE FOR UPDATE
FOR KEY SHARE ✅ ✅ ✅ ❌
FOR SHARE ✅ ✅ ❌ ❌
FOR NO KEY UPDATE ✅ ❌ ❌ ❌
FOR UPDATE ❌ ❌ ❌ ❌
PostgreSQL 내부에서는 페이지 단위의 공유/배타적 잠금도 존재하며, 이는 데이터 페이지 단위의 접근 제어를 위한 메커니즘입니다.
테이블과 인덱스의 데이터를 저장하는 디스크 페이지 단위(보통 8KB)에 대한 잠금입니다.
⚠️ PostgreSQL의 내부 메모리 자료구조 보호용으로 사용되는 락입니다.
사용자가 명시적으로 사용하는 건 아니지만, 성능 병목에서 중요한 원인 중 하나입니다.
Buffer, WAL, Shared Memory 등에서 접근 충돌 방지 용도입니다.
PostgreSQL은 shared buffer, WAL, catalog 등 공용 자원을 여러 세션이 공유하므로, 자료 구조 보호용 락이 필요합니다.
이 락이 경합되면, 세션이 wait_event_type = 'LWLock' 상태로 대기하게 됩니다.
⇒ shared buffer, WAL, catalog 등 공용 자원을 여러 세션이 공유하므로, 해당 영역에 접근하는 데에도 접근 제어가 필요합니다. 이때 사용하는 것이 LWLock입니다.
참고 자료:
| 요청 Lock ↓ / 보유 Lock → | ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE |
|---|---|---|---|---|---|---|---|
| ACCESS SHARE | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| ROW SHARE | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| ROW EXCLUSIVE | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| SHARE | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| SHARE ROW EXCLUSIVE | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| EXCLUSIVE | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| ACCESS EXCLUSIVE | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
사용자가 임의의 키/값에 대해 트랜잭션 간 잠금을 설정할 수 있는 어플리케이션 레벨 동기화 도구입니다.
PostgreSQL 내부 구현에서 아주 짧은 시간 동안만 사용되는 초경량 락입니다.
CPU level 경합이 날 수 있지만 빠릅니다. 단, 고속 경합이 누적되면 병목 원인이 될 수 있습니다.
이렇게 공부하고 나니 아래와 같이 정리할 수 있을 것 같습니다.
로그성 테이블의 FK 참조와 무거운 쿼리로 인하여 FOR UPDATE, KEY SHARE 락 다수 발생
추가로 insert 및 autovacuum 이 lock 경합을 심화시켜 트랜잭션을 정리하지 못함
결과적으로 동시 트랜잭션을 관리하는 MultiXact가 spilover 되며 에러 발생
정말 상상도 못한 원인이었고, 덕분에 postgreSQL을 공부하는 결과가 되었네요...
절대 로그성 테이블에 FK를 걸지 맙시다...ㅠ