Skip to content
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

Comimt task 3 #5

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.3
2.6.1
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

сбросил версию потому что инета не хватает чтобы закачать все гемы, на мобилке пока сижу.

18 changes: 12 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
# frozen_string_literal: true

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.3'
ruby '2.6.1'

gem 'rails', '~> 5.2.3'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'pg', '>= 0.18', '< 2.0'
gem 'puma', '~> 3.11'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'rails', '~> 5.2.3'

group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'byebug', platforms: %i[mri mingw x64_mingw]
end

group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'web-console', '>= 3.3.0'
end

group :test do
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]

gem 'activerecord-import'
gem 'oj'
gem 'ruby-prof', '~> 0.18'
35 changes: 21 additions & 14 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ GEM
activemodel (= 5.2.3)
activesupport (= 5.2.3)
arel (>= 9.0)
activerecord-import (1.0.2)
activerecord (>= 3.2)
activestorage (5.2.3)
actionpack (= 5.2.3)
activerecord (= 5.2.3)
Expand All @@ -43,15 +45,15 @@ GEM
minitest (~> 5.1)
tzinfo (~> 1.1)
arel (9.0.0)
bindex (0.6.0)
bootsnap (1.4.2)
bindex (0.8.1)
bootsnap (1.4.5)
msgpack (~> 1.0)
builder (3.2.3)
byebug (11.0.1)
concurrent-ruby (1.1.5)
crass (1.0.4)
erubi (1.8.0)
ffi (1.10.0)
ffi (1.11.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
i18n (1.6.0)
Expand All @@ -69,16 +71,17 @@ GEM
mimemagic (~> 0.3.2)
method_source (0.9.2)
mimemagic (0.3.3)
mini_mime (1.0.1)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.11.3)
msgpack (1.2.9)
nio4r (2.3.1)
nokogiri (1.10.2)
msgpack (1.3.1)
nio4r (2.5.1)
nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
oj (3.9.1)
pg (1.1.4)
puma (3.12.1)
rack (2.0.6)
rack (2.0.7)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.3)
Expand All @@ -97,18 +100,19 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
rails-html-sanitizer (1.2.0)
loofah (~> 2.2, >= 2.2.2)
railties (5.2.3)
actionpack (= 5.2.3)
activesupport (= 5.2.3)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
rake (12.3.2)
rake (12.3.3)
rb-fsevent (0.10.3)
rb-inotify (0.10.0)
ffi (~> 1.0)
ruby-prof (0.18.0)
ruby_dep (1.5.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
Expand All @@ -126,25 +130,28 @@ GEM
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
websocket-driver (0.7.0)
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
websocket-extensions (0.1.4)

PLATFORMS
ruby

DEPENDENCIES
activerecord-import
bootsnap (>= 1.1.0)
byebug
listen (>= 3.0.5, < 3.2)
oj
pg (>= 0.18, < 2.0)
puma (~> 3.11)
rails (~> 5.2.3)
ruby-prof (~> 0.18)
tzinfo-data
web-console (>= 3.3.0)

RUBY VERSION
ruby 2.6.3p62
ruby 2.6.1p33

BUNDLED WITH
2.0.2
1.17.3
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

Expand Down
14 changes: 13 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,31 @@
В этом задании вам предлагается оптимизировать учебное `rails`-приложение.

Для запуска потребуется:

- `ruby 2.6.3`
- `postgres`

Запуск и использование:

- `bundle install`
- `bin/setup`
- `rails s`
- `open http://localhost:3000/автобусы/Самара/Москва`

## Описание учебного приложения

Зайдя на страницу `автобусы/Самара/Москва` вы увидите расписание автобусов по этому направлению.

## Что оптимизировать

### Импорт данных

При выполнении `bin/setup` в базу данных загружаются данные о рейсах из файла `fixtures/small.json`

Сама загрузка данных из файла делается очень наивно.

В комплекте с заданием поставляются файлы

- `example.json`
- `small.json` (1K трипов)
- `medium.json` (10K трипов)
Expand All @@ -33,25 +38,30 @@
`rake reload_json[fixtures/large.json]`

Для импорта этого объёма данных

- вам может помочь гем https://github.com/zdennis/activerecord-import
- избегайте создания лишних транзакций
- профилируйте скрипт импорта изученными инструментами и оптимизируйте его!

### Отображение расписаний

Сами страницы расписаний тоже формируются не эффективно и при росте объёмов начинают сильно тормозить.

Нужно найти и устранить проблемы, замедляющие формирование этих страниц.

Попробуйте воспользоваться

- [ ] `rack-mini-profiler`
- [ ] `rails panel`
- [ ] `bullet`
- [ ] `explain` запросов

### Сдача задания

`PR` в этот репозиторий с кодом и описанием.

В описании указать:

- за какое время выполняется импорт файла `fixtures/large.json`
- за какое время рендерится страница `автобусы/Самара/Москва`

Expand All @@ -60,7 +70,8 @@
Лучше написать защититься от такой регрессии тестом.

### bonus
*Советую приступать к бонусу только после завершения основной части ДЗ.*

_Советую приступать к бонусу только после завершения основной части ДЗ._

В качестве бонуса нужно справиться с импортом файлов `1M.json` (`codename mega`) и `10M.json` (`codename hardcore`)

Expand All @@ -72,6 +83,7 @@
### Мета-информация о данных

При реализации импорта нужно учесть наши инсайдерские знания о данных:

- первичным ключом для автобуса считаем `(model, number)`
- уникальных автобусов в файле `10M.json` ~ `10_000`
- ункикльных городов в файле `10M.json` ~ `100`
Expand Down
2 changes: 2 additions & 0 deletions app/models/buses_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class BusesService < ApplicationRecord
end
2 changes: 2 additions & 0 deletions config.ru
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# This file is used by Rack-based servers to start the application.

require_relative 'config/environment'
Expand Down
132 changes: 106 additions & 26 deletions lib/tasks/utils.rake
Original file line number Diff line number Diff line change
@@ -1,34 +1,114 @@
# frozen_string_literal: true

# Наивная загрузка данных из json-файла в БД
# rake reload_json[fixtures/small.json]

require 'benchmark'

@cities = {}
@services = {}
@buses = {}

def import_trip(trip)
from_id = @cities[trip['from']]
unless from_id
from_id = @cities.size + 1
@cities[trip['from']] = from_id
end

to_id = @cities[trip['to']]
unless to_id
to_id = @cities.size + 1
@cities[trip['to']] = to_id
end

trip['bus']['services'].each do |service|
service_id = @services[service]
unless service_id
service_id = @services.size + 1
@services[service] = service_id
end
end

bus_id = @buses[trip['bus']['number']].try(:[], :id)
unless bus_id
bus_id = @buses.size + 1
services = trip['bus']['services'].map do |s|
@services[s]
end
@buses[trip['bus']['number']] = { id: bus_id, model: trip['bus']['model'], services: services }
end

bus_id = @buses[trip['bus']['number']][:id]
ActiveRecord::Base.connection.raw_connection.put_copy_data("#{@cities[trip['from']]};#{@cities[trip['to']]};#{trip['start_time']};#{trip['duration_minutes']};#{trip['price_cents']};#{bus_id}\n")
end

task :reload_json, [:file_name] => :environment do |_task, args|
json = JSON.parse(File.read(args.file_name))

ActiveRecord::Base.transaction do
City.delete_all
Bus.delete_all
Service.delete_all
Trip.delete_all
ActiveRecord::Base.connection.execute('delete from buses_services;')

json.each do |trip|
from = City.find_or_create_by(name: trip['from'])
to = City.find_or_create_by(name: trip['to'])
time = Benchmark.realtime do
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 ';'"

City.delete_all
Bus.delete_all
Service.delete_all
Trip.delete_all
ActiveRecord::Base.connection.execute('delete from buses_services;')
ActiveRecord::Base.connection.reset_pk_sequence!('cities')
ActiveRecord::Base.connection.reset_pk_sequence!('buses')
ActiveRecord::Base.connection.reset_pk_sequence!('services')

ActiveRecord::Base.connection.raw_connection.copy_data trips_command do
File.open(args.file_name) do |ff|
nesting = 0
str = +''

until ff.eof?
ch = ff.read(1) # читаем по одному символу
if ch == '{' # начинается объект, повышается вложенность
nesting += 1
str << ch
elsif ch == '}' # заканчивается объект, понижается вложенность
nesting -= 1
str << ch
if nesting == 0 # если закончился объкет уровня trip, парсим и импортируем его
trip = Oj.load(str)

import_trip(trip)

str = +''
end
elsif nesting >= 1
str << ch
end
end
end
end

cities = []
@cities.each do |city|
cities << City.new(name: city[0])
end

buses = []
buses_services = []
@buses.each do |value|
buses << Bus.new(number: value[0], model: value[1][:model])
value[1][:services].map do |s|
buses_services << BusesService.new(bus_id: value[1][:id], service_id: s)
end
end

services = []
trip['bus']['services'].each do |service|
s = Service.find_or_create_by(name: service)
services << s
@services.each do |value|
services << Service.new(name: value[0])
end
bus = Bus.find_or_create_by(number: trip['bus']['number'])
bus.update(model: trip['bus']['model'], services: services)

Trip.create!(
from: from,
to: to,
bus: bus,
start_time: trip['start_time'],
duration_minutes: trip['duration_minutes'],
price_cents: trip['price_cents'],
)

City.import cities
Service.import services
Bus.import buses
BusesService.import buses_services
end
end

puts "Finish in #{time.round(2)}"
end
11 changes: 11 additions & 0 deletions test/fixtures/buses_services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

# This model initially had no columns defined. If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
one: {}
# column: value
#
two: {}
# column: value
Loading