앱의 지연 시간 증가 문제 해결

많은 경우 애플리케이션의 지연 시간이 상승하면 5xx 서버 오류가 발생합니다. 따라서 각 문제의 원인이 동일하다고 가정할 때 오류 및 지연 시간 급증의 근본 원인을 좁히기 위해 비슷한 문제 해결 단계를 따르는 것이 좋습니다.

문제 범위 지정

첫째, 관련 정보를 수집하여 문제 범위를 최대한 좁게 정의합니다. 다음은 관련 있을 수 있는 정보에 대한 몇 가지 제안 사항입니다.

  • 영향을 받는 애플리케이션 ID, 서비스, 버전은 무엇인가요?
  • 영향을 받는 앱의 특정 엔드포인트는 무엇인가요?
  • 해당 영향이 모든 클라이언트에 전역적으로 또는 특정 클라이언트 하위 집합에 적용되었나요?
  • 해당 이슈의 시작 및 종료 시간은 언제인가요? 시간대를 지정해야 합니다.
  • 어떤 오류가 발생하나요?
  • 일반적으로 특정 백분위수의 증가로 지정되는 관측된 지연 시간 델타는 무엇인가요? 예를 들어 90번째 백분위수에서 2초 단위로 증가하는 지연 시간이 있습니다.
  • 지연 시간은 어떻게 측정되었나요? 특히 클라이언트에서 측정되었나요? 아니면 Cloud Logging 또는 App Engine 서빙 인프라로 제공되는 Cloud Monitoring 지연 시간 데이터로 확인 가능한가요?
  • 애플리케이션의 종속 항목은 무엇이고 여기에 이슈가 발생한 적이 있나요?
  • 최근에 이 문제를 트리거했을 수 있는 코드, 구성, 워크로드 변경이 수행되었나요?

애플리케이션에는 위 제안사항을 넘어 문제 범위를 더 좁히기 위해 사용할 수 있는 자체 커스텀 모니터링 및 로깅이 포함될 수 있습니다. 문제 범위를 정의하면 가능한 근본 원인을 파악하고 다음 문제 해결 단계를 결정하는 데 도움이 됩니다.

실패한 항목 확인

그런 다음 요청 경로에서 지연 시간 또는 오류를 일으킬 가능성이 높은 구성요소가 무엇인지 확인합니다. 요청 경로의 기본 구성요소는 다음과 같습니다.

클라이언트 -> 인터넷 -> Google 프런트엔드(GFE) -> App Engine 서빙 인프라 -> 애플리케이션 인스턴스

1단계에서 수집한 정보로 장애 원인을 확인할 수 없으면 일반적으로 애플리케이션 인스턴스의 상태 및 성능을 먼저 조사해야 합니다.

문제가 애플리케이션 인스턴스에 있는지 여부를 확인하는 한 가지 방법은 App Engine 요청 로그를 확인하는 것입니다. 이러한 로그에 HTTP 상태 코드 오류 또는 지연 시간 증가가 표시되면 일반적으로 애플리케이션을 실행 중인 인스턴스에 문제가 있음을 의미합니다.

요청 로그에서 증가한 오류 및 지연 시간이 애플리케이션 인스턴스 자체로 인해 발생하지 않을 수 있는 시나리오도 있습니다. 애플리케이션의 인스턴스 수가 트래픽 수준에 맞게 수직 확장되지 않았으면 인스턴스가 과부하되어 오류 및 지연 시간이 증가할 수 있습니다.

Cloud Monitoring에 증가한 오류 또는 지연 시간이 표시되면 일반적으로 App Engine 측정항목을 기록하는 부하 분산기의 업스트림에 문제가 있다고 결론을 지을 수 있습니다. 대부분의 경우에 이것은 애플리케이션 인스턴스의 문제를 가리킵니다.

그러나 요청 로그가 아닌 모니터링 측정항목에서 지연 시간 또는 오류 증가가 확인되면 추가 조사가 필요할 수 있습니다. 부하 분산 레이어의 장애를 나타내거나 부하 분산기가 요청을 라우팅할 수 없을 정도로 심각한 장애가 인스턴스에 발생했음을 나타낼 수 있습니다. 이러한 경우를 구분하기 위해서는 이슈가 시작되기 바로 전의 요청 로그를 조사해야 합니다. 요청 로그에서 장애 바로 직전에 지연 시간이 증가한 것으로 표시되었으면 부하 분산기가 요청 라우팅을 중지하기 전 애플리케이션 인스턴스 자체에 장애가 발생하기 시작했음을 나타냅니다.

이슈를 일으킬 수 있는 시나리오

다음은 사용자에게 발생했을 수 있는 몇 가지 시나리오에 대한 설명입니다.

클라이언트

지리적 리전에 클라이언트 IP 매핑

Google은 DNS 조회에 사용되는 클라이언트 IP 주소를 기반으로 App Engine 애플리케이션의 호스트 이름을 클라이언트에 가장 가까운 GFE로 변환합니다. 클라이언트의 DNS 리졸버가 EDNS0 프로토콜을 사용하지 않는 경우 클라이언트 요청이 가장 가까운 GFE로 라우팅되지 않을 수 있습니다.

인터넷

인터넷 연결 불량

클라이언트에 다음 명령어를 실행하여 인터넷 연결 불량이 문제인지 확인합니다.

$ curl -s -o /dev/null -w '%{time_connect}\n' <hostname>

일반적으로 time_connect의 값은 가장 가까운 Google 프런트엔드에 대한 클라이언트 연결의 지연 시간을 나타냅니다. 이 연결이 느리면 traceroute를 사용하여 추가 문제 해결을 수행해서 네트워크에서 지연을 일으키는 홉을 확인할 수 있습니다.

여러 지리적 위치에 있는 클라이언트에서 테스트를 실행할 수 있습니다. 요청은 클라이언트 위치를 기준으로 가장 가까운 Google 데이터 센터로 자동으로 라우팅됩니다.

낮은 대역폭의 클라이언트

애플리케이션이 빠르게 응답하더라도 App Engine 서빙 인프라가 전송 가능한 속도로 빠르게 네트워크로 패킷을 전송하지 못하도록 만드는 네트워크 병목 현상으로 인해 응답 속도가 느려질 수 있습니다.

Google 프런트엔드(GFE)

HTTP/2 행렬 막힘

여러 요청을 병렬로 전송하는 HTTP/2 클라이언트는 GFE에서 행렬 막힘으로 인해 지연 시간이 증가할 수 있습니다. 가장 좋은 솔루션은 QUIC 프로토콜을 사용하도록 클라이언트를 업그레이드하는 것입니다.

커스텀 도메인을 위한 SSL 종료

GFE는 SSL 연결을 종료합니다. appspot.com 도메인이 아닌 커스텀 도메인을 사용하는 경우 SSL 종료를 위해 추가 홉이 필요합니다. 그 결과 일부 리전에서 실행되는 애플리케이션에 지연 시간이 추가될 수 있습니다.

App Engine 서빙 인프라

서비스 전체 이슈

Google은 https://status.cloud.google.com/에 심각한 서비스 전체 이슈에 대한 세부정보를 게시합니다. Google에서는 서비스 전체 이슈가 모든 인스턴스에 한 번에 영향을 주지 않도록 점진적 출시가 사용됩니다.

자동 확장

너무 빠른 트래픽 수직 확장

App Engine 자동 확장이 트래픽 증가만큼 빠르게 인스턴스를 확장하지 못해서 일시적으로 과부하가 발생할 수 있습니다. 일반적으로 이 문제는 트래픽이 최종 사용자에 의해 유기적으로 생성되지 않고 대신 컴퓨터 프로그램에 의해 생성되는 경우에 발생합니다. 이를 해결하는 가장 좋은 방법은 트래픽을 생성하는 시스템을 제한하는 것입니다.

트래픽 급증

트래픽 급증은 지연 시간에 영향을 주지 않고 자동 확장된 애플리케이션을 가능한 것보다 더 빠르게 수직 확장해야 하는 경우에 지연 시간 증가를 일으킬 수 있습니다. 최종 사용자 트래픽은 일반적으로 잦은 트래픽 급증을 일으키지 않습니다. 이 오류가 표시되면 트래픽 급증의 원인이 무엇인지 조사해야 합니다. 일괄 시스템이 일정 간격으로 실행되는 경우 트래픽을 원활하게 만들거나 다른 확장 설정을 사용할 수 있습니다.

자동 확장 처리 설정

자동 확장 처리는 애플리케이션의 확장 특성을 기준으로 구성할 수 있습니다. 이러한 설정 매개변수는 특정 시나리오 중에 최적이 아닐 수 있습니다.

App Engine 가변형 환경 애플리케이션은 CPU 사용률에 따라 확장됩니다. 그러나 CPU 기반 확장이 발생하지 않기 때문에 이슈 중 애플리케이션이 I/O에 바인딩되어 인스턴스가 많은 수의 요청으로 과부하될 수 있습니다.

App Engine 표준 환경 확장 설정은 너무 공격적으로 설정된 경우 지연 시간을 일으킬 수 있습니다. 로그에 상태 코드 500Request was aborted after waiting too long to attempt to service your request 메시지가 포함된 서버 응답이 표시되면 이는 요청이 유휴 인스턴스를 기다리며 대기 중인 큐에서 시간 초과되었음을 의미합니다.

