diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..5ec60dd7
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,11 @@
+DB_USERNAME=postgres
+DB_PASSWORD=postgres
+DB_HOST=db
+DB_PORT=5432
+DB_POOL=5
+DB_NAME=optimization_3
+
+PGHERO_USERNAME=postgres
+PGHERO_PASSWORD=postgres
+
+RAILS_ENV=development
diff --git a/.gitignore b/.gitignore
index 59c74047..40188e3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,7 @@
 /.bundle
+/.idea
 /tmp
 /log
 /public
+.env
+fixtures/1M.json
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..a7ac5b35
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+FROM ruby:2.6.3-alpine
+
+RUN apk update  && apk upgrade && apk add --update --no-cache \
+  build-base libc-dev tzdata bash htop shared-mime-info \
+  postgresql-dev postgresql-client
+
+WORKDIR /opt/app
+
+COPY Gemfile* ./
+
+RUN gem install bundler -v 2.0.2
+RUN bundle install
+
+COPY . .
diff --git a/Gemfile b/Gemfile
index e20b1260..eacc035e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,10 +3,21 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
 
 ruby '2.6.3'
 
+gem 'activerecord-import'
+gem 'dotenv-rails'
 gem 'rails', '~> 5.2.3'
 gem 'pg', '>= 0.18', '< 2.0'
 gem 'puma', '~> 3.11'
 gem 'bootsnap', '>= 1.1.0', require: false
+gem 'mimemagic', '0.3.10'
+gem 'memory_profiler'
+gem 'rspec'
+gem 'rspec-benchmark'
+gem 'ruby-prof'
+gem 'stackprof'
+gem 'pghero', '>= 2'
+gem 'rack-mini-profiler', require: false
+gem 'bullet'
 
 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 fccf6f5f..074747d9 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.4.1)
+      activerecord (>= 4.2)
     activestorage (5.2.3)
       actionpack (= 5.2.3)
       activerecord (= 5.2.3)
@@ -43,13 +45,24 @@ GEM
       minitest (~> 5.1)
       tzinfo (~> 1.1)
     arel (9.0.0)
+    benchmark-malloc (0.2.0)
+    benchmark-perf (0.6.0)
+    benchmark-trend (0.4.0)
     bindex (0.6.0)
     bootsnap (1.4.2)
       msgpack (~> 1.0)
     builder (3.2.3)
+    bullet (7.0.7)
+      activesupport (>= 3.0.0)
+      uniform_notifier (~> 1.11)
     byebug (11.0.1)
     concurrent-ruby (1.1.5)
     crass (1.0.4)
+    diff-lcs (1.5.0)
+    dotenv (2.8.1)
+    dotenv-rails (2.8.1)
+      dotenv (= 2.8.1)
+      railties (>= 3.2)
     erubi (1.8.0)
     ffi (1.10.0)
     globalid (0.4.2)
@@ -67,8 +80,11 @@ GEM
       mini_mime (>= 0.1.1)
     marcel (0.3.3)
       mimemagic (~> 0.3.2)
+    memory_profiler (1.0.1)
     method_source (0.9.2)
-    mimemagic (0.3.3)
+    mimemagic (0.3.10)
+      nokogiri (~> 1)
+      rake
     mini_mime (1.0.1)
     mini_portile2 (2.4.0)
     minitest (5.11.3)
@@ -77,8 +93,12 @@ GEM
     nokogiri (1.10.2)
       mini_portile2 (~> 2.4.0)
     pg (1.1.4)
+    pghero (2.8.3)
+      activerecord (>= 5)
     puma (3.12.1)
     rack (2.0.6)
+    rack-mini-profiler (3.1.0)
+      rack (>= 1.2.0)
     rack-test (1.1.0)
       rack (>= 1.0, < 3)
     rails (5.2.3)
@@ -109,6 +129,25 @@ GEM
     rb-fsevent (0.10.3)
     rb-inotify (0.10.0)
       ffi (~> 1.0)
+    rspec (3.12.0)
+      rspec-core (~> 3.12.0)
+      rspec-expectations (~> 3.12.0)
+      rspec-mocks (~> 3.12.0)
+    rspec-benchmark (0.6.0)
+      benchmark-malloc (~> 0.2)
+      benchmark-perf (~> 0.6)
+      benchmark-trend (~> 0.4)
+      rspec (>= 3.0)
+    rspec-core (3.12.1)
+      rspec-support (~> 3.12.0)
+    rspec-expectations (3.12.2)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-mocks (3.12.5)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-support (3.12.0)
+    ruby-prof (1.4.3)
     ruby_dep (1.5.0)
     sprockets (3.7.2)
       concurrent-ruby (~> 1.0)
@@ -117,10 +156,12 @@ GEM
       actionpack (>= 4.0)
       activesupport (>= 4.0)
       sprockets (>= 3.0.0)
+    stackprof (0.2.24)
     thor (0.20.3)
     thread_safe (0.3.6)
     tzinfo (1.2.5)
       thread_safe (~> 0.1)
+    uniform_notifier (1.16.0)
     web-console (3.7.0)
       actionview (>= 5.0)
       activemodel (>= 5.0)
@@ -134,12 +175,23 @@ PLATFORMS
   ruby
 
 DEPENDENCIES
+  activerecord-import
   bootsnap (>= 1.1.0)
+  bullet
   byebug
+  dotenv-rails
   listen (>= 3.0.5, < 3.2)
+  memory_profiler
+  mimemagic (= 0.3.10)
   pg (>= 0.18, < 2.0)
+  pghero (>= 2)
   puma (~> 3.11)
+  rack-mini-profiler
   rails (~> 5.2.3)
+  rspec
+  rspec-benchmark
+  ruby-prof
+  stackprof
   tzinfo-data
   web-console (>= 3.3.0)
 
diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb
index acb38be2..fd45b1a0 100644
--- a/app/controllers/trips_controller.rb
+++ b/app/controllers/trips_controller.rb
@@ -1,7 +1,29 @@
 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)
+    @from = params[:from]
+    @to = params[:to]
+    @trips_count = trips_query.count
+    @trips = trips_query.order(:start_time)
+                        .joins(bus: :services)
+                        .select(trips_select.join(','))
+                        .group('trips.id, buses.id')
+  end
+
+  private
+
+  def trips_query
+    @cities ||= City.where(name: [@from, @to]).pluck(:name, :id).to_h
+    Trip.where(from_id: @cities[@from], to_id: @cities[@to])
+  end
+
+  def trips_select
+    [
+      'trips.start_time',
+      'trips.duration_minutes',
+      'trips.price_cents',
+      'array_agg(services.name) as service_names',
+      'buses.number as bus_number',
+      'buses.model as bus_model'
+    ]
   end
 end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 10a4cba8..767a072b 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
 class ApplicationRecord < ActiveRecord::Base
   self.abstract_class = true
 end
diff --git a/app/models/bus.rb b/app/models/bus.rb
index 1dcc54cb..0e5b3cc5 100644
--- a/app/models/bus.rb
+++ b/app/models/bus.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
 class Bus < ApplicationRecord
   MODELS = [
     'Икарус',
diff --git a/app/models/city.rb b/app/models/city.rb
index 19ec7f36..dc8306cc 100644
--- a/app/models/city.rb
+++ b/app/models/city.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
 class City < ApplicationRecord
   validates :name, presence: true, uniqueness: true
   validate :name_has_no_spaces
diff --git a/app/models/service.rb b/app/models/service.rb
index 9cbb2a32..23c6aca2 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
 class Service < ApplicationRecord
   SERVICES = [
     'WiFi',
diff --git a/app/models/trip.rb b/app/models/trip.rb
index 9d63dfff..347446b0 100644
--- a/app/models/trip.rb
+++ b/app/models/trip.rb
@@ -1,5 +1,6 @@
+# frozen_string_literal: true
 class Trip < ApplicationRecord
-  HHMM_REGEXP = /([0-1][0-9]|[2][0-3]):[0-5][0-9]/
+  HHMM_REGEXP = /([0-1][0-9]|[2][0-3]):[0-5][0-9]/.freeze
 
   belongs_to :from, class_name: 'City'
   belongs_to :to, class_name: 'City'
diff --git a/app/views/trips/_delimiter.html.erb b/app/views/trips/_delimiter.html.erb
deleted file mode 100644
index 3f845ad0..00000000
--- a/app/views/trips/_delimiter.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-====================================================
diff --git a/app/views/trips/_service.html.erb b/app/views/trips/_service.html.erb
deleted file mode 100644
index 178ea8c0..00000000
--- a/app/views/trips/_service.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<li><%= "#{service.name}" %></li>
diff --git a/app/views/trips/_services.html.erb b/app/views/trips/_services.html.erb
deleted file mode 100644
index 2de639fc..00000000
--- a/app/views/trips/_services.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-<li>Сервисы в автобусе:</li>
-<ul>
-  <% services.each do |service| %>
-    <%= render "service", service: service %>
-  <% end %>
-</ul>
diff --git a/app/views/trips/_trip.html.erb b/app/views/trips/_trip.html.erb
deleted file mode 100644
index fa1de9aa..00000000
--- a/app/views/trips/_trip.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<li><%= "Отправление: #{trip.start_time}" %></li>
-<li><%= "Прибытие: #{(Time.parse(trip.start_time) + trip.duration_minutes.minutes).strftime('%H:%M')}" %></li>
-<li><%= "В пути: #{trip.duration_minutes / 60}ч. #{trip.duration_minutes % 60}мин." %></li>
-<li><%= "Цена: #{trip.price_cents / 100}р. #{trip.price_cents % 100}коп." %></li>
-<li><%= "Автобус: #{trip.bus.model} №#{trip.bus.number}" %></li>
diff --git a/app/views/trips/index.html.erb b/app/views/trips/index.html.erb
index a60bce41..2c7b0d6c 100644
--- a/app/views/trips/index.html.erb
+++ b/app/views/trips/index.html.erb
@@ -1,16 +1,26 @@
 <h1>
-  <%= "Автобусы #{@from.name} – #{@to.name}" %>
+  <%= "Автобусы #{@from} – #{@to}" %>
 </h1>
 <h2>
-  <%= "В расписании #{@trips.count} рейсов" %>
+  <%= "В расписании #{@trips_count} рейсов" %>
 </h2>
 
 <% @trips.each do |trip| %>
   <ul>
-    <%= render "trip", trip: trip %>
-    <% if trip.bus.services.present? %>
-      <%= render "services", services: trip.bus.services %>
+    <li><%= "Отправление: #{trip.start_time}" %></li>
+    <li><%= "Прибытие: #{(Time.parse(trip.start_time) + trip.duration_minutes.minutes).strftime('%H:%M')}" %></li>
+    <li><%= "В пути: #{trip.duration_minutes / 60}ч. #{trip.duration_minutes % 60}мин." %></li>
+    <li><%= "Цена: #{trip.price_cents / 100}р. #{trip.price_cents % 100}коп." %></li>
+    <li><%= "Автобус: #{trip.bus_model} №#{trip.bus_number}" %></li>
+
+    <% if trip.service_names.present? %>
+      <li>Сервисы в автобусе:</li>
+      <ul>
+        <% trip.service_names.each do |service| %>
+          <li><%= "#{service}" %></li>
+        <% end %>
+      </ul>
     <% end %>
   </ul>
-  <%= render "delimiter" %>
+  ====================================================
 <% end %>
diff --git a/case-study.md b/case-study.md
new file mode 100644
index 00000000..01366282
--- /dev/null
+++ b/case-study.md
@@ -0,0 +1,93 @@
+Первым шагом при выполнении задания была настройка запуска в докер и проверка корректности работы.
+После первого запуска скрипта на экспорт данных было заметно что он работает не очень быстро.
+
+### 1 часть задания. Оптимизация скрипта
+
+Скрипт решила оптимизировать первоначально по памяти, т.к. имеет место обработка файла + active record наверняка может создавать лишние объекты
+ну и хотелось в этом больше потренироваться.
+Как и ожидалось после запуска memoryprofiler больше всего памяти приходится на activerecord, поэтому так же как и во втором
+задании поэтапно начинаю оптимизацию.
+
+#### Замеры до оптимизации:
+- Время выполнения (fixtures/small.json): 6.97 сек
+- Потребляемая память (fixtures/small.json): 119 MB
+
+Далее перечислю основные моменты:
+1. Первое что решаю сделать это добавить frozen_string_literal, что дает небольшое улучшение.
+2. Далее, исходя из отчета самое большое количество памяти выделено на /activerecord-5.2.3/lib/active_record/log_subscriber.rb
+Заменила log_level на error для скрипта. Здесь же убрала bootsnap, т.к. периодически падал профилировщик. Точка роста сместилась
+3. Т.к далее основная нагрузка приходится на active record, решаю по максимуму убрать использование объектов active record. 
+Для этого выношу код в отдельный класс, заменяю запросы удаления на truncate, подключаю гем activerecord-import и с помощью него уже строю импорт данных
+В результате время выполнения сократилась, но потребление памяти не очень. На первом месте остались объекты active record
+4. Далее решила в импорте не использовать recursive, а собрать все в массив и импортировать его и это уже принесло хороший результат:
+- Время выполнения (fixtures/small.json): 0.81 сек
+- Потребляемая память (fixtures/small.json): 113 MB
+
+
+- Время выполнения (fixtures/medium.json): 4.07 сек
+- Потребляемая память (fixtures/small.json): 112 MB
+
+ 
+- Время выполнения (fixtures/large.json):  34.86 сек
+- Потребляемая память (fixtures/small.json): 335 MB
+
+В результате оптимизации active record перестал быть главной точкой роста.
+
+5. После профилирования по времени на первом месте оказался PG::Connection#async_exec, судя по записанным данным самое большое количество запросов
+приходится на добавление связи bus и service. Ради интереса решила попробовать сделать их запись в одном запросе, что 
+привело к снижению времени обработки. После этого решила попробовать по аналогии записывать и данные по trip и в итоге получила 
+значительное снижение по времени, т.о от гема activerecord-import отказалась. Результаты:
+
+- Время выполнения (fixtures/small.json): 0.39 сек
+- Потребляемая память (fixtures/small.json): 110 MB
+
+
+- Время выполнения (fixtures/medium.json): 1.5 сек
+- Потребляемая память (fixtures/medium.json): 116 MB
+
+
+- Время выполнения (fixtures/large.json):  13.28 сек
+- Потребляемая память (fixtures/large.json): 330 MB
+
+
+- Время выполнения (fixtures/1M.json):  137.6 сек
+- Потребляемая память (fixtures/1M.json): 1895 MB
+
+По памяти много, но по времени кажется неплохо. На этом результате решила остановиться пока, потому что времени катастрофически не хватает.
+Потоковая обработка интересна, если получится как нибудь попробую предложенный пример записи в БД.
+
+### 2 часть задания. Оптимизация страницы расписаний
+
+После импорта файла large.json страница загружалась примерно 24 секунды.
+Первым делом решила попробовать поставить pgHero, т.к. было интересно попробовать этот инструмент.
+
+Сразу после первого прогона был найден медленный запрос:
+```sql
+SELECT "services".* FROM "services" INNER JOIN "buses_services" ON "services"."id" = "buses_services"."service_id" WHERE "buses_services"."bus_id" = $1
+```
+
+Поставила bullet и rack-mini-profiler.
+- Далее поправила запросы исходя из алертов и добавила .
+- из профайлера видно что очень много запросов вида:
+```sql
+SELECT "services".* FROM "services" INNER JOIN "buses_services" ON "services"."id" = "buses_services"."service_id" WHERE "buses_services"."bus_id" = $1; 
+```
+то же показывает и pgHero, поэтому поправила этот момент тем то изменила выборку по trips - добавила в select только нужные для
+отображения аттрибуты и добавила joins.
+- объединила рендеры в один для удобства поиска необходимых аттрибутов
+- заменила поиск города по названию (влияет не сильно но тем не менее)
+
+В результате время отображения на сократилось до 182ms. По pgHero и bullet проблем больше не выявлено.
+
+Дальше я решила посмотреть анализ самого долгого запроса, который стал основным.
+По нему получается что самым долгим является часть джойна с buses_services, т.к там нет индексов. Такая же проблема на
+таблицах buses и trips. Добавляю индексы.
+
+В результате время отображения на сократилось до 130ms (с включенным профилировщиком)
+
+### Итоги
+
+Очень понравилось использовать pgHero, действительно удобный и визуально приятный инструмент.
+С bullet и rack-mini-profiler немного уже работала, поэтому они остаются полезными)
+
+P.S Прошу прощения за такую задержку с выполнением дз. Постараюсь наверстать все пропущенное до конца курса.
diff --git a/config/application.rb b/config/application.rb
index 9c331097..ea1a1364 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -10,6 +10,7 @@ module Task4
   class Application < Rails::Application
     # Initialize configuration defaults for originally generated Rails version.
     config.load_defaults 5.2
+    config.log_level = :error
 
     # Settings in config/environments/* take precedence over those specified here.
     # Application configuration can go into files in config/initializers
diff --git a/config/boot.rb b/config/boot.rb
index b9e460ce..b6915e55 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -1,4 +1,5 @@
 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
 
 require 'bundler/setup' # Set up gems listed in the Gemfile.
-require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
+# отключила т.к. падал профилировщик
+# require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
diff --git a/config/database.yml b/config/database.yml
index e116cfa6..11508d48 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -17,13 +17,15 @@
 default: &default
   adapter: postgresql
   encoding: unicode
-  # For details on connection pooling, see Rails configuration guide
-  # http://guides.rubyonrails.org/configuring.html#database-pooling
-  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+  username: <%= ENV.fetch('DB_USERNAME') { 'postgres' } %>
+  password: <%= ENV.fetch('DB_PASSWORD') { 'postgres' } %>
+  host: <%= ENV.fetch('DB_HOST') { 'db' } %>
+  port: <%= ENV.fetch('DB_PORT') { 5432 } %>
+  pool: <%= ENV.fetch('DB_POOL') { 5 } %>
 
 development:
   <<: *default
-  database: task-4_development
+  database: <%= ENV.fetch('DB_NAME') %>
 
   # The specified database role being used to connect to postgres.
   # To create additional roles in postgres see `$ createuser --help`.
@@ -57,7 +59,7 @@ development:
 # Do not set this db to the same as development or production.
 test:
   <<: *default
-  database: task-4_test
+  database: optimization_3_test
 
 # As with config/secrets.yml, you never want to store sensitive information,
 # like your database password, in your source code. If your source code is
@@ -80,6 +82,4 @@ test:
 #
 production:
   <<: *default
-  database: task-4_production
-  username: task-4
-  password: <%= ENV['TASK-4_DATABASE_PASSWORD'] %>
+  database: task-optimization_3_production
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 1311e3e4..903a8661 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,4 +1,13 @@
 Rails.application.configure do
+  config.after_initialize do
+    Bullet.enable        = true
+    Bullet.alert         = true
+    Bullet.bullet_logger = true
+    Bullet.console       = true
+    Bullet.rails_logger  = true
+    Bullet.add_footer    = true
+  end
+
   # Settings specified here will take precedence over those in config/application.rb.
 
   # In the development environment your application's code is reloaded on
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 613d8289..1b5e478c 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -51,7 +51,7 @@
 
   # Use the lowest log level to ensure availability of diagnostic information
   # when problems arise.
-  config.log_level = :debug
+  config.log_level = :error
 
   # Prepend all log lines with the following tags.
   config.log_tags = [ :request_id ]
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 0a38fd3c..bc5971ab 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,4 +1,10 @@
 Rails.application.configure do
+  config.after_initialize do
+    Bullet.enable        = true
+    Bullet.bullet_logger = true
+    Bullet.raise         = true # raise an error if n+1 query occurs
+  end
+
   # Settings specified here will take precedence over those in config/application.rb.
 
   # The test environment is used exclusively to run your application's
diff --git a/config/initializers/rack_mini_profiler.rb b/config/initializers/rack_mini_profiler.rb
new file mode 100644
index 00000000..14e63a30
--- /dev/null
+++ b/config/initializers/rack_mini_profiler.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+if Rails.env.development?
+  require "rack-mini-profiler"
+
+  # The initializer was required late, so initialize it manually.
+  Rack::MiniProfilerRails.initialize!(Rails.application)
+end
diff --git a/config/routes.rb b/config/routes.rb
index a2da6a7b..c60fe546 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
 Rails.application.routes.draw do
+  mount PgHero::Engine, at: 'pghero'
   # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
   get "/" => "statistics#index"
   get "автобусы/:from/:to" => "trips#index"
diff --git a/db/migrate/20230419131514_enable_pg_statements.rb b/db/migrate/20230419131514_enable_pg_statements.rb
new file mode 100644
index 00000000..d5ec3fa2
--- /dev/null
+++ b/db/migrate/20230419131514_enable_pg_statements.rb
@@ -0,0 +1,5 @@
+class EnablePgStatements < ActiveRecord::Migration[5.2]
+  def change
+    enable_extension 'pg_stat_statements'
+  end
+end
\ No newline at end of file
diff --git a/db/migrate/20230421134615_create_index_on_bus_services.rb b/db/migrate/20230421134615_create_index_on_bus_services.rb
new file mode 100644
index 00000000..5db9ef50
--- /dev/null
+++ b/db/migrate/20230421134615_create_index_on_bus_services.rb
@@ -0,0 +1,6 @@
+class CreateIndexOnBusServices < ActiveRecord::Migration[5.2]
+  def change
+    add_index :buses_services, :bus_id
+    add_index :buses_services, :service_id
+  end
+end
\ No newline at end of file
diff --git a/db/migrate/20230421134619_create_index_on_trips.rb b/db/migrate/20230421134619_create_index_on_trips.rb
new file mode 100644
index 00000000..585cd9df
--- /dev/null
+++ b/db/migrate/20230421134619_create_index_on_trips.rb
@@ -0,0 +1,7 @@
+class CreateIndexOnTrips < ActiveRecord::Migration[5.2]
+  def change
+    add_index :trips, :bus_id
+    add_index :trips, [:from_id, :to_id]
+    add_index :trips, :start_time
+  end
+end
\ No newline at end of file
diff --git a/db/schema.rb b/db/schema.rb
index f6921e45..58f9dab1 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,9 +10,10 @@
 #
 # 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: 2023_04_21_134619) 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|
@@ -23,6 +24,8 @@
   create_table "buses_services", force: :cascade do |t|
     t.integer "bus_id"
     t.integer "service_id"
+    t.index ["bus_id"], name: "index_buses_services_on_bus_id"
+    t.index ["service_id"], name: "index_buses_services_on_service_id"
   end
 
   create_table "cities", force: :cascade do |t|
@@ -40,6 +43,9 @@
     t.integer "duration_minutes"
     t.integer "price_cents"
     t.integer "bus_id"
+    t.index ["bus_id"], name: "index_trips_on_bus_id"
+    t.index ["from_id", "to_id"], name: "index_trips_on_from_id_and_to_id"
+    t.index ["start_time"], name: "index_trips_on_start_time"
   end
 
 end
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..4e7e63f1
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,39 @@
+version: '3.4'
+services:
+  db:
+    image: postgres:10
+    environment:
+      POSTGRES_USER: $DB_USERNAME
+      POSTGRES_PASSWORD: $DB_PASSWORD
+    ports:
+      - "5432:5432"
+    command:
+      - "postgres"
+      - "-c"
+      - "shared_preload_libraries=pg_stat_statements"
+      - "-c"
+      - "pg_stat_statements.track=all"
+      - "-p"
+      - "5432"
+    volumes:
+      - db:/var/lib/postgresql/data
+  web:
+    tty: true
+    stdin_open: true
+    image: rails-optimization-task3
+    build:
+      context: .
+    command: sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
+    volumes:
+      - .:/opt/app:cached
+#    tmpfs:
+#      - /opt/app/tmp/
+#      - /opt/app/log/
+    ports:
+      - "3000:3000"
+    depends_on:
+      - db
+    environment:
+      RAILS_ENV: $RAILS_ENV
+volumes:
+  db:
diff --git a/lib/operations/import.rb b/lib/operations/import.rb
new file mode 100644
index 00000000..5939a4e4
--- /dev/null
+++ b/lib/operations/import.rb
@@ -0,0 +1,102 @@
+module Operations
+  class Import
+    def initialize
+      @services = {}
+      @cities = {}
+      @first_trips_line = true
+      @first_buses_line = true
+    end
+
+    def truncate_tables!
+      ActiveRecord::Base.connection.execute('truncate table cities, buses, services, trips, buses_services RESTART IDENTITY;')
+    end
+
+    def find_city_id(name)
+      @cities[name] ||= ActiveRecord::Base.connection.execute("insert into cities (name) values ('#{name}') ON CONFLICT DO NOTHING RETURNING id").first['id']
+    end
+
+    def import_services!
+      ActiveRecord::Base.connection.execute("insert into services (name) values ('#{Service::SERVICES.join('\'),(\'')}') RETURNING id, name").each do |result|
+        @services[result['name']] = result['id']
+      end
+    end
+
+    def insert_bus(model:, number:)
+      ActiveRecord::Base.connection.execute("insert into buses (model, number) values ('#{model}', '#{number}') ON CONFLICT DO NOTHING RETURNING id").first['id']
+    end
+
+    def insert_bus_services(bus_id:, service_names:)
+      service_names.map! { |n| "(#{bus_id}, #{find_service_id(n)})" }
+      ActiveRecord::Base.connection.execute("insert into buses_services (bus_id, service_id) values #{service_names.join(',')} ON CONFLICT DO NOTHING")
+    end
+
+    def buses_services_sql_file
+      # bus_id, service_id
+      @buses_services_sql_file ||= File.open('log/buses_services_sql.txt', 'w+')
+    end
+
+    def trips_sql_file
+      # start_time, duration_minutes, price_cents, from_id, to_id, bus_id
+      @trips_sql_file ||= File.open('log/trips_sql.txt', 'w+')
+    end
+
+    def write_trip(bus_id, raw_data)
+      if @first_trips_line
+        trips_sql_file.write 'INSERT INTO trips (start_time, duration_minutes, price_cents, from_id, to_id, bus_id) VALUES '
+        @first_trips_line = false
+      else
+        trips_sql_file.write ','
+      end
+
+      trips_sql_file.write <<SQL
+('#{raw_data['start_time']}',
+#{raw_data['duration_minutes']},
+#{raw_data['price_cents']},
+#{find_city_id(raw_data['from'])},
+#{find_city_id(raw_data['to'])},
+#{bus_id})
+SQL
+    end
+
+    def write_bus_service(bus_id, service_name)
+      if @services[service_name]
+        if @first_buses_line
+          buses_services_sql_file.write 'INSERT INTO buses_services (bus_id, service_id) VALUES '
+          @first_buses_line = false
+        else
+          buses_services_sql_file.write ','
+        end
+
+        buses_services_sql_file.write "(#{bus_id},#{@services[service_name]})"
+      end
+    end
+
+    def work(file)
+      json = JSON.parse(File.read(file))
+      truncate_tables!
+
+      ActiveRecord::Base.transaction do
+        # т.к. сервисов ограниченное небольшое количество их можно сразу добавить
+        import_services!
+
+        json.each do |raw_data|
+          bus_id = insert_bus(model: raw_data['bus']['model'], number: raw_data['bus']['number'])
+
+          raw_data['bus']['services'].each do |service_name|
+            write_bus_service bus_id, service_name
+          end
+
+          write_trip bus_id, raw_data
+        end
+
+        buses_services_sql_file.rewind
+        trips_sql_file.rewind
+        ActiveRecord::Base.connection.execute(buses_services_sql_file.read)
+        ActiveRecord::Base.connection.execute(trips_sql_file.read)
+      end
+
+      buses_services_sql_file.close
+      trips_sql_file.close
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/tasks/profile.rake b/lib/tasks/profile.rake
new file mode 100644
index 00000000..0028eff4
--- /dev/null
+++ b/lib/tasks/profile.rake
@@ -0,0 +1,43 @@
+namespace :profile do
+  def target_task
+    Operations::Import.new.work('fixtures/large.json')
+  end
+
+  task by_memory: :environment do
+    report = MemoryProfiler.report do
+      target_task
+    end
+
+    report.pretty_print(scale_bytes: true)
+  end
+
+  task by_wall_time: :environment do
+    GC.disable
+    RubyProf.measure_mode = RubyProf::WALL_TIME
+
+    result = RubyProf.profile { target_task }
+
+    printer = RubyProf::FlatPrinter.new(result)
+    printer.print(File.open('log/ruby_prof_reports/flat.txt', 'w+'))
+
+    printer = RubyProf::GraphHtmlPrinter.new(result)
+    printer.print(File.open('log/ruby_prof_reports/graph.html', 'w+'))
+
+    # printer = RubyProf::CallStackPrinter.new(result)
+    # printer.print(File.open('log/ruby_prof_reports/callstack.html', 'w+'))
+
+    GC.enable
+  end
+
+  task benchmark: :environment do
+    time = Benchmark.realtime { target_task }
+    puts time.round(2)
+    puts "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024)
+
+    puts Bus.count
+    puts City.count
+    puts Service.count
+    puts Trip.count
+    puts Service.last.buses.count
+  end
+end
\ No newline at end of file
diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake
index 540fe871..d2418833 100644
--- a/lib/tasks/utils.rake
+++ b/lib/tasks/utils.rake
@@ -1,34 +1,7 @@
+# frozen_string_literal: true
+require 'operations/import'
 # Наивная загрузка данных из json-файла в БД
 # rake reload_json[fixtures/small.json]
 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'])
-      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)
-
-      Trip.create!(
-        from: from,
-        to: to,
-        bus: bus,
-        start_time: trip['start_time'],
-        duration_minutes: trip['duration_minutes'],
-        price_cents: trip['price_cents'],
-      )
-    end
-  end
+  Operations::Import.new.work(args.file_name)
 end