가이드: Go 앱 최적화

이 가이드에서는 프로필 데이터를 수집하도록 구성된 의도적으로 비효율적인 Go 애플리케이션을 배포합니다. Profiler 인터페이스를 사용하여 프로필 데이터를 보고 잠재적 최적화를 파악합니다. 그런 다음 애플리케이션을 수정 및 배포하고 수정 효과를 평가합니다.

시작하기 전에

  1. Google 계정으로 로그인합니다.

    아직 계정이 없으면 새 계정을 등록하세요.

  2. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기 페이지로 이동

  3. 프로젝트에 Cloud Profiler API를 사용 설정하려면 Google Cloud Console 탐색창에서 Profiler를 클릭하거나 다음 버튼을 사용합니다.

    Profiler로 이동

  4. Cloud Shell을 열려면 Google Cloud Console 툴바에서 Cloud Shell 활성화를 클릭합니다.

    Cloud Shell 활성화

    잠시 후 Google Cloud Console에서 Cloud Shell 세션이 열립니다.

    Cloud Shell 세션

샘플 애플리케이션

기본 목표는 서버가 처리할 수 있는 초당 쿼리 수를 최대화하는 것입니다. 부수적인 목표는 불필요한 메모리 할당을 제거하여 메모리 사용량을 줄이는 것입니다.

서버는 gRPC 프레임워크를 사용하여 단어 또는 구문을 수신한 후 셰익스피어 작품에서 해당 단어 또는 구문이 나타나는 횟수를 반환합니다.

서버가 처리할 수 있는 초당 평균 쿼리 수는 서버 부하 테스트를 통해 결정됩니다. 테스트를 실행할 때마다 클라이언트 시뮬레이터가 호출되어 20개의 순차적 쿼리를 실행하도록 지시합니다. 테스트가 한 번 완료되면 클라이언트 시뮬레이터에서 전송한 쿼리 수, 경과 시간, 초당 평균 쿼리 수가 표시됩니다.

서버 코드는 의도적으로 비효율적입니다.

샘플 애플리케이션 실행

샘플 애플리케이션을 다운로드하고 실행합니다.

  1. Cloud Shell에서 다음 명령어를 실행합니다.

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
    cd golang-samples/profiler/shakesapp
    
  2. 버전을 1로 설정하고 테스트 횟수를 15회로 설정하여 애플리케이션을 실행합니다.

    go run . -version 1 -num_rounds 15
    

    1~2분 후에 프로필 데이터가 표시됩니다. 다음 예시와 유사한 프로필 데이터가 표시됩니다.

    CPU 시간 사용량의 초기 Flame 그래프

    스크린샷에서 프로필 유형CPU time으로 설정되어 있음을 확인합니다. 이는 CPU 사용 데이터가 Flame 그래프에 표시됨을 나타냅니다.

    Cloud Shell에 다음 샘플 출력이 표시됩니다.

    $ go run . -version 1 -num_rounds 15
    2020/08/27 17:27:34 Simulating client requests, round 1
    2020/08/27 17:27:34 Stackdriver Profiler Go Agent version: 20200618
    2020/08/27 17:27:34 profiler has started
    2020/08/27 17:27:34 creating a new profile via profiler service
    2020/08/27 17:27:51 Simulated 20 requests in 17.3s, rate of 1.156069 reqs / sec
    2020/08/27 17:27:51 Simulating client requests, round 2
    2020/08/27 17:28:10 Simulated 20 requests in 19.02s, rate of 1.051525 reqs / sec
    2020/08/27 17:28:10 Simulating client requests, round 3
    2020/08/27 17:28:29 Simulated 20 requests in 18.71s, rate of 1.068947 reqs / sec
    ...
    2020/08/27 17:44:32 Simulating client requests, round 14
    2020/08/27 17:46:04 Simulated 20 requests in 1m32.23s, rate of 0.216849 reqs / sec
    2020/08/27 17:46:04 Simulating client requests, round 15
    2020/08/27 17:47:52 Simulated 20 requests in 1m48.03s, rate of 0.185134 reqs / sec
    

    Cloud Shell 출력에는 각 반복의 경과 시간과 평균 요청 비율이 표시됩니다. 애플리케이션이 시작될 때 '17.3초에 요청 20개 시뮬레이션, 초당 요청 1.156069개'라는 항목은 서버가 초당 약 1개의 요청을 실행함을 나타냅니다. 마지막 회에서 '1분 48.03초에 요청 20개 시뮬레이션, 초당 요청 0.185134개'라는 항목은 서버가 5초마다 약 1개의 요청을 실행함을 나타냅니다.

CPU 시간 프로필을 사용하여 초당 쿼리 수 최대화

초당 쿼리 수를 최대화하는 한 가지 방법은 CPU가 많이 사용되는 메서드를 파악하고 구현을 최적화하는 것입니다. 이 섹션에서는 CPU 시간 프로필을 사용하여 서버에서 CPU를 많이 사용하는 메서드를 식별합니다.

CPU 시간 사용량 확인

Flame 그래프의 루트 프레임은 애플리케이션이 10초의 수집 간격 동안 사용한 총 CPU 시간을 나열합니다.

Flame 그래프의 루트 프레임이 확장된 뷰

이 예시에서 서비스는 2.37 s를 사용했습니다. 시스템이 단일 코어에서 실행되는 경우 CPU 시간 2.37초는 코어의 23.7% 사용에 해당합니다. 자세한 내용은 사용 가능한 프로파일링 유형을 참조하세요.

애플리케이션 수정

변경사항 평가

변경사항을 평가하려면 다음을 수행합니다.

  1. 애플리케이션 버전을 2로 설정하여 애플리케이션을 실행합니다.

    go run . -version 2 -num_rounds 40
    

    이후 섹션에서는 최적화를 통해 한 번 실행하는 데 걸리는 시간이 수정되지 않은 애플리케이션의 실행 시간보다 훨씬 짧게 나타납니다. 애플리케이션 실행 시간을 프로필을 수집하고 업로드할 수 있을 만큼 길게 늘리기 위해 횟수가 증가합니다.

  2. 애플리케이션이 완료될 때까지 기다린 후 이 애플리케이션 버전의 프로필 데이터를 봅니다.

    • 지금을 클릭하여 최신 프로필 데이터를 로드합니다. 자세한 내용은 시간 범위를 참조하세요.
    • 버전 메뉴에서 2를 선택합니다.

예를 들어 Flame 그래프는 다음과 같습니다.

버전 2의 CPU 시간 사용량을 보여주는 Flame 그래프

이 그림에서 루트 프레임은 7.8 s 값을 표시합니다. 문자열 일치 함수를 변경한 결과, 애플리케이션에서 사용하는 CPU 시간이 2.37초에서 7.8초로 증가하거나 애플리케이션의 CPU 코어 사용량이 23.7%에서 78%로 변경되었습니다.

프레임 너비는 CPU 시간 사용량에 비례합니다. 이 예시에서 GetMatchCount의 프레임 너비는 함수가 애플리케이션에서 사용한 모든 CPU 시간의 약 49%를 사용한다는 것을 나타냅니다. 원래 Flame 그래프에서 이 동일한 프레임은 그래프 너비의 약 72%를 차지했습니다. 정확한 CPU 시간 사용량을 보려면 프레임 도움말을 사용하거나 포커스 함수 목록을 사용하면 됩니다.

버전 2의 CPU 시간 사용량을 보여주는 포커스 함수 목록

Cloud Shell의 출력은 수정된 버전이 초당 약 5.8개 요청을 완료함을 나타냅니다.

$ go run . -version 2 -num_rounds 40
2020/08/27 18:21:40 Simulating client requests, round 1
2020/08/27 18:21:40 Stackdriver Profiler Go Agent version: 20200618
2020/08/27 18:21:40 profiler has started
2020/08/27 18:21:40 creating a new profile via profiler service
2020/08/27 18:21:44 Simulated 20 requests in 3.67s, rate of 5.449591 reqs / sec
2020/08/27 18:21:44 Simulating client requests, round 2
2020/08/27 18:21:47 Simulated 20 requests in 3.72s, rate of 5.376344 reqs / sec
2020/08/27 18:21:47 Simulating client requests, round 3
2020/08/27 18:21:51 Simulated 20 requests in 3.58s, rate of 5.586592 reqs / sec
...
2020/08/27 18:23:51 Simulating client requests, round 39
2020/08/27 18:23:54 Simulated 20 requests in 3.46s, rate of 5.780347 reqs / sec
2020/08/27 18:23:54 Simulating client requests, round 40
2020/08/27 18:23:58 Simulated 20 requests in 3.4s, rate of 5.882353 reqs / sec

애플리케이션 이 작은 변경사항에는 두 가지 효과가 있었습니다.

  • 초당 요청 수가 초당 1개에서 5.8개로 증가했습니다.

  • CPU 사용률을 초당 요청 수로 나눈 요청당 CPU 시간은 23.7%에서 13.4%로 감소했습니다.

    CPU 시간 사용량이 2.37초(단일 CPU 코어 사용량 23.7%)로 증가해도 요청당 CPU 시간은 7.8초(CPU 코어의 78%)로 감소합니다.

할당된 힙 프로필을 사용하여 리소스 사용량 개선

이 섹션에서는 힙과 할당된 힙 프로필을 사용하여 애플리케이션에서 많은 할당량을 사용하는 메서드를 파악하는 방법을 설명합니다.

  • 힙 프로필은 프로필이 수집되는 순간 프로그램의 힙에 할당된 메모리 양을 보여줍니다.

  • 할당된 힙 프로필은 프로필이 수집된 간격에 프로그램의 힙에 할당된 총 메모리 양을 표시합니다. 이러한 값을 프로필 수집 간격인 10초로 나누어 할당 속도로 변환하면 됩니다.

힙 프로필 수집 사용 설정

  1. 애플리케이션 버전을 3로 설정된 애플리케이션을 실행하고 힙 및 할당된 힙 프로필 모음을 사용 설정합니다.

    go run . -version 3 -num_rounds 40 -heap -heap_alloc
    
  2. 애플리케이션이 완료될 때까지 기다린 후 이 애플리케이션 버전의 프로필 데이터를 봅니다.

    • 지금을 클릭하여 최신 프로필 데이터를 로드합니다.
    • 버전 메뉴에서 3을 선택합니다.
    • 프로파일러 유형 메뉴에서 할당된 힙을 선택합니다.

    예를 들어 Flame 그래프는 다음과 같습니다.

    버전 3에 할당된 힙 프로필의 Flame 그래프

힙 할당 속도 확인

루트 프레임은 프로필이 수집되는 10초 동안 할당된 총 힙의 양을 모든 프로필에 대한 평균으로 표시합니다. 이 예시에서 루트 프레임은 평균적으로 1.535GiB의 메모리가 할당되었음을 보여줍니다.

애플리케이션 수정

변경사항 평가

변경사항을 평가하려면 다음을 수행합니다.

  1. 애플리케이션 버전을 4로 설정하여 애플리케이션을 실행합니다.

    go run . -version 4 -num_rounds 60 -heap -heap_alloc
    
  2. 애플리케이션이 완료될 때까지 기다린 후 이 애플리케이션 버전의 프로필 데이터를 봅니다.

    • 지금을 클릭하여 최신 프로필 데이터를 로드합니다.
    • 버전 메뉴에서 4를 선택합니다.
    • 프로파일러 유형 메뉴에서 할당된 힙을 선택합니다.
  3. 힙 할당 속도에서 readFiles를 변경했을 때의 효과를 수량화하기 위해 버전 4에 할당된 힙 프로필을 3에 대해 수집된 힘 프로필과 비교합니다.

    버전 4와 3간 할당된 힙 프로필 비교

    루트 프레임의 도움말에서는 버전 4를 사용하면 프로필 수집 중에 할당된 메모리의 평균 양이 버전 3에 비해 1.301GiB 만큼 감소했음을 보여줍니다. readFiles.func1의 도움말은 1.045GiB 감소를 보여줍니다.

    할당된 힙 프로필 유형에 대한 readfiles도움말 비교

  4. 가비지 컬렉션에 미치는 영향을 수량화하려면 CPU 시간 프로필 비교를 구성합니다. 다음 스크린샷에서는 Go 가비지 수집기 runtime.gcBgMarkWorker.*의 스택을 보여주는 필터가 적용됩니다. 스크린샷은 가비지 컬렉션의 CPU 사용량이 16.8%에서 4.97%로 감소했음을 보여줍니다.

    v4 및 v3 백그라운드 가비지 컬렉션 프로세스의 CPU 시간 사용량 비교

  5. 애플리케이션에서 처리하는 초당 요청 수의 변경에 따른 영향이 있는지 확인하려면 Cloud Shell에서 출력을 확인합니다. 이 예시에서 버전 4는 초당 최대 15개의 요청을 완료하며, 이는 버전 3의 초당 요청 5.8개보다 훨씬 높습니다.

    $ go run . -version 4 -num_rounds 60 -heap -heap_alloc
    2020/08/27 21:51:42 Simulating client requests, round 1
    2020/08/27 21:51:42 Stackdriver Profiler Go Agent version: 20200618
    2020/08/27 21:51:42 profiler has started
    2020/08/27 21:51:42 creating a new profile via profiler service
    2020/08/27 21:51:44 Simulated 20 requests in 1.47s, rate of 13.605442 reqs / sec
    2020/08/27 21:51:44 Simulating client requests, round 2
    2020/08/27 21:51:45 Simulated 20 requests in 1.3s, rate of 15.384615 reqs / sec
    2020/08/27 21:51:45 Simulating client requests, round 3
    2020/08/27 21:51:46 Simulated 20 requests in 1.31s, rate of 15.267176 reqs / sec
    ...
    

    애플리케이션에서 제공하는 초당 쿼리 수가 늘어나는 것은 가비지 컬렉션 소요 시간이 줄어들기 때문일 수 있습니다.

  • 힙 프로필을 보면 readFiles 수정의 영향을 더 잘 이해할 수 있습니다. 버전 4와 버전 3의 힙 프로필을 비교해 보면 힙 사용량이 70.95MiB에서 18.47MiB로 감소했음을 알 수 있습니다.

    버전 4와 버전 3의 힙 사용량 비교

요약

이 빠른 시작에서는 CPU 시간 및 할당된 힙 프로필을 사용하여 애플리케이션의 잠재적 최적화를 파악했습니다. 초당 요청 수를 극대화하고 불필요한 할당을 제거하는 것이 목표였습니다.

  • CPU 시간 프로필을 사용하여 CPU를 많이 사용하는 함수를 식별했습니다. 간단한 변경사항을 적용한 후 서버의 요청 속도는 초당 약 1회에서 초당 5.8로 증가했습니다.

  • 할당된 힙 프로필을 사용하여 shakesapp/server.go 함수 readFiles가 할당 비율이 높은 것으로 확인되었습니다. readFiles를 최적화한 후 서버의 요청 속도는 초당 요청 15개로 증가했고, 10초 프로필 수집 중에 할당된 평균 메모리 양은 1.301GiB 만큼 감소했습니다.

다음 단계

  • 프로필이 수집되어 Google Cloud 프로젝트로 전송되는 방법에 대한 자세한 내용은 프로필 수집을 참조하세요.

  • DevOps 관련 리소스를 읽고 연구 프로그램을 살펴보세요.

Cloud Profiler 에이전트 실행 방법에 대한 자세한 내용은 다음을 참조하세요.