From 7b213063bad3ca0d57e587bd1f9294eb1691b70c Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Fri, 6 Mar 2020 00:57:43 +0300 Subject: [PATCH 1/8] load test --- .gitignore | 15 ++++-- Gemfile | 3 +- Gemfile.lock | 4 +- app/models/trip.rb | 15 ++++++ case-study.md | 113 +++++++++++++++++++++++++++++++++++++++ lib/tasks/utils.rake | 5 ++ test/system/load_test.rb | 11 ++++ 7 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 case-study.md create mode 100644 test/system/load_test.rb diff --git a/.gitignore b/.gitignore index 59c74047..8c3b46a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ -/.bundle -/tmp -/log -/public +log/ +tmp/ +.generators +.idea/ +data*.txt +result.json +ruby_prof_reports/ +.ruby-version +.rubocop.yml +/docker-valgrind-massif/ +/stackprof_reports/ diff --git a/Gemfile b/Gemfile index e20b1260..731d877a 100644 --- a/Gemfile +++ b/Gemfile @@ -20,7 +20,8 @@ group :development do end group :test do + gem 'minitest' 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: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/Gemfile.lock b/Gemfile.lock index fccf6f5f..01cd756c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -137,14 +137,14 @@ DEPENDENCIES bootsnap (>= 1.1.0) byebug listen (>= 3.0.5, < 3.2) + minitest pg (>= 0.18, < 2.0) puma (~> 3.11) rails (~> 5.2.3) - tzinfo-data web-console (>= 3.3.0) RUBY VERSION ruby 2.6.3p62 BUNDLED WITH - 2.0.2 + 2.1.4 diff --git a/app/models/trip.rb b/app/models/trip.rb index 9d63dfff..d7bed72a 100644 --- a/app/models/trip.rb +++ b/app/models/trip.rb @@ -29,4 +29,19 @@ def to_h }, } end + + def to_h_old + { + 'from' => from.name, + 'to' => to.name, + 'start_time' => start_time, + 'duration_minutes' => duration_minutes, + 'price_cents' => price_cents, + 'bus' => { + 'number' => bus.number, + 'model' => bus.model, + 'services' => bus.services.map(&:name), + }, + } + end end diff --git a/case-study.md b/case-study.md new file mode 100644 index 00000000..bde9f94d --- /dev/null +++ b/case-study.md @@ -0,0 +1,113 @@ +# Case-study оптимизации + +## Актуальная проблема + +Необходимо импортировать файл с данными fixtures/large.json с 100_000 трипов менее чем за 1 минуту. + +У нас уже была кфлу задача, которая умела делать нужную обработку. + +Но она недостаточно производительна +Так: + small.json с 1K трипов обрабатывается за 10 секунд + medium.json с 10K трипов обрабатывается за 80 секунд + +По грубым оценкам large.json с 100K трипов будет обрабатываться не менее 800 секунд. + +## Формирование метрики +Для анализа влияния изменений на скорость работы возьмем время обработки файла small.json - 10 секунд + +## Гарантия корректности работы оптимизированной программы +Для проверки корректности работы обновленной программы был написан тест, который заполнял БД данными из файла example.json, потом выгружал БД в json и сравнивал с исходным. + +## Feedback-Loop +Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за 10-20 секунд + +1. Для получения обратной связи я пользуюсь командой которая проверяет корректность работы, потом замеряет метрику +1. После чего я запускаю профайлер(ы) и по отчетам анализирую изменения + +## Вникаем в детали системы, чтобы найти главные точки роста + +Для того, чтобы найти "точки роста" для оптимизации я воспользовался +- гемом memory_profiler + +Вот какие проблемы удалось найти и решить + +### Находка №1 +- memory_profiler показал, что самую большую часть памяти среди объектов занимают массивы +- решено отказаться от сбора данных в массивы, а обрабатывать и записывать отчет по каждому пользователю +- метрика уменьшилась до 15 мегабайт +- как изменился отчёт профилировщика теперь массивы занимают 13.36 MB + +### Изменение метрики + Замеряю использованную память при обработке 200_000 строк - 15 MB + Замеряю использованную память при обработке 1_500_000 строк - 17 MB + Замеряю использованную память при обработке всего рабочего файла - 17 MB + +## Результаты +В результате проделанной оптимизации удалось обработать файл с данными. +Грубое измерение метрики показывает, что программа укладывается в бюджет. + +Удалось улучшить метрику системы и уложиться в заданный бюджет. + +*Какими ещё результами можете поделиться* + +## Защита от регрессии производительности + +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с фиксацией размера потребляемой памяти при обработки файла с 500 тысячами строк. + +## Checklist +- [+] Построить и проанализировать отчёт гемом `memory_profiler` +- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `Flat`; +**не нагляден** + +- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `Graph`; +**удобен, но надо разносить код в именованые методы** + +- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `CallStack`; +**очень удобное представление, но надо разносить код в именованые методы** + +- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`; +**В убунту не нашел QCachegrind, есть kcachegrind, там нет отображения в виде блок-схемы** + +- [+] Построить и проанализировать текстовый отчёт `stackprof` для рабочего файла + + При попытке получить данные по методу "Oh My ZSH!" воспринимает опцию --method Object#work как какую-то команду для себя. Надо писать --method "Object#work" + + **Не наглядно** + - [-] Построить и проанализировать отчёт `flamegraph` с помощью `stackprof` и визуализировать его в `speedscope.app`; +**Не понял как** + +- [+] Построить график потребления памяти в `valgrind massif visualier` и включить скриншот в описание вашего `PR`; +![](valgrind-massif-visualizer.png) + +# Что еще можно сделать + +### Находка №2 +- По опыту известно, что библиотека json исолпьзует довольно много памяти +- форматировать вывод самостоятельно +- для ускорения feedback-loop взята метрика: память для обработки 1_500_000 записей, она составляет 17700 - 18000 килобайт +- Метрика значимо не изменилась + +### Находка №3 +- отчет MemoryProfiler показывает, что очень много раз выделяются одинаковые строки +- использовать константы +- Метрика значимо не изменилась +- использование памяти с выключенным GC снизилось с 98 до 90 мб. + +### Находка №4 +- отчет MemoryProfiler показывает, что все еще много раз выделюятся одинаковые строки при определение типа записи в исходном файле и разбиении их +- оптимизировать эти строки +- Метрика снизилась до 16900-17100 кб +- использование памяти с выключенным GC снизилось с 90 до 86 мб. +- ** читаемость кода снизилась** + +### Находка №5 +- отчет MemoryProfiler показывает, что строка "result.json" выделяется столько раз, сколько происходит записаь в файл, несмотря на использование константы +- один раз открыть файл для записи +- Метрика значимо не изменилась +- использование памяти с выключенным GC снизилось с до 78 мб. + +Отчет MemoryProfiler не показывает избыточного выделения памяти. + + + diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake index 540fe871..7b6800b0 100644 --- a/lib/tasks/utils.rake +++ b/lib/tasks/utils.rake @@ -1,6 +1,10 @@ # Наивная загрузка данных из json-файла в БД # rake reload_json[fixtures/small.json] + +desc 'Загрузка данных из файла' task :reload_json, [:file_name] => :environment do |_task, args| + desc 'Загрузка данных из файла' + start_time = Time.now json = JSON.parse(File.read(args.file_name)) ActiveRecord::Base.transaction do @@ -31,4 +35,5 @@ task :reload_json, [:file_name] => :environment do |_task, args| ) end end + puts "Execution time #{Time.now - start_time} " end diff --git a/test/system/load_test.rb b/test/system/load_test.rb new file mode 100644 index 00000000..74356ec3 --- /dev/null +++ b/test/system/load_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' +require 'json' + +class TestLoad < ActiveSupport::TestCase + def test_correct_load + source_json = JSON.load(File.read('fixtures/example.json')) + system 'bin/rake reload_json[fixtures/example.json]' + result_json = Trip.all.map(&:to_h_old) + assert_equal source_json, result_json + end +end \ No newline at end of file From 3e0f3fa8f3b5d5bdc5b0e5139bd121ce4b98a931 Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Sun, 8 Mar 2020 01:14:24 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 3 + Gemfile.lock | 8 +++ app/models/bus.rb | 11 +++ app/models/city.rb | 6 ++ app/models/service.rb | 14 ++++ case-study.md | 145 +++++++++++++++++++-------------------- config/routes.rb | 2 + db/schema.rb | 16 ++++- lib/tasks/utils.rake | 54 +++++++++------ test/system/load_test.rb | 2 +- 10 files changed, 164 insertions(+), 97 deletions(-) diff --git a/Gemfile b/Gemfile index 731d877a..393b00c7 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,9 @@ gem 'rails', '~> 5.2.3' gem 'pg', '>= 0.18', '< 2.0' gem 'puma', '~> 3.11' gem 'bootsnap', '>= 1.1.0', require: false +gem 'pghero' +gem 'oj' +gem 'activerecord-import' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 01cd756c..bec59763 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,6 +33,8 @@ GEM activemodel (= 5.2.3) activesupport (= 5.2.3) arel (>= 9.0) + activerecord-import (1.0.4) + activerecord (>= 3.2) activestorage (5.2.3) actionpack (= 5.2.3) activerecord (= 5.2.3) @@ -76,7 +78,10 @@ GEM nio4r (2.3.1) nokogiri (1.10.2) mini_portile2 (~> 2.4.0) + oj (3.10.2) pg (1.1.4) + pghero (2.4.1) + activerecord (>= 5) puma (3.12.1) rack (2.0.6) rack-test (1.1.0) @@ -134,11 +139,14 @@ PLATFORMS ruby DEPENDENCIES + activerecord-import bootsnap (>= 1.1.0) byebug listen (>= 3.0.5, < 3.2) minitest + oj pg (>= 0.18, < 2.0) + pghero puma (~> 3.11) rails (~> 5.2.3) web-console (>= 3.3.0) diff --git a/app/models/bus.rb b/app/models/bus.rb index 1dcc54cb..1918e7d5 100644 --- a/app/models/bus.rb +++ b/app/models/bus.rb @@ -17,4 +17,15 @@ class Bus < ApplicationRecord validates :number, presence: true, uniqueness: true validates :model, inclusion: { in: MODELS } + + def self.find_cached_or_create(bus_data) + @all_buses ||= Bus.all.map{ |bus| [bus.number, bus]}.to_h + unless @all_buses[bus_data['number']] + @all_buses[bus_data['number']] ||= Bus.create!( + model: bus_data['model'], + services: Service.find_dumped(bus_data['services']), + number: bus_data['number']) + end + @all_buses[bus_data['number']] + end end diff --git a/app/models/city.rb b/app/models/city.rb index 19ec7f36..ae3a7a7c 100644 --- a/app/models/city.rb +++ b/app/models/city.rb @@ -5,4 +5,10 @@ class City < ApplicationRecord def name_has_no_spaces errors.add(:name, "has spaces") if name.include?(' ') end + + def self.find_cached_or_create(name) + name = name.gsub(' ','') + @all_cities ||= City.all.map{|city| [city.name, city]}.to_h + @all_cities[name] ||= City.create!(name:name) + end end diff --git a/app/models/service.rb b/app/models/service.rb index 9cbb2a32..d88c8d64 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -16,4 +16,18 @@ class Service < ApplicationRecord validates :name, presence: true validates :name, inclusion: { in: SERVICES } + + def self.find_dumped(names) + @all_dumped ||= dump_all_to_db + @all_dumped.slice(*names).values + end + + private + + def self.dump_all_to_db + SERVICES.sort.reverse.each do |name| + Service.find_or_create_by!(name: name) + end + Service.all.map{ |s| [s.name, s]}.to_h + end end diff --git a/case-study.md b/case-study.md index bde9f94d..ef6f17f1 100644 --- a/case-study.md +++ b/case-study.md @@ -4,110 +4,105 @@ Необходимо импортировать файл с данными fixtures/large.json с 100_000 трипов менее чем за 1 минуту. -У нас уже была кфлу задача, которая умела делать нужную обработку. +У нас уже была rake задача, которая умела делать нужную обработку. Но она недостаточно производительна Так: - small.json с 1K трипов обрабатывается за 10 секунд + small.json с 1K трипов обрабатывается за 9 секунд medium.json с 10K трипов обрабатывается за 80 секунд По грубым оценкам large.json с 100K трипов будет обрабатываться не менее 800 секунд. ## Формирование метрики -Для анализа влияния изменений на скорость работы возьмем время обработки файла small.json - 10 секунд +Для анализа влияния изменений на скорость работы возьмем время обработки файла small.json - 9 секунд ## Гарантия корректности работы оптимизированной программы Для проверки корректности работы обновленной программы был написан тест, который заполнял БД данными из файла example.json, потом выгружал БД в json и сравнивал с исходным. ## Feedback-Loop -Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за 10-20 секунд -1. Для получения обратной связи я пользуюсь командой которая проверяет корректность работы, потом замеряет метрику -1. После чего я запускаю профайлер(ы) и по отчетам анализирую изменения - +1. Проверка корректности работы, замер метрики, сбор отчета +2. Изучение отчетов профайлеров + +Например + rails test test/system/load_test.rb && rake reload_json"[fixtures/small.json]" && rake pghero:capture_query_stats + ## Вникаем в детали системы, чтобы найти главные точки роста Для того, чтобы найти "точки роста" для оптимизации я воспользовался -- гемом memory_profiler +- pg_hero Вот какие проблемы удалось найти и решить ### Находка №1 -- memory_profiler показал, что самую большую часть памяти среди объектов занимают массивы -- решено отказаться от сбора данных в массивы, а обрабатывать и записывать отчет по каждому пользователю -- метрика уменьшилась до 15 мегабайт -- как изменился отчёт профилировщика теперь массивы занимают 13.36 MB - -### Изменение метрики - Замеряю использованную память при обработке 200_000 строк - 15 MB - Замеряю использованную память при обработке 1_500_000 строк - 17 MB - Замеряю использованную память при обработке всего рабочего файла - 17 MB - -## Результаты -В результате проделанной оптимизации удалось обработать файл с данными. -Грубое измерение метрики показывает, что программа укладывается в бюджет. - -Удалось улучшить метрику системы и уложиться в заданный бюджет. - -*Какими ещё результами можете поделиться* - -## Защита от регрессии производительности - -Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с фиксацией размера потребляемой памяти при обработки файла с 500 тысячами строк. - -## Checklist -- [+] Построить и проанализировать отчёт гемом `memory_profiler` -- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `Flat`; -**не нагляден** - -- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `Graph`; -**удобен, но надо разносить код в именованые методы** - -- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `CallStack`; -**очень удобное представление, но надо разносить код в именованые методы** - -- [+] Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`; -**В убунту не нашел QCachegrind, есть kcachegrind, там нет отображения в виде блок-схемы** - -- [+] Построить и проанализировать текстовый отчёт `stackprof` для рабочего файла - - При попытке получить данные по методу "Oh My ZSH!" воспринимает опцию --method Object#work как какую-то команду для себя. Надо писать --method "Object#work" - - **Не наглядно** - - [-] Построить и проанализировать отчёт `flamegraph` с помощью `stackprof` и визуализировать его в `speedscope.app`; -**Не понял как** - -- [+] Построить график потребления памяти в `valgrind massif visualier` и включить скриншот в описание вашего `PR`; -![](valgrind-massif-visualizer.png) - -# Что еще можно сделать +- 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 -- По опыту известно, что библиотека json исолпьзует довольно много памяти -- форматировать вывод самостоятельно -- для ускорения feedback-loop взята метрика: память для обработки 1_500_000 записей, она составляет 17700 - 18000 килобайт -- Метрика значимо не изменилась +- 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 -- отчет MemoryProfiler показывает, что очень много раз выделяются одинаковые строки -- использовать константы -- Метрика значимо не изменилась -- использование памяти с выключенным GC снизилось с 98 до 90 мб. +- pg_hero показал что запросы ```SELECT "services".* FROM "services" WHERE "services"."name" = $1 LIMIT $2``` занимают 18% времени +- принято решение заполнить все возможные сервисы предварительно и собрать в хеш и не запрашивать бд +- метрика снизилась примерно на 1,5 секунды до 7,2 секунд +- запрос исчез из отчета ### Находка №4 -- отчет MemoryProfiler показывает, что все еще много раз выделюятся одинаковые строки при определение типа записи в исходном файле и разбиении их -- оптимизировать эти строки -- Метрика снизилась до 16900-17100 кб -- использование памяти с выключенным GC снизилось с 90 до 86 мб. -- ** читаемость кода снизилась** +- pg_hero показал что запросы ```SELECT "cities".* FROM "cities" WHERE "cities"."name" = $1 LIMIT $2``` занимают 12% времени +- принято решение кешировать города в хеш +- метрика снизилась до 6 секунд +- запросы стал занимать менее 0.1% времени ### Находка №5 -- отчет MemoryProfiler показывает, что строка "result.json" выделяется столько раз, сколько происходит записаь в файл, несмотря на использование константы -- один раз открыть файл для записи -- Метрика значимо не изменилась -- использование памяти с выключенным GC снизилось с до 78 мб. +- 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 секунда. Переходим на эту метрику + +### Находка №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 самые емкие по времени, но с этим ничего не поделать -Отчет MemoryProfiler не показывает избыточного выделения памяти. +## Результаты +В результате проделанной оптимизации удалось обработать файл с данными за заданое время. Сейчас оно составляет 22 секунды. + +###Какими ещё результами можете поделиться +1. Гем activerecord-import выполняет вставку без валидации записей. Его использование приносит дополнительный риск. +1. В rails 6 в классе ActiveRecord есть метод insert_all. Имеет те же недостатки. +1. +``` +rake reload_json"[1M.json]" + ActiveRecord::RecordInvalid: Validation failed: Name has spaces +``` + + + +## Защита от регрессии производительности - +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест ... diff --git a/config/routes.rb b/config/routes.rb index a2da6a7b..92916b51 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,4 +2,6 @@ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html get "/" => "statistics#index" get "автобусы/:from/:to" => "trips#index" + + mount PgHero::Engine, at: "pghero" end diff --git a/db/schema.rb b/db/schema.rb index f6921e45..45a43f99 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,25 +10,39 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_03_30_193044) do +ActiveRecord::Schema.define(version: 2020_03_07_174443) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" enable_extension "plpgsql" create_table "buses", force: :cascade do |t| t.string "number" t.string "model" + t.index ["number"], name: "index_buses_on_number" end create_table "buses_services", force: :cascade do |t| t.integer "bus_id" t.integer "service_id" + t.index ["service_id", "bus_id"], name: "index_buses_services_on_service_id_and_bus_id" end create_table "cities", force: :cascade do |t| t.string "name" end + create_table "pghero_query_stats", force: :cascade do |t| + t.text "database" + t.text "user" + t.text "query" + t.bigint "query_hash" + t.float "total_time" + t.bigint "calls" + t.datetime "captured_at" + t.index ["database", "captured_at"], name: "index_pghero_query_stats_on_database_and_captured_at" + end + create_table "services", force: :cascade do |t| t.string "name" end diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake index 7b6800b0..c6b98760 100644 --- a/lib/tasks/utils.rake +++ b/lib/tasks/utils.rake @@ -1,11 +1,20 @@ # Наивная загрузка данных из json-файла в БД # rake reload_json[fixtures/small.json] +desc 'Очистка запросов в pg_hero' +task :empty_pghero_query_stats => :environment do + # puts ActiveRecord::Base.connection.execute('delete from pg_statements').inspect + + puts ActiveRecord::Base.connection.execute('delete from pghero_query_stats').inspect +end + desc 'Загрузка данных из файла' task :reload_json, [:file_name] => :environment do |_task, args| - desc 'Загрузка данных из файла' - start_time = Time.now - json = JSON.parse(File.read(args.file_name)) + time = []; i = 0 + time[i] = Time.now; i += 1 + json = Oj.load(File.read(args.file_name)) + time[i] = Time.now; i += 1 + puts "json = JSON.parse(File.read(args.file_name)) #{time[-1] - time[-2]}" ActiveRecord::Base.transaction do City.delete_all @@ -13,27 +22,32 @@ task :reload_json, [:file_name] => :environment do |_task, args| Service.delete_all Trip.delete_all ActiveRecord::Base.connection.execute('delete from buses_services;') + trips_array = [] - json.each do |trip| - from = City.find_or_create_by(name: trip['from']) - to = City.find_or_create_by(name: trip['to']) - services = [] - trip['bus']['services'].each do |service| - s = Service.find_or_create_by(name: service) - services << s - end - bus = Bus.find_or_create_by(number: trip['bus']['number']) - bus.update(model: trip['bus']['model'], services: services) + json.each_with_index do |trip, index| - Trip.create!( - from: from, - to: to, - bus: bus, + 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'], - ) + price_cents: trip['price_cents'] + } + trips_array << trip_hash + if i%1000 == 999 + Trip.import(trips_array) + puts i + trips_array =[] + end end + Trip.import(trips_array) + # Bus.save_bus_service_cache end - puts "Execution time #{Time.now - start_time} " + puts "Execution time #{Time.now - time[0]} " end diff --git a/test/system/load_test.rb b/test/system/load_test.rb index 74356ec3..a94e073e 100644 --- a/test/system/load_test.rb +++ b/test/system/load_test.rb @@ -5,7 +5,7 @@ class TestLoad < ActiveSupport::TestCase def test_correct_load source_json = JSON.load(File.read('fixtures/example.json')) system 'bin/rake reload_json[fixtures/example.json]' - result_json = Trip.all.map(&:to_h_old) + result_json = Trip.all.order(:id).map(&:to_h_old) assert_equal source_json, result_json end end \ No newline at end of file From 5b211cf9b7f1a0477671940cc039c0c08be6b6c5 Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Sun, 8 Mar 2020 01:15:13 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .byebug_history | 26 ++ .gitignore | 1 + app/models/buses_service.rb | 2 + ...0200306143220_create_pghero_query_stats.rb | 15 + ...0200307171251_add_index_to_bus_services.rb | 5 + ...0200307174443_add_index_number_to_buses.rb | 5 + db/structure.sql | 379 ++++++++++++++++++ test/fixtures/buses_services.yml | 11 + test/models/buses_service_test.rb | 7 + 9 files changed, 451 insertions(+) create mode 100644 .byebug_history create mode 100644 app/models/buses_service.rb create mode 100644 db/migrate/20200306143220_create_pghero_query_stats.rb create mode 100644 db/migrate/20200307171251_add_index_to_bus_services.rb create mode 100644 db/migrate/20200307174443_add_index_number_to_buses.rb create mode 100644 db/structure.sql create mode 100644 test/fixtures/buses_services.yml create mode 100644 test/models/buses_service_test.rb diff --git a/.byebug_history b/.byebug_history new file mode 100644 index 00000000..a1438e60 --- /dev/null +++ b/.byebug_history @@ -0,0 +1,26 @@ +exit +учше +exit +[Aexit +@services +next +bus_data +bus +next +bus +next +bus_data +next +bus_data +next +@all_buses[bus_data['number']] +next +bus_data +@all_buses +next +@all_buses +next +@all_buses +next +exit +continue diff --git a/.gitignore b/.gitignore index 8c3b46a1..63d269a4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ ruby_prof_reports/ .rubocop.yml /docker-valgrind-massif/ /stackprof_reports/ +1M.json diff --git a/app/models/buses_service.rb b/app/models/buses_service.rb new file mode 100644 index 00000000..60660e3f --- /dev/null +++ b/app/models/buses_service.rb @@ -0,0 +1,2 @@ +class BusesService < ApplicationRecord +end diff --git a/db/migrate/20200306143220_create_pghero_query_stats.rb b/db/migrate/20200306143220_create_pghero_query_stats.rb new file mode 100644 index 00000000..fbf41263 --- /dev/null +++ b/db/migrate/20200306143220_create_pghero_query_stats.rb @@ -0,0 +1,15 @@ +class CreatePgheroQueryStats < ActiveRecord::Migration[5.2] + def change + create_table :pghero_query_stats do |t| + t.text :database + t.text :user + t.text :query + t.integer :query_hash, limit: 8 + t.float :total_time + t.integer :calls, limit: 8 + t.timestamp :captured_at + end + + add_index :pghero_query_stats, [:database, :captured_at] + end +end diff --git a/db/migrate/20200307171251_add_index_to_bus_services.rb b/db/migrate/20200307171251_add_index_to_bus_services.rb new file mode 100644 index 00000000..61d6d5fa --- /dev/null +++ b/db/migrate/20200307171251_add_index_to_bus_services.rb @@ -0,0 +1,5 @@ +class AddIndexToBusServices < ActiveRecord::Migration[5.2] + def change + add_index :buses_services, [:service_id, :bus_id] + end +end diff --git a/db/migrate/20200307174443_add_index_number_to_buses.rb b/db/migrate/20200307174443_add_index_number_to_buses.rb new file mode 100644 index 00000000..04b54561 --- /dev/null +++ b/db/migrate/20200307174443_add_index_number_to_buses.rb @@ -0,0 +1,5 @@ +class AddIndexNumberToBuses < ActiveRecord::Migration[5.2] + def change + add_index :buses, :number + end +end diff --git a/db/structure.sql b/db/structure.sql new file mode 100644 index 00000000..190a9d6a --- /dev/null +++ b/db/structure.sql @@ -0,0 +1,379 @@ +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +-- +-- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public; + + +-- +-- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION pg_stat_statements IS 'track execution statistics of all SQL statements executed'; + + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.ar_internal_metadata ( + key character varying NOT NULL, + value character varying, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: buses; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.buses ( + id bigint NOT NULL, + number character varying, + model character varying +); + + +-- +-- Name: buses_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.buses_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: buses_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.buses_id_seq OWNED BY public.buses.id; + + +-- +-- Name: buses_services; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.buses_services ( + id bigint NOT NULL, + bus_id integer, + service_id integer +); + + +-- +-- Name: buses_services_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.buses_services_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: buses_services_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.buses_services_id_seq OWNED BY public.buses_services.id; + + +-- +-- Name: cities; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.cities ( + id bigint NOT NULL, + name character varying +); + + +-- +-- Name: cities_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.cities_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: cities_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.cities_id_seq OWNED BY public.cities.id; + + +-- +-- Name: pghero_query_stats; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.pghero_query_stats ( + id bigint NOT NULL, + database text, + "user" text, + query text, + query_hash bigint, + total_time double precision, + calls bigint, + captured_at timestamp without time zone +); + + +-- +-- Name: pghero_query_stats_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.pghero_query_stats_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: pghero_query_stats_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.pghero_query_stats_id_seq OWNED BY public.pghero_query_stats.id; + + +-- +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schema_migrations ( + version character varying NOT NULL +); + + +-- +-- Name: services; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.services ( + id bigint NOT NULL, + name character varying +); + + +-- +-- Name: services_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.services_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: services_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.services_id_seq OWNED BY public.services.id; + + +-- +-- Name: trips; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.trips ( + id bigint NOT NULL, + from_id integer, + to_id integer, + start_time character varying, + duration_minutes integer, + price_cents integer, + bus_id integer +); + + +-- +-- Name: trips_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.trips_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: trips_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.trips_id_seq OWNED BY public.trips.id; + + +-- +-- Name: buses id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.buses ALTER COLUMN id SET DEFAULT nextval('public.buses_id_seq'::regclass); + + +-- +-- Name: buses_services id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.buses_services ALTER COLUMN id SET DEFAULT nextval('public.buses_services_id_seq'::regclass); + + +-- +-- Name: cities id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.cities ALTER COLUMN id SET DEFAULT nextval('public.cities_id_seq'::regclass); + + +-- +-- Name: pghero_query_stats id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.pghero_query_stats ALTER COLUMN id SET DEFAULT nextval('public.pghero_query_stats_id_seq'::regclass); + + +-- +-- Name: services id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.services ALTER COLUMN id SET DEFAULT nextval('public.services_id_seq'::regclass); + + +-- +-- Name: trips id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.trips ALTER COLUMN id SET DEFAULT nextval('public.trips_id_seq'::regclass); + + +-- +-- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ar_internal_metadata + ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key); + + +-- +-- Name: buses buses_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.buses + ADD CONSTRAINT buses_pkey PRIMARY KEY (id); + + +-- +-- Name: buses_services buses_services_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.buses_services + ADD CONSTRAINT buses_services_pkey PRIMARY KEY (id); + + +-- +-- Name: cities cities_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.cities + ADD CONSTRAINT cities_pkey PRIMARY KEY (id); + + +-- +-- Name: pghero_query_stats pghero_query_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.pghero_query_stats + ADD CONSTRAINT pghero_query_stats_pkey PRIMARY KEY (id); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: services services_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.services + ADD CONSTRAINT services_pkey PRIMARY KEY (id); + + +-- +-- Name: trips trips_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.trips + ADD CONSTRAINT trips_pkey PRIMARY KEY (id); + + +-- +-- Name: index_pghero_query_stats_on_database_and_captured_at; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_pghero_query_stats_on_database_and_captured_at ON public.pghero_query_stats USING btree (database, captured_at); + + +-- +-- PostgreSQL database dump complete +-- + +SET search_path TO "$user", public; + +INSERT INTO "schema_migrations" (version) VALUES +('20190330192820'), +('20190330192933'), +('20190330193017'), +('20190330193027'), +('20190330193044'), +('20200306143220'); + + diff --git a/test/fixtures/buses_services.yml b/test/fixtures/buses_services.yml new file mode 100644 index 00000000..80aed36e --- /dev/null +++ b/test/fixtures/buses_services.yml @@ -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 diff --git a/test/models/buses_service_test.rb b/test/models/buses_service_test.rb new file mode 100644 index 00000000..704c7adf --- /dev/null +++ b/test/models/buses_service_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class BusesServiceTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 5e543bebf207700d5711382c838a47583cbd1936 Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Sun, 8 Mar 2020 20:38:06 +0300 Subject: [PATCH 4/8] load perfomance test --- .byebug_history | 26 -------------------------- .gitignore | 1 + app/models/buses_service.rb | 2 -- case-study.md => case-study.db.md | 12 +++--------- case-study.view.md | 2 ++ lib/tasks/utils.rake | 12 +----------- test/system/load_test.rb | 6 ++++++ 7 files changed, 13 insertions(+), 48 deletions(-) delete mode 100644 .byebug_history delete mode 100644 app/models/buses_service.rb rename case-study.md => case-study.db.md (94%) create mode 100644 case-study.view.md diff --git a/.byebug_history b/.byebug_history deleted file mode 100644 index a1438e60..00000000 --- a/.byebug_history +++ /dev/null @@ -1,26 +0,0 @@ -exit -учше -exit -[Aexit -@services -next -bus_data -bus -next -bus -next -bus_data -next -bus_data -next -@all_buses[bus_data['number']] -next -bus_data -@all_buses -next -@all_buses -next -@all_buses -next -exit -continue diff --git a/.gitignore b/.gitignore index 63d269a4..263ef72b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ ruby_prof_reports/ /docker-valgrind-massif/ /stackprof_reports/ 1M.json +.byebug_history diff --git a/app/models/buses_service.rb b/app/models/buses_service.rb deleted file mode 100644 index 60660e3f..00000000 --- a/app/models/buses_service.rb +++ /dev/null @@ -1,2 +0,0 @@ -class BusesService < ApplicationRecord -end diff --git a/case-study.md b/case-study.db.md similarity index 94% rename from case-study.md rename to case-study.db.md index ef6f17f1..cb967064 100644 --- a/case-study.md +++ b/case-study.db.md @@ -1,4 +1,4 @@ -# Case-study оптимизации +# Case-study оптимизации загрузки данных ## Актуальная проблема @@ -94,15 +94,9 @@ ###Какими ещё результами можете поделиться 1. Гем activerecord-import выполняет вставку без валидации записей. Его использование приносит дополнительный риск. 1. В rails 6 в классе ActiveRecord есть метод insert_all. Имеет те же недостатки. -1. -``` -rake reload_json"[1M.json]" - ActiveRecord::RecordInvalid: Validation failed: Name has spaces -``` - - +1. При необходимости быстрой работы с бд надо организовывать ее на низком уровне. ## Защита от регрессии производительности -Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест ... +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с проверкой времени загрузки файла medium.json менее чем за 8 секунд. diff --git a/case-study.view.md b/case-study.view.md new file mode 100644 index 00000000..fe8b9d28 --- /dev/null +++ b/case-study.view.md @@ -0,0 +1,2 @@ +# Case-study оптимизации отображения +## Актуальная проблема diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake index c6b98760..283901ed 100644 --- a/lib/tasks/utils.rake +++ b/lib/tasks/utils.rake @@ -4,18 +4,12 @@ desc 'Очистка запросов в pg_hero' task :empty_pghero_query_stats => :environment do # puts ActiveRecord::Base.connection.execute('delete from pg_statements').inspect - puts ActiveRecord::Base.connection.execute('delete from pghero_query_stats').inspect end desc 'Загрузка данных из файла' task :reload_json, [:file_name] => :environment do |_task, args| - time = []; i = 0 - time[i] = Time.now; i += 1 json = Oj.load(File.read(args.file_name)) - time[i] = Time.now; i += 1 - puts "json = JSON.parse(File.read(args.file_name)) #{time[-1] - time[-2]}" - ActiveRecord::Base.transaction do City.delete_all Bus.delete_all @@ -28,7 +22,6 @@ task :reload_json, [:file_name] => :environment do |_task, args| 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 = { @@ -40,14 +33,11 @@ task :reload_json, [:file_name] => :environment do |_task, args| price_cents: trip['price_cents'] } trips_array << trip_hash - if i%1000 == 999 + if index%1000 == 999 Trip.import(trips_array) - puts i trips_array =[] end end Trip.import(trips_array) - # Bus.save_bus_service_cache end - puts "Execution time #{Time.now - time[0]} " end diff --git a/test/system/load_test.rb b/test/system/load_test.rb index a94e073e..cc45b2c4 100644 --- a/test/system/load_test.rb +++ b/test/system/load_test.rb @@ -8,4 +8,10 @@ def test_correct_load result_json = Trip.all.order(:id).map(&:to_h_old) assert_equal source_json, result_json end + def test_perfomance_load + time1 = Time.now + system 'bin/rake reload_json[fixtures/medium.json]' + duration = Time.now - time1 + assert(duration < 8) + end end \ No newline at end of file From a913ff959295a496b2270f840d552e02daab77a8 Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Sun, 8 Mar 2020 21:44:48 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=A2=D1=8E=D0=BD=D0=B8=D0=BD=D0=B3=20?= =?UTF-8?q?=D0=BE=D1=82=D1=87=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- case-study.db.md | 6 +++--- case-study.view.md | 15 +++++++++++++++ lib/tasks/pghero.rake | 5 +++++ lib/tasks/utils.rake | 12 ++++-------- 4 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 lib/tasks/pghero.rake diff --git a/case-study.db.md b/case-study.db.md index cb967064..a577a71f 100644 --- a/case-study.db.md +++ b/case-study.db.md @@ -92,11 +92,11 @@ В результате проделанной оптимизации удалось обработать файл с данными за заданое время. Сейчас оно составляет 22 секунды. ###Какими ещё результами можете поделиться -1. Гем activerecord-import выполняет вставку без валидации записей. Его использование приносит дополнительный риск. -1. В rails 6 в классе ActiveRecord есть метод insert_all. Имеет те же недостатки. +1. Также можно применить activerecord-import на остальные модели. +1. В rails 6 в классе ActiveRecord есть метод insert_all. 1. При необходимости быстрой работы с бд надо организовывать ее на низком уровне. ## Защита от регрессии производительности -Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с проверкой времени загрузки файла medium.json менее чем за 8 секунд. +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с проверкой загрузки файла medium.json менее чем за 8 секунд. diff --git a/case-study.view.md b/case-study.view.md index fe8b9d28..e4e95d38 100644 --- a/case-study.view.md +++ b/case-study.view.md @@ -1,2 +1,17 @@ # Case-study оптимизации отображения ## Актуальная проблема + +При большом объеме данных страница расписания показывается слишком медленно. +http://localhost:3000/%D0%B0%D0%B2%D1%82%D0%BE%D0%B1%D1%83%D1%81%D1%8B/%D0%A1%D0%B0%D0%BC%D0%B0%D1%80%D0%B0/%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 + +Идеальное время отклика страницы - до 50 миллисекунд. +Сейчас на странице 1004 рейсов, что за 50 миллисекунд собрать малореально. + +Сейчас время отклика составляет 11_006 миллисекунд. + +## Формирование метрики + +Метрикой будет время ответа сервера, записаное в его логах. Сейчас это 11 секунд. + +## Гарантия корректности работы оптимизированной программы +Так как работа по оптимизации не должна воздействовать на содержимое страницы \ No newline at end of file diff --git a/lib/tasks/pghero.rake b/lib/tasks/pghero.rake new file mode 100644 index 00000000..a8c133f6 --- /dev/null +++ b/lib/tasks/pghero.rake @@ -0,0 +1,5 @@ +desc 'Очистка запросов в pg_hero' +task :empty_pghero_query_stats => :environment do + # puts ActiveRecord::Base.connection.execute('delete from pg_statements').inspect + puts ActiveRecord::Base.connection.execute('delete from pghero_query_stats').inspect +end \ No newline at end of file diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake index 283901ed..7ca838f8 100644 --- a/lib/tasks/utils.rake +++ b/lib/tasks/utils.rake @@ -1,14 +1,9 @@ # Наивная загрузка данных из json-файла в БД # rake reload_json[fixtures/small.json] -desc 'Очистка запросов в pg_hero' -task :empty_pghero_query_stats => :environment do - # puts ActiveRecord::Base.connection.execute('delete from pg_statements').inspect - puts ActiveRecord::Base.connection.execute('delete from pghero_query_stats').inspect -end - desc 'Загрузка данных из файла' task :reload_json, [:file_name] => :environment do |_task, args| + time1 = Time.now json = Oj.load(File.read(args.file_name)) ActiveRecord::Base.transaction do City.delete_all @@ -34,10 +29,11 @@ task :reload_json, [:file_name] => :environment do |_task, args| } trips_array << trip_hash if index%1000 == 999 - Trip.import(trips_array) + Trip.import(trips_array, validate: true, validate_uniqueness: true) trips_array =[] end end - Trip.import(trips_array) + Trip.import(trips_array, validate: true, validate_uniqueness: true) end + puts "Imported in #{Time.now - time1}" end From 44c33c95ba9ec09a82f853ecf2136c1d69fac8b1 Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Sun, 8 Mar 2020 22:09:34 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D1=82=D1=8E=D0=BD=D0=B8=D0=B3=20=D0=BE?= =?UTF-8?q?=D1=82=D1=87=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/city.rb | 2 +- app/services/trips_load.rb | 36 ++++++++++++++++++++++++++++++++++++ case-study.db.md | 6 +++++- case-study.view.md | 3 ++- lib/tasks/utils.rake | 32 +------------------------------- 5 files changed, 45 insertions(+), 34 deletions(-) create mode 100644 app/services/trips_load.rb diff --git a/app/models/city.rb b/app/models/city.rb index ae3a7a7c..d17ab288 100644 --- a/app/models/city.rb +++ b/app/models/city.rb @@ -7,7 +7,7 @@ def name_has_no_spaces end def self.find_cached_or_create(name) - name = name.gsub(' ','') + #name = name.gsub(' ','') @all_cities ||= City.all.map{|city| [city.name, city]}.to_h @all_cities[name] ||= City.create!(name:name) end diff --git a/app/services/trips_load.rb b/app/services/trips_load.rb new file mode 100644 index 00000000..79a93250 --- /dev/null +++ b/app/services/trips_load.rb @@ -0,0 +1,36 @@ +class TripsLoad + + def self.perform(file_name) + json = Oj.load(File.read(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;') + 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 \ No newline at end of file diff --git a/case-study.db.md b/case-study.db.md index a577a71f..3db6647f 100644 --- a/case-study.db.md +++ b/case-study.db.md @@ -95,7 +95,11 @@ 1. Также можно применить activerecord-import на остальные модели. 1. В rails 6 в классе ActiveRecord есть метод insert_all. 1. При необходимости быстрой работы с бд надо организовывать ее на низком уровне. - +1. +```rake -v reload_json"[1M.json]" +rake aborted! +ActiveRecord::RecordInvalid: Validation failed: Name has spaces +``` ## Защита от регрессии производительности Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с проверкой загрузки файла medium.json менее чем за 8 секунд. diff --git a/case-study.view.md b/case-study.view.md index e4e95d38..5d8d3909 100644 --- a/case-study.view.md +++ b/case-study.view.md @@ -4,7 +4,8 @@ При большом объеме данных страница расписания показывается слишком медленно. http://localhost:3000/%D0%B0%D0%B2%D1%82%D0%BE%D0%B1%D1%83%D1%81%D1%8B/%D0%A1%D0%B0%D0%BC%D0%B0%D1%80%D0%B0/%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 -Идеальное время отклика страницы - до 50 миллисекунд. +Для данных + Сейчас на странице 1004 рейсов, что за 50 миллисекунд собрать малореально. Сейчас время отклика составляет 11_006 миллисекунд. diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake index 7ca838f8..4932de94 100644 --- a/lib/tasks/utils.rake +++ b/lib/tasks/utils.rake @@ -4,36 +4,6 @@ desc 'Загрузка данных из файла' task :reload_json, [:file_name] => :environment do |_task, args| time1 = Time.now - json = Oj.load(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;') - 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 + TripsLoad.perform(args.file_name) puts "Imported in #{Time.now - time1}" end From 9872efde45201cc0ef950379fad07209e4017430 Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Wed, 11 Mar 2020 22:51:38 +0300 Subject: [PATCH 7/8] finish --- Gemfile | 2 +- Gemfile.lock | 3 + app/controllers/trips_controller.rb | 2 +- app/views/trips/_services.html.erb | 2 +- app/views/trips/index.html.erb | 17 +++-- case-study.db.md | 6 +- case-study.view.md | 64 +++++++++++++++++-- ...200311194043_add_from_to_index_to_trips.rb | 7 ++ test/integration/trips_index_test.rb | 9 +++ test/system/load_index.rb | 11 ++++ 10 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20200311194043_add_from_to_index_to_trips.rb create mode 100644 test/integration/trips_index_test.rb create mode 100644 test/system/load_index.rb diff --git a/Gemfile b/Gemfile index 393b00c7..12dc5c95 100644 --- a/Gemfile +++ b/Gemfile @@ -10,13 +10,13 @@ gem 'bootsnap', '>= 1.1.0', require: false gem 'pghero' gem 'oj' gem 'activerecord-import' - 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] end group :development do + gem 'rack-mini-profiler' # 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' diff --git a/Gemfile.lock b/Gemfile.lock index bec59763..47d997e4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,6 +84,8 @@ GEM activerecord (>= 5) puma (3.12.1) rack (2.0.6) + rack-mini-profiler (1.1.6) + rack (>= 1.2.0) rack-test (1.1.0) rack (>= 1.0, < 3) rails (5.2.3) @@ -148,6 +150,7 @@ DEPENDENCIES pg (>= 0.18, < 2.0) pghero puma (~> 3.11) + rack-mini-profiler rails (~> 5.2.3) web-console (>= 3.3.0) diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb index acb38be2..b2b1fa06 100644 --- a/app/controllers/trips_controller.rb +++ b/app/controllers/trips_controller.rb @@ -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 end end diff --git a/app/views/trips/_services.html.erb b/app/views/trips/_services.html.erb index 2de639fc..0be503e1 100644 --- a/app/views/trips/_services.html.erb +++ b/app/views/trips/_services.html.erb @@ -1,6 +1,6 @@
  • Сервисы в автобусе:
  • diff --git a/app/views/trips/index.html.erb b/app/views/trips/index.html.erb index a60bce41..019edb52 100644 --- a/app/views/trips/index.html.erb +++ b/app/views/trips/index.html.erb @@ -2,15 +2,24 @@ <%= "Автобусы #{@from.name} – #{@to.name}" %>

    - <%= "В расписании #{@trips.count} рейсов" %> + <%= "В расписании #{@trips.size} рейсов" %>

    <% @trips.each do |trip| %> - <%= render "delimiter" %> + ==================================================== <% end %> diff --git a/case-study.db.md b/case-study.db.md index 3db6647f..a577a71f 100644 --- a/case-study.db.md +++ b/case-study.db.md @@ -95,11 +95,7 @@ 1. Также можно применить activerecord-import на остальные модели. 1. В rails 6 в классе ActiveRecord есть метод insert_all. 1. При необходимости быстрой работы с бд надо организовывать ее на низком уровне. -1. -```rake -v reload_json"[1M.json]" -rake aborted! -ActiveRecord::RecordInvalid: Validation failed: Name has spaces -``` + ## Защита от регрессии производительности Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с проверкой загрузки файла medium.json менее чем за 8 секунд. diff --git a/case-study.view.md b/case-study.view.md index 5d8d3909..2e070c58 100644 --- a/case-study.view.md +++ b/case-study.view.md @@ -2,17 +2,71 @@ ## Актуальная проблема При большом объеме данных страница расписания показывается слишком медленно. -http://localhost:3000/%D0%B0%D0%B2%D1%82%D0%BE%D0%B1%D1%83%D1%81%D1%8B/%D0%A1%D0%B0%D0%BC%D0%B0%D1%80%D0%B0/%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 -Для данных +http://localhost:3000/%D0%B0%D0%B2%D1%82%D0%BE%D0%B1%D1%83%D1%81%D1%8B/%D0%A1%D0%B0%D0%BC%D0%B0%D1%80%D0%B0/%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 -Сейчас на странице 1004 рейсов, что за 50 миллисекунд собрать малореально. - +Для уточнения задачи были загружены данные файла fixtures/large.json +Сейчас на странице 1004 рейсов Сейчас время отклика составляет 11_006 миллисекунд. +Бюджет не задан четко, но время загрузки должно быть "адекватно" + +Идеальным временем зарузки страницы считаетсчя 50-100 миллисекунд. + ## Формирование метрики Метрикой будет время ответа сервера, записаное в его логах. Сейчас это 11 секунд. ## Гарантия корректности работы оптимизированной программы -Так как работа по оптимизации не должна воздействовать на содержимое страницы \ No newline at end of file + +Так как работа по оптимизации не должна воздействовать на содержимое страницы был написан тест сверяющий страницу до внесенных измений с новой. + +## Вникаем в детали системы, чтобы найти главные точки роста + +Для того, чтобы найти "точки роста" для оптимизации я воспользовался +- gem 'rack-mini-profiler' + +Вот какие проблемы удалось найти и решить + +### Находка №0 +- Страница загружается 11 секунд только первый раз, потом она загружается 4,5 секунд + +### Находка №1 +- rack-mini-profiler показал, что запросы для каждого trip делается дополнительный запрос по buses +- добавил eager_load для bus +- метрика именьшилась до 3,3 секунды +- Лишние запросы исчезли + +### Находка №2 +- rack-mini-profiler показал что запросы для каждого trip делается дополнительный запрос по services +- добавил eager_load для services +- метрика именьшилась до 2,4 секунды +- Лишние запросы исчезли. Осталось 4 sql запроса, из время выполнения 113 миллискунд + +### Находка №3 +- rack-mini-profiler показал что основное время занимает больше число вызово рендеринга trips/_services, который каждый раз рендерит trips/_service +- надо ускорить/упростить вызов +- метрика именьшилась до 1,1 секунды +- Лишние вызовы исчезли. + +### Находка №4 +- в процессе просмотр кода обнаружено, что для указания количества рейсов используется метод count, который делает дополнительный запрос +- загружать полностью выборку и использовать size +- метрика не изменилась +- Лишний запрос исчез. + +### Находка №5 +- rack-mini-profiler показал что основное время занимает рендеринг индекса +- просмотреть, что можно оптимизировать там. +- решено собрать всё в один файл. Новый файл занимает 25 и остается читабельным +- метрика уменьшилась первоначально до 0,6 секунды при повторных вызовах до 0,2 секунды. + +## Результаты +В результате проделанной оптимизации удалось получить страницу c 1000 рейсами менее чем за 0,6 секунды, что считааю достаточным показателем. + +## Защита от регрессии производительности +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан тест с проверкой загрузки индексной страницы на 100 поездках (файле medium.json) менее чем за 0.3 секунд. Ограничение пришлось огрубить,так как загрузка большего объема сильно замедлит тестирование. + +## Чем еще поделиться +1. Все ускорилось и без индекса по trips, хотя он очень просился. Эксперимент показал, что время контретно этого запроса уменьшается с 20 до 9 ms. Что для единичного запроса незначительно. + diff --git a/db/migrate/20200311194043_add_from_to_index_to_trips.rb b/db/migrate/20200311194043_add_from_to_index_to_trips.rb new file mode 100644 index 00000000..6bf571ef --- /dev/null +++ b/db/migrate/20200311194043_add_from_to_index_to_trips.rb @@ -0,0 +1,7 @@ +class AddFromToIndexToTrips < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def change + add_index :trips, [:from_id, :to_id], algorithm: :concurrently + end +end diff --git a/test/integration/trips_index_test.rb b/test/integration/trips_index_test.rb new file mode 100644 index 00000000..dac6a6ac --- /dev/null +++ b/test/integration/trips_index_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class TripsIndexTest < ActionDispatch::IntegrationTest + test "Index for 100 trips in 0.3 second" do + TripsLoad.perform('fixtures/medium.json') + total = Benchmark.measure{ get URI.escape('/автобусы/Самара/Москва') }.total + assert(total < 0.3) + end +end diff --git a/test/system/load_index.rb b/test/system/load_index.rb new file mode 100644 index 00000000..c1e7c4e7 --- /dev/null +++ b/test/system/load_index.rb @@ -0,0 +1,11 @@ +require 'test_helper' +require 'json' + +class TestIndex < ActiveSupport::TestCase + def test_index_content + file_name = "tmp\#{Time.now.to_i}.html" + system "wget http://localhost:3000/%D0%B0%D0%B2%D1%82%D0%BE%D0%B1%D1%83%D1%81%D1%8B/%D0%A1%D0%B0%D0%BC%D0%B0%D1%80%D0%B0/%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 -q -O #{file_name}" + diff_result = ` diff fixtures/sample.html #{file_name} -d` + assert(diff_result[0..3] == "6c6\n") + end +end \ No newline at end of file From ace9d0264b5642d470a3ecb3bbd121166a5ba224 Mon Sep 17 00:00:00 2001 From: Alexey Shemyakin <2alexeysh@gmail.com> Date: Wed, 11 Mar 2020 23:05:28 +0300 Subject: [PATCH 8/8] remove old files --- db/schema.rb | 3 +- db/structure.sql | 379 ------------------------------ test/fixtures/buses_services.yml | 11 - test/models/buses_service_test.rb | 7 - test/system/load_index.rb | 11 - test/system/load_test.rb | 7 +- 6 files changed, 5 insertions(+), 413 deletions(-) delete mode 100644 db/structure.sql delete mode 100644 test/fixtures/buses_services.yml delete mode 100644 test/models/buses_service_test.rb delete mode 100644 test/system/load_index.rb diff --git a/db/schema.rb b/db/schema.rb index 45a43f99..df42c848 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_03_07_174443) do +ActiveRecord::Schema.define(version: 2020_03_11_194043) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -54,6 +54,7 @@ t.integer "duration_minutes" t.integer "price_cents" t.integer "bus_id" + t.index ["from_id", "to_id"], name: "index_trips_on_from_id_and_to_id" end end diff --git a/db/structure.sql b/db/structure.sql deleted file mode 100644 index 190a9d6a..00000000 --- a/db/structure.sql +++ /dev/null @@ -1,379 +0,0 @@ -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - --- --- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: - --- - -CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; - - --- --- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: - --- - -COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; - - --- --- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: - --- - -CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public; - - --- --- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: - --- - -COMMENT ON EXTENSION pg_stat_statements IS 'track execution statistics of all SQL statements executed'; - - -SET default_tablespace = ''; - -SET default_with_oids = false; - --- --- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.ar_internal_metadata ( - key character varying NOT NULL, - value character varying, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL -); - - --- --- Name: buses; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.buses ( - id bigint NOT NULL, - number character varying, - model character varying -); - - --- --- Name: buses_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.buses_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: buses_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.buses_id_seq OWNED BY public.buses.id; - - --- --- Name: buses_services; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.buses_services ( - id bigint NOT NULL, - bus_id integer, - service_id integer -); - - --- --- Name: buses_services_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.buses_services_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: buses_services_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.buses_services_id_seq OWNED BY public.buses_services.id; - - --- --- Name: cities; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.cities ( - id bigint NOT NULL, - name character varying -); - - --- --- Name: cities_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.cities_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: cities_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.cities_id_seq OWNED BY public.cities.id; - - --- --- Name: pghero_query_stats; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.pghero_query_stats ( - id bigint NOT NULL, - database text, - "user" text, - query text, - query_hash bigint, - total_time double precision, - calls bigint, - captured_at timestamp without time zone -); - - --- --- Name: pghero_query_stats_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.pghero_query_stats_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: pghero_query_stats_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.pghero_query_stats_id_seq OWNED BY public.pghero_query_stats.id; - - --- --- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.schema_migrations ( - version character varying NOT NULL -); - - --- --- Name: services; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.services ( - id bigint NOT NULL, - name character varying -); - - --- --- Name: services_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.services_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: services_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.services_id_seq OWNED BY public.services.id; - - --- --- Name: trips; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.trips ( - id bigint NOT NULL, - from_id integer, - to_id integer, - start_time character varying, - duration_minutes integer, - price_cents integer, - bus_id integer -); - - --- --- Name: trips_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.trips_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: trips_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.trips_id_seq OWNED BY public.trips.id; - - --- --- Name: buses id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.buses ALTER COLUMN id SET DEFAULT nextval('public.buses_id_seq'::regclass); - - --- --- Name: buses_services id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.buses_services ALTER COLUMN id SET DEFAULT nextval('public.buses_services_id_seq'::regclass); - - --- --- Name: cities id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.cities ALTER COLUMN id SET DEFAULT nextval('public.cities_id_seq'::regclass); - - --- --- Name: pghero_query_stats id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.pghero_query_stats ALTER COLUMN id SET DEFAULT nextval('public.pghero_query_stats_id_seq'::regclass); - - --- --- Name: services id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.services ALTER COLUMN id SET DEFAULT nextval('public.services_id_seq'::regclass); - - --- --- Name: trips id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.trips ALTER COLUMN id SET DEFAULT nextval('public.trips_id_seq'::regclass); - - --- --- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.ar_internal_metadata - ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key); - - --- --- Name: buses buses_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.buses - ADD CONSTRAINT buses_pkey PRIMARY KEY (id); - - --- --- Name: buses_services buses_services_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.buses_services - ADD CONSTRAINT buses_services_pkey PRIMARY KEY (id); - - --- --- Name: cities cities_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.cities - ADD CONSTRAINT cities_pkey PRIMARY KEY (id); - - --- --- Name: pghero_query_stats pghero_query_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.pghero_query_stats - ADD CONSTRAINT pghero_query_stats_pkey PRIMARY KEY (id); - - --- --- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.schema_migrations - ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); - - --- --- Name: services services_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.services - ADD CONSTRAINT services_pkey PRIMARY KEY (id); - - --- --- Name: trips trips_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.trips - ADD CONSTRAINT trips_pkey PRIMARY KEY (id); - - --- --- Name: index_pghero_query_stats_on_database_and_captured_at; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_pghero_query_stats_on_database_and_captured_at ON public.pghero_query_stats USING btree (database, captured_at); - - --- --- PostgreSQL database dump complete --- - -SET search_path TO "$user", public; - -INSERT INTO "schema_migrations" (version) VALUES -('20190330192820'), -('20190330192933'), -('20190330193017'), -('20190330193027'), -('20190330193044'), -('20200306143220'); - - diff --git a/test/fixtures/buses_services.yml b/test/fixtures/buses_services.yml deleted file mode 100644 index 80aed36e..00000000 --- a/test/fixtures/buses_services.yml +++ /dev/null @@ -1,11 +0,0 @@ -# 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 diff --git a/test/models/buses_service_test.rb b/test/models/buses_service_test.rb deleted file mode 100644 index 704c7adf..00000000 --- a/test/models/buses_service_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class BusesServiceTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/system/load_index.rb b/test/system/load_index.rb deleted file mode 100644 index c1e7c4e7..00000000 --- a/test/system/load_index.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'test_helper' -require 'json' - -class TestIndex < ActiveSupport::TestCase - def test_index_content - file_name = "tmp\#{Time.now.to_i}.html" - system "wget http://localhost:3000/%D0%B0%D0%B2%D1%82%D0%BE%D0%B1%D1%83%D1%81%D1%8B/%D0%A1%D0%B0%D0%BC%D0%B0%D1%80%D0%B0/%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 -q -O #{file_name}" - diff_result = ` diff fixtures/sample.html #{file_name} -d` - assert(diff_result[0..3] == "6c6\n") - end -end \ No newline at end of file diff --git a/test/system/load_test.rb b/test/system/load_test.rb index cc45b2c4..6e2fbed4 100644 --- a/test/system/load_test.rb +++ b/test/system/load_test.rb @@ -8,10 +8,9 @@ def test_correct_load result_json = Trip.all.order(:id).map(&:to_h_old) assert_equal source_json, result_json end + def test_perfomance_load - time1 = Time.now - system 'bin/rake reload_json[fixtures/medium.json]' - duration = Time.now - time1 - assert(duration < 8) + total = Benchmark.measure{ system 'bin/rake reload_json[fixtures/medium.json]'}.total + assert(total < 8) end end \ No newline at end of file