특정 작업이 도는 동안 다른 화면이 멈춰서 잠금 경합으로 보였다. 어느 코드가 그 테이블을 잠그는지부터 찾아야 했다.
Table of contents
Open Table of contents
갱신 코드가 0건인데 멈추는 화면
멈추는 화면이 건드리는 테이블을 확인하고 그 테이블을 갱신하는 코드를 전부 검색했지만 문제의 작업 쪽에서는 한 건도 나오지 않았다. 그 작업은 그 테이블을 고치는 코드를 갖고 있지 않았다.
그런데 실제로는 그 작업이 도는 동안에만 화면이 멈추므로 시간 상관관계는 분명했다. 코드에는 없고 현상에는 있는 상태에서 조사가 막혔다.
시간 상관관계를 인과로 단정한 보고
앞선 장애 보고서에는 그 작업이 카테고리 테이블을 갱신하면서 배타 잠금을 걸었다고 원인이 적혀 있었다. 그러나 코드 어디에도 그 갱신이 없으므로 그 문장은 관측이 아니라 추론이었다.
그것을 멈추니 풀렸다는 관측에서 그것이 갱신해서 잠갔다는 인과로 한 단계를 건너뛴 서술이었다. 시간 상관관계는 무엇이 관여했는지는 말해 주지만 어떤 방식으로 관여했는지는 말해 주지 않는다.
제약조건이 거는 암묵적 잠금
검토를 받으면서 외래키 제약이 거는 잠금이 아니냐는 지적을 받고 관계를 확인했다. 연결 테이블이 카테고리 테이블을 참조하는 외래키가 실제로 걸려 있었다.
연결 테이블에 행을 넣으면 참조 무결성을 확인하기 위해 부모 행에 공유 잠금이 걸리고 그것이 커밋까지 유지된다. 삽입 한 번이 부모 행을 잠그는 것인데 그 코드에는 카테고리를 갱신하는 문장이 한 줄도 없다.
잠금을 걸고 나서 하는 긴 작업
작업 구조를 보니 트랜잭션 안에서 연결을 먼저 넣고 그다음에 옵션 수십 개를 복제하고 있었다. 잠금을 먼저 잡은 뒤에 오래 걸리는 일을 하는 순서였다.
그동안 다른 쪽에서 그 카테고리를 편집하려 하면 배타 잠금을 요청하는데 공유와 배타는 함께 잡히지 않으므로 대기한다. 많이 쓰이는 카테고리일수록 그 충돌이 자주 일어난다.
진단 순서와 결함의 개수
이후로 잠금 경합을 볼 때 갱신 코드를 먼저 찾고 안 나오면 그 테이블을 참조하는 외래키를 확인하고 마지막으로 트랜잭션 범위를 보는 순서로 진행한다. 두 번째 단계를 빼면 이런 종류의 원인은 검색으로 끝까지 안 나온다.
파고 보니 독립된 결함이 셋이었는데 잠금 뒤의 긴 작업이라는 구조 문제와 그 안의 무거운 조회라는 성능 문제와 실제로 안 바뀌는 값이 변경으로 인식되는 표면 문제였다. 앞선 보고서는 세 번째만 원인으로 지목하고 있었고 표면에서 멈추면 구조가 안 보인다.
정리
- 제약조건이 거는 잠금은 코드 검색으로 안 잡힌다
- 연결 테이블에 삽입하면 참조되는 부모 행이 잠긴다
- 그 잠금은 커밋까지 유지된다
- 시간 상관관계는 관여 여부만 말하고 방식은 말하지 않는다
- 잠금을 먼저 잡고 긴 작업을 하면 그 시간만큼 대기가 생긴다
- 진단은 갱신 코드와 외래키 관계와 트랜잭션 범위 순으로 본다
- 결함이 여럿일 수 있고 표면에서 멈추면 구조가 안 보인다
- 검토를 받으면 알고는 있던 지식이 이 상황과 연결된다