-
Notifications
You must be signed in to change notification settings - Fork 115
Task 3 #31
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?
Task 3 #31
Changes from all commits
7b21306
3e0f3fa
5b211cf
5e543be
a913ff9
44c33c9
9872efd
ace9d02
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 |
---|---|---|
@@ -1,4 +1,13 @@ | ||
/.bundle | ||
/tmp | ||
/log | ||
/public | ||
log/ | ||
tmp/ | ||
.generators | ||
.idea/ | ||
data*.txt | ||
result.json | ||
ruby_prof_reports/ | ||
.ruby-version | ||
.rubocop.yml | ||
/docker-valgrind-massif/ | ||
/stackprof_reports/ | ||
1M.json | ||
.byebug_history |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,6 @@ class TripsController < ApplicationController | |
def index | ||
@from = City.find_by_name!(params[:from]) | ||
@to = City.find_by_name!(params[:to]) | ||
@trips = Trip.where(from: @from, to: @to).order(:start_time) | ||
@trips = Trip.eager_load(bus: [:services]).where(from: @from, to: @to).order(:start_time).load | ||
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. Плюсик за |
||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
class TripsLoad | ||
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. Плюсик за вынос в сервис |
||
|
||
def self.perform(file_name) | ||
json = Oj.load(File.read(file_name)) | ||
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. Тут разбухание памяти при загрузке большого файла |
||
ActiveRecord::Base.transaction do | ||
City.delete_all | ||
Bus.delete_all | ||
Service.delete_all | ||
Trip.delete_all | ||
ActiveRecord::Base.connection.execute('delete from buses_services;') | ||
trips_array = [] | ||
|
||
json.each_with_index do |trip, index| | ||
|
||
from = City.find_cached_or_create(trip['from']) | ||
to = City.find_cached_or_create(trip['to']) | ||
bus = Bus.find_cached_or_create(trip['bus']) | ||
|
||
trip_hash = { | ||
from_id: from.id, | ||
to_id: to.id, | ||
bus_id: bus.id, | ||
start_time: trip['start_time'], | ||
duration_minutes: trip['duration_minutes'], | ||
price_cents: trip['price_cents'] | ||
} | ||
trips_array << trip_hash | ||
if index%1000 == 999 | ||
Trip.import(trips_array, validate: true, validate_uniqueness: true) | ||
trips_array =[] | ||
end | ||
end | ||
Trip.import(trips_array, validate: true, validate_uniqueness: true) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
<li>Сервисы в автобусе:</li> | ||
<ul> | ||
<% services.each do |service| %> | ||
<%= render "service", service: service %> | ||
<li><%= "#{service.name}" %></li> | ||
<% end %> | ||
</ul> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Case-study оптимизации загрузки данных | ||
|
||
## Актуальная проблема | ||
|
||
Необходимо импортировать файл с данными fixtures/large.json с 100_000 трипов менее чем за 1 минуту. | ||
|
||
У нас уже была rake задача, которая умела делать нужную обработку. | ||
|
||
Но она недостаточно производительна | ||
Так: | ||
small.json с 1K трипов обрабатывается за 9 секунд | ||
medium.json с 10K трипов обрабатывается за 80 секунд | ||
|
||
По грубым оценкам large.json с 100K трипов будет обрабатываться не менее 800 секунд. | ||
|
||
## Формирование метрики | ||
Для анализа влияния изменений на скорость работы возьмем время обработки файла small.json - 9 секунд | ||
|
||
## Гарантия корректности работы оптимизированной программы | ||
Для проверки корректности работы обновленной программы был написан тест, который заполнял БД данными из файла example.json, потом выгружал БД в json и сравнивал с исходным. | ||
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. 👍 👍 |
||
|
||
## Feedback-Loop | ||
|
||
1. Проверка корректности работы, замер метрики, сбор отчета | ||
2. Изучение отчетов профайлеров | ||
|
||
Например | ||
rails test test/system/load_test.rb && rake reload_json"[fixtures/small.json]" && rake pghero:capture_query_stats | ||
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. 👍 |
||
|
||
## Вникаем в детали системы, чтобы найти главные точки роста | ||
|
||
Для того, чтобы найти "точки роста" для оптимизации я воспользовался | ||
- pg_hero | ||
|
||
Вот какие проблемы удалось найти и решить | ||
|
||
### Находка №1 | ||
- pg_hero показал что запросы | ||
```SELECT "services".* FROM "services" INNER JOIN "buses_services" ON "services"."id" = "buses_services"."service_id" WHERE "buses_services"."bus_id" = $1``` | ||
занимают 25% времени. explain показал последовательное чтение. | ||
- добавить индекс ```:buses_services, [:bus_id, :service_id] ``` | ||
- значимого изменения метрики нет | ||
- в отчете pg_hero запрос переместился с верхней строчки вниз | ||
|
||
### Находка №2 | ||
- pg_hero показал что запросы ```SELECT ? AS one FROM "buses" WHERE "buses"."number" = $1 AND "buses"."id" != $2 LIMIT $3``` занимают 23% времени | ||
- добавить индекс ```:buses, :number``` | ||
- метрика изменилась незначительно на 0.3 секунды | ||
- в отчете pg_hero запрос переместился с верхней строчки вниз | ||
|
||
### Находка №3 | ||
- pg_hero показал что запросы ```SELECT "services".* FROM "services" WHERE "services"."name" = $1 LIMIT $2``` занимают 18% времени | ||
- принято решение заполнить все возможные сервисы предварительно и собрать в хеш и не запрашивать бд | ||
- метрика снизилась примерно на 1,5 секунды до 7,2 секунд | ||
- запрос исчез из отчета | ||
|
||
### Находка №4 | ||
- pg_hero показал что запросы ```SELECT "cities".* FROM "cities" WHERE "cities"."name" = $1 LIMIT $2``` занимают 12% времени | ||
- принято решение кешировать города в хеш | ||
- метрика снизилась до 6 секунд | ||
- запросы стал занимать менее 0.1% времени | ||
|
||
### Находка №5 | ||
- pg_hero показал что запросы | ||
```SELECT "services".* FROM "services" INNER JOIN "buses_services" ON "services"."id" = "buses_services"."service_id" WHERE "buses_services"."bus_id" = $1``` | ||
занимают 47% времени. | ||
- принято решение переписать поиск и заполнение автобуса | ||
- метрика уменьшилась до 4,8 секунды | ||
- запрос исчез из отчета | ||
|
||
### Протеситирована метрика время обработки medium.json - 21 секунда. Переходим на эту метрику | ||
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. 👍 |
||
|
||
### Находка №6 | ||
- pg_hero показал что запросы | ||
```SELECT "buses".* FROM "buses" WHERE "buses"."number" = $1 LIMIT $2``` занимают 33% времени | ||
- принято решение кешировать автобусы аналогично городам | ||
- метрика снизилась до 14 секунд | ||
- запрос исчез из отчета | ||
|
||
### Находка №7 | ||
- pg_hero показал что запросы вставки в таблицу trip занимают 40% времени | ||
- вставлять записи пачками | ||
- ничего не изменилось, потому что activerecord запрограммирован вставлять по одной | ||
|
||
### Находка №8 | ||
- Существует гем activerecord-import, который позволяет вставлять записи пачками | ||
- применить гем | ||
- метрика уменьшилась до 7 секунд | ||
- запросы вставки в таблицу trips самые емкие по времени, но с этим ничего не поделать | ||
|
||
## Результаты | ||
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. Очень чётко сработали! |
||
В результате проделанной оптимизации удалось обработать файл с данными за заданое время. Сейчас оно составляет 22 секунды. | ||
|
||
###Какими ещё результами можете поделиться | ||
1. Также можно применить activerecord-import на остальные модели. | ||
1. В rails 6 в классе ActiveRecord есть метод insert_all. | ||
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. При необходимости быстрой работы с бд надо организовывать ее на низком уровне. | ||
|
||
## Защита от регрессии производительности | ||
|
||
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с проверкой загрузки файла medium.json менее чем за 8 секунд. | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
👍