-
Notifications
You must be signed in to change notification settings - Fork 114
home 3 #118
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?
home 3 #118
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 |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| class BusesService < ApplicationRecord | ||
| belongs_to :bus | ||
| belongs_to :service | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,14 @@ | ||
| <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> | ||
| <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? %> | ||
| <li>Сервисы в автобусе:</li> | ||
| <ul> | ||
| <%= render partial: "service", collection: trip.bus.services %> | ||
|
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. 👍 |
||
| </ul> | ||
| <% end %> | ||
| </ul> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,15 +2,8 @@ | |
| <%= "Автобусы #{@from.name} – #{@to.name}" %> | ||
| </h1> | ||
| <h2> | ||
| <%= "В расписании #{@trips.count} рейсов" %> | ||
| <%= "В расписании #{@trips.size} рейсов" %> | ||
| </h2> | ||
|
|
||
| <% @trips.each do |trip| %> | ||
| <ul> | ||
| <%= render "trip", trip: trip %> | ||
| <% if trip.bus.services.present? %> | ||
| <%= render "services", services: trip.bus.services %> | ||
| <% 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. кажется делимитер потерялся его тоже можно параметров в render collection задать, https://guides.rubyonrails.org/layouts_and_rendering.html#spacer-templates |
||
| <% end %> | ||
|
|
||
| <%= render partial: "trip", collection: @trips %> | ||
|
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. 👍 👍 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| # Case-study оптимизации | ||
|
|
||
| ## Актуальные проблемы | ||
| В нашем проекте возникло несколько серьёзных проблем. | ||
| 1) Долгий и не оптимальный импорт данных, при при увеличении объема | ||
| 2) Страница Отображение расписаний, начи нает тормозить при увеличении данных. | ||
|
|
||
| ## Импорт данных | ||
| В приложении есть rake таска, которая удаляет все ранее загруженные данные, и добавляет новые из выбранного файла. | ||
| ``` | ||
| bin/rake reload_json[file] | ||
| ``` | ||
| Возникла проблема что с увеличением данных программа выполнятеся долго. | ||
| Я решил исправить эту проблему, оптимизировав эту программу. | ||
| ### Формирование метрики | ||
| Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику, программа должна выполнять файл large.json меньше чем за 60 сек. | ||
|
|
||
| Предворительно фиксируем что | ||
| - small.json: "Result Time 21.576042000000598" | ||
| - medium.json: "Result Time 125.41547200000787" | ||
|
|
||
| ### Гарантия корректности работы оптимизированной программы | ||
| Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
|
|
||
| Так же добавился тест чтобы проверить лоигку работы и скорость работы на разных объемах данных | ||
|
|
||
|
|
||
| ### Feedback-Loop | ||
| Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений. | ||
|
|
||
| Вот как я построил `feedback_loop`: | ||
| 1) запустил программа на разных кол-вах данных | ||
| 2) проверил логи, чтобы отследить проблемные места | ||
| 3) внес изменения | ||
| 4) прогнал тесты | ||
| 5) зафиксировал результат | ||
|
|
||
|
|
||
| ### Ваша находка | ||
| - запустил импорт exmaple.json файла и начал смотреть логи | ||
|
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. как говорит Nate Berkopec, если бы все смотрели логи, у меня бы не было работы |
||
| - по логами и логике видно что оснавное время тратится на лишние взаимодейстия с базой Select или сразу Insert. | ||
| - Оптимальным решением будет собрать данные сперва и потом импортировать их через active-storage-import, если не хочется тянуть гем то можно и чесез insert_all | ||
| + так же пришлось для удобсва создать можель на связь BusesService | ||
| + все объекты сохранял сразу в переменную и hash | ||
| ``` | ||
| to = cities[trip['to']] ||= City.new(name: trip['to']) | ||
|
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. 👍 |
||
| ``` | ||
| 1) в переменную которая использутся в других обектах | ||
| 2) аггрегатор который собиарет всё вместе | ||
| PS. Если не использовать одиночную переменную, а вытаскивать из агрегатора(везде это Hash), то тратится много времени на поиск объекта(если использовать только Hash) | ||
| ``` | ||
| "1 0.75399" | ||
| "2 20.596119" | ||
| "3 20.603491" | ||
| "4 20.698374" | ||
| "5 20.700828" | ||
| "6 76.098364" | ||
| "7 99.592368" | ||
| "Result Time 99.59396600000036" | ||
| ``` | ||
| - как результат я уложился в поставленную задачу, весь импорт large файла занимает 29.5 сек | ||
| ``` | ||
| "1 0.954356" | ||
| "2 7.650278" | ||
| "3 7.654027" | ||
| "4 7.734918" | ||
| "5 7.752177" | ||
| "6 8.211075" | ||
| "7 29.495458" | ||
| "Result Time 29.50383200000215" | ||
| ``` | ||
| ## Страница Отображение расписаний | ||
| Когда рейсов становится слишком много страница с расписанием грузится очень медленно. | ||
| С файлом `medium.json` время страницы `http://localhost:3000/автобусы/Самара/Москва` - 595ms | ||
| С файлом `large.json` время страницы `http://localhost:3000/автобусы/Самара/Москва` - 11363ms | ||
| ### Формирование метрики | ||
| Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы, найти очевидые слабые точки и решить их. | ||
| ### Ваша находка 1 | ||
| - на данных из medium.json, посещаю страницу | ||
| - для начала смотрим на rack-mini-profiler | ||
| ``` | ||
| Rendering: trips/index.html.erb 295.5 +8.0 138 sql 57.6 | ||
| Rendering: trips/_trip.html.erb 3.0 +16.0 1 sql 0.5 | ||
| ``` | ||
| так же смотрим через bullet, он подсвечивает что есть N+1 | ||
| ``` | ||
| USE eager loading detected | ||
| Trip => [:bus] | ||
| Add to your query: .includes([:bus]) | ||
| ``` | ||
| - добавим preload, чтобы заранее подтянуть все необходимые данные: | ||
| ``` | ||
| @trips = Trip.where(from: @from, to: @to).order(:start_time).preload(bus: :services) | ||
| ``` | ||
| - смотрим как результата bullet больше не выдаёт предупреждение, а в rack-mini-profiler видно что мы избивились от N+1 | ||
| ``` | ||
| Executing: trips#index 5.4 +3.0 2 sql 0.5 | ||
| Rendering: trips/index.html.erb 228.3 +7.5 5 sql 8.5 | ||
| ``` | ||
| время запроса уменьшилось для medium.json с 600ms до 357ms, а для large.json c 11363ms до 8529.1 ms | ||
| ### Ваша находка 2 | ||
| - для начала смотрим на rack-mini-profiler, видно что лишних запросов больше нету, но тратится много времени на рендеринг _services | ||
| - | ||
| ``` | ||
| GET http://localhost:3000/%D0%B0%D0%B2%D1%82%... 1109.9 +0.0 | ||
| Executing: trips#index 17.0 +2.0 2 sql 1.2 | ||
| Rendering: trips/index.html.erb 3060.9 +7.3 5 sql 95.2 | ||
| Rendering: trips/_services.html.erb 2.0 +1405.0 | ||
| Rendering: trips/_services.html.erb 2.0 +1410.9 | ||
| ``` | ||
| - заменяем обычный rednder на `render partial` | ||
| - скорость загрузки страницы упала с 8529ms до 3485.5ms | ||
| ``` | ||
| GET http://localhost:3000/%D0%B0%D0%B2%D1%82%... 1131.7 +0.0 | ||
| Executing: trips#index 5.2 +3.0 2 sql 0.6 | ||
| Rendering: trips/index.html.erb 2348.6 +7.4 5 sql 52.8 | ||
| ``` | ||
| ### Ваша находка 3 | ||
| 1) через rack-mini-profiler видим что есть один запрос с фильтрами, можем попробовать ускорить его через индексы | ||
| ``` | ||
| T+18.3 ms | ||
| SELECT "trips".* FROM "trips" WHERE "trips"."from_id" = $1 AND "trips"."to_id" = $2 ORDER BY "trips"."start_time" ASC; | ||
| ``` | ||
| 2) добавил индекс на фильтруемые данные сразу отсортированные | ||
| `add_index :trips, %i[from_id to_id start_time], order: {start_time: :asc}, algorithm: :concurrently` | ||
|
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. я бы start_time не стал в этот составной индекс добавлять [from_id, to_id] можно, а start_time лучше отдельно |
||
| 3) как результат запрос стал немного быстрее | ||
| ``` | ||
| T+9.5 ms | ||
| SELECT "trips".* FROM "trips" WHERE "trips"."from_id" = $1 AND "trips"."to_id" = $2 ORDER BY "trips"."start_time" ASC; | ||
| ``` | ||
| но так же прибавил в памяти, для нас пока не влияет но надо учитывать index_trips_on_from_id_and_to_id_and_start_time 2.19 MB. | ||
| Выйгрыш по скорости не такой большой | ||
|
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. в данном случае маленькая изначально доля уходит на выполнение этого SQL-запроса, поэтому выигрыша большого не может быть но если бы мы заходили с точки зрения оптимизации БД, то там бы это очень помогло |
||
| ### Ваша находка 4 | ||
| 1) через rack-mini-profiler видим что есть запрос на count, попробуем заменить его на обработку через ruby. | ||
| ``` | ||
| T+8.6 ms | ||
| SELECT COUNT(*) FROM "trips" WHERE "trips"."from_id" = $1 AND "trips"."to_id" = $2; | ||
| ``` | ||
| общее время 3194.9 ms | ||
| 2) заменил count на size (пришлось добавить еще load в trips controller: `@trips = Trip.where(from: @from, to: @to).order(:start_time).preload(bus: :services).load` ) | ||
| 3) как результат запрос в базу пропал, скорость всего запроса упала в среднем до 2993.5 ms | ||
| ## Результаты | ||
| - В результате проделанной оптимизации наконец удалось обработать файл с данными и уложиться в поставленные нами метрики. | ||
| - Удалось улучшить отрисовку страницы расписаний с 11.3с до 0.3с. | ||
|
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. 0,3 или 3? |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| 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,7 @@ | ||
| class AddIndexToTripsFromIdAndToId < ActiveRecord::Migration[8.0] | ||
| disable_ddl_transaction! | ||
|
|
||
| def change | ||
| add_index :trips, %i[from_id to_id start_time], order: {start_time: :asc}, algorithm: :concurrently | ||
| 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.
<3