2월 15일 회고

아이슬랜드 배포 시 장애 분석

얼마 전 아이슬랜드 배포 스크립트를 수정 했다. 아이슬랜드는 서버 한 대씩 LB에서 Service down 상태로 인지하게 한 후 재시작 시키는 Rolling deployment 방식을 사용하고 있다. 작년 11월 LB Vendor가 교체 됐다. 이 때 LB에서 Service down으로 인지하는 조건이 변경 되었다. 해당 LB 설정에 맞게 배포 스크립트를 수정 했어야 하는데, 그 부분을 놓치고 있다가 뒤늦게 발견했다. 그래서 한동안 L4의 서비스 테이블에서 제거 되지 않은 채로 앱을 재기동 하고 있었다.

그래서 나는 밥값을 할 기쁜 마음으로 해당 LB 설정에 맞게 배포 스크립트를 조금 수정 했다. 그런데 이상하게 수정된 배포 스크립트를 사용하여 배포할 때 나서 기존 보다 더 많은 에러가 클라 쪽에서 발견됐다. 원인을 생각해보고 인프라 팀과 함께 파보며 클라와 LB, 서버가 Transport layer에서 커넥션을 관리하는 방식에 대해서 많이 공부할 수 있었다.

우선 변경 된 LB의 health check 관련 스펙은 이랬다.

  • 5초 마다  Health check
  • 최초 1회 실패후 3회 retry, 계속 전부 실패 시 service down (이것은 아래서 나의 오해로 밝혀진다.)
  •  Down State flush 옵션을 통해 서비스가 Down 상태가 되면 세션 테이블을 정리한다.
+기존 배포 스크립트
1. 헬스체크 파일 삭제
2. access.log에 health_check가 한 번 이상 들어 온 것을 확인
3. (health_check 한 번 실패로는 down 되지 않기 때문에 추가 요청이 들어오는 상태에서)
shutdown.sh => sleep 1 => start.sh 통해 앱 재기동

3번 과정에서

  • 클라이언트는 FIN, ACK를 받고 커넥션을 정리 했을 것이다.
  • LB는 최대 5초(Health check 주기) 이내에 서버로 부터 RST를 받고 세션 테이블을 정리 했을 것이다.

4번 과정에서 세션 테이블에 해당 서버가 다시 추가 되었을 것이다.

이 때 클라이언트는 새로운 FIN, ACK을 받고 새로운 연결을 맺기 위해 LB로 요청을 날리지만, H최대  5초(health check 주기)간 세션 테이블이 정리 되지 않은 상태로 서버가 내려가 있어 RST를 받고 Connection refused 등을 일으켰을 것이다.

+신규 배포
1. 헬스체크 파일 삭제
2. 20초간 배포 script sleep
3. 15초 ~ 20초 구간에 service down 처리 되고 access.log를 통해 추가 요청이 들어오지 않음.
4. (추가 요청이 없는 상태에서)shutdown.sh => sleep 1 => start.sh 통해 앱 재기동

3번 과정에서

  • LB는 health_check 4회 실패 후 세션 테이블에서 해당 서버를 정리 했을 것이다. – 1)
  • 클라는 FIN, ACK을 받지 못했기 때문에 해당 세션으로 요청을 보낸다.
  • LB는 이 요청에 대해 RST를 내려준다.

4번 과정에서 세션 테이블에 해당 서버가 다시 추가 되었을 것이다. – 2)

그런데 여기서 세션 테이블에 1) 에서 2)가 되기 전 까지 최대 5초 간 불필요한 sleep 구간이 있다. 이 때 들어오는 요청들에 대해서 RST를 받고 에러가 발생한다.

이렇게 정리해보면 클라이언트가 RST를 받는 시간은 최대 5초로 같다. 그런데 tcp 덤프를 떠서 확인해 보니 내가 잘못 알고 있던 점을 찾게 된다.

+ src host tcpdump

65557번 패킷을 보면 src host가 PSH, ACK 요청을 보내지만 다음 라인에서 바로 RST 응답을 받는게 보입니다. 그리고 13초 정도 뒤에 dst host에서 세션 종료를 요청하는 FIN, ACK 패킷이 날아옵니다.

+ dst hot tcpdump

src host tcpdump 상에서 RST 패킷을 받는 시점이 타임아웃이 발생하는 시간과 거의 동일하여 dst host의 패킷도 확인해봤는데 여기서는 RST 패킷이 보이지 않았습니다.

최초 LB의 세션 테이블에서 서비스가 제외 된 후에 13초가 지나서야 앱이 내려가고 FIN, ACK이 날라간다. 13초간 RST가 발생할 수 있는 환경이 되는 것이다.

  1. 아이슬랜드는 http server인 undertow server와 thrft server인 grizzly가 하나의 프로세스에서 같이 돌아가는데, http server를 먼저 graceful하게 죽이는 동안 grizzly의 서비스는 열려 있다. 이 때문에 세션 테이블에서 내려간 채로 LB가 RST를 내려줘야 하는 시간이 길어지게 되는 것이다.
  2. access log와 app log를 살펴보니 LB의 세션 테이블에서 제외되어 요청이 더 이상 들어오지 않는 상태에서 앱이 죽기까지 7초가 걸렸다. 이건 진짜 최대 5초여야 하는데… Health check log와 LB event log를 확인해보니 LB가 최대 4번 health check를 시도하는 것이 아니라 3번 시도 후 down 처리하는 것을 발견했다. 이 때문에 최대 10초의 간극이 벌어지는 것이다.

이제 service down 되는 것을 sleep 20을 통해 기다리지 않고 health check 횟수를 세번 세어서 판단하면, 현재보다 RST 발생 시간을 5초 ~ 10초 줄일 수 있다. 1번은 어떻게 해야할지 고민이다.

또 궁금한 점은, 나의 지식과 가설 대로라면 기존 스크립트에서는 클라가 신규 세션 요청을 할 때마다 LB가 세션 테이블을 유지하는 동안은 반복해서 RST가 내려가야 한다. 이렇게 반복되어 RST가 발생하는 케이스가 많을 텐데 에러가 많이 잡히지 않았던 점은 의문이다. 세션 테이블의 구성과 동작에 대해서 찾아보고  tcp dump를 떠봐야 이해가 될 것 같다.

신규 API

멜론 쪽에서 제공 받아야할 api가 모두 만들어져서 톡 api도 구현했다. 멜론에게 전달 받아서 톡 클라로 내려주기만 하면 되는 간단한 api였다. 간단한 api를 만듦에도 고민 포인트는 있었다. 새로운 api를 만드는 건 사실 처음인데, 성공 케이스와 실패 케이스의 구분이 날 고민하게 만들었다.

  • 성공 시 들어가는 정보들이 담기는 필드를 실패 케이스에서도 빈 값 및 default 값으로 내려 줄 것인가?
  • 실패 케이스에서는 필드를 지워서 내려주는 것이 깔끔해보인다.
  • 하지만 정적 언어를 쓰는 경우에는 어차피 그 값이 없는 필드가 null 값이 될거 아닌가?
  • 그래도 지워서 내리는게 깔끔할 거 같다.
  • 근데 지금 글을 쓰다 보니 이미 같은 컨트롤러의 다른 api들이 빈 값을 내려주는 형식을 취하고 있고, 위에 위에 이유 때문에 빈 값 처리 하는게 나을거 같다는 생각이 든다.

월요일에 가서 수정해야지… 그리고 금요일까지 샌드박스에 올렸으면 좋았을텐데 좀 늦었다. 월요일엔 다 올려 놓자.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

search previous next tag category expand menu location phone mail time cart zoom edit close