앞서 WAL Record를 생성하고, 그 WAL Record를 WAL Buffer에 저장하는 과정에 대해서 살펴보았습니다.
이번에는 WAL Record를 WAL Buffer에 저장하는 과정에서 발생할 수 있는 대표적인 Wait Event인 LWLock:WALInsert와 LWLock:WALBufMapping에 대해서 알아보겠습니다.
📢 LWLock:WALInsert, LWLock:WALBufMapping에서 LWLock은 Event Type을 나타내며 WALInsert과 WALBufMapping는 Wait Event명을 가리킵니다.
LWLock:WALInsert
LWLock:WALInsert는 프로세스가 WAL Record를 WAL Buffer에 저장하기 위해 필요한 WALInsertLock* 획득을 대기할 때 발생하는 Wait Event입니다. 즉, 다른 프로세스가 WAL Record를 WAL Buffer에 저장하는 작업을 완료할 때까지 대기합니다.
📢 WALInsertLockWALInsertLock은 WAL Buffer 내 WAL Page에 WAL Record를 저장하기 위해 획득해야 하는 LWLock 유형의 Lock입니다. WALInsertLock은 해당 Lock을 획득한 프로세스 외 다른 프로세스가 WAL Record를 WAL Buffer에 저장하는 작업을 제한합니다.
- 다중화되어 여러 프로세스에 의해 동시에 사용될 수 있는 특징이 있으며, 그 수는 NUM_XLOGINSERT_LOCKS(default:
8
) 파라미터로 설정되어 있습니다.- 오직 Exclusive Mode로만 제공되므로, WALInsertLock을 획득한 각 프로세스는 모두 해당 Lock을 독점적으로 사용합니다.
- WAL Record를 WAL Buffer에 저장하는 전체 과정 동안 획득 상태를 유지합니다.
LWLock:WALBufMapping
LWLock:WALBufMapping은 WAL Buffer 내 공간이 부족하여 WAL Page 확장 작업을 위해 사용하는 WALBufMappingLock* 획득을 대기할 때 발생합니다.
📢 WALBufMappingLockWALBufMappingLock은 WAL Page 확장 시, WAL Page를 초기화하거나 WAL Page 맵핑을 변경할 때 필요한 LWLock 유형의 Lock입니다. 이는 프로세스가 WAL Page를 확장하는 동안 다른 프로세스의 Access를 제한합니다.
- WALInsertLock과 달리, 다중화되어 있지 않고 하나의 Lock으로 관리됩니다. 따라서 하나의 프로세스만 독점적으로 WALBufMappingLock을 획득할 수 있습니다.
- WAL Record를 WAL Buffer에 저장하는 과정에서 필요에 따라 추가로 획득하기 때문에, WALBufMappingLock의 획득은 WALInertLock 보유 시간을 길어지게 하는 요인이 될 수 있습니다.
- WALBufMappingLock도 Exclusive Mode로만 사용됩니다.
다음은 WAL Record를 저장 과정에서 상황에 따라 필요한 Lock을 획득하는 과정을 도식화 한 그림입니다.
그림에서 볼 수 있듯이, WALInsertLock은 WAL Record를 저장하는 전체 과정 동안 획득 상태를 유지합니다. 이 Lock이 유지하는 동안 여러 요인으로 인해 Wait Event는 발생할 수 있으며 이에 따라 Lock의 유지 시간이 길어질 수 있습니다. 따라서 각각의 Lock 동작 과정을 이해함으로써, 이러한 Lock들이 서로 어떻게 연관되어 있으며 그 상호작용이 데이터베이스 성능에 직접적인 영향을 미칠 수 있다는 점을 기억해야 합니다.
① 새로운 WAL Record 생성합니다.
② WAL Record를 WAL Buffer에 저장하기 위해 WALInsertLock 획득 요청합니다.
→ YES : 획득 후 ③으로 이동
→ NO : 획득 실패 (Wait Event - LWLock:WALInsert)
③ WAL Record를 저장할 위치를 확인하고 WAL Record 크기에 따라 필요한 공간을 계산하여 예약합니다.
④ 여유 공간을 확인하여 WAL Page 확장이 필요한지 확인합니다.
→ YES : ⑤으로 이동
→ NO : 예약한 공간으로 WAL Record 저장하고, ⑦로 이동
⑤ WAL Page 확장 작업
a. WALBufMappingLock을 요청합니다.
→ YES : 획득 성공하면 ⑤-b로 이동
→ NO : 획득 실패 (Wait Event - LWLock:WALBufMapping)
b. 새로운 페이지를 할당 가능한지 확인합니다.
→ YES : ⑤-c로 이동
→ NO : ⑥으로 이동
c. 새로운 페이지를 할당하여 WAL Page 확장 후, WALBufMappingLock 해제합니다.
⑥ WAL Page Write 작업
a. WALWriteLock*을 요청합니다.
→ YES : 획득한 후 ⑥-b로 이동
→ NO : 획득 실패 (Wait Event - LWLock:WALWrite)
b. 기존 페이지를 재 사용하기 위해 WAL Page Write 및 초기화 진행을 합니다. 마치면 WALWriteLock을 해제합니다.
⑦ WAL Buffer에 WAL Record 저장이 끝나면 WALInsertLock을 해제합니다.
📢 WALWriteLockWALWriteLock은 WAL Record를 WAL Buffer에서 디스크로 Write 할 때 필요한 LWLock 유형의 Lock입니다. WAL Buffer에 공간이 부족하여 WAL Page 확장 시, 새로운 페이지를 찾지 못해 기존 페이지를 재 사용해야 하는 경우에는 먼저 디스크로 Write 작업을 하기 위해 WALWriteLock을 사용합니다.
주요 발생 원인
위에서 설명한 두 Wait Event 모두 WAL Buffer에서 수행되는 작업과 관련되므로 WAL Buffer 크기, 그리고 WAL Buffer에 저장되는 단위인 WAL Record의 양과 크기에 영향을 줄 수 있는 부분들이 중요한 요소가 됩니다.
- 데이터베이스 변경 작업이 빈번한 환경에서 WAL Buffer의 크기가 충분하지 않은 것은 Wait Event 발생의 원인이 될 수 있습니다. WAL Buffer를 작은 값으로 설정하면 WAL Record 저장 과정에서 WAL Page 확장이 필요한 상황이 많아지면서 WAL BufMappingLock을 요청하고 이에 따라 WALInsertLock의 획득 유지 시간도 길어집니다.
- Full Page Write 기능을 활성화한다면, Checkpoint가 빈번하게 수행되는 경우 블록(페이지) 전체 데이터를 포함한 WAL Record가 많아지면서 WAL Buffer 공간이 더 빠른 속도로 가득 차게 됩니다. 이는 WAL Page 확장이 더 빈번하게 이루어질 수 있다는 것을 나타내며, 이에 따라 WALInsertLock과 WALBufMappingLock을 점유하는 시간이 길어져 Lock 경합에 따른 Wait Event 발생이 증가할 수 있습니다.
- 데이터베이스 변경 작업을 수행하는 동시 트랜잭션이 증가하면 WAL Record도 더 많은 양이 한꺼번에 생성됩니다. 그로 인해 WAL Buffer 공간이 빠르게 소모되면서 관련 Lock에 대한 요청도 증가하고 Lock 경합 발생 가능성이 높아질 수 있습니다.
📢 WAL Buffer 크기는 wal_buffers 파라미터를 통해 설정합니다. Default 값은
-1
이며, 이는 shared_buffers에 설정된 값의 1/32에 해당하는 크기로 설정한다는 의미입니다.
📢 Full Page Write는 full_page_writes 파라미터를 통해 설정하며, Default 값은
on
입니다.on
인 경우 Checkpoint 이후 수행되는 첫 번째 변경 사항에 대해서는 블록(페이지) 전체 데이터를 WAL Record에 저장합니다.
해결 방안
- WAL Buffer의 크기를 데이터 변경 작업에 대한 Workload를 충분히 수용할 수 있도록 설정하면, WAL Page 확장이 필요한 상황을 줄일 수 있습니다. 그 결과, WALBufMappingLock과 같은 관련 Lock에 대한 경합도 감소하여 시스템 성능을 향상할 수 있습니다.
- 안정적인 복구보다 성능을 우선하는 데이터베이스 환경에서는 Full Page Write 기능을 비 활성화하여 WAL Record 크기를 줄이는 방법을 선택할 수 있습니다. 이를 통해 WAL Buffer 내 WAL Page 공간이 빠르게 소모되는 것을 방지할 수 있습니다. 그러나 이 방법은 데이터 복구 시 안정성을 저하시킬 수 있으므로 환경에 맞는 신중한 설정이 필요합니다.
반면, 데이터 보존의 중요도가 높은 환경에서는 Full Page Write를 비 활성화하는 것은 일부 데이터가 손실 초래할 수 있으므로 권장되지 않습니다. 이 경우, wal_compression 파라미터를 조정하여 WAL Record의 크기를 줄이는 방법을 사용할 수 있습니다. wal_compression은 블록(페이지) 전체가 포함된 WAL Record의 해당 부분을 압축하여 WAL Record 크기를 줄이고 저장하는 방식으로 동작합니다. 이를 통해 데이터 안정적으로 유지하면서 성능을 어느 정도 개선할 수 있습니다. - 위와 같이 데이터베이스 파라미터를 조정하여 해결이 어려운 경우, 동시 트랜잭션 수 자체를 제한하는 것도 하나의 해결 방법이 될 수 있습니다.
📢 wal_compression의 Default 값은
off
이지만, WAL Record를 압축하여 더 작은 크기로 저장하고자 한다면on
으로 설정할 수 있습니다. 그러나 압축과 해제 작업은 CPU 사용률을 증가시킬 수 있기 때문에, I/O 성능에 미치는 영향을 고려하여 설정해야 합니다. 이 설정으로 저장 공간을 절약할 수 있지만, 추가적인 CPU 리소스를 소모하므로 시스템의 전체 성능에 미치는 영향까지 종합적으로 평가한 후 결정하는 것이 중요합니다.
[CASE 1] 파라미터 조정
먼저 WAL Buffer 크기를 결정하는 wal_buffers와 WAL Record의 크기에 영향을 주는 full_page_writes를 조정하면서 LWLock:WALInsert와 LWLock:WALBufMapping의 발생을 확인해보겠습니다.
(wal_buffers를 큰 값으로 설정할수록 많은 양의 WAL Record를 저장할 수 있으며, full_page_writes는 on
로 설정하면 off
보다 더 큰 사이즈의 WAL Record이 생성됩니다.)
insert into t_wal
select pg_catalog.generate_series(1,10000),
(select array_agg(c1) from pg_catalog.generate_series(1,100) t(c1));
위와 같이 10,000건의 데이터에 대한 Insert Command를 수행하고 Dtrace를 활용하여 Lock 통계 정보 결과를 확인해 보겠습니다.
1) wal_buffers=512KB
, full_page_writes=on
Lock statistics:
================
Locks per tranche
+----------------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
| Tranche | Acquired | AcquireOrWait (Acquired) | AcquireOrWait (Waited) | ConditionalAcquire (Acquired) | ConditionalAcquire (Failed) | Waits | Wait time (ns) |
+----------------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
| ... | ... | ... | ... | ... | ... | ... | ... |
| WALBufMapping | 356 | 0 | 0 | 0 | 0 | 0 | 0 |
| WALInsert | 10001 | 0 | 0 | 0 | 0 | 1 | 34559 |
| WALWrite | 178 | 1 | 0 | 0 | 0 | 6 | 9280283 |
| ... | ... | ... | ... | ... | ... | ... | ... |
+----------------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
- WAL Buffer와 관련된 세 개의 Lock에 대한 결과를 보면, 모두 Lock을 획득 요청한 건수가 많은 것을 볼 수 있습니다.
WALInsertLock
과WALWriteLock
에 대해서는 획득하는 과정에서 대기가 발생했음을 Waits와 Wait time 항목을 통해 알 수 있습니다. 이는 곧 WAL Record를 WAL Buffer에 저장하는 과정과 WAL Buffer에서 디스크로 Write를 수행하는 과정에서 대기가 발생을 의미합니다.WALBufMappingLock
에 대한 요청이 있던 것으로 보아, WAL Page 확장이 필요한 상황이 있었다는 것을 알 수 있고,WALWriteLock
에 대한 결과를 통해 WAL Page 확장 과정에서 WAL Record를 Write 하는 작업도 수행되었을 것으로 해석할 수 있습니다.WALInsertLock
에 대한 대기의 원인 중 하나로WALBufMappingLock
과WALWriteLock
으로 인하여WALInsertLock
획득 유지 시간이 길어진 것을 생각할 수 있습니다.
2) wal_buffers=4MB
, full_page_writes=on
이번에는 이전 설정 대비, wal_buffers를 4MB
로 크게 설정하여 동일한 Insert Command를 수행한 후, 결과를 비교해 보겠습니다.
Lock statistics:
================
Locks per tranche
+--------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
| Tranche | Acquired | AcquireOrWait (Acquired) | AcquireOrWait (Waited) | ConditionalAcquire (Acquired) | ConditionalAcquire (Failed) | Waits | Wait time (ns) |
+--------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
| ... | ... | ... | ... | ... | ... | ... | ... |
| WALInsert | 10001 | 0 | 0 | 0 | 0 | 0 | 0 |
| WALWrite | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... |
+--------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+-
WALBufMappingLock
에 대한 요청이 없었다는 것을 볼 수 있습니다. wal_buffers를 조정하여 WAL Buffer에 더 많은 공간을 할당하였으므로, 그만큼 더 WAL Record를 수용할 수 있기 때문에 새로운 WAL Page를 할당을 위한 확장 작업이 필요하지 않았다는 것을 알 수 있습니다.WALInsertLock
은 이전과 동일한10001
건이지만, Wait time은0
으로 줄어들었습니다. WAL Record를 저장하는 과정을 동일하게 수행할 때 이번에는 대기가 전혀 발생하지 않았음을 의미합니다.WALWriteLock
에 대한 요청도 감소한 것을 확인할 수 있습니다. (179 →1
)
WAL Page 확장으로 인한 WAL Record에 대한 Write 작업은 불필요하므로, 그에 수반되는 Write 작업이 발생하지 않은 것에 영향을 받아 줄어들었습니다.
wal_buffers 파라미터를 큰 값으로 설정한다면 WALBufMappingLock
과 WALWriteLock
에 대한 요청 건수 자체를 줄일 수 있으며, 더 나아가 WALInsertLock
에 대한 Wait 발생에도 영향을 미친다는 것을 확인할 수 있습니다.
3) wal_buffers=512KB
, full_page_writes=off
마지막으로 wal_buffers는 조정 없이, full_page_writes만 off
로 변경한 후 Insert Command를 수행하였습니다. 블록(페이지) 전체를 포함하여 저장하는 기능을 비 활성화하였으므로, Checkpoint 수행에 영향을 받아 WAL Record 크기가 커지지 않습니다. 전반적으로 WAL Record 크기가 이번보다 작아지는 효과가 있을 것으로 예상할 수 있습니다.
Lock statistics:
================
Locks per tranche
+--------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
| Tranche | Acquired | AcquireOrWait (Acquired) | AcquireOrWait (Waited) | ConditionalAcquire (Acquired) | ConditionalAcquire (Failed) | Waits | Wait time (ns) |
+--------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
| ... | ... | ... | ... | ... | ... | ... | ... |
| WALBufMapping | 272 | 0 | 0 | 0 | 0 | 0 | 0 |
| WALInsert | 10001 | 0 | 0 | 0 | 0 | 0 | 0 |
| WALWrite | 136 | 1 | 0 | 0 | 0 | 7 | 15483028 |
| ... | ... | ... | ... | ... | ... | ... | ... |
+--------------------+----------+--------------------------+------------------------+-------------------------------+-----------------------------+-------+----------------+
WALBufMappingLock
요청이 줄어들었습니다. (356→272
)
WAL Page 확장 작업이 이루어졌으나, full_page_writes가on
이었을 때보다 덜 수행되었다는 것을 의미합니다.WALInsertLock
은 이번에도10001
건으로 동일하며, Wait time도0
입니다.WALWriteLock
에 대한 요청도 다소 줄어든 것을 볼 수 있습니다. (179→136
+1
)
wal_buffers를 큰 값으로 조정한 경우보다 큰 차이는 아니지만, full_page_writes를 조정함으로써WALBufMappingLock
요청이 감소하였으며 이는 WAL Page 확장 작업이 줄어들었다는 것을 의미합니다. 그로 인한 WAL Record Write 작업이 감소했기 때문에WALWriteLock
요청도 함께 감소했다는 것을 알 수 있습니다.
📢
WALWriteLock
에 대한 요청은 줄어들었지만, Waits와 Wait time은 오히려 증가한 것을 볼 수 있습니다.WALWriteLock
은WALBufMappingLock
,WALInsertLock
하고는 달리 WAL Buffer 동작 과정 외에도 I/O 동작과 밀접한 연관이 있으므로 I/O 성능에 따라 Waits와 Wait time 결과는 달라질 수 있습니다.
세 가지 경우로 테스트를 진행한 결과, full_page_writes를 조정했을 때 보다 wal_buffers를 조정했을 때가 효과가 더 크다는 것을 확인할 수 있습니다. 따라서 WAL Buffer 관련 Wait Event 발생이 문제가 되는 환경에서 wal_buffers를 먼저 조정하여 최적화하고, 그러고 나서 full_page_writes와 wal_compression과 같은 파라미터를 추가로 검토하여 WAL Record 크기를 줄이는 방법을 적용하여 문제를 해결할 수 있습니다.
[CASE 2] 동시 트랜잭션 제한
LWLock:WALBufMapping, LWLock:WALInsert는 모두 LWLock 유형의 Lock을 획득하는 과정에서 발생하는 Lock 경합으로 인한 Wait Event입니다. 다시 말해 WAL Buffer에 충분한 공간이 있어 많은 양의 WAL Record를 저장할 수 있는 환경일지라도 동시에 여러 프로세스에서 WAL Record 저장 작업을 위해 Lock을 획득하고자 한다면 Lock 경합을 피할 수 없으며, Wait Event는 발생할 수밖에 없습니다.
동시 트랜잭션 수에 따라 Wait Event 발생 빈도에 어떤 변화가 있는지, 동시 트랜잭션 수를 조정하면서 확인해 보겠습니다.
1) 동시 트랜잭션 : 3
, wal_buffers=4MB
, full_page_writes=on
select *
from pg_wait_sampling_profile
where "event_type"='LWLock' and "event" like 'WAL%'
order by pid, event;
pid|event_type|event|queryid|count|
---+----------+-----+-------+-----+
3
개의 트랜잭션을 시작하여 데이터 변경 작업을 수행하는 쿼리를 동시에 실행한 후, pg_wait_sampling_profile을 조회하여 Wait Event 발생량을 조회하였습니다. 조회 결과 0
건으로 WAL Buffer 관련 Wait Event가 발생하지 않은 것으로 확인됩니다.
2) 동시 트랜잭션 : 10
, wal_buffers=4MB
, full_page_writes=on
이번에는 동시 트랜잭션 수를 3개에서 10
개로 변화를 준 다음 동일한 조건으로 테스트를 진행하였습니다.
wal_buffers, full_page_writes와 같은 데이터베이스 파라미터를 동일하게 설정한 환경에서 동시 트랜잭션 수가 계속해서 증가한다면 프로세스 간 Lock 경합 발생 확률이 높아집니다. 그 결과 Wait Event 발생 빈도가 전반적으로 모두 급격하게 증가한 것을 확인할 수 있습니다.
📢 LWLock:WALWrite
새로운 페이지를 찾아서 WAL Page 확장을 할 수 없다면 먼저 사용 중인 페이지를 디스크로 Write 후 재 사용해야 합니다. 이때 디스크 Write 작업을 위해 WALWriteLock을 획득해야 하며, WALWriteLock은 하나로 관리되기 때문에 두 개 이상의 프로세스가 요청하게 되면 나머지 프로세스에서 LWLock:WALWrite라는 Wait Event가 발생됩니다.
마무리
- WALInsertLock은 WAL Record를 WAL Buffer에 저장하기 위해 사용되는 Lock이다. 여러 프로세스가 동시에 사용할 수 있지만, 그 수는 제한적이며(Default: 8), 모든 WALInsertLock이 이미 사용 중일 경우, 추가로 Lock을 획득하려는 프로세스는 대기 상태에 들어가게 된다.(LWLock:WALInsert)
- WALBufMappingLock은 WAL Page 확장을 위해 획득하는 Lock으로, 하나의 프로세스만 독점적으로 획득할 수 있다. WAL Page 확장이 필요할 때 WALBufMappingLock을 획득하지 못한 다른 프로세스들은 대기하게 된다.(LWLock:WALBufMapping)
- LWLock:WALInsert와 LWLock:WALBufMapping은 모두 WAL Buffer 내 작업과 관련이 있으므로, WAL Buffer 크기가 Workload를 감당하지 못할 경우 Wait Event 발생 빈도를 줄이기 위해 WAL Buffer 크기를 더 크게 조정하는 것이 권장된다. 이와 함께, full_page_writes와 wal_compression 같은 설정을 통해 WAL Buffer에 저장되는 WAL Record의 양을 줄이는 방법도 검토해 적용할 수 있다.
- 대부분의 Lock은 제한된 리소스이다. 따라서 동시에 여러 프로세스가 Lock을 요청할수록, Lock 획득을 위한 경합이 빈번해지며, 작업을 즉시 수행하지 못하고 대기하는 프로세스가 늘어나게 된다. 이에 따라 동시 트랜잭션 수는 Wait Event 발생 빈도를 줄이기 위해 조정해야 할 중요한 요소 중 하나이다.
'엑셈 경쟁력 > DB 인사이드' 카테고리의 다른 글
DB 인사이드 | PWI - WAL Buffer > WAL Record 저장 (0) | 2025.02.10 |
---|---|
DB 인사이드 | PostgreSQL New Feature - 17 Release (4) (0) | 2025.01.16 |
DB 인사이드 | PostgreSQL New Feature - 17 Release (3) (0) | 2024.12.30 |
DB 인사이드 | PostgreSQL New Feature - 17 Release (2) (0) | 2024.12.30 |
DB 인사이드 | PostgreSQL New Feature - 17 Release (1) (0) | 2024.12.30 |
댓글