요약
- APM은 앱이 직접 만든 응답만 기록한다. 앱이 죽거나 연결이 끊겨서 ALB가 대신 만든 502·503·504는 앱을 거치지 않으니 남지 않는다.
- 어떤 서비스에서 7일간 ALB가 만든 502가 3,218건인데 앱이 만든 5xx는 643건이었다. APM만 보면 실패의 80%를 못 본다.
- "APM에 에러가 별로 없으니 괜찮다"는 판단은 위험하다. CloudWatch를 따로 봐야 한다.
본문
왜 안 보이나
ALB가 세는 5xx는 두 종류다.
HTTPCode_Target_5XX_Count— 앱이 5xx를 응답했다. 앱을 거쳤으니 APM에도 남는다.HTTPCode_ELB_5XX_Count— ALB가 스스로 만들었다. 타깃에 연결이 안 되거나, 응답 도중 끊기거나, 타임아웃이 났을 때다. 앱이 응답을 만든 적이 없으니 APM에 없다.
APM 에이전트는 앱 안에 붙어서 요청 시작과 끝을 잰다. 프로세스가 죽거나 워커가 응답 중간에 끊기면 기록을 남길 주체 자체가 사라진다.
얼마나 차이 나나
운영 중인 Django API 서비스에서 7일치를 봤다.
| 지표 | 7일 합계 |
|---|---|
| 전체 요청 | 약 8,490만 |
| ALB가 만든 502 | 3,218 |
| 앱이 만든 5xx | 643 |
| 연결 실패 | 0 |
ALB가 만든 실패가 앱이 만든 것의 5배다. 연결 실패가 0인데 502가 나온다는 건, 연결은 됐는데 응답 도중에 끊겼다는 뜻이다. gunicorn 워커 타임아웃이나 재시작이 유력하다.
한 장애 구간에서는 APM이 5xx를 3건으로 보여줬는데, 같은 5분에 CloudWatch는 502를 15건 세고 있었다. APM만 보고 장애 규모를 판단하면 5분의 1로 줄여서 본다.
특히 위험해지는 상황
로드밸런싱을 least outstanding requests로 바꿀 때다. 이 방식은 처리 중인 요청이 적은 타깃을 고르는데, 즉시 실패하는 타깃은 처리 중인 요청이 0에 가까워서 오히려 더 많이 받게 된다. 그런데 그 실패 모드가 정확히 APM에 안 보이는 종류다. "APM에서 5xx가 몇 건뿐이라 안전하다"는 근거는 볼 수 없는 데이터에 기댄 셈이 된다.
어떻게 확인하나
건수보다 뭉쳐 있는지가 중요하다. 타깃 하나가 갑자기 실패를 뿜는 패턴이 있었는지 5분 단위로 본다.
aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB \
--metric-name HTTPCode_ELB_502_Count \
--dimensions Name=LoadBalancer,Value=<lb-dimension> \
--period 300 --statistics Sum
위 사례에서는 3일간 864개 창 중 621개에 502가 있었고, 창당 평균 2.3건에 최대 18건이었다. 늘 조금씩 깔려 있는 것이지 한 번에 터진 적은 없었다.
한계도 있다. CloudWatch는 ALB가 만든 5xx를 타깃별로 나눠주지 않는다. "뭉친 적 없다"까지가 여기서 알 수 있는 전부고, "특정 타깃에 몰리지 않았다"를 직접 보려면 ALB 액세스 로그를 뜯어야 한다.
관련 노트
- New Relic 파이썬 에이전트의 패키지 목록 수집이 gevent 허브를 harvest마다 최대 2초 막는다
- 동시 처리 요청 수를 응답시간으로 계산했다면 응답시간을 두 번 센 것이다
- Gunicorn 워커 수가 많아지면 thundering herd로 인해 일부 워커만 요청을 받는다