One PID to Lock Them All: Finding the Source of... | Crunchy Data Blog
Crunchy Data
crunchydata의 블로그를 요약/의역한 글입니다.
---
Crunchy Bridge 고객들의 문제들을 해결하다 보면 종종 PostgreSQL 락을 접할 때가 있습니다. 락은 여러 쿼리에 대해 연쇄적인 효과를 유발할 수 있습니다. 만약 한 프로세스가 테이블을 점유하고 있으면, 나머지 쿼리들은 락이 풀릴 때까지 기다릴 뿐이죠. 프로덕션에서 이런 락 문제는 큰 문제를 유발할 수 있으며 심하면 앱이 다운되는 현상도 일어납니다.
락이 왜 일어나는지, 락을 점유하고 있는 그 한 프로세스를 어떻게 찾는지, 그리고 해결 방법까지 알려드리겠습니다.
락이 걸리는 원인을 찾아서
일반적으로 락은 빠르게 발견하기 어렵습니다. 느려진 서비스, 응답 없는 쿼리 등, 뭔가 이상한 낌새를 눈치 챈다면 재빠르게 그 원인을 찾아보는 게 좋습니다.
대기하고 있는 프로세스 확인하기
pg_stat_activity를 살펴보고 active한 프로세스인데 wait_event와 wait_event_type이 NULL이 아닌 것을 살펴보세요. 만약 연결이 active하고 락을 기다리고 있다면, wait_event와 wait_event_type은 NULL이 아닐겁니다. 이런 프로세스들을 찾았다면 어딘가에 기록해두세요.
(원글에 코드 예시 있음)
어떤 프로세스가 테이블을 락하고 있는지 확인하기
대기 프로세스들을 찾았다면 이번엔 락을 걸고 있는 프로세스를 찾아봅시다. 일단 pg_locks를 조회하는 것으로 시작합니다.
(원글에 코드 예시 있음)
결과 필드 중 locktype을 보게되면 relation이라는 값을 볼 수 있는데, 이 OID를 활용해서 락을 걸고 있는 프로세스를 찾을 수 있습니다.
락을 걸고 있는 프로세스 찾기
다시 한번 pg_locks에 아까 찾은 OID를 가지고 조회하면 락을 걸고 있는 PID를 확인할 수 있습니다.
(원글에 코드 예시 있음)
프로세스가 어떤 작업을 수행 중인지 확인하기
이제 PID를 가지고 다시 한번 pg_stat_activity를 조회해서 어떤 작업을 수행 중인지 확인해봅시다. 보통 현재 수행 중인 트랜잭션을 보여줄겁니다.
(원글에 코드 예시 있음)
모든 것의 원흉
앞서 보여준 방식은 간단한 예시입니다. 무엇을 찾아야 할 지 알고 있다면 금방 찾을 수 있죠. 현실에서는 여러 쿼리들이 중첩되어 있거나, 작업들이 서로 서로 기다리고 있거나, 모든 것을 막고 있는 단 하나의 PID를 찾기 까다로울 수 있습니다. 하지만, 제 동료가 작성한 이 쿼리를 사용하면 원흉이 되는 락을 물고 있는 프로세스를 찾을 수 있습니다.
(원글에 쿼리 예시 있음)
원흉을 끝내는 방법
문제가되는 프로세스를 찾았으니 락을 풀 수 있는 방법에 대해 알아봅시다.
Commit
만약 해당 쿼리가 idle in transaction 상태라면 BEGIN으로 시작한 코드를 끝내지 않았을 가능성이 큽니다. 이때 변경 사항을 반영하고 싶다면 COMMIT 명령문을 보내주면 됩니다.
Rollback
만약 변경 사항이 의도한 것이 아니거나 에러가 났다면 ROLLBACK으로 트랜잭션을 물릴 수 있습니다.
Cancel PID
만약 트랜잭션 자체를 직접 실행한게 아니라면 실행되고 있는 작업을 pg_cancel_backend(PID)로 취소 할 수 있습니다.
Backend 연결과 프로세스 끊기
만약 위에 취소 방식이 먹히지 않는다면, pg_terminate_backend(PID)로 연결된 프로세스를 끊어버릴 수 있습니다.
Postgres는 왜 잠기는가?
Postgres의 멀티 버전, 동시성 제어 시스템은 기술적으로 상당히 뛰어나서 대부분의 경우 조회, 생성, 수정을 별 문제없이 할 수 있도록 해줍니다. 주로 두 가지 락이 존재하는데:
공유 락 - 동시에 리소스에 접근할 수 있는 백엔드/세션이 하나 이상
배타 락 - 동시에 리소스에 접근할 수 있는 백엔드/세션이 단 하나
주로 다른 프로세스를 기다리게 하고 문제가 되는 락은 배타 락입니다. 테이블에 배타 락이 걸리는 경우는 여러가지인데, 아래 사례들이 일반적입니다.
Alter Table
ALTER TABLE 쿼리는 ACCESS EXCLUSIVE 락을 가져가는데 이게 가장 흔한 배타 락 사례입니다. 이는 Raw 쿼리로도 요청 될 때도 있고 가끔 마이그레이션 도중 사용하는 ORM에 의해서 요청 될 때도 있습니다.
ORM 프레임워크
ORM 프레임워크는 데드락을 유발하는 순환 참조 사례를 눈에 띄지 않게 할 수 있습니다. 앱 쪽 에러가 발생 될 때, 트랜잭션 범위 내에 있는 작업들이 수행되면서 에러나면 락이 유발될 수 있고 이후 작업들이 느려질 수 있습니다.
Create index
CREATE INDEX CONCURRENTLY를 사용하지 않으면 인덱스를 생성할때도 배타 락이 걸립니다.
Vacuum
VACUUM FULL은 ACCESS EXCLUSIVE 락을 테이블에 걸어버리니 꼭 필요할 때만 사용되어야 합니다.
기타
Postgres 문서를 보면 여러 종류의 락들을 확인할 수 있고 서로 어떻게 상호작용 하는지 예시로 확인 할 수 있습니다.
락을 대비하기
추후 락이 발생했을 때를 대비해 봅시다.
lock_waits 로깅하기
log_lock_waits 설정을 해두면 쿼리가 락을 기다리는 것을 로그로 남길 수 있습니다. 설정한다고 다른 오버헤드는 발생하지 않으니 프로덕션에서도 활용할 수 있습니다.
lock timeout 설정하기
lock_timeout을 미리 설정해 두는 것을 권장합니다. 일정 시간 안에 트랜잭션 처리가 되지 않으면 락을 풀어버리는 걸 자동화할 수 있습니다.
---
원글에 락을 걸고 있는 PID를 찾는 쿼리는 저장해두면 언젠가 쓸 일이 있을 것 같습니다 😏
원글: https://www.crunchydata.com/blog/one-pid-to-lock-them-all-finding-the-source-of-the-lock-in-postgres
다음 내용이 궁금하다면?
이미 회원이신가요?
2024년 1월 27일 오후 12:39
매
... 더 보기최
... 더 보기우리는 성장이라는 단어를 좋아합니다.
특히 기업의 입장에서는 성장은 관리해야 할 필수 요소 중 하나죠.
코
... 더 보기