PostgreSQL Lock

seongha_h·2025년 7월 18일

PostgreSQL

목록 보기
2/2

지난 게시글에 이어서 lock에 대해 학습해 보겠습니다.

🔐 Lock

동시성 제어와 데이터 정합성을 위해 자원 접근 제어 메커니즘입니다.

Lock 종류

PostgreSQL의 lock은 다음과 같은 종류가 있습니다.
또한, 각 lock 종류들은 내부에 lock mode를 통해 한번 더 lock을 제어합니다.

  • Table-level Lock: 테이블 단위의 잠금
  • Row-level Lock: 특정 row에 대한 잠금 (MVCC 기반)
  • Advisory Lock: 사용자 정의 잠금
  • Lightweight Lock(LWLock): PostgreSQL 내부 자료구조 용
  • SpinLock: 내부 구현 수준에서 사용

🔹 Table-level Lock

테이블 수준의 Lock은 우리가 흔히 아는 그 테이블에 Lock을 설정하는 것입니다.
각 수준별로 충돌하는 lock이 다릅니다.

  1. ACCESS SHARE (AccessShareLock)
    읽기 전용 쿼리에서 획득되며, 다른 읽기/쓰기 작업에 영향을 주지 않습니다.
  • 획득 예시 : SELECT
  • 충돌 : ACCESS EXCLUSIVE
  1. ROW SHARE (RowShareLock)
    row-level lock이 발생하는 읽기 쿼리에서 획득합니다.
  • 획득 예시 : SELECT FOR UPDATE, SELECT FOR SHARE
  • 충돌 : EXCLUSIVE, ACCESS EXCLUSIVE

그리고 FOR UPDATE나 FOR SHARE로 선택되지 않은 다른 참조된 테이블들에 대해서는 ACCESS SHARE 락을 추가로 획득합니다.

  1. ROW EXCLUSIVE (RowExclusiveLock)
    테이블의 데이터를 수정하는 대부분의 쿼리에서 획득합니다.
  • 획득 예시 : INSERT, UPDATE, DELETE
  • 충돌 : SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE, ACCESS EXCLUSIVE
  1. SHARE UPDATE EXCLUSIVE (ShareUpdateExclusiveLock)
    데이터 변경은 막지 않지만, 동시에 스키마 변경은 제한합니다.
  • 획득 예시 : VACUUM, ANALYZE, CREATE INDEX CONCURRENTLY, 일부 ALTER TABLE
  • 충돌 : 대부분의 락과 충돌 (SHARE, ROW EXCLUSIVE, 등)
  1. SHARE (ShareLock)
    읽기는 허용하지만, 데이터 수정으로부터 테이블을 보호합니다. 다른 shareLock은 허용합니다.
  • 획득 예시 : CREATE INDEX (동시성 없이)
  • 충돌 : ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, EXCLUSIVE, 등
  1. EXCLUSIVE (ExclusiveLock)
    읽기만 허용하고, 나머지 트랜잭션을 차단합니다. (수정 or 구조변경 등)
    이 락을 획득한 트랜잭션만 shareLock이 허용됩니다.
  • 획득 예시 : REFRESH MATERIALIZED VIEW CONCURRENTLY
  • 충돌 : 거의 모든 락과 충돌 (ROW SHARE 제외)
  1. ACCESS EXCLUSIVE (AccessExclusiveLock)
    테이블에 대한 모든 접근을 차단합니다.
  • 획득 예시 : DROP TABLE, TRUNCATE, VACUUM FULL, CLUSTER, 기본 LOCK TABLE
  • 충돌 : 모든 락과 충돌

테이블 락 모드 충돌 관계표

요청 락 모드기존 락 모드 →ACCESS SHAREROW SHAREROW EXCL.SHARE UPDATE EXCL.SHARESHARE ROW EXCL.EXCL.ACCESS EXCL.
ACCESS SHARE
ROW SHARE
ROW EXCL.
SHARE UPDATE EXCL.
SHARE
SHARE ROW EXCL.
EXCL.
ACCESS EXCL.

🔹 Row-level Lock

PostgreSQL은 기본적으로 row-level lock을 사용하며, 동시성 제어와 무결성 유지를 위해 매우 중요합니다.

row-level lock은 데이터를 조회하는 select 에 영향을 주지 않지만, 동일한 행에 대한 쓰기 작업은 차단합니다.
또한, 동일 트랜잭션 내의 서브트랜잭션 간에는 충돌되는 락을 동시에 가질 수 있습니다.

  1. FOR UDATE
    select 된 행에 가장 강력한 lock을 설정합니다.
    다른 트랜잭션의 읽기/쓰기 대부분을 차단합니다.
  • 획득 예시 : SELECT ... FOR UPDATE, DELETE, PK/UNIQUE 컬럼을 변경하는 UPDATE
  • 충돌 : FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE
  1. FOR NO KEY UPDATE
    FOR UPDATE보다 약한 락입니다. 키 변경 외의 UPDATE에서 사용합니다.
    예를 들어 key값이 아닌 컬럼들의 변경에서 사용합니다.
  • 획득 예시 : UPDATE (PK/UNIQUE 컬럼 변경이 없는 경우), SELECT ... FOR NO KEY UPDATE
  • 충돌 : FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE
  1. FOR SHARE
    공유 락입니다. 행 변경만 막고 다른 읽기는 허용합니다.
  • 획득 예시 : SELECT ... FOR SHARE
  • 충돌 : FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE
  1. FOR KEY SHARE
    가장 약한 락입니다. 외래키 검사등에 사용합니다.
  • 획득 예시 : 외래키 FK 검사, SELECT ... FOR KEY SHARE
  • 충돌 : FOR UPDATE만 충돌

Row Lock 충돌 관계 요약표

요청한 락 ↓ / 기존 락 → FOR KEY SHARE FOR SHARE FOR NO KEY UPDATE FOR UPDATE
FOR KEY SHARE ✅ ✅ ✅ ❌
FOR SHARE ✅ ✅ ❌ ❌
FOR NO KEY UPDATE ✅ ❌ ❌ ❌
FOR UPDATE ❌ ❌ ❌ ❌

Page-level Locks

PostgreSQL 내부에서는 페이지 단위의 공유/배타적 잠금도 존재하며, 이는 데이터 페이지 단위의 접근 제어를 위한 메커니즘입니다.

테이블과 인덱스의 데이터를 저장하는 디스크 페이지 단위(보통 8KB)에 대한 잠금입니다.

  • 여러 세션이 동일한 페이지를 동시에 수정하지 못하도록 보호
  • 공유 버퍼(pool)에서 동시 읽기/쓰기 시 데이터 정합성 유지

🔹 Lightweight Lock (LWLock) 경량 Lock

⚠️ PostgreSQL의 내부 메모리 자료구조 보호용으로 사용되는 락입니다.
사용자가 명시적으로 사용하는 건 아니지만, 성능 병목에서 중요한 원인 중 하나입니다.

Buffer, WAL, Shared Memory 등에서 접근 충돌 방지 용도입니다.

PostgreSQL은 shared buffer, WAL, catalog 등 공용 자원을 여러 세션이 공유하므로, 자료 구조 보호용 락이 필요합니다.

이 락이 경합되면, 세션이 wait_event_type = 'LWLock' 상태로 대기하게 됩니다.

shared buffer, WAL, catalog 등 공용 자원을 여러 세션이 공유하므로, 해당 영역에 접근하는 데에도 접근 제어가 필요합니다. 이때 사용하는 것이 LWLock입니다.

참고 자료:

PostgreSQL Lock 충돌 관계 (Table-Level 기준)

요청 Lock ↓ / 보유 Lock →ACCESS SHAREROW SHAREROW EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE
ACCESS SHARE
ROW SHARE
ROW EXCLUSIVE
SHARE
SHARE ROW EXCLUSIVE
EXCLUSIVE
ACCESS EXCLUSIVE

🔹 Advisory lock

사용자가 임의의 키/값에 대해 트랜잭션 간 잠금을 설정할 수 있는 어플리케이션 레벨 동기화 도구입니다.

  • 트랜잭션 레벨 lock -> 트랜잭션 종료시 자동 해제
  • 세션 레벨 lock -> 세션 종료시 까지 유지

🔹 Spin lock

PostgreSQL 내부 구현에서 아주 짧은 시간 동안만 사용되는 초경량 락입니다.
CPU level 경합이 날 수 있지만 빠릅니다. 단, 고속 경합이 누적되면 병목 원인이 될 수 있습니다.

  • 메모리 접근 동기화 용도 (ex: counter 증가)
  • 유저가 볼 수 없고 오직 내부적으로만 사용
  • SpinLock은 wait 없이 반복적으로 lock을 시도해서 CPU 사용량이 높을 수 있음

결과?

이렇게 공부하고 나니 아래와 같이 정리할 수 있을 것 같습니다.

로그성 테이블의 FK 참조와 무거운 쿼리로 인하여 FOR UPDATE, KEY SHARE 락 다수 발생
추가로 insert 및 autovacuum 이 lock 경합을 심화시켜 트랜잭션을 정리하지 못함
결과적으로 동시 트랜잭션을 관리하는 MultiXact가 spilover 되며 에러 발생

정말 상상도 못한 원인이었고, 덕분에 postgreSQL을 공부하는 결과가 되었네요...
절대 로그성 테이블에 FK를 걸지 맙시다...ㅠ

출처

profile
https://github.com/Fixtar

0개의 댓글