-
Notifications
You must be signed in to change notification settings - Fork 114
Add task 3 #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add task 3 #97
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ | |
| /tmp | ||
| /log | ||
| /public | ||
| .byebug_history | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 2.6.3 | ||
| 2.7.6 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,49 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| source 'https://rubygems.org' | ||
| git_source(:github) { |repo| "https://github.com/#{repo}.git" } | ||
|
|
||
| ruby '2.6.3' | ||
| ruby '2.7.6' | ||
|
|
||
| gem 'rails', '~> 5.2.3' | ||
|
|
||
| gem 'pg', '>= 0.18', '< 2.0' | ||
| gem 'puma', '~> 3.11' | ||
|
|
||
| gem 'bootsnap', '>= 1.1.0', require: false | ||
| gem 'mimemagic', '~> 0.3.10' | ||
|
|
||
| gem 'activerecord-import' | ||
|
|
||
| gem 'fast_jsonparser' | ||
| gem 'oj' | ||
|
|
||
| gem 'strong_migrations' | ||
|
|
||
| group :development, :test do | ||
| # Call 'byebug' anywhere in the code to stop execution and get a debugger console | ||
| gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] | ||
| gem 'benchmark', '~> 0.2.1' | ||
| gem 'byebug', platforms: %i[mri mingw x64_mingw] | ||
| gem 'rspec-benchmark', '~> 0.6.0' | ||
| # gem 'rspec-rails', '~> 5.0.0' | ||
|
|
||
| gem 'bullet' | ||
| gem 'pghero' | ||
| gem 'pg_query', '>= 2' | ||
|
|
||
| gem 'memory_profiler' | ||
| gem 'rack-mini-profiler', require: false | ||
| gem 'stackprof' | ||
| end | ||
|
|
||
| group :development do | ||
| # Access an interactive console on exception pages or by calling 'console' anywhere in the code. | ||
| gem 'web-console', '>= 3.3.0' | ||
| gem 'listen', '>= 3.0.5', '< 3.2' | ||
| gem 'web-console', '>= 3.3.0' | ||
| end | ||
|
|
||
| group :test do | ||
| end | ||
|
|
||
| # Windows does not include zoneinfo files, so bundle the tzinfo-data gem | ||
| gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] | ||
| gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| task: | ||
| bundle exec rake reload_json[fixtures/large.json] | ||
|
|
||
| test: | ||
| bundle exec rails test | ||
|
|
||
| .PHONY: test |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| class BusesService < ApplicationRecord | ||
| belongs_to :bus | ||
| belongs_to :service | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,15 +2,24 @@ | |
| <%= "Автобусы #{@from.name} – #{@to.name}" %> | ||
| </h1> | ||
| <h2> | ||
| <%= "В расписании #{@trips.count} рейсов" %> | ||
| <%= "В расписании #{@trips.load.size} рейсов" %> | ||
| </h2> | ||
|
|
||
| <% @trips.each do |trip| %> | ||
| <ul> | ||
| <%= render "trip", trip: trip %> | ||
| <% if trip.bus.services.present? %> | ||
| <%= render "services", services: trip.bus.services %> | ||
| <li><%= "Отправление: #{trip.start_time}" %></li> | ||
| <li><%= "Прибытие: #{(Time.parse(trip.start_time) + trip.duration_minutes.minutes).strftime('%H:%M')}" %></li> | ||
| <li><%= "В пути: #{trip.duration_minutes / 60}ч. #{trip.duration_minutes % 60}мин." %></li> | ||
| <li><%= "Цена: #{trip.price_cents / 100}р. #{trip.price_cents % 100}коп." %></li> | ||
| <li><%= "Автобус: #{trip.bus.model} №#{trip.bus.number}" %></li> | ||
| <% if trip.bus.services.any? %> | ||
| <li>Сервисы в автобусе:</li> | ||
| <ul> | ||
| <% trip.bus.services.each do |service| %> | ||
| <li><%= "#{service.name}" %></li> | ||
| <% end %> | ||
| </ul> | ||
| <% end %> | ||
| </ul> | ||
| <%= render "delimiter" %> | ||
| ==================================================== | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Можно ещё с рендерингом коллекций и даже указать шаблон для разделителя https://guides.rubyonrails.org/layouts_and_rendering.html#spacer-templates |
||
| <% end %> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # Case-study оптимизации | ||
|
|
||
| ## Актуальная проблема | ||
| 1. Долгий импорт данных в БД | ||
| 2. Долгое отображение расписаний | ||
|
|
||
| ## Формирование метрики | ||
| Mетрика: *время импорта данных в БД в секундах, расчитываемое на файле `large.json`.* | ||
| Бюджет: 60 секунд. | ||
|
|
||
| ## Гарантия корректности работы оптимизированной программы | ||
| Тест соответствия скорости импорта бюджету | ||
| Тест корректности импорта (`fixtures/example.json`) | ||
|
|
||
| ## Вникаем в детали системы, чтобы найти главные точки роста | ||
| Стартовый benchmark: | ||
| # small.json - 4.13s | ||
| # medium.json - 57.1s | ||
|
|
||
| ### Неоптимальная запись в БД | ||
| Использование гема activerecord-import: | ||
| small.json - 2.26s | ||
| medium.json - 18.78s | ||
|
|
||
| Забавно, что использование гема oj для загрузки json дало обратный результат: | ||
| small.json - 2.31s | ||
| medium.json - 19.25s | ||
| large.json - 193.09s | ||
| В итоге остановился на FastJsonParse, добавили batch_size | ||
| small.json - 2.11s | ||
| medium.json - 18.28s | ||
| large.json - 182.09s | ||
|
|
||
| Осталось закэшировать bus_services, воспользуемся отсутствием false: id в schema | ||
| small.json - 0.8s | ||
| medium.json - 1.92s | ||
| large.json - 8.34s | ||
| Разница драматическая, на этом эту часть заканчиваем | ||
|
|
||
| Поставил pg_hero - eму все нравится, полезной инфы - нет | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Хмм, обычно он подсказывает индексы создать, если погонять запросы |
||
| Rails panel - не заработал | ||
| Bullet разумеется помог, но он меня достал уже на рабочем Проекте), здесь все гораздо хуже, ибо он лезет ото всюду - из поискового окна, прямо со страницы... там конечно все настраивается, но он все равно мне не нравится | ||
| rack-mini-profiler - великолепен: ненавязчивый и удобный, всегда им пользуюсь | ||
|
|
||
| Render time, start: | ||
| 5746.0 ms 1886 sql - RMP | ||
| Completed 200 OK in 5737ms (Views: 4483.1ms | ActiveRecord: 1247.9ms) | ||
|
|
||
| ### Рендеринг страницы | ||
| - Добавляем индексы поисковым полям: City#name, Trip#from/to (составной) | ||
| - size вместо count для trips | ||
| - в trip.html прелоадим bus для trip и services для bus | ||
| - #any? вместо #presence? | ||
| Completed 200 OK in 2865ms (Views: 2834.6ms | ActiveRecord: 24.0ms) | ||
| 2874.2 ms 7 sql - запросы выглядят хорошо, но выигрыш по времени небольшой | ||
|
|
||
| Bullet молчит - но по RMP видно что многовато времени в сумме на паршиалы, объединим их в один | ||
| Completed 200 OK in 223ms (Views: 194.9ms | ActiveRecord: 22.5ms) | ||
| 231.5 ms 7 sql - разница драматическая, не все паршиалы одинаково полезны). Заканчиваем. | ||
|
|
||
| Render time, finish | ||
| Completed 200 OK in 223ms (Views: 194.9ms | ActiveRecord: 22.5ms) | ||
| 231.5 ms 7 sql | ||
|
|
||
| ## Результаты | ||
| - импорт файла `fixtures/large.json`: 8-9 s | ||
| - время рендеринга страницы `автобусы/Самара/Москва` на базе файла large.json: 200-300 ms | ||
|
|
||
| *Какими ещё результами можете поделиться* | ||
| - Bullet ожидаемо утомителен | ||
| - RMP ожидаемо хорош | ||
| - паршиалы - красиво и архитектурно, но ни дороговато ли они стоят подчас? Это главный вывод для меня сегодня. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Можно оставить себе удобство паршлов если рендерить коллекцией https://guides.rubyonrails.org/layouts_and_rendering.html#rendering-collections там не такая критичная просадка по производительности по сравнению с циклом |
||
| P.S. На работе пользуюсь AppSignal вместо Rollbar - пока доволен | ||
|
|
||
| ## Защита от регрессии производительности | ||
| Для защиты от потери достигнутого прогресса при дальнейших изменениях программы написал тесты: | ||
| Тест соответствия скорости импорта бюджету | ||
| Тест корректности импорта (`fixtures/example.json`) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| if Rails.env.development? | ||
| require 'rack-mini-profiler' | ||
|
|
||
| # initialization is skipped so trigger it | ||
| Rack::MiniProfiler.config.show_total_sql_count = true | ||
| Rack::MiniProfilerRails.initialize!(Rails.application) | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 👍 , явно и сразу понятно что происходит, загрузили в массив, взяли размер