1. 개요
클라이언트 앱 및 프런트엔드 웹 개발자는 일반적으로 코드 성능을 개선하기 위해 Android 스튜디오 CPU 프로파일러 또는 Chrome에 포함된 프로파일링 도구와 같은 도구를 사용하지만, 백엔드 서비스 담당자는 이에 상응하는 기술을 접근하기 어렵거나 잘 채택하지 못했습니다. Cloud Profiler는 코드가 Google Cloud Platform 또는 다른 곳에서 실행 중이든 서비스 개발자에게 동일한 기능을 제공합니다.
이 도구는 프로덕션 애플리케이션에서 CPU 사용량 및 메모리 할당 정보를 수집합니다. 또한 이렇게 수집된 정보로 애플리케이션의 소스 코드를 분석하여 가장 많은 리소스를 사용하고 있는 부분을 파악할 수 있도록 돕고 코드의 성능적 특성을 알려줍니다. 이 도구가 사용하는 수집 기술의 오버헤드가 낮기 때문에 프로덕션 환경에서 지속적으로 사용하기에 적합합니다.
이 Codelab에서는 Go 프로그램용으로 Cloud Profiler를 설정하는 방법을 알아보고 도구로 확인할 수 있는 애플리케이션 성능에 대한 유용한 정보를 알아봅니다.
학습할 내용
- Cloud Profiler로 프로파일링하도록 Go 프로그램을 구성하는 방법
- Cloud Profiler를 사용하여 성능 데이터를 수집, 확인, 분석하는 방법을 알아봅니다.
필요한 항목
이 튜토리얼을 어떻게 사용하실 계획인가요?
귀하의 Google Cloud Platform 사용 경험을 평가해 주세요.
<ph type="x-smartling-placeholder">2. 설정 및 요구사항
자습형 환경 설정
- Cloud 콘솔에 로그인하고 새 프로젝트를 만들거나 기존 프로젝트를 다시 사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.
모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억하세요(위의 이름은 이미 사용되었으므로 사용할 수 없습니다). 이 ID는 나중에 이 Codelab에서 PROJECT_ID
라고 부릅니다.
- 그런 후 Google Cloud 리소스를 사용할 수 있도록 Cloud Console에서 결제를 사용 설정해야 합니다.
이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 가이드를 마친 후 비용이 결제되지 않도록 리소스 종료 방법을 알려주는 '삭제' 섹션의 안내를 따르세요. Google Cloud 신규 사용자에게는 미화$300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.
Google Cloud Shell
Google Cloud는 노트북에서 원격으로 작동할 수 있지만, 이 Codelab에서는 보다 간단하게 설정할 수 있도록 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.
Cloud Shell 활성화
- Cloud Console에서 Cloud Shell 활성화를 클릭합니다.
이전에 Cloud Shell을 시작한 적이 없는 경우 기능을 설명하는 중간 화면 (스크롤해야 볼 수 있는 부분)이 표시됩니다. 이 경우 계속을 클릭합니다 (다시 표시되지 않음). 이 일회성 화면은 다음과 같습니다.
Cloud Shell을 프로비저닝하고 연결하는 데 몇 분 정도만 걸립니다.
가상 머신에는 필요한 개발 도구가 모두 들어 있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 Codelab에서 대부분의 작업은 브라우저나 Chromebook만 사용하여 수행할 수 있습니다.
Cloud Shell에 연결되면 인증이 완료되었고 프로젝트가 해당 프로젝트 ID로 이미 설정된 것을 볼 수 있습니다.
- Cloud Shell에서 다음 명령어를 실행하여 인증되었는지 확인합니다.
gcloud auth list
명령어 결과
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Cloud Shell에서 다음 명령어를 실행하여 gcloud 명령어가 프로젝트를 알고 있는지 확인합니다.
gcloud config list project
명령어 결과
[core] project = <PROJECT_ID>
또는 다음 명령어로 설정할 수 있습니다.
gcloud config set project <PROJECT_ID>
명령어 결과
Updated property [core/project].
3. Cloud Profiler로 이동
Cloud 콘솔에서 'Profiler'를 클릭하여 Profiler UI로 이동합니다. 를 클릭합니다.
또는 Cloud 콘솔 검색창을 사용하여 Profiler UI로 이동할 수 있습니다. 'Cloud Profiler'를 입력하기만 하면 됩니다. 찾은 항목을 선택합니다 어느 쪽이든 Profiler UI에 'No data to display'(표시할 데이터 없음)가 표시됩니다. 메시지가 표시됩니다. 새 프로젝트이므로 아직 수집된 프로파일링 데이터가 없습니다.
이제 프로필을 만들 차례입니다.
4. 벤치마크 프로파일링
GitHub에서 제공되는 간단한 합성 Go 애플리케이션을 사용합니다. 아직 열려 있는 Cloud Shell 터미널(Profiler UI에 'No data to display(표시할 데이터 없음)' 메시지가 계속 표시됨)에서 다음 명령어를 실행합니다.
$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...
그런 다음 애플리케이션 디렉터리로 전환합니다.
$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp
디렉터리에는 'main.go'가 포함되어 있습니다. 파일 - 프로파일링 에이전트가 사용 설정된 합성 앱입니다.
main.go
...
import (
...
"cloud.google.com/go/profiler"
)
...
func main() {
err := profiler.Start(profiler.Config{
Service: "hotapp-service",
DebugLogging: true,
MutexProfiling: true,
})
if err != nil {
log.Fatalf("failed to start the profiler: %v", err)
}
...
}
프로파일링 에이전트는 기본적으로 CPU, 힙, 스레드 프로필을 수집합니다. 이 코드는 뮤텍스('경합'이라고도 함) 프로필의 수집을 활성화합니다.
이제 프로그램을 실행합니다.
$ go run main.go
프로그램이 실행되면 프로파일링 에이전트는 구성된 5가지 유형의 프로필을 주기적으로 수집합니다. 시간 경과에 따라 무작위로 수집되므로 (유형마다 분당 프로필 1개의 평균 속도가 적용됨) 각 유형을 수집하는 데 최대 3분이 걸릴 수 있습니다. 프로필을 만들면 프로그램에서 알려줍니다. 메시지는 위 구성에서 DebugLogging
플래그로 사용 설정됩니다. 그렇지 않으면 에이전트가 자동으로 실행됩니다.
$ go run main.go 2018/03/28 15:10:24 profiler has started 2018/03/28 15:10:57 successfully created profile THREADS 2018/03/28 15:10:57 start uploading profile 2018/03/28 15:11:19 successfully created profile CONTENTION 2018/03/28 15:11:30 start uploading profile 2018/03/28 15:11:40 successfully created profile CPU 2018/03/28 15:11:51 start uploading profile 2018/03/28 15:11:53 successfully created profile CONTENTION 2018/03/28 15:12:03 start uploading profile 2018/03/28 15:12:04 successfully created profile HEAP 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:04 successfully created profile THREADS 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:25 successfully created profile HEAP 2018/03/28 15:12:25 start uploading profile 2018/03/28 15:12:37 successfully created profile CPU ...
첫 번째 프로필이 수집된 직후 UI가 자동으로 업데이트됩니다. 이후에는 자동 업데이트되지 않으므로 새 데이터를 보려면 Profiler UI를 수동으로 새로고침해야 합니다. 이렇게 하려면 시간 간격 선택기에서 Now 버튼을 두 번 클릭합니다.
UI가 새로고침되면 다음과 같이 표시됩니다.
프로필 유형 선택기에는 사용 가능한 5가지 프로필 유형이 표시됩니다.
이제 각 프로필 유형과 중요한 UI 기능을 검토한 후 몇 가지 실험을 수행해 보겠습니다. 이 단계에서는 더 이상 Cloud Shell 터미널이 필요하지 않으므로 Ctrl+C를 누르고 'exit'를 입력하여 종료할 수 있습니다.
5. Profiler 데이터 분석
지금까지 데이터를 수집했으니 이제 좀 더 자세히 살펴보겠습니다. 프로덕션 환경에서 다양한 종류의 성능 문제에 대한 전형적인 동작을 시뮬레이션하는 합성 앱 (소스는 GitHub에서 제공)을 사용합니다.
CPU 집약적인 코드
CPU 프로필 유형을 선택합니다. UI에서 이 함수를 로드하면 Flame 그래프에 load
함수의 리프 블록 4개가 표시됩니다. 이 블록은 모든 CPU 소비를 합산합니다.
이 함수는 특히 타이트 루프를 실행하여 많은 CPU 사이클을 소비하도록 작성되어 있습니다.
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
이 함수는 4개의 호출 경로(busyloop
→ {foo1
, foo2
} → {bar
, baz
} → load
)를 통해 busyloop
()에서 간접적으로 호출됩니다. 함수 상자의 너비는 특정 호출 경로의 상대적 비용을 나타냅니다. 이 경우 네 개의 경로 모두 비용은 거의 동일합니다. 실제 프로그램에서는 실적 면에서 가장 중요한 통화 경로를 최적화하는 데 초점을 맞추고자 합니다. 큰 상자가 포함된 더 비싼 경로를 시각적으로 강조하는 Flame 그래프는 이러한 경로를 쉽게 식별할 수 있도록 해줍니다.
프로필 데이터 필터를 사용하여 데이터 표시를 더욱 세부적으로 조정할 수 있습니다. 예를 들어 '스택 표시'를 추가해 보세요. 'baz'를 지정하는 필터 를 필터 문자열로 설정합니다. 아래 스크린샷과 같이 표시됩니다. load()
의 호출 경로 4개 중 2개만 표시됩니다. 이 두 경로는 'baz' 문자열이 있는 함수를 거치는 유일한 경로입니다. 볼 수 있습니다 이러한 필터링은 더 큰 프로그램의 일부분에만 집중하려는 경우에 유용합니다 (예: 프로그램의 일부만 소유하고 있기 때문).
메모리 집약적인 코드
이제 '힙'으로 전환합니다. 프로필 유형입니다. 이전 실험에서 만든 필터는 삭제해야 합니다. 이제 alloc
에서 호출된 allocImpl
가 앱에서 메모리의 기본 소비자로 표시되는 Flame 그래프가 표시됩니다.
Flame 그래프 위의 요약 표에는 앱에서 사용된 총 메모리 양이 평균 약 57.4MiB임을 알 수 있으며 대부분은 allocImpl
함수로 할당됩니다. 이 함수를 구현하면 놀라운 일이 아닙니다.
main.go
func allocImpl() {
// Allocate 64 MiB in 64 KiB chunks
for i := 0; i < 64*16; i++ {
mem = append(mem, make([]byte, 64*1024))
}
}
이 함수는 한 번 실행되어 64MiB를 더 작은 청크로 할당한 다음 해당 청크에 대한 포인터를 전역 변수에 저장하여 가비지 컬렉션으로부터 보호합니다. 프로파일러에서 사용하는 것으로 표시되는 메모리 양은 64MiB와 약간 다릅니다. Go 힙 프로파일러는 통계 도구이므로 오버헤드가 낮지만 바이트 단위의 측정값은 아닙니다. 이와 같이 약 10% 의 차이를 보게 되더라도 놀라지 마세요.
IO 집약적인 코드
'스레드'를 선택하는 경우 프로필 유형 선택기에서 디스플레이가 wait
및 waitImpl
함수로 대부분의 너비를 차지하는 플레임 그래프로 전환됩니다.
위의 Flame 그래프 요약에서 wait
함수의 호출 스택을 늘리는 goroutine이 100개 있음을 확인할 수 있습니다. 이러한 대기를 시작하는 코드가 다음과 같다는 점을 고려하면 이는 정확합니다.
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}
이 프로필 유형은 프로그램이 예상치 못한 대기 시간 (예: I/O)을 소비하는지 파악하는 데 유용합니다. 이러한 호출 스택은 CPU 시간의 상당 부분을 소비하지 않기 때문에 일반적으로 CPU 프로파일러에 의해 샘플링되지 않습니다. '비슷한 사진 숨기기'를 사용하면 스레드 프로필이 포함된 필터. 예를 들어 gopark,
호출로 끝나는 모든 스택을 숨기는 용도로 사용합니다. 이러한 스택은 유휴 goroutine이고 I/O에서 기다리는 것보다 흥미가 떨어지는 경우가 많기 때문입니다.
스레드 프로필 유형은 또한 스레드가 프로그램의 다른 부분에서 소유한 뮤텍스를 오랫동안 기다리는 프로그램 지점을 식별하는 데 도움이 될 수 있지만, 여기에 대해서는 다음 프로필 유형이 더 유용합니다.
경합 집약적인 코드
경합 프로필 유형은 가장 '원치 않는' 항목을 식별합니다. 프로그램에 잠겨 있는 것을 볼 수 있습니다 이 프로필 유형은 Go 프로그램에서 사용할 수 있지만 'MutexProfiling: true
'을(를) 지정하여 명시적으로 사용 설정해야 합니다. 확인할 수 있습니다 수집은 goroutine A에 의해 잠금 해제될 때 특정 잠금이 잠금 해제될 때까지 기다리는 다른 goroutine B가 있는 횟수를 'Contentions' 측정항목 아래에 기록하는 방식으로 작동합니다. 또한 차단된 goroutine이 잠금을 기다린 시간도 '지연' 측정항목에 기록합니다. 이 예시에는 단일 경합 스택이 있고 잠금의 총 대기 시간은 10.5초였습니다.
이 프로필을 생성하는 코드는 뮤텍스를 두고 싸우는 4개의 goroutine으로 구성됩니다.
main.go
func contention(d time.Duration) {
contentionImpl(d)
}
func contentionImpl(d time.Duration) {
for {
mu.Lock()
time.Sleep(d)
mu.Unlock()
}
}
...
func main() {
...
for i := 0; i < 4; i++ {
go contention(time.Duration(i) * 50 * time.Millisecond)
}
}
6. 요약
이 실습에서는 Cloud Profiler와 함께 사용할 Go 프로그램을 구성하는 방법을 배웠습니다. 또한 이 도구를 사용하여 실적 데이터를 수집, 확인, 분석하는 방법도 배웠습니다. 이제 새로 익힌 기술을 Google Cloud Platform에서 실행하는 실제 서비스에 적용할 수 있습니다.
7. 축하합니다.
Cloud Profiler를 구성하고 사용하는 방법을 알아봤습니다.
자세히 알아보기
- Cloud Profiler: https://cloud.google.com/profiler/
- Cloud Profiler에서 사용하는 Go 런타임/pprof 패키지: https://golang.org/pkg/runtime/pprof/
라이선스
이 작업물은 Creative Commons Attribution 2.0 일반 라이선스에 따라 사용이 허가되었습니다.