[동시성] 락 대기가 클라이언트 타임아웃보다 길면 안 되는 이유

부제: 락 대기·해제가 요청 하나를 줄줄이 막던 버스트를 잡은 이야기
사용자 정보를 갱신할 때 락을 건다.
같은 사용자를 두 요청이 동시에 고치지 못하게 막으려는 것이다.
평소엔 잘 돌았는데, 어느 시간대에 요청들이 줄줄이 60초씩 멈추는 일이 몰려서 났다.
줄줄이 60초씩 멈췄다
버스트를 뜯어보니 세 가지가 겹쳐 있었다.
첫째, 락 대기 시간이 하필 클라이언트 타임아웃과 정확히 같았다(둘 다 60초).
그래서 새 요청이 락을 기다리는 60초 사이에 클라이언트가 먼저 끊겼다.
락이 풀리는 순간이 하필 클라이언트가 막 떠난 직후였다.
둘째, 락 해제를 취소로부터 보호하지 않았다.
요청이 취소되면 그 취소가 전파되면서, 락을 푸는 코드가 절반만 실행됐다.
그러면 락이 제대로 안 풀리고, 만료 시간(60초)이 될 때까지 남았다.
셋째, 이렇게 안 풀리고 남은 락이 뒤따르는 요청들을 줄줄이 60초씩 대기시켰다.
새는 락 하나가 버스트를 만든 것이다.
대기는 클라이언트보다 짧게, 해제는 취소로부터 보호
세 군데를 고쳤다.
락을 붙잡는 시간을 60초에서 30초로 줄였다.
실제 작업은 몇 초면 끝나니 넉넉하고, 혹시 락이 새더라도 남는 시간이 절반으로 준다.
락을 기다리는 시간은 60초에서 10초로 줄였다(fail-fast).
클라이언트가 끊기기 한참 전에 빠르게 포기하고, 뒤에서 도는 복구 작업이 다음 사이클에 다시 시도하게 했다.
괜히 오래 붙들고 있다 클라이언트만 잃는 것보다 낫다.
락 해제는 취소로부터 감쌌다.
요청이 취소된 상태로 정리 구간에 들어와도, 해제 코드는 끝까지 완주하게 했다.
그래야 락이 새지 않는다.
정리
- 락 대기(60초)가 클라이언트 타임아웃(60초)과 같아, 기다리다 클라이언트가 먼저 끊겼다
- 해제가 취소 전파로 절반만 실행돼 락이 만료까지 남았다
- 새는 락이 뒤 요청들을 줄줄이 대기시켜 버스트가 났다
- 락 대기는 클라이언트 타임아웃보다 짧게(fail-fast), 해제는 취소로부터 보호(shield), 보유 시간도 단축
락을 기다리는 시간은 반드시 클라이언트가 버티는 시간보다 짧아야 한다.
그리고 정리 코드는 취소로부터 지켜야 한다.
안 그러면 새는 락 하나가 요청을 줄줄이 무너뜨린다.