Запрос ActiveRecord для коллекции с использованием SELECT MAX, демонстрирующий низкую производительность в Rails

#ruby-on-rails #postgresql #indexing #max #rails-activerecord

#ruby-on-rails #postgresql #индексирование #макс #rails-activerecord

Вопрос:

У меня есть 3 основные модели, потенциально могут быть тысячи компонентов и тестовых записей.

 class Component
  has_many :test_records, as: :testable
end

class TestRecord
  belongs_to :testable, polymorphic: true
  belongs_to :test_form
end

class TestForm
  has_many :test_records
end
 

Тестовые записи создаются после тестирования проверяемого элемента, и они приводят к пропуску или сбою, сохраняемому как логическое значение

 test_record.passed
 

То, что я пытаюсь сделать, это извлечь самую последнюю тестовую запись для каждого компонента и определить, прошла она или нет. Сначала я проверяю, был ли он протестирован в приведенной ниже области:

   scope :tested, ->(test_form_id) { 
    where("test_records.created_at > ?", 45.days.ago)
    .order(created_at: :desc)
    .distinct
    .joins(:test_records)
    .where("test_records.test_form_id = ?", test_form_id)
    .active
  }
 

и затем я передаю его:

   scope :passed, ->(test_form_id, type) {
    tested(test_form_id)
    .includes(:test_records)
    .where('test_records.test_form_id = ?', test_form_id)
    .where("test_records.created_at = (
     SELECT MAX(test_records.created_at) 
     FROM test_records 
     WHERE test_records.testable_id = #{type}.id)")
    .where("tested_records.passed = none")
   }
 

Выполнение этого запроса в рабочей среде занимает 20 секунд.

Я пробовал следующее:

 scope :passed, ->(test_form_id, type) {
      tested(test_form_id)
      .distinct('ON (test_records.testable_id)')
      .order('test_records.created_at DESC')
      .where('test_records.test_form_id = ?', test_form_id)
      .where('test_records.passed = ?', true)
      .includes(:test_records)
  }
 

но он не дает правильного результата, потому что он отфильтровывает переданную тестовую запись == false перед вызовом distinct.

TLDR: у меня есть компоненты, которые были протестированы, я хочу выбрать последнюю test_record, которая была создана для определенной test_form, если test_record.passed == true; В настоящее время запрос занимает в 40 раз больше времени, чем хотелось бы, и я был бы признателен за любую помощь в очистке моего активного запроса записи

Спасибо!

редактировать: вот PG, ОБЪЯСНЯЮЩИЙ результаты анализа:

     Unique  (cost=1155.71..1155.76 rows=1 width=150) (actual time=8504.313..8504.326 rows=2 loops=1)
   ->  Sort  (cost=1155.71..1155.72 rows=1 width=150) (actual time=8504.312..8504.317 rows=2 loops=1)
         Sort Key: components.created_at DESC, components.id, components.barcode, components.description, components.make, components.model, components.system_id, components.updated_at, components.active, components.component_type, components.abbreviation, components.address, test_records.id, test_records.user_id, test_records.passed, test_records.created_at, test_records.updated_at, test_records.manufacturer_test_form_id
         Sort Method: quicksort  Memory: 25kB
         ->  Hash Join  (cost=798.86..1155.70 rows=1 width=150) (actual time=8502.399..8504.226 rows=2 loops=1)
               Hash Cond: ((test_records.testable_id = components.id) AND (test_records.created_at = (SubPlan 1)))
               ->  Hash Join  (cost=42.09..243.44 rows=9 width=70) (actual time=1.000..1.010 rows=4 loops=1)
                     Hash Cond: (test_records.testable_id = components_systems.component_id)
                     ->  Bitmap Heap Scan on test_records  (cost=19.85..218.41 rows=718 width=62) (actual time=0.409..0.718 rows=418 loops=1)
                           Recheck Cond: (test_form_id = 14)
                           Filter: (passed AND (created_at > '2020-11-03 02:49:31.234492'::timestamp without time zone) AND ((testable_type)::text = 'Component'::text))
                           Rows Removed by Filter: 1106
                           Heap Blocks: exact=92
                           ->  Bitmap Index Scan on index_test_records_on_test_form_id  (cost=0.00..19.67 rows=1518 width=0) (actual time=0.072..0.073 rows=1525 loops=1)
                                 Index Cond: (test_form_id = 14)
                     ->  Hash  (cost=16.43..16.43 rows=465 width=8) (actual time=0.214..0.215 rows=478 loops=1)
                           Buckets: 1024  Batches: 1  Memory Usage: 27kB
                           ->  Index Only Scan using index_components_systems_on_system_id_and_component_id on components_systems  (cost=0.29..16.43 rows=465 width=8) (actual time=0.039..0.119 rows=478 loops=1)
                                 Index Cond: (system_id = 2747)
                                 Heap Fetches: 0
               ->  Hash  (cost=704.23..704.23 rows=3503 width=88) (actual time=8499.537..8499.538 rows=1509 loops=1)
                     Buckets: 4096  Batches: 1  Memory Usage: 246kB
                     ->  Bitmap Heap Scan on components  (cost=43.81..704.23 rows=3503 width=88) (actual time=0.197..3.433 rows=3478 loops=1)
                           Recheck Cond: (component_profile_id = 13)
                           Filter: (active AND active)
                           Rows Removed by Filter: 22
                           Heap Blocks: exact=235
                           ->  Bitmap Index Scan on index_components_on_component_profile_id  (cost=0.00..42.94 rows=3553 width=0) (actual time=0.165..0.165 rows=3695 loops=1)
                                 Index Cond: (component_profile_id = 13)
                     SubPlan 1
                       ->  Aggregate  (cost=345.43..345.44 rows=1 width=8) (actual time=2.440..2.440 rows=1 loops=3480)
                             ->  Seq Scan on test_records test_records_1  (cost=0.00..345.43 rows=1 width=8) (actual time=1.775..2.437 rows=0 loops=3480)
                                   Filter: (testable_id = components.id)
                                   Rows Removed by Filter: 13897
 Planning Time: 0.794 ms
 Execution Time: 8504.505 ms
 

Ответ №1:

Ну, решение было действительно более простым, чем я думал. Я добавил миграцию ниже:

 add_index :test_records, [:testable_id, :testable_type]
 

После этого запрос упал до 1.010 мс