앱이 최종 사용자 트래픽을 처리하는 경우 App Engine 표준 환경 수동 확장을 사용하지 마세요. 태스크 큐와 같은 워크로드에는 수동 확장이 더 좋습니다. 인스턴스를 충분히 프로비저닝한 경우에도 수동 확장을 사용할 때 대기 시간이 증가하는 것을 확인할 수 있습니다.

대기 시간에 민감한 애플리케이션에는 App Engine 표준 환경 기본 확장을 사용하지 마세요. 이 확장 유형은 지연 시간 대신 비용을 최소화하도록 설계되었습니다.

App Engine 표준 환경의 기본 확장 설정은 대부분의 애플리케이션에서 최적의 지연 시간을 제공합니다. 여전히 요청의 대기 시간이 높게 표시되면 최소 인스턴스 수를 지정할 수 있습니다. 유휴 인스턴스를 최소화해서 비용을 줄이도록 확장 설정을 조정할 경우에는 부하가 갑자기 증가할 때 지연 시간이 급증할 위험이 있습니다.

기본 확장 설정을 사용하여 성능을 벤치마크한 후 이러한 설정을 변경할 때마다 새로 벤치마크를 실행하는 것이 좋습니다.

배포

배포 후 잠시 동안 지연 시간이 증가하면 트래픽 마이그레이션 이전에 충분히 수직 확장이 수행되지 않았음을 나타냅니다. 신규 인스턴스에서 로컬 캐시가 워밍업되지 않아서 이전 인스턴스보다 처리가 더 느려질 수 있습니다.

지연 시간 급증을 방지하려면 기존 앱 버전과 동일한 버전 이름을 사용하여 App Engine 앱을 배포하지 마세요. 기존 버전 이름을 재사용할 경우에는 트래픽을 새 버전으로 느리게 마이그레이션할 수 없습니다. 짧은 시간 내에 모든 인스턴스가 재시작되기 때문에 요청이 더 느릴 수 있습니다. 또한 이전 버전으로 되돌리려면 다시 배포해야 합니다.

애플리케이션 인스턴스

애플리케이션 코드

애플리케이션 코드의 문제는 간헐적이거나 쉽게 재현되지 않을 때 특히 디버깅하기 어려울 수 있습니다. 문제 진단을 돕기 위해서는 로깅, 모니터링, 추적를 사용하여 애플리케이션을 계측하는 것이 좋습니다. Cloud Profiler를 사용하여 문제 진단을 시도할 수 있습니다. 각 요청에 대해 추가 타이밍 정보를 업로드하려면 Cloud Trace를 사용해서 이 요청 로드 지연 시간 진단 예시를 참조하세요.

또한 App Engine 내에서는 실행이 불가능할 수 있는 언어 특정 디버깅 도구를 실행할 수 있게 해주는 로컬 개발 환경에서 문제 재현을 시도할 수 있습니다.

App Engine 가변형 환경에서 실행할 경우에는 인스턴스에 SSH로 연결하여 스레드 덤프를 가져와서 애플리케이션의 현재 상태를 확인할 수 있습니다. 부하 테스트에서 또는 앱을 로컬로 실행하여 문제 재현을 시도할 수 있습니다. 인스턴스 크기를 늘려서 이것으로 문제가 해결되는지 확인할 수 있습니다. 예를 들어 RAM을 늘리면 가비지 컬렉션으로 인해 지연이 발생하는 애플리케이션의 문제가 해결될 수 있습니다.

앱이 실패하는 방식, 발생하는 병목 현상을 잘 이해하기 위해서는 실패할 때까지 애플리케이션 부하를 테스트할 수 있습니다. 최대 인스턴스 수를 설정한 후 애플리케이션이 실패할 때까지 점진적으로 부하를 늘립니다.

지연 시간 문제가 새 버전의 애플리케이션 코드 배포와 상관된 경우 롤백을 통해 새 버전으로 인해 이슈가 발생했는지 확인할 수 있습니다. 지속적으로 배포하는 경우 배포가 충분히 빈번할 수 있으므로 배포가 시작된 시간을 기준으로 배포가 이슈의 원인이 되었는지 여부를 판단하기 어려울 수 있습니다.

애플리케이션은 Datastore 내부 또는 다른 위치에 구성 설정을 저장할 수 있습니다. 구성 변경사항에 대한 타임라인을 만들 수 있으면 이러한 변경사항이 지연 시간 증가의 시작 시간과 일치하는지 확인하는 데 도움이 됩니다.

워크로드 변경

워크로드 변경은 지연 시간 증가를 일으킬 수 있습니다. 변경된 워크로드를 나타낼 수 있는 일부 모니터링 측정항목에는 API 사용량 또는 지연 시간은 물론 QPS가 포함됩니다. 또한 요청 및 응답 크기의 변경사항을 확인할 수 있습니다.

상태 점검 실패

App Engine 가변형 환경 부하 분산기는 상태 점검이 실패한 인스턴스로의 요청 라우팅을 중지합니다. 그 결과 다른 인스턴스의 부하가 증가하고 연쇄적으로 실패가 발생할 수 있습니다. App Engine 가변형 환경 Nginx 로그에는 상태 점검이 실패한 인스턴스가 표시됩니다. 로그 및 모니터링을 분석하여 인스턴스가 비정상적으로 된 이유를 확인하거나 일시적인 실패에 덜 민감하도록 상태 점검을 구성합니다. 부하 분산기가 비정상 인스턴스에 대해 트래픽 라우팅을 중지할 때는 잠시 지연이 발생합니다. 이러한 지연으로 인해 부하 분산기가 요청을 재시도할 수 없으면 오류가 급증될 수 있습니다.

App Engine 표준 환경에는 상태 점검이 사용되지 않습니다.

메모리 압력

모니터링 결과에 메모리 사용량이 톱니 패턴으로 표시되거나 배포와 상관되는 메모리 사용량 저하가 표시되면 메모리 누수로 인한 성능 문제일 수 있습니다. 메모리 누수는 잦은 가비지 컬렉션을 일으켜서 높은 지연 시간으로 이어질 수 있습니다. 코드에서 문제를 쉽게 추적할 수 없으면 메모리가 더 많은 더 큰 인스턴스를 프로비저닝하여 문제를 해결할 수 있습니다.

리소스 누수

애플리케이션 인스턴스에 인스턴스 기간과 상관되는 지연 시간 증가가 표시되면 성능 문제를 일으키는 리소스 누수가 있기 때문일 수 있습니다. 또한 이 유형의 문제에서는 배포 바로 직후에 지연 시간 저하가 표시됩니다. 예를 들어 높은 CPU 사용량으로 인해 시간이 지날수록 더 느려지는 데이터 구조는 CPU에 바인딩된 워크로드가 더 느려지게 만들 수 있습니다.

코드 최적화

지연 시간을 줄이기 위해 App Engine에서 코드를 최적화하는 방법:

  • 오프라인 작업: 사용자 요청으로 인해 메일 전송과 같은 작업이 완료될 때까지 기다리는 대기 작업이 차단되지 않도록 Cloud Tasks를 사용합니다.

  • 비동기 API 호출: API 호출이 완료될 때까지 기다리는 코드가 차단되지 않도록 합니다. NDB와 같은 라이브러리에서 이에 대한 내장 지원이 제공됩니다.

  • 일괄 API 호출: 일반적으로 개별 호출을 전송하는 것보다는 일괄적인 API 호출이 더 빠릅니다.

  • 데이터 모델 비정규화: 데이터 모델을 비정규화하여 데이터 지속성 레이어에 수행되는 호출의 지연 시간을 줄입니다.

종속 항목

지연 시간 급증이 종속 항목 실패와 상관되었는지 검색할 수 있도록 애플리케이션의 종속 항목을 모니터링할 수 있습니다.

종속 항목의 지연 시간 증가는 트래픽 증가는 물론 워크로드의 변경사항으로 인해 발생할 수 있습니다.

비확장 종속 항목

App Engine 인스턴스 수의 수직 확장에 따라 종속 항목이 확장되지 않을 경우 트래픽이 증가할 때 종속 항목이 과부하될 수 있습니다. 확장되지 않을 수 있는 종속 항목의 예시에는 SQL 데이터베이스가 있습니다. 애플리케이션 인스턴스 수가 많을수록 데이터베이스 연결 수가 늘어나서 데이터베이스가 시작되지 않아 연쇄적 실패가 발생할 수 있습니다.

이를 복구하는 한 가지 방법은 다음과 같습니다.

  1. 데이터베이스에 연결되지 않는 새로운 기본 버전을 배포합니다.
  2. 이전 기본 버전을 종료합니다.
  3. 데이터베이스에 연결되는 새로운 비기본 버전을 배포합니다.
  4. 트래픽을 새 버전으로 느리게 마이그레이션합니다.

잠재적인 예방 조치는 적응형 제한을 사용하여 종속 항목에 대한 요청을 줄이도록 애플리케이션을 설계하는 것입니다.

캐싱 레이어 실패

요청 속도를 높이는 한 가지 좋은 방법은 여러 캐싱 레이어를 사용하는 것입니다.

  • 에지 캐싱
  • Memcache
  • 인스턴스 내 메모리

갑작스러운 지연 시간 증가는 캐싱 레이어 중 하나의 실패로 인해 발생할 수 있습니다. 예를 들어 Memcache 플러시는 더 많은 요청이 더 느린 Datastore로 이동하도록 만들 수 있습니다.