-
Notifications
You must be signed in to change notification settings - Fork 115
Homework solution #116
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?
Homework solution #116
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 | ||
.DS_Store |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
--require rails_helper | ||
--color |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ruby 3.4.1 |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# Case-study оптимизации | ||
|
||
## Актуальные проблемы | ||
В проекте возникли несколько серьёзных проблем. | ||
- Долгий импорт данных, при объеме данных более 30 мегабайт | ||
- При большом объеме данных начинает тормозить страница расписаний. | ||
|
||
### Импорт данных | ||
В приложении есть rake таска, которая удаляет все ранее загруженные данные, и добавляет новые из предоставленного файла. | ||
``` | ||
bin/rake reload_json[file] | ||
``` | ||
Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго. | ||
Я решил исправить эту проблему, оптимизировав эту программу. | ||
|
||
#### Формирование метрики | ||
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я добавил вывод времени выполнения программы (определяю временную метку в начале выполнения и в конце, и смотрю разницу). | ||
При первом запуске программы с medium.json файлом она отработала за ~ 1 минуту. | ||
|
||
#### Гарантия корректности работы оптимизированной программы | ||
Для гарантии был добавлен тест, который в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
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. 👍 |
||
``` | ||
bundle exec rspec spec/tasks/utils_spec.rb | ||
``` | ||
|
||
#### Профилирование | ||
Чтобы понять какие проблемы с программой, я решил использовать логи, которые записываются в `log/development.log`. | ||
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. 👍 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. Как говорит Nate Berkopec, если бы все смотрели логи, у меня не было бы работы |
||
|
||
#### Поиск проблем | ||
После первого запуска программы, логи показали очень большое количество SELECT запросов для таблиц cities, services и buses, это связано было с тем, что перед созданием записи мы проверяли наличие существующей записи. | ||
Было решено добавить HASH переменные, в которые можно было бы по ключу добавлять записи, чтобы при необходимости получать нужную запись по ключу. | ||
После этого программа для medium.json файла стала отрабатывать за ~ 12 секунд. | ||
|
||
С large.json программа отрабатывала за ~ 1 минуту. | ||
Логи показали на большое количество INSERT trips, потому что записи создавались по отдельности. Было решено использовать метод upsert_all, который добавляет записи одним запросом. | ||
После этого программа для large.json файла стала отрабатывать за ~ 7 секунд. | ||
|
||
Логи показали что осталисось много INSERT INTO "buses_services", но их пока не понятно как загружать | ||
``` | ||
Bulk insert or upsert is currently not supported for has_many through association | ||
``` | ||
|
||
#### Результаты | ||
В результате проделанной оптимизации наконец удалось обработать файл с данными. | ||
Удалось улучшить метрику с более 1 минуты, до примерно 7 секунд и уложиться в заданный бюджет. | ||
|
||
#### Защита от регрессии производительности | ||
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был добавлен тест. | ||
``` | ||
bundle exec rspec spec/tasks/utils_spec.rb | ||
``` | ||
|
||
### Отображение расписаний | ||
Веб приложение отображает страницу расписания автобусов по направлениям. Она быстро отвечает при небольшом количестве данных, но при большом количестве данных начинаеат долго загружаться. Я решил исправить эту проблему оптимизировав загрузку страницы. | ||
Для того, чтобы иметь возможность быстро проверять гипотезы я решил загрузить большое количество данных, найти самый популярный маршрут и посмотеть за сколько страница загружается. | ||
|
||
#### Формирование метрики | ||
Стандартный набор в браузере - панель разработчиков, в разделе Network, определять за сколько загружается страница. Дополнительно я добавил гем "mini-profiler-resources" который так же показывает время загрузки страницы. | ||
Я загрузил данные из large.json файла и открыл страницу самого популярного маршрута Волгоград – Рыбинск, 1095 рейсов, страница грузилась ~ 8 секунд. Считается что лучшем временем для загрузки страницы является менее 2 секунд. | ||
|
||
#### Гарантия корректности работы оптимизированной страницы | ||
Для гарантии был добавлен тест, который в фидбек-лупе позволяет не допустить ошибок при оптимизации. | ||
``` | ||
bundle exec rspec spec/controllers/trips_controller_spec.rb | ||
``` | ||
|
||
#### Профилирование | ||
Чтобы понимать какие возможны проблемы на странице я добавил "mini-profiler-resources", который показывает что происходит при загрузке страницы. Дополнительно я установил pghero, чтобы анализировать sql запросы. | ||
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. не пойму что такое mini-profiler-resources? rack-mini-profiler имеется в виду? погуглил "mini-profiler-resources" - ничего не нашёл 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. Лол, даже сам не понимаю от куда я мог это взять))) да, должен быть rack-mini-profiler |
||
|
||
#### Поиск проблем | ||
Используя mini-profiler-resources я обнаруж что на странице делается 1975 sql запросов. Pghero так же показал что есть 2 запроса, которые делаюся более 1900 раз - Автобусы и Сервисы автобусов. Предполагаю что проблема на странице N+1 проблема. Для исправления добавил includes к Trip, для того чтобы формировались несколько запросов на все данные. После этого страница стала грузится за ~2.5 секунды, а количество sql запросов стало 5. | ||
|
||
После mini-profiler-resources показал что на странице очень много рендрерится шаблонов - списка услуг и сами услуги. Я решил избавится от шаблонов и перенести всё основной шаблон. После чего страница стала грузится за 0,6 секунд | ||
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. я уже видел в чатике, что вы уже об этом узнали, но напишу что можно было бы использовать render collection API, там можно даже задать шаблон делимитера параметром https://guides.rubyonrails.org/layouts_and_rendering.html#spacer-templates так получается не так сильно тормозит, но при этом можно сохранить удобство разбивки вьюх по паршлам |
||
|
||
#### Результаты | ||
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. можно было бы ещё индексов накинуть; именно для рендеринга страницы тут это не так критично, хотя тоже помогает но если бы мы заходили с точки зрения оптимизации БД - там бы это очень сильно помогло (в тч составной индекс можно на trips(from, to)) 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. Да, про индексы я знаю, но не один инструмент не показал в этом необходимости, для large файла. Наверное с бонусными файлами потребовалось бы, но отложил их на потом. 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. Старался следовать философии оптимизации) оптимизировать только Главные точки и остановится когда достигли желаемого) |
||
В результате проделанной оптимизации страница стала грузиться быстрее. | ||
Удалось улучшить метрику с более 8 секунд, до менее 1 секунды и уложиться в заданный бюджет. | ||
|
||
#### Защита от регрессии производительности | ||
Для защиты от потери достигнутого прогресса при дальнейших изменениях был добавлен тест. | ||
``` | ||
bundle exec rspec spec/controllers/trips_controller_spec.rb | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
Rails.application.routes.draw do | ||
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html | ||
mount PgHero::Engine, at: "pghero" | ||
get "автобусы/:from/:to" => "trips#index" | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
class CreatePgheroQueryStats < ActiveRecord::Migration[8.0] | ||
def change | ||
create_table :pghero_query_stats do |t| | ||
t.text :database | ||
t.text :user | ||
t.text :query | ||
t.integer :query_hash, limit: 8 | ||
t.float :total_time | ||
t.integer :calls, limit: 8 | ||
t.timestamp :captured_at | ||
end | ||
|
||
add_index :pghero_query_stats, [:database, :captured_at] | ||
end | ||
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.
простое и достаточное решение 👍