InfoGrab DocsInfoGrab Docs

프로파일링

요약

성능 문제를 보다 쉽게 추적할 수 있도록 GitLab은 다양한 프로파일링 도구를 제공합니다. 일부 도구는 기본적으로 사용 가능하며, 나머지는 명시적으로 활성화해야 합니다. `Gitlab::Profiler.profile` 메서드와 이에 대응하는 `bin/profile-url` 스크립트를 사용하면

# 프로파일링

성능 문제를 보다 쉽게 추적할 수 있도록 GitLab은 다양한 프로파일링 도구를 제공합니다.

일부 도구는 기본적으로 사용 가능하며, 나머지는 명시적으로 활성화해야 합니다.

## URL 프로파일링

`Gitlab::Profiler.profile` 메서드와 이에 대응하는 `bin/profile-url` 스크립트를 사용하면

특정 URL에 대한 GET 또는 POST 요청을 프로파일링할 수 있습니다.

익명 사용자(기본값) 또는 특정 사용자로 프로파일링할 수 있습니다.

프로파일러의 첫 번째 인수는 인스턴스 호스트명을 포함한 전체 URL이거나,

앞에 슬래시가 붙는 절대 경로입니다.

기본적으로 보고서 덤프는 임시 파일에 저장되며,

[Stackprof API](/19.1/development/profiling/#reading-a-gitlabprofiler-report)를 사용하여 상호작용할 수 있습니다.

스크립트를 사용할 때는 인수를 전달하지 않으면 명령줄 도움말을 확인할 수 있습니다.

대화형 콘솔 세션에서 메서드를 사용하면, 해당 콘솔 세션 내의 애플리케이션 코드 변경 사항이

프로파일러 출력에 반영됩니다.

예시:

```

Gitlab::Profiler.profile('/my-user')

# Returns the location of the temp file where the report dump is stored

class UsersController; def show; sleep 100; end; end

Gitlab::Profiler.profile('/my-user')

# Returns the location of the temp file where the report dump is stored

# where 100 seconds is spent in UsersController#show#

인가가 필요한 라우트의 경우 `Gitlab::Profiler`에 사용자를 제공해야 합니다.

다음과 같이 사용합니다:

```

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first)#

`Gitlab::Profiler.profile`에 `logger:` 키워드 인수를 전달하면

ActiveRecord와 ActionController 로그 출력을 해당 로거로 전송합니다.

추가 옵션은 메서드 소스에 설명되어 있습니다.

```

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, logger: Logger.new($stdout))#

샘플링 데이터의 출력 파일(`out`)을 구성하려면 `profiler_options` 해시를 전달합니다. 예시:

```

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, profiler_options: { out: 'tmp/profile.dump' })#

## GitLab::Profiler 보고서 읽기

샘플링 데이터에 대해 Stackprof를 실행하여 시간이 어디서 소요되었는지 요약을 확인할 수 있습니다. 예시:

```

stackprof tmp/profile.dump#

샘플링 데이터 예시:

```

Mode: wall(1000) Samples: 8745 (6.92% miss rate)

GC: 1399 (16.00%)#

TOTAL (pct) SAMPLES (pct) FRAME 1022 (11.7%) 1022 (11.7%) Sprockets::PathUtils#stat 957 (10.9%) 957 (10.9%) (marking) 493 (5.6%) 493 (5.6%) Sprockets::PathUtils#entries 576 (6.6%) 471 (5.4%) Mustermann::AST::Translator#decorator_for 439 (5.0%) 439 (5.0%) (sweeping) 630 (7.2%) 241 (2.8%) Sprockets::Cache::FileStore#get 208 (2.4%) 208 (2.4%) ActiveSupport::FileUpdateChecker#watched 206 (2.4%) 206 (2.4%) Digest::Instance#file 544 (6.2%) 176 (2.0%) Sprockets::Cache::FileStore#safe_open 176 (2.0%) 176 (2.0%) ActiveSupport::FileUpdateChecker#max_mtime 268 (3.1%) 147 (1.7%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache 140 (1.6%) 140 (1.6%) ActiveSupport::BacktraceCleaner#add_gem_filter 116 (1.3%) 116 (1.3%) Bootsnap::CompileCache::ISeq.storage_to_output 160 (1.8%) 113 (1.3%) Gem::Version#<=> 109 (1.2%) 109 (1.2%) block in
108 (1.2%) 108 (1.2%) Gem::Version.new 131 (1.5%) 105 (1.2%) Sprockets::EncodingUtils#unmarshaled_deflated 1166 (13.3%) 82 (0.9%) Mustermann::RegexpBased#initialize 82 (0.9%) 78 (0.9%) FileUtils.touch 72 (0.8%) 72 (0.8%) Sprockets::Manifest.compile_match_filter 71 (0.8%) 70 (0.8%) Grape::Router#compile! 91 (1.0%) 65 (0.7%) ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#query 93 (1.1%) 64 (0.7%) ActionDispatch::Journey::Path::Pattern::AnchoredRegexp#accept 59 (0.7%) 59 (0.7%) Mustermann::AST::Translator.dispatch_table 62 (0.7%) 59 (0.7%) Rails::BacktraceCleaner#initialize 2492 (28.5%) 49 (0.6%) Sprockets::PathUtils#stat_directory 242 (2.8%) 49 (0.6%) Gitlab::Instrumentation::RedisBase.add_call_details 47 (0.5%) 47 (0.5%) URI::RFC2396_Parser#escape 46 (0.5%) 46 (0.5%) ##__setobj__
44 (0.5%) 44 (0.5%) Sprockets::Base#normalize_logical_path#

플레임그래프를 생성할 수도 있습니다:

```

stackprof --d3-flamegraph tmp/profile.dump > flamegraph.html#

자세한 내용은 Stackprof 문서를 참조하세요.

## Speedscope 플레임그래프

성능 바에서 플레임그래프 샘플링 모드 버튼을 선택하거나,

요청에 `performance_bar=flamegraph` 파라미터를 추가하여

특정 URL에 대한 플레임그래프를 생성할 수 있습니다.

[

](/19.1/development/img/speedscope_v13_12.png)

뷰에 대한 자세한 정보는 Speedscope 문서를 참조하세요.

다양한 샘플링 모드에 대한 자세한 정보는 Stackprof 문서를 참조하세요.

이 기능은 성능 바에 접근할 수 있는 모든 사용자에게 활성화됩니다.

## Bullet

Bullet은 N+1 쿼리 문제를 추적하는 데 사용할 수 있는 Gem입니다.

쿼리 문제를 Rails 로그와 브라우저 콘솔에 기록합니다.

Bullet 섹션은 [성능 바](/19.1/administration/monitoring/performance/performance_bar/)에 표시됩니다.

[

](/19.1/development/img/bullet_v13_0.png)

Bullet은 기본적으로 개발 모드에서만 활성화됩니다. 그러나 Bullet 로깅은 노이즈가 많기 때문에 로깅은 비활성화되어 있습니다.

Bullet과 그 로깅을 구성하려면 다음과 같이 합니다:

  • 환경에서 Bullet을 수동으로 활성화 또는 비활성화하려면 `config/gitlab.yml`에 다음 줄을 추가하고
`enabled` 값을 필요에 따라 변경합니다:

```

bullet:

enabled: false#
  • Bullet 로깅을 활성화하려면 GitLab을 시작하기 전에 `ENABLE_BULLET` 환경 변수를
비어 있지 않은 값으로 설정합니다:

```

ENABLE_BULLET=true bundle exec rails s#

Bullet로 `N+1` 쿼리를 발견한 후속 조치로,

회귀를 방지하기 위해 [QueryRecorder 테스트](/19.1/development/database/query_recorder/) 작성을 고려해 보세요.

## 시스템 통계

프로파일링 중이나 이후에 메모리 소비량, CPU 소요 시간, 가비지 컬렉터 통계 등

Ruby 가상 머신 프로세스에 대한 상세 정보를 얻고 싶을 수 있습니다.

이러한 정보는 다양한 도구를 통해 개별적으로 쉽게 생성할 수 있지만,

편의를 위해 이 데이터를 JSON 페이로드로 내보내는 요약 엔드포인트가 추가되었습니다:

```

curl localhost:3000/-/metrics/system | jq#

출력 예시:

```

{

"version": "ruby 2.7.2p137 (2020-10-01 revision a8323b79eb) [x86_64-linux-gnu]", "gc_stat": { "count": 118, "heap_allocated_pages": 11503, "heap_sorted_length": 11503, "heap_allocatable_pages": 0, "heap_available_slots": 4688580, "heap_live_slots": 3451712, "heap_free_slots": 1236868, "heap_final_slots": 0, "heap_marked_slots": 3451450, "heap_eden_pages": 11503, "heap_tomb_pages": 0, "total_allocated_pages": 11503, "total_freed_pages": 0, "total_allocated_objects": 32679478, "total_freed_objects": 29227766, "malloc_increase_bytes": 84760, "malloc_increase_bytes_limit": 32883343, "minor_gc_count": 88, "major_gc_count": 30, "compact_count": 0, "remembered_wb_unprotected_objects": 114228, "remembered_wb_unprotected_objects_limit": 228456, "old_objects": 3185330, "old_objects_limit": 6370660, "oldmalloc_increase_bytes": 21838024, "oldmalloc_increase_bytes_limit": 119181499 }, "memory_rss": 1326501888, "memory_uss": 1048563712, "memory_pss": 1139554304, "time_cputime": 82.885264633, "time_realtime": 1610459445.5579069, "time_monotonic": 24001.23145713, "worker_id": "puma_0"
}

이 엔드포인트는 Rails 웹 워커에서만 사용 가능합니다. Sidekiq 워커는 이 방법으로 검사할 수 없습니다.

## 성능에 영향을 미치는 설정

### 애플리케이션 설정

  • `development` 환경은 기본적으로 핫 리로딩이 활성화된 상태로 작동합니다.
이로 인해 Rails가 매 요청마다 파일 변경 사항을 확인하고,

핫 리로드가 단일 스레드이기 때문에 잠재적인 경쟁 조건 잠금이 발생할 수 있습니다.

  • `development` 환경은 요청이 발생하면 코드를 지연 로드할 수 있으며,
이로 인해 첫 번째 요청은 항상 느립니다.

프로파일링/벤치마킹을 위해 이러한 기능을 비활성화하려면

GitLab을 시작하기 전에 `RAILS_PROFILE` 환경 변수를 `true`로 설정합니다.

예를 들어 GDK를 사용할 때:

  • GDK 루트 디렉터리에 `env.runit` 파일을 생성합니다.
  • `env.runit` 파일에 `export RAILS_PROFILE=true`를 추가합니다.
  • `gdk restart`로 GDK를 재시작합니다.
이 환경 변수는 개발 모드에서만 적용됩니다.

### GC 설정

Ruby의 가비지 컬렉터(GC)는 애플리케이션 성능에 직접 영향을 미치는 다양한 환경 변수를 통해 튜닝할 수 있습니다.

다음 테이블에는 이러한 변수와 기본값이 나열되어 있습니다.

| 환경 변수 | 기본값 | | RUBY_GC_HEAP_INIT_SLOTS | 10000 | | RUBY_GC_HEAP_FREE_SLOTS | 4096 | | RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO | 0.20 | | RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO | 0.40 | | RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO | 0.65 | | RUBY_GC_HEAP_GROWTH_FACTOR | 1.8 | | RUBY_GC_HEAP_GROWTH_MAX_SLOTS | 0 (비활성화) | | RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR | 2.0 | | RUBY_GC_MALLOC_LIMIT(_MIN) | (16 1024 1024 / 16MB /) | | RUBY_GC_MALLOC_LIMIT_MAX | (32 1024 1024 / 32MB /) | | RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR | 1.4 | | RUBY_GC_OLDMALLOC_LIMIT(_MIN) | (16 1024 1024 / 16MB /) | | RUBY_GC_OLDMALLOC_LIMIT_MAX | (128 1024 1024 / 128MB /) | | RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR | 1.2 |

(출처)

GitLab은 애플리케이션 성능 향상, 메모리 요구 사항 감소 또는 둘 다를 위해

이러한 설정을 변경하기로 결정할 수 있습니다.

유휴 상태의 GitLab 인스턴스에서 이러한 각 설정이 GC 성능, 메모리 사용량 및

애플리케이션 시작 시간에 어떤 영향을 미치는지는 `scripts/perf/gc/collect_gc_stats.rb` 스크립트를 실행하여 확인할 수 있습니다.

GC 통계 및 일반 타이밍 데이터를 CSV 형식으로 표준 출력에 출력합니다.

## 성능 이슈 조사 예시

Pipeline Authoring 팀은 파이프라인 생성 성능 이슈를 해결하기 위해

[stackprof 플레임그래프](/19.1/development/profiling/#speedscope-flamegraphs) 및 [`memory_profiler`](/19.1/development/performance/#using-memory-profiler)와 같은 기존 프로파일링 방법과

새로운 방법인 `ruby-prof`를 함께 사용했습니다.

### stackprof 플레임그래프 사용

[성능 바](/19.1/administration/monitoring/performance/performance_bar/)는 stackprof 보고서를 얻고

클릭 한 번으로 플레임그래프를 볼 수 있는 훌륭한 도구입니다;

[

](/19.1/development/img/performance_bar_flamegraph_link_v18_4.png)

그러나 GET 요청 이외에는 사용할 수 없습니다.

POST 요청에 대한 플레임그래프를 얻으려면 API 요청에 `performance_bar=flamegraph` 파라미터를 사용합니다.

이 경우 [머지 리퀘스트의 파이프라인 생성 엔드포인트](/19.1/api/merge_requests/#create-merge-request-pipeline)에 대한 플레임그래프를 확인하고자 합니다.

일반적으로 다음 명령을 사용하여 stackprof 보고서를 JSON 파일로 가져올 수 있지만,

`Gitlab::PerformanceBar.allowed_for_user?(request.env['warden']&.user)` 사용자 제어로 인해

웹 인터페이스를 통해 인증된 사용자만 허용됩니다.

```

# This will not work on production

curl --request POST \

--output flamegraph.json \ --header 'Content-Type: application/json' \ --header 'PRIVATE-TOKEN: :token' \
"https://gitlab.example.com/api/v4/projects/:id/merge_requests/:iid/pipelines?performance_bar=flamegraph"#

이를 우회하기 위해 요청을 `curl`로 복사하여 터미널에서 사용합니다.

[

](/19.1/development/img/performance_copy_as_curl_v17_7.png)

다음과 같은 `curl` 명령을 사용할 수 있습니다:

```

curl "https://gitlab.com/api/v4/projects/:id/merge_requests/:iid/pipelines" \

-H 'accept: application/json, text/plain, /' \ -H 'content-type: application/json' \ -H 'cookie: xyz' \ -H 'x-csrf-token: xyz' \
--data-raw '{"async":true}'#
  • 요청 본문에 `async` 파라미터가 있는 것을 주목하세요.
파이프라인 생성 엔드포인트의 실제 성능을 얻으려면 이를 제거해야 합니다.
  • 요청에 `performance_bar=flamegraph` 파라미터를 추가해야 합니다.
  • JSON 응답을 파일로 저장하기 위해 `--output flamegraph.json` 파라미터를 추가해야 합니다.
  • 마지막으로 JSON 응답만 허용해야 합니다.
```

curl "https://gitlab.com/api/v4/projects/:id/merge_requests/:iid/pipelines?performance_bar=flamegraph" \

-X POST \ -o flamegraph.json \ -H 'accept: application/json' \ -H 'content-type: application/json' \ -H 'cookie: xyz' \
-H 'x-csrf-token: xyz'#

그런 다음 `flamegraph.json` 파일을 `https://www.speedscope.app/` 웹사이트에서 사용하여 플레임그래프를 확인합니다.

[

](/19.1/development/img/performance_speedscope_example_v17_7.png)

예를 들어, 이 speedscope 플레임그래프를 조사할 때 `kubernetes_variables` 메서드가

많은 시간을 소비하는 것을 확인하고 이슈를 생성했습니다.

[

](/19.1/development/img/performance_speedscope_example_kubernetes_v17_7.png)

### ruby-prof 사용

시간이 가장 많이 소요되는 곳을 확인하는 또 다른 방법은 `ruby-prof`를 사용하는 것입니다.

Gemfile에 포함된 gem이 아니므로 먼저 Gemfile에 추가하고 `bundle install`을 실행해야 합니다.

문제를 조사하려면 복제된 리포지터리가 필요합니다.

이를 위해 프로덕션 인스턴스에서 개발 인스턴스로 리포지터리를 미러링할 수 있습니다.

그런 다음 `ruby-prof` 프로파일러를 실행하여 시간이 어디서 소요되는지 확인합니다.

```

# RAILS_PROFILE=true GITALY_DISABLE_REQUEST_LIMITS=true rails console

require 'ruby-prof'

ActiveRecord::Base.logger = nil

project = Project.find_by_full_path('root/gitlab-mirror')

user = project.first_owner

merge_request = project.merge_requests.find_by_iid(1)

profile = RubyProf::Profile.new

profile.exclude_common_methods! # see https://github.com/ruby-prof/ruby-prof/blob/1.7.0/lib/ruby-prof/exclude_common_methods.rb

profile.start

Gitlab::SafeRequestStore.ensure_request_store do

Ci::CreatePipelineService .new(project, user, ref: merge_request.source_branch) .execute(:merge_request_event, merge_request: merge_request) .payload

end; nil

result = profile.stop

callstack_printer = RubyProf::CallStackPrinter.new(result)

File.open('tmp/ruby-prof-callstack-report.html', 'w') do

callstack_printer.print(file)

end

::Ci::DestroyPipelineService.new(project, user).execute(Ci::Pipeline.last)#

[

](/19.1/development/img/performance_ruby-prof_example_v17_7.png)

여기서 `Ci::GenerateKubeconfigService`가 약 2,000번 호출되는 것을 확인할 수 있습니다.

이는 추가 조사가 필요하다는 좋은 지표입니다.

### memory_profiler 사용

[`memory_profiler`](/19.1/development/performance/#using-memory-profiler)는 메모리 사용량을 프로파일링하는 도구입니다.

높은 메모리 사용량은 성능 이슈로 이어질 수 있으므로 이 또한 중요합니다.

`stackprof`에서 했던 것처럼 `performance_bar` 파라미터와 함께 `curl`을 사용할 수도 있습니다.

```

curl "https://gitlab.com/api/v4/projects/:id/merge_requests/:iid/pipelines?performance_bar=memory" \

-X POST \ -o flamegraph.json \ -H 'accept: application/json' \ -H 'content-type: application/json' \ -H 'cookie: xyz' \
-H 'x-csrf-token: xyz'#

그러나 요청에 60초 타임아웃이 있기 때문에 프로덕션에서는 작동하지 않습니다.

따라서 메모리 프로파일을 얻으려면 개발 환경을 사용해야 합니다.

자세한 정보는 [메모리 프로파일러 문서](/19.1/development/performance/#using-memory-profiler)에서 확인할 수 있습니다.

```

# RAILS_PROFILE=true GITALY_DISABLE_REQUEST_LIMITS=true rails console

require 'memory_profiler'

ActiveRecord::Base.logger = nil

project = Project.find_by_full_path('root/gitlab-mirror')

user = project.first_owner

merge_request = project.merge_requests.find_by_iid(1)

# Warmup

Ci::CreatePipelineService

.new(project, user, ref: merge_request.source_branch) .execute(:merge_request_event, merge_request: merge_request); nil

report = MemoryProfiler.report do

Gitlab::SafeRequestStore.ensure_request_store do Ci::CreatePipelineService .new(project, user, ref: merge_request.source_branch) .execute(:merge_request_event, merge_request: merge_request); nil end

end; nil

output = File.open('tmp/memory-profile-report.txt', 'w')

report.pretty_print(output, detailed_report: true, scale_bytes: true, normalize_paths: true)#

결과:

```

#

# Note: I redacted some parts related to the gems and the Rails framework.

# also, the output is shortened for readability.

#

Total allocated: 1.30 GB (12974240 objects)

Total retained: 29.67 MB (335085 objects)

allocated memory by gem#

675.48 MB gitlab/lib ...

allocated memory by file#

253.68 MB gitlab/lib/gitlab/ci/variables/collection/item.rb 143.58 MB gitlab/lib/gitlab/ci/variables/collection.rb 51.66 MB gitlab/lib/gitlab/config/entry/configurable.rb 20.89 MB gitlab/lib/gitlab/ci/pipeline/expression/lexeme/base.rb ...

allocated memory by location#

107.12 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:64 70.22 MB gitlab/lib/gitlab/ci/variables/collection.rb:28 57.66 MB gitlab/lib/gitlab/ci/variables/collection.rb:82 45.70 MB gitlab/lib/gitlab/config/entry/configurable.rb:67 42.35 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:17 42.35 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:80 41.32 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:76 20.10 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:72
...

이 예시에서는 파일 및 위치별 할당된 메모리를 확인하여 메모리 사용량을 최적화할 수 있는 곳을 파악합니다.

그리고 최근 작업에서

메모리 사용량을 개선하는 방법을 찾아 다음 결과를 얻었습니다;

```

#

# Note: I redacted some parts related to the gems and the Rails framework.

# also, the output is shortened for readability.

#

Total allocated: 1.08 GB (11171148 objects)

Total retained: 29.67 MB (335082 objects)

allocated memory by gem#

495.88 MB gitlab/lib ...

allocated memory by file#

112.44 MB gitlab/lib/gitlab/ci/variables/collection.rb 105.24 MB gitlab/lib/gitlab/ci/variables/collection/item.rb 51.66 MB gitlab/lib/gitlab/config/entry/configurable.rb 20.89 MB gitlab/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
...

이 예시 파이프라인에서의 총 메모리 절감량: 약 200 MB.

프로파일링

GitLab v19.1
원문 보기
요약

성능 문제를 보다 쉽게 추적할 수 있도록 GitLab은 다양한 프로파일링 도구를 제공합니다. 일부 도구는 기본적으로 사용 가능하며, 나머지는 명시적으로 활성화해야 합니다. `Gitlab::Profiler.profile` 메서드와 이에 대응하는 `bin/profile-url` 스크립트를 사용하면

# 프로파일링

성능 문제를 보다 쉽게 추적할 수 있도록 GitLab은 다양한 프로파일링 도구를 제공합니다.

일부 도구는 기본적으로 사용 가능하며, 나머지는 명시적으로 활성화해야 합니다.

## URL 프로파일링

`Gitlab::Profiler.profile` 메서드와 이에 대응하는 `bin/profile-url` 스크립트를 사용하면

특정 URL에 대한 GET 또는 POST 요청을 프로파일링할 수 있습니다.

익명 사용자(기본값) 또는 특정 사용자로 프로파일링할 수 있습니다.

프로파일러의 첫 번째 인수는 인스턴스 호스트명을 포함한 전체 URL이거나,

앞에 슬래시가 붙는 절대 경로입니다.

기본적으로 보고서 덤프는 임시 파일에 저장되며,

[Stackprof API](/19.1/development/profiling/#reading-a-gitlabprofiler-report)를 사용하여 상호작용할 수 있습니다.

스크립트를 사용할 때는 인수를 전달하지 않으면 명령줄 도움말을 확인할 수 있습니다.

대화형 콘솔 세션에서 메서드를 사용하면, 해당 콘솔 세션 내의 애플리케이션 코드 변경 사항이

프로파일러 출력에 반영됩니다.

예시:

```

Gitlab::Profiler.profile('/my-user')

# Returns the location of the temp file where the report dump is stored

class UsersController; def show; sleep 100; end; end

Gitlab::Profiler.profile('/my-user')

# Returns the location of the temp file where the report dump is stored

# where 100 seconds is spent in UsersController#show#

인가가 필요한 라우트의 경우 `Gitlab::Profiler`에 사용자를 제공해야 합니다.

다음과 같이 사용합니다:

```

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first)#

`Gitlab::Profiler.profile`에 `logger:` 키워드 인수를 전달하면

ActiveRecord와 ActionController 로그 출력을 해당 로거로 전송합니다.

추가 옵션은 메서드 소스에 설명되어 있습니다.

```

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, logger: Logger.new($stdout))#

샘플링 데이터의 출력 파일(`out`)을 구성하려면 `profiler_options` 해시를 전달합니다. 예시:

```

Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, profiler_options: { out: 'tmp/profile.dump' })#

## GitLab::Profiler 보고서 읽기

샘플링 데이터에 대해 Stackprof를 실행하여 시간이 어디서 소요되었는지 요약을 확인할 수 있습니다. 예시:

```

stackprof tmp/profile.dump#

샘플링 데이터 예시:

```

Mode: wall(1000) Samples: 8745 (6.92% miss rate)

GC: 1399 (16.00%)#

TOTAL (pct) SAMPLES (pct) FRAME 1022 (11.7%) 1022 (11.7%) Sprockets::PathUtils#stat 957 (10.9%) 957 (10.9%) (marking) 493 (5.6%) 493 (5.6%) Sprockets::PathUtils#entries 576 (6.6%) 471 (5.4%) Mustermann::AST::Translator#decorator_for 439 (5.0%) 439 (5.0%) (sweeping) 630 (7.2%) 241 (2.8%) Sprockets::Cache::FileStore#get 208 (2.4%) 208 (2.4%) ActiveSupport::FileUpdateChecker#watched 206 (2.4%) 206 (2.4%) Digest::Instance#file 544 (6.2%) 176 (2.0%) Sprockets::Cache::FileStore#safe_open 176 (2.0%) 176 (2.0%) ActiveSupport::FileUpdateChecker#max_mtime 268 (3.1%) 147 (1.7%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache 140 (1.6%) 140 (1.6%) ActiveSupport::BacktraceCleaner#add_gem_filter 116 (1.3%) 116 (1.3%) Bootsnap::CompileCache::ISeq.storage_to_output 160 (1.8%) 113 (1.3%) Gem::Version#<=> 109 (1.2%) 109 (1.2%) block in
108 (1.2%) 108 (1.2%) Gem::Version.new 131 (1.5%) 105 (1.2%) Sprockets::EncodingUtils#unmarshaled_deflated 1166 (13.3%) 82 (0.9%) Mustermann::RegexpBased#initialize 82 (0.9%) 78 (0.9%) FileUtils.touch 72 (0.8%) 72 (0.8%) Sprockets::Manifest.compile_match_filter 71 (0.8%) 70 (0.8%) Grape::Router#compile! 91 (1.0%) 65 (0.7%) ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#query 93 (1.1%) 64 (0.7%) ActionDispatch::Journey::Path::Pattern::AnchoredRegexp#accept 59 (0.7%) 59 (0.7%) Mustermann::AST::Translator.dispatch_table 62 (0.7%) 59 (0.7%) Rails::BacktraceCleaner#initialize 2492 (28.5%) 49 (0.6%) Sprockets::PathUtils#stat_directory 242 (2.8%) 49 (0.6%) Gitlab::Instrumentation::RedisBase.add_call_details 47 (0.5%) 47 (0.5%) URI::RFC2396_Parser#escape 46 (0.5%) 46 (0.5%) ##__setobj__
44 (0.5%) 44 (0.5%) Sprockets::Base#normalize_logical_path#

플레임그래프를 생성할 수도 있습니다:

```

stackprof --d3-flamegraph tmp/profile.dump > flamegraph.html#

자세한 내용은 Stackprof 문서를 참조하세요.

## Speedscope 플레임그래프

성능 바에서 플레임그래프 샘플링 모드 버튼을 선택하거나,

요청에 `performance_bar=flamegraph` 파라미터를 추가하여

특정 URL에 대한 플레임그래프를 생성할 수 있습니다.

[

](/19.1/development/img/speedscope_v13_12.png)

뷰에 대한 자세한 정보는 Speedscope 문서를 참조하세요.

다양한 샘플링 모드에 대한 자세한 정보는 Stackprof 문서를 참조하세요.

이 기능은 성능 바에 접근할 수 있는 모든 사용자에게 활성화됩니다.

## Bullet

Bullet은 N+1 쿼리 문제를 추적하는 데 사용할 수 있는 Gem입니다.

쿼리 문제를 Rails 로그와 브라우저 콘솔에 기록합니다.

Bullet 섹션은 [성능 바](/19.1/administration/monitoring/performance/performance_bar/)에 표시됩니다.

[

](/19.1/development/img/bullet_v13_0.png)

Bullet은 기본적으로 개발 모드에서만 활성화됩니다. 그러나 Bullet 로깅은 노이즈가 많기 때문에 로깅은 비활성화되어 있습니다.

Bullet과 그 로깅을 구성하려면 다음과 같이 합니다:

  • 환경에서 Bullet을 수동으로 활성화 또는 비활성화하려면 `config/gitlab.yml`에 다음 줄을 추가하고
`enabled` 값을 필요에 따라 변경합니다:

```

bullet:

enabled: false#
  • Bullet 로깅을 활성화하려면 GitLab을 시작하기 전에 `ENABLE_BULLET` 환경 변수를
비어 있지 않은 값으로 설정합니다:

```

ENABLE_BULLET=true bundle exec rails s#

Bullet로 `N+1` 쿼리를 발견한 후속 조치로,

회귀를 방지하기 위해 [QueryRecorder 테스트](/19.1/development/database/query_recorder/) 작성을 고려해 보세요.

## 시스템 통계

프로파일링 중이나 이후에 메모리 소비량, CPU 소요 시간, 가비지 컬렉터 통계 등

Ruby 가상 머신 프로세스에 대한 상세 정보를 얻고 싶을 수 있습니다.

이러한 정보는 다양한 도구를 통해 개별적으로 쉽게 생성할 수 있지만,

편의를 위해 이 데이터를 JSON 페이로드로 내보내는 요약 엔드포인트가 추가되었습니다:

```

curl localhost:3000/-/metrics/system | jq#

출력 예시:

```

{

"version": "ruby 2.7.2p137 (2020-10-01 revision a8323b79eb) [x86_64-linux-gnu]", "gc_stat": { "count": 118, "heap_allocated_pages": 11503, "heap_sorted_length": 11503, "heap_allocatable_pages": 0, "heap_available_slots": 4688580, "heap_live_slots": 3451712, "heap_free_slots": 1236868, "heap_final_slots": 0, "heap_marked_slots": 3451450, "heap_eden_pages": 11503, "heap_tomb_pages": 0, "total_allocated_pages": 11503, "total_freed_pages": 0, "total_allocated_objects": 32679478, "total_freed_objects": 29227766, "malloc_increase_bytes": 84760, "malloc_increase_bytes_limit": 32883343, "minor_gc_count": 88, "major_gc_count": 30, "compact_count": 0, "remembered_wb_unprotected_objects": 114228, "remembered_wb_unprotected_objects_limit": 228456, "old_objects": 3185330, "old_objects_limit": 6370660, "oldmalloc_increase_bytes": 21838024, "oldmalloc_increase_bytes_limit": 119181499 }, "memory_rss": 1326501888, "memory_uss": 1048563712, "memory_pss": 1139554304, "time_cputime": 82.885264633, "time_realtime": 1610459445.5579069, "time_monotonic": 24001.23145713, "worker_id": "puma_0"
}

이 엔드포인트는 Rails 웹 워커에서만 사용 가능합니다. Sidekiq 워커는 이 방법으로 검사할 수 없습니다.

## 성능에 영향을 미치는 설정

### 애플리케이션 설정

  • `development` 환경은 기본적으로 핫 리로딩이 활성화된 상태로 작동합니다.
이로 인해 Rails가 매 요청마다 파일 변경 사항을 확인하고,

핫 리로드가 단일 스레드이기 때문에 잠재적인 경쟁 조건 잠금이 발생할 수 있습니다.

  • `development` 환경은 요청이 발생하면 코드를 지연 로드할 수 있으며,
이로 인해 첫 번째 요청은 항상 느립니다.

프로파일링/벤치마킹을 위해 이러한 기능을 비활성화하려면

GitLab을 시작하기 전에 `RAILS_PROFILE` 환경 변수를 `true`로 설정합니다.

예를 들어 GDK를 사용할 때:

  • GDK 루트 디렉터리에 `env.runit` 파일을 생성합니다.
  • `env.runit` 파일에 `export RAILS_PROFILE=true`를 추가합니다.
  • `gdk restart`로 GDK를 재시작합니다.
이 환경 변수는 개발 모드에서만 적용됩니다.

### GC 설정

Ruby의 가비지 컬렉터(GC)는 애플리케이션 성능에 직접 영향을 미치는 다양한 환경 변수를 통해 튜닝할 수 있습니다.

다음 테이블에는 이러한 변수와 기본값이 나열되어 있습니다.

| 환경 변수 | 기본값 | | RUBY_GC_HEAP_INIT_SLOTS | 10000 | | RUBY_GC_HEAP_FREE_SLOTS | 4096 | | RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO | 0.20 | | RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO | 0.40 | | RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO | 0.65 | | RUBY_GC_HEAP_GROWTH_FACTOR | 1.8 | | RUBY_GC_HEAP_GROWTH_MAX_SLOTS | 0 (비활성화) | | RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR | 2.0 | | RUBY_GC_MALLOC_LIMIT(_MIN) | (16 1024 1024 / 16MB /) | | RUBY_GC_MALLOC_LIMIT_MAX | (32 1024 1024 / 32MB /) | | RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR | 1.4 | | RUBY_GC_OLDMALLOC_LIMIT(_MIN) | (16 1024 1024 / 16MB /) | | RUBY_GC_OLDMALLOC_LIMIT_MAX | (128 1024 1024 / 128MB /) | | RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR | 1.2 |

(출처)

GitLab은 애플리케이션 성능 향상, 메모리 요구 사항 감소 또는 둘 다를 위해

이러한 설정을 변경하기로 결정할 수 있습니다.

유휴 상태의 GitLab 인스턴스에서 이러한 각 설정이 GC 성능, 메모리 사용량 및

애플리케이션 시작 시간에 어떤 영향을 미치는지는 `scripts/perf/gc/collect_gc_stats.rb` 스크립트를 실행하여 확인할 수 있습니다.

GC 통계 및 일반 타이밍 데이터를 CSV 형식으로 표준 출력에 출력합니다.

## 성능 이슈 조사 예시

Pipeline Authoring 팀은 파이프라인 생성 성능 이슈를 해결하기 위해

[stackprof 플레임그래프](/19.1/development/profiling/#speedscope-flamegraphs) 및 [`memory_profiler`](/19.1/development/performance/#using-memory-profiler)와 같은 기존 프로파일링 방법과

새로운 방법인 `ruby-prof`를 함께 사용했습니다.

### stackprof 플레임그래프 사용

[성능 바](/19.1/administration/monitoring/performance/performance_bar/)는 stackprof 보고서를 얻고

클릭 한 번으로 플레임그래프를 볼 수 있는 훌륭한 도구입니다;

[

](/19.1/development/img/performance_bar_flamegraph_link_v18_4.png)

그러나 GET 요청 이외에는 사용할 수 없습니다.

POST 요청에 대한 플레임그래프를 얻으려면 API 요청에 `performance_bar=flamegraph` 파라미터를 사용합니다.

이 경우 [머지 리퀘스트의 파이프라인 생성 엔드포인트](/19.1/api/merge_requests/#create-merge-request-pipeline)에 대한 플레임그래프를 확인하고자 합니다.

일반적으로 다음 명령을 사용하여 stackprof 보고서를 JSON 파일로 가져올 수 있지만,

`Gitlab::PerformanceBar.allowed_for_user?(request.env['warden']&.user)` 사용자 제어로 인해

웹 인터페이스를 통해 인증된 사용자만 허용됩니다.

```

# This will not work on production

curl --request POST \

--output flamegraph.json \ --header 'Content-Type: application/json' \ --header 'PRIVATE-TOKEN: :token' \
"https://gitlab.example.com/api/v4/projects/:id/merge_requests/:iid/pipelines?performance_bar=flamegraph"#

이를 우회하기 위해 요청을 `curl`로 복사하여 터미널에서 사용합니다.

[

](/19.1/development/img/performance_copy_as_curl_v17_7.png)

다음과 같은 `curl` 명령을 사용할 수 있습니다:

```

curl "https://gitlab.com/api/v4/projects/:id/merge_requests/:iid/pipelines" \

-H 'accept: application/json, text/plain, /' \ -H 'content-type: application/json' \ -H 'cookie: xyz' \ -H 'x-csrf-token: xyz' \
--data-raw '{"async":true}'#
  • 요청 본문에 `async` 파라미터가 있는 것을 주목하세요.
파이프라인 생성 엔드포인트의 실제 성능을 얻으려면 이를 제거해야 합니다.
  • 요청에 `performance_bar=flamegraph` 파라미터를 추가해야 합니다.
  • JSON 응답을 파일로 저장하기 위해 `--output flamegraph.json` 파라미터를 추가해야 합니다.
  • 마지막으로 JSON 응답만 허용해야 합니다.
```

curl "https://gitlab.com/api/v4/projects/:id/merge_requests/:iid/pipelines?performance_bar=flamegraph" \

-X POST \ -o flamegraph.json \ -H 'accept: application/json' \ -H 'content-type: application/json' \ -H 'cookie: xyz' \
-H 'x-csrf-token: xyz'#

그런 다음 `flamegraph.json` 파일을 `https://www.speedscope.app/` 웹사이트에서 사용하여 플레임그래프를 확인합니다.

[

](/19.1/development/img/performance_speedscope_example_v17_7.png)

예를 들어, 이 speedscope 플레임그래프를 조사할 때 `kubernetes_variables` 메서드가

많은 시간을 소비하는 것을 확인하고 이슈를 생성했습니다.

[

](/19.1/development/img/performance_speedscope_example_kubernetes_v17_7.png)

### ruby-prof 사용

시간이 가장 많이 소요되는 곳을 확인하는 또 다른 방법은 `ruby-prof`를 사용하는 것입니다.

Gemfile에 포함된 gem이 아니므로 먼저 Gemfile에 추가하고 `bundle install`을 실행해야 합니다.

문제를 조사하려면 복제된 리포지터리가 필요합니다.

이를 위해 프로덕션 인스턴스에서 개발 인스턴스로 리포지터리를 미러링할 수 있습니다.

그런 다음 `ruby-prof` 프로파일러를 실행하여 시간이 어디서 소요되는지 확인합니다.

```

# RAILS_PROFILE=true GITALY_DISABLE_REQUEST_LIMITS=true rails console

require 'ruby-prof'

ActiveRecord::Base.logger = nil

project = Project.find_by_full_path('root/gitlab-mirror')

user = project.first_owner

merge_request = project.merge_requests.find_by_iid(1)

profile = RubyProf::Profile.new

profile.exclude_common_methods! # see https://github.com/ruby-prof/ruby-prof/blob/1.7.0/lib/ruby-prof/exclude_common_methods.rb

profile.start

Gitlab::SafeRequestStore.ensure_request_store do

Ci::CreatePipelineService .new(project, user, ref: merge_request.source_branch) .execute(:merge_request_event, merge_request: merge_request) .payload

end; nil

result = profile.stop

callstack_printer = RubyProf::CallStackPrinter.new(result)

File.open('tmp/ruby-prof-callstack-report.html', 'w') do

callstack_printer.print(file)

end

::Ci::DestroyPipelineService.new(project, user).execute(Ci::Pipeline.last)#

[

](/19.1/development/img/performance_ruby-prof_example_v17_7.png)

여기서 `Ci::GenerateKubeconfigService`가 약 2,000번 호출되는 것을 확인할 수 있습니다.

이는 추가 조사가 필요하다는 좋은 지표입니다.

### memory_profiler 사용

[`memory_profiler`](/19.1/development/performance/#using-memory-profiler)는 메모리 사용량을 프로파일링하는 도구입니다.

높은 메모리 사용량은 성능 이슈로 이어질 수 있으므로 이 또한 중요합니다.

`stackprof`에서 했던 것처럼 `performance_bar` 파라미터와 함께 `curl`을 사용할 수도 있습니다.

```

curl "https://gitlab.com/api/v4/projects/:id/merge_requests/:iid/pipelines?performance_bar=memory" \

-X POST \ -o flamegraph.json \ -H 'accept: application/json' \ -H 'content-type: application/json' \ -H 'cookie: xyz' \
-H 'x-csrf-token: xyz'#

그러나 요청에 60초 타임아웃이 있기 때문에 프로덕션에서는 작동하지 않습니다.

따라서 메모리 프로파일을 얻으려면 개발 환경을 사용해야 합니다.

자세한 정보는 [메모리 프로파일러 문서](/19.1/development/performance/#using-memory-profiler)에서 확인할 수 있습니다.

```

# RAILS_PROFILE=true GITALY_DISABLE_REQUEST_LIMITS=true rails console

require 'memory_profiler'

ActiveRecord::Base.logger = nil

project = Project.find_by_full_path('root/gitlab-mirror')

user = project.first_owner

merge_request = project.merge_requests.find_by_iid(1)

# Warmup

Ci::CreatePipelineService

.new(project, user, ref: merge_request.source_branch) .execute(:merge_request_event, merge_request: merge_request); nil

report = MemoryProfiler.report do

Gitlab::SafeRequestStore.ensure_request_store do Ci::CreatePipelineService .new(project, user, ref: merge_request.source_branch) .execute(:merge_request_event, merge_request: merge_request); nil end

end; nil

output = File.open('tmp/memory-profile-report.txt', 'w')

report.pretty_print(output, detailed_report: true, scale_bytes: true, normalize_paths: true)#

결과:

```

#

# Note: I redacted some parts related to the gems and the Rails framework.

# also, the output is shortened for readability.

#

Total allocated: 1.30 GB (12974240 objects)

Total retained: 29.67 MB (335085 objects)

allocated memory by gem#

675.48 MB gitlab/lib ...

allocated memory by file#

253.68 MB gitlab/lib/gitlab/ci/variables/collection/item.rb 143.58 MB gitlab/lib/gitlab/ci/variables/collection.rb 51.66 MB gitlab/lib/gitlab/config/entry/configurable.rb 20.89 MB gitlab/lib/gitlab/ci/pipeline/expression/lexeme/base.rb ...

allocated memory by location#

107.12 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:64 70.22 MB gitlab/lib/gitlab/ci/variables/collection.rb:28 57.66 MB gitlab/lib/gitlab/ci/variables/collection.rb:82 45.70 MB gitlab/lib/gitlab/config/entry/configurable.rb:67 42.35 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:17 42.35 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:80 41.32 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:76 20.10 MB gitlab/lib/gitlab/ci/variables/collection/item.rb:72
...

이 예시에서는 파일 및 위치별 할당된 메모리를 확인하여 메모리 사용량을 최적화할 수 있는 곳을 파악합니다.

그리고 최근 작업에서

메모리 사용량을 개선하는 방법을 찾아 다음 결과를 얻었습니다;

```

#

# Note: I redacted some parts related to the gems and the Rails framework.

# also, the output is shortened for readability.

#

Total allocated: 1.08 GB (11171148 objects)

Total retained: 29.67 MB (335082 objects)

allocated memory by gem#

495.88 MB gitlab/lib ...

allocated memory by file#

112.44 MB gitlab/lib/gitlab/ci/variables/collection.rb 105.24 MB gitlab/lib/gitlab/ci/variables/collection/item.rb 51.66 MB gitlab/lib/gitlab/config/entry/configurable.rb 20.89 MB gitlab/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
...

이 예시 파이프라인에서의 총 메모리 절감량: 약 200 MB.