From 423a93cb385e8e98dca0e6f7998a7bdf6abf8082 Mon Sep 17 00:00:00 2001 From: elenachekhina Date: Sun, 9 Mar 2025 13:00:20 +0400 Subject: [PATCH 1/3] cache --- Gemfile | 2 + Gemfile.lock | 5 + app/views/stories/_main_stories_feed.html.erb | 4 +- case-study.md | 217 ++++++++++++++++++ 4 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 case-study.md diff --git a/Gemfile b/Gemfile index b107eb92..0310ad01 100644 --- a/Gemfile +++ b/Gemfile @@ -104,6 +104,8 @@ gem "uglifier", "~> 4.1" gem "validate_url", "~> 1.0" gem "webpacker", "~> 3.6" gem "webpush", "~> 0.3" +gem 'rack-mini-profiler' +gem 'newrelic_rpm' group :development do gem "better_errors", "~> 2.5" diff --git a/Gemfile.lock b/Gemfile.lock index 130d7472..fc4dc3de 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -646,6 +646,7 @@ GEM net-smtp (0.5.1) net-protocol netrc (0.11.0) + newrelic_rpm (9.17.0) nio4r (2.7.4) nokogiri (1.15.7-aarch64-linux) racc (~> 1.4) @@ -726,6 +727,8 @@ GEM rack (2.2.11) rack-host-redirect (1.3.0) rack + rack-mini-profiler (3.3.1) + rack (>= 1.2.0) rack-protection (2.2.4) rack rack-proxy (0.7.7) @@ -1081,6 +1084,7 @@ DEPENDENCIES liquid (~> 4.0) memory_profiler (~> 0.9) nakayoshi_fork + newrelic_rpm nokogiri (~> 1.10) octokit (~> 4.13) omniauth (~> 1.9) @@ -1098,6 +1102,7 @@ DEPENDENCIES pusher (~> 1.3) pusher-push-notifications (~> 1.0) rack-host-redirect (~> 1.3) + rack-mini-profiler rack-timeout (~> 0.5) rails (~> 5.1.6) rails-assets-airbrake-js-client (~> 1.5)! diff --git a/app/views/stories/_main_stories_feed.html.erb b/app/views/stories/_main_stories_feed.html.erb index b6cd0a65..3e175010 100644 --- a/app/views/stories/_main_stories_feed.html.erb +++ b/app/views/stories/_main_stories_feed.html.erb @@ -55,7 +55,9 @@ <% if !user_signed_in? && i == 4 %> <%= render "stories/sign_in_invitation" %> <% end %> - <%= render "articles/single_story", story: story %> + <% cache story do %> + <%= render "articles/single_story", story: story %> + <% end %> <% end %> <% end %> <% if @stories.size > 1 %> diff --git a/case-study.md b/case-study.md new file mode 100644 index 00000000..ff2c335f --- /dev/null +++ b/case-study.md @@ -0,0 +1,217 @@ +# Case-study оптимизации + +## Актуальная проблема +В проекте dev.to нашли проблему - долгая загрузка стартовой страницы. Я решила исправить эту проблему +Первый запуск страницы ~ 17 секунд, дальнейшая работа ~ 1.1 секунды + +## Часть 1 +## Формирование метрики +Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: +1) Время загрузки стартовой страницы должно укладываться в 0.3 секунды + +## Вникаем в детали системы, чтобы найти главные точки роста +Для того, чтобы найти "точки роста" для оптимизации я воспользовался NewRelic + +### Ваша находка №1 +подала нагрузку через ab и посмотрела на результаты: +``` +Server Software: +Server Hostname: localhost +Server Port: 3000 + +Document Path: /?pp=disable +Document Length: 158916 bytes + +Concurrency Level: 5 +Time taken for tests: 86.244 seconds +Complete requests: 100 +Failed requests: 0 +Total transferred: 15945400 bytes +HTML transferred: 15891600 bytes +Requests per second: 1.16 [#/sec] (mean) +Time per request: 4312.189 [ms] (mean) +Time per request: 862.438 [ms] (mean, across all concurrent requests) +Transfer rate: 180.55 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 0 2 1.0 2 8 +Processing: 793 4244 548.1 4147 5170 +Waiting: 791 4241 547.9 4138 5168 +Total: 801 4246 547.8 4149 5173 + +Percentage of the requests served within a certain time (ms) + 50% 4149 + 66% 4338 + 75% 4653 + 80% 4750 + 90% 4992 + 95% 5133 + 98% 5162 + 99% 5173 + 100% 5173 (longest request) +``` + +Посмотрим на результаты в newRelic. +4.49 s Average response time +4.37 s Median response time +5.23 s 95th percentile response time +5.32 s 99th percentile response time +0 Apdex score +0 % Average error rate +6.67 rpm Average throughput + +Один запрос: +``` +| Component | Count | Duration | Percentage | Slow spans | +|--------------------------------------------|-------|----------|------------|------------| +| articles/_single_story.html.erb/Partial | 24 | 1526 ms | 29.21% | 0 | +| stories/_main_stories_feed.html.erb/Partial| 1 | 1336 ms | 25.57% | 0 | +| stories#index | 1 | 934 ms | 17.88% | 0 | +| articles/index.html.erb/Rendering | 1 | 726 ms | 13.89% | 0 | +| layouts/application.html.erb/Rendering | 1 | 277 ms | 5.3% | 0 | +| Postgres User find | 2 | 130 ms | 2.49% | 0 | +| Other | 133 | 296 ms | 5.67% | 0 | +``` + + +Попробуем закешировать articles/single_story +Результаты: +``` +Server Software: +Server Hostname: localhost +Server Port: 3000 + +Document Path: /?pp=disable +Document Length: 157624 bytes + +Concurrency Level: 5 +Time taken for tests: 19.581 seconds +Complete requests: 100 +Failed requests: 0 +Total transferred: 15816200 bytes +HTML transferred: 15762400 bytes +Requests per second: 5.11 [#/sec] (mean) +Time per request: 979.025 [ms] (mean) +Time per request: 195.805 [ms] (mean, across all concurrent requests) +Transfer rate: 788.82 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 0 2 1.0 2 7 +Processing: 254 958 119.6 963 1261 +Waiting: 249 954 119.3 960 1261 +Total: 261 960 119.3 966 1262 + +Percentage of the requests served within a certain time (ms) + 50% 966 + 66% 989 + 75% 1017 + 80% 1033 + 90% 1110 + 95% 1165 + 98% 1203 + 99% 1262 + 100% 1262 (longest request) +``` +New Relic: +1.11 s Average response time +1.11 s Median response time +1.3 s 95th percentile response time +1.38 s 99th percentile response time +0.5 Apdex score +0 % Average error rate +33 rpm Average throughput + +Один запрос: +``` +| Component | Count | Duration | Percentage | Slow spans | +|--------------------------------------------|-------|-------------|------------|------------| +| articles/index.html.erb/Rendering | 1 | 318.06 ms | 30.29% | 0 | +| layouts/application.html.erb/Rendering | 1 | 202.58 ms | 19.29% | 0 | +| stories#index | 1 | 134.29 ms | 12.79% | 0 | +| Postgres Article find | 2 | 91.16 ms | 8.68% | 0 | +| articles/_sidebar_additional.html.erb/Partial | 1 | 68.5 ms | 6.52% | 0 | +| Rack::MiniProfiler#call | 1 | 67.9 ms | 6.47% | 0 | +| Other | 108 | 167.52 ms | 15.95% | 0 | +``` + +Если поменять параметр cuncurrency level на 1, то результаты будут: +``` +Server Software: +Server Hostname: localhost +Server Port: 3000 + +Document Path: /?pp=disable +Document Length: 157624 bytes + +Concurrency Level: 1 +Time taken for tests: 20.940 seconds +Complete requests: 100 +Failed requests: 0 +Total transferred: 15816200 bytes +HTML transferred: 15762400 bytes +Requests per second: 4.78 [#/sec] (mean) +Time per request: 209.397 [ms] (mean) +Time per request: 209.397 [ms] (mean, across all concurrent requests) +Transfer rate: 737.62 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 1 2 0.8 2 8 +Processing: 180 207 25.4 201 330 +Waiting: 180 207 25.4 200 330 +Total: 182 209 25.4 203 332 + +Percentage of the requests served within a certain time (ms) + 50% 203 + 66% 212 + 75% 216 + 80% 222 + 90% 235 + 95% 264 + 98% 308 + 99% 332 + 100% 332 (longest request) +``` +NewRelic: +213 ms Average response time +203 ms Median response time +290 ms 95th percentile response time +370 ms 99th percentile response time +1 Apdex score +0 % Average error rate +29 rpm Average throughput + +Один запрос: +``` +| Component | Count | Duration | Percentage | Slow spans | +|---------------------------------------------|-------|-------------|------------|------------| +| articles/index.html.erb/Rendering | 1 | 70.3 ms | 26.43% | 0 | +| layouts/application.html.erb/Rendering | 1 | 63.14 ms | 23.74% | 0 | +| Bullet::Rack#call | 1 | 34.35 ms | 12.91% | 0 | +| stories#index | 1 | 32.17 ms | 12.09% | 0 | +| stories/_main_stories_feed.html.erb/Partial | 1 | 19.36 ms | 7.28% | 0 | +| ActiveRecord::Migration::CheckPending#call | 1 | 8 ms | 3.01% | 0 | +| Other | 109 | 38.68 ms | 14.54% | 0 | +``` + +В целом, без конкурентности запрос укладывается в бюджет. +Однако было бы неплохо разобравться почему так происходит. Мое предположение, что это происходит из-за блокировки обших ресурсов. Например базы данных. + +## Часть 2 + +Сделаем окружение local_production и проверим скорость на нем + + + + + +## Результаты +В результате проделанной оптимизации наконец удалось обработать файл с данными. +Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. + +*Какими ещё результами можете поделиться* + +## Защита от регрессии производительности +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* From 1bf3a310c8f98974f186c1f308c3eee80ce595c3 Mon Sep 17 00:00:00 2001 From: elenachekhina Date: Sun, 9 Mar 2025 16:25:05 +0400 Subject: [PATCH 2/3] local_production --- .dev_to/compose.yml | 12 +++- case-study.md | 52 ++++++++++++--- config/environments/local_production.rb | 86 +++++++++++++++++++++++++ config/secrets.yml | 3 + config/webpacker.yml | 5 +- 5 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 config/environments/local_production.rb diff --git a/.dev_to/compose.yml b/.dev_to/compose.yml index 6db3e51f..33ad9064 100644 --- a/.dev_to/compose.yml +++ b/.dev_to/compose.yml @@ -58,12 +58,20 @@ services: web: <<: *backend - command: bundle exec rails server -b 0.0.0.0 + command: > + bash -c ' + if [ "$RAILS_ENV" = "local_production" ]; then + NODE_ENV=production bundle exec rake assets:precompile + fi + bundle exec rails server -b 0.0.0.0 + ' ports: - '3000:3000' depends_on: + <<: *backend_depends_on webpacker: condition: service_started + required: false # sidekiq: # condition: service_started @@ -113,6 +121,8 @@ services: <<: *env WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache + profiles: + - development volumes: bundle: diff --git a/case-study.md b/case-study.md index ff2c335f..1708046f 100644 --- a/case-study.md +++ b/case-study.md @@ -196,22 +196,58 @@ NewRelic: | Other | 109 | 38.68 ms | 14.54% | 0 | ``` -В целом, без конкурентности запрос укладывается в бюджет. +В целом, без прараллельности запрос укладывается в бюджет, проверим будет ли укладывтаься в бюджет с прараллельностью в local_production. Однако было бы неплохо разобравться почему так происходит. Мое предположение, что это происходит из-за блокировки обших ресурсов. Например базы данных. ## Часть 2 Сделаем окружение local_production и проверим скорость на нем +``` +Server Software: +Server Hostname: localhost +Server Port: 3000 +Document Path: /?pp=disable +Document Length: 136405 bytes +Concurrency Level: 5 +Time taken for tests: 3.745 seconds +Complete requests: 100 +Failed requests: 0 +Total transferred: 13684800 bytes +HTML transferred: 13640500 bytes +Requests per second: 26.70 [#/sec] (mean) +Time per request: 187.251 [ms] (mean) +Time per request: 37.450 [ms] (mean, across all concurrent requests) +Transfer rate: 3568.48 [Kbytes/sec] received +Connection Times (ms) + min mean[+/-sd] median max +Connect: 1 2 0.7 2 5 +Processing: 107 175 48.3 163 336 +Waiting: 103 169 48.2 156 336 +Total: 109 177 48.1 166 337 +Percentage of the requests served within a certain time (ms) + 50% 166 + 66% 180 + 75% 192 + 80% 208 + 90% 265 + 95% 287 + 98% 330 + 99% 337 + 100% 337 (longest request) +``` +NewRelic: +180 ms Average response time +130 ms Median response time +254 ms 95th percentile response time +332 ms 99th percentile response time +0.99 Apdex score +0.33 % Average error rate +10 rpm Average throughput ## Результаты -В результате проделанной оптимизации наконец удалось обработать файл с данными. -Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. - -*Какими ещё результами можете поделиться* - -## Защита от регрессии производительности -Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* +В результате проделанной оптимизации удалось ускорить загрузку до ~1 секунды в development окружении. +В local_production время ускорилось до ~0.2 секунд diff --git a/config/environments/local_production.rb b/config/environments/local_production.rb new file mode 100644 index 00000000..b1fdb52e --- /dev/null +++ b/config/environments/local_production.rb @@ -0,0 +1,86 @@ +# rubocop:disable Metrics/BlockLength + +Rails.application.configure do + # Verifies that versions and hashed value of the package contents in the project's package.json + config.webpacker.check_yarn_integrity = false + + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = true + config.assets.compile = true + + # Do not eager load code on boot. + config.eager_load = true + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + config.action_controller.perform_caching = true + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = false + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = false + + # Supress logger output for asset requests. + config.assets.quiet = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + + config.action_controller.asset_host = nil + config.action_mailer.perform_caching = false + + config.app_domain = "localhost:3000" + + config.action_mailer.default_url_options = { host: "localhost:3000" } + config.action_mailer.delivery_method = :smtp + config.action_mailer.perform_deliveries = true + config.action_mailer.default_url_options = { host: config.app_domain } + config.action_mailer.smtp_settings = { + address: "smtp.gmail.com", + port: "587", + enable_starttls_auto: true, + user_name: '<%= ENV["DEVELOPMENT_EMAIL_USERNAME"] %>', + password: '<%= ENV["DEVELOPMENT_EMAIL_PASSWORD"] %>', + authentication: :plain, + domain: "localhost:3000" + } + + config.action_mailer.preview_path = "#{Rails.root}/spec/mailers/previews" + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + config.public_file_server.enabled = true + + config.file_watcher = ActiveSupport::EventedFileUpdateChecker + + # Install the Timber.io logger + send_logs_to_timber = ENV["SEND_LOGS_TO_TIMBER"] || "false" # <---- set to false to stop sending dev logs to Timber.io + log_device = send_logs_to_timber == "true" ? Timber::LogDevices::HTTP.new(ENV["TIMBER"]) : STDOUT + logger = Timber::Logger.new(log_device) + logger.level = config.log_level + config.logger = ActiveSupport::TaggedLogging.new(logger) +end + +# rubocop:enable Metrics/BlockLength diff --git a/config/secrets.yml b/config/secrets.yml index 73f5e05c..2c88e8e5 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -16,6 +16,9 @@ development: test: secret_key_base: 42dd7834039ebbea271af22635a6782ee15e519b14629c5276bfcdd4cff841e9926994784bb43a335a8f8c9739bb254ea3afe831839d4dc65654ec7516ec25f0 +local_production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] || 'a2e5c4e715b21f1b7cac76d31fda8a8e57ae1c1230253f07980fcd36534b623785401294644587ee841d94e18a844ce5f31733a50cf0a737616efea2ae503810' %> + # Do not keep production secrets in the repository, # instead read values from the environment. diff --git a/config/webpacker.yml b/config/webpacker.yml index 2dfcd170..4fad4c5c 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -46,7 +46,7 @@ test: # Compile test packs to a separate directory public_output_path: packs-test -production: +production: &production <<: *default # Production depends on precompilation of packs prior to booting for performance. @@ -54,3 +54,6 @@ production: # Cache manifest.json for performance cache_manifest: true + +local_production: + <<: *production From b37178938aabeea249bf7477577c0c2ba3fd2b12 Mon Sep 17 00:00:00 2001 From: elenachekhina Date: Sun, 9 Mar 2025 16:31:24 +0400 Subject: [PATCH 3/3] fix misprint --- case-study.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/case-study.md b/case-study.md index 1708046f..645a8268 100644 --- a/case-study.md +++ b/case-study.md @@ -10,7 +10,7 @@ 1) Время загрузки стартовой страницы должно укладываться в 0.3 секунды ## Вникаем в детали системы, чтобы найти главные точки роста -Для того, чтобы найти "точки роста" для оптимизации я воспользовался NewRelic +Для того, чтобы найти "точки роста" для оптимизации я воспользовалась NewRelic ### Ваша находка №1 подала нагрузку через ab и посмотрела на результаты: