-
Notifications
You must be signed in to change notification settings - Fork 115
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
Task3 #4
Task3 #4
Changes from all commits
a931e93
99365d2
d693dfb
dc29753
845e809
47b9623
e949fb9
74f4e70
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 |
---|---|---|
@@ -0,0 +1,21 @@ | ||
FROM ruby:2.6.3 | ||
|
||
RUN apt-get update && \ | ||
apt-get install g++ valgrind net-tools tmux -y \ | ||
massif-visualizer \ | ||
--no-install-recommends \ | ||
&& apt-get install -y postgresql postgresql-contrib \ | ||
&& apt-get install sudo \ | ||
&& apt-get purge --auto-remove -y curl \ | ||
&& rm -rf /var/lib/apt/lists/* \ | ||
&& rm -rf /src/*.deb | ||
|
||
RUN groupadd -r massif && useradd -r -g massif massif \ | ||
&& mkdir -p /home/massif/test && chown -R massif:massif /home/massif | ||
USER massif | ||
WORKDIR /home/massif/test | ||
|
||
RUN gem install bundler | ||
COPY Gemfile /home/massif/test | ||
COPY Gemfile.lock /home/massif/test | ||
RUN bundle install |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
class BusesService < ApplicationRecord | ||
belongs_to :bus, touch: true | ||
belongs_to :service, touch: true | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
<li><%= "#{service.name}" %></li> | ||
<li><%= "#{service.name}" %></li> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,4 @@ | ||
<li>Сервисы в автобусе:</li> | ||
<ul> | ||
<% services.each do |service| %> | ||
<%= render "service", service: service %> | ||
<% end %> | ||
<%= render partial: "service", collection: services %> | ||
</ul> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,13 @@ | ||
<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> | ||
<% cache trip do %> | ||
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. 👍 |
||
<ul> | ||
<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.present? %> | ||
<%= render "services", services: trip.bus.services %> | ||
<% end %> | ||
</ul> | ||
<%= render "delimiter" %> | ||
<% end %> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/bin/bash | ||
|
||
docker build . -t spajic/docker-valgrind-massif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
# Case-study оптимизации | ||
|
||
## Актуальная проблема | ||
В нашем проекте возникла серьёзная проблема. | ||
|
||
Наше приложенене неплохо себя показало на тестовых данных, однако оказалось, что в реальной жизни нам потребуется | ||
загружать и отображать значительно большее количество рейсов, чем мы первоначально планировали. | ||
|
||
От бизнеса был получен файл large.json объемом 32MB, выявивший две проблемы: | ||
|
||
1) Слишком медленная загрузка данных в базу | ||
2) Слишком долгое отображение страниц веб-приложением | ||
|
||
Попробуем решить обе проблемы. | ||
|
||
## Гарантия корректности работы оптимизированной программы | ||
Напишем тест, проверяющий корректную загрузку и отображение рейсов из файла example.json. Для этого перенесем | ||
логику обработки файла из рейк-таски в отдельный класс и напишем тест, проверяющий корректное отображение страницы | ||
рейсов Самара - Москва | ||
|
||
## Первая проблема - Слишком медленная загрузка данных в базу | ||
|
||
### Метрика | ||
Я решил использовать две метрики - непосредственно скорость обработки файла и потребление памяти. | ||
В проекте удобно предоставляются файлы разного размера, что позволит переходить от меньшего файла к большему по мере | ||
оптимизации. | ||
|
||
### Feedback-Loop | ||
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил `feedback-loop`, | ||
который позволил мне получать обратную связь по эффективности сделанных изменений за 5-10 секунд | ||
|
||
`feedback-loop` представляет из себя запуск rake таски импорта файла | ||
|
||
- bin/rake reload_json[fixtures/small.json] | ||
|
||
в вывод которой я добавил отчет о затраченном времени и потреблении памяти | ||
|
||
## Динамика потребления памяти | ||
Прежде всего выясним динамику потребления памяти при импорте данных из файла. Для этого | ||
используем valgrind massif visualier | ||
 | ||
На графике виден стабильный крутой рост потребления памяти на всем протяжении времени работы скрипта. | ||
|
||
Попробуем провести рефакторинг таким образом, чтобы не накапливать данные в памяти. | ||
|
||
Так же в отчете ruby_prof qcachegrind видно, что значительно время занимают find_by / | ||
find_or_create_by методы. Попробуем от них избавиться. | ||
|
||
Значения метрик для файла small.json - 12 секунд, 71 mb | ||
|
||
После рефакторинга импорт файла small.json стал занимать 0.91 секунды. Это позволило | ||
имторировать файл large.json за 26 секунд (используя 347 мб рам), что соответствует бюджету. | ||
|
||
Коммитим изменения | ||
|
||
## Вторая проблема - медленное отображение расписаний после импорта large.json | ||
|
||
## Метрика | ||
|
||
Я решил использовать в качестве метрики скорость, с которой rails отвечает на запрос страницы | ||
`http://127.0.0.1:3000/автобусы/Самара/Москва`, эти значения можно легко увидеть прямо в | ||
консоли `rails s` | ||
|
||
Старотовые значения метрики такие | ||
- Completed 200 OK in 7408ms (Views: 6562.6ms | ActiveRecord: 843.7ms) | ||
|
||
#rack-mini-profiler n+1 | ||
|
||
Подключим rack-mini-profiler и попробуем найти точки роста для оптимизации быстродействия страницы | ||
|
||
В логе запущенного приложения можно увидеть подозрительно большое количество sql запросов, а если | ||
рассмотреть страницу `http://127.0.0.1:3000/автобусы/Самара/Москва` через rack-mini-profiler | ||
можно увидеть явную проблему n+1, страница вызывает 650 sql запросов на рендер каждого _trip. | ||
|
||
Попробуем устранить проблему, добавив include d ds,jhre трипов | ||
|
||
метрика | ||
|
||
- Completed 200 OK in 7408ms (Views: 6562.6ms | ActiveRecord: 843.7ms) | ||
|
||
попробуем внести изменения | ||
|
||
Количество sql запросов снижается до 8, метрика значительно улучшается (особенно в части ActiveRecord) | ||
|
||
- Completed 200 OK in 6053ms (Views: 5985.4ms | ActiveRecord: 64.6ms) | ||
|
||
коммитим | ||
|
||
#rack-mini-profiler рендернг коллекций | ||
|
||
По показанию сметрики мы видем, что основное время занимает рендеринг видов, | ||
а в отчете rack-mini-profiler можно увидеть множество записей о пендеринге trips/_services и | ||
самих трипов. Попробуем применить групповой рендеринг, чтобы ускорить отображение страницы | ||
|
||
текущая метрика | ||
|
||
- Completed 200 OK in 7408ms (Views: 6562.6ms | ActiveRecord: 843.7ms) | ||
|
||
попробуем внести изменения | ||
|
||
метрика уменьшается до 2,5 секунд | ||
|
||
- Completed 200 OK in 2601ms (Views: 2552.5ms | ActiveRecord: 45.8ms) | ||
|
||
коммитим | ||
|
||
# bullet | ||
|
||
Подключим bullet и попробуем найти точки роста для оптимизации быстродействия страницы | ||
|
||
bullet не обнаружил проблем. Для теста отключил ранее найденный includes, посмотрел как проблема | ||
отобразилась в буллет-всплывашке. Других точек роста увидеть не удалось. | ||
|
||
# rails panel | ||
|
||
Подключим rails panel и попробуем найти точки роста для оптимизации быстродействия страницы | ||
|
||
Заметно, что рендер видов все еще занимает приличное время. Попроуем закешировать trip-ы | ||
|
||
теперь страница открывается менее чем за секунду, коммитим | ||
|
||
# explain запросов и добавление индексов | ||
|
||
Изучив запросы к бд, я добавил несколько индексов, в том числе составной индекс для trips -> | ||
[:from_id, :to_id, :start_time], что значительно ускорило выборку данных из базы. | ||
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.json страницы менее чем за | ||
0.5 секунды, по сравнению с первонатальным кодом страница ускорилась примерно в 150 раз | ||
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. 👍 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class CreateIndexes < ActiveRecord::Migration[5.2] | ||
def change | ||
add_index(:buses_services, :bus_id) | ||
add_index(:cities, :name) | ||
add_index(:trips, :from_id) | ||
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. Этот индекс получается лишний, если есть составной на |
||
add_index(:trips, :to_id) | ||
add_index(:trips, [:from_id, :to_id, :start_time]) | ||
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.
👍 Частенько просто удаляют
partial
-ы и лепят всё в один файл