Задание №3
В этом задании вам предлагается оптимизировать учебное rails
-приложение.
Для запуска потребуется:
ruby 2.6.3
postgres
Запуск и использование:
bundle install
bin/setup
rails s
open http://localhost:3000/автобусы/Самара/Москва
Описание учебного приложения
Зайдя на страницу автобусы/Самара/Москва
вы увидите расписание автобусов по этому направлению.
Что оптимизировать
A. Импорт данных
При выполнении bin/setup
в базу данных загружаются данные о рейсах из файла fixtures/small.json
Сама загрузка данных из файла делается очень наивно (и не эффективно).
В комплекте с заданием поставляются файлы
example.json
small.json
(1K трипов)medium.json
(10K трипов)large.json
(100K трипов)
Нужно оптимизировать механизм перезагрузки расписания из файла так, чтобы он импортировал файл large.json
в пределах минуты.
rake reload_json[fixtures/large.json]
Для импорта этого объёма данных
- вам может помочь гем https://github.com/zdennis/activerecord-import
- избегайте создания лишних транзакций
- профилируйте скрипт импорта изученными инструментами и оптимизируйте его!
Б. Отображение расписаний
Сами страницы расписаний тоже формируются не эффективно и при росте объёмов начинают сильно тормозить.
Нужно найти и устранить проблемы, замедляющие формирование этих страниц.
Попробуйте воспользоваться
-
rack-mini-profiler
-
rails panel
-
bullet
-
explain
запросов
Сдача задания
PR
в этот репозиторий с кодом и описанием.
В описании указать:
- за какое время выполняется импорт файла
fixtures/large.json
- за какое время рендерится страница
автобусы/Самара/Москва
Перед сдачей нужно убедиться, что результат работы страницы автобусы/Самара/Москва
для данных из файла fixtures/example.json
не изменился, то есть не было внесено никаких функциональных изменений, только оптимизации.
Лучше защититься от такой регрессии тестом.
bonus
Советую приступать к бонусу только после завершения основной части ДЗ.
В качестве бонуса нужно справиться с импортом файлов 1M.json
(codename mega
) и 10M.json
(codename hardcore
)
Подсказки
Мета-информация о данных
При реализации импорта нужно учесть наши инсайдерские знания о данных:
- первичным ключом для автобуса считаем
(model, number)
- уникальных автобусов в файле
10M.json
~10_000
- ункикльных городов в файле
10M.json
~100
- сервисов ровно
10
, те что перечислены вService::SERVICES
Стриминг
Файл 10M.json
весит ~ 3Gb
.
Поэтому лучше не пытаться грузить его целиком в память и парсить.
Вместо этого лучше читать и парсить его потоково.
Это более-менее привычная схема, но знали ли вы, что в Posgtres
тоже можно импортировать данные потоком?
Вот набросок потокового чтения из файла с потоковой записью в Postgres
:
@cities = {}
ActiveRecord::Base.transaction do
trips_command =
"copy trips (from_id, to_id, start_time, duration_minutes, price_cents, bus_id) from stdin with csv delimiter ';'"
ActiveRecord::Base.connection.raw_connection.copy_data trips_command do
File.open(file_name) do |ff|
nesting = 0
str = +""
while !ff.eof?
ch = ff.read(1) # читаем по одному символу
case
when ch == '{' # начинается объект, повышается вложенность
nesting += 1
str << ch
when ch == '}' # заканчивается объект, понижается вложенность
nesting -= 1
str << ch
if nesting == 0 # если закончился объкет уровня trip, парсим и импортируем его
trip = Oj.load(str)
import(trip)
progress_bar.increment
str = +""
end
when nesting >= 1
str << ch
end
end
end
end
end
def import(trip)
from_id = @cities[trip['from']]
if !from_id
from_id = cities.size + 1
@cities[trip['from']] = from_id
end
# ...
# стримим подготовленный чанк данных в postgres
connection.put_copy_data("#{from_id};#{to_id};#{trip['start_time']};#{trip['duration_minutes']};#{trip['price_cents']};#{bus_id}\n")
end
Plan
- чистим базу
- идём по огромному файлу
- по пути формируем в памяти вспомогательные справочники ограниченного размера (
cities
,buses
,buses_services
) - сразу же стримим основные данные в базу (
trips
), чтобы не накапливать их - после завершения файла сохраняем в базу сформированные справочники
Notes
Можно использовать любые библиотеки для потоковой обработки json
и вообще