Рабочий захватывает модель Active Record

Я рубин-юниор. Мое приложение позволяет пользователю вводить контакты и / или загружать файл CSV.

  • Работая в моей локальной ветке, если я добавлю контакт - контакт будет добавлен из View, Controller и dBase, и там он отлично работает.
  • Если я разрешаю пользователю импортировать файл CSV. Начнется импорт файла. Однако теперь пользователь не может добавить контакт через приложение. По сути, он зависает до завершения импорта CSV.

Я использую следующие версии:

  ruby "2.3.0"
  gem "rails", "4.2.5.1" gem "pg", "0.17.1" # postgresql database 
  gem "delayed_job_active_record", ">= 4.0.0.beta1" # background job
  processing gem "delayed_job_web", ">= 1.2.0" # web interface for delayed job

Также использую:

> class CsvUploader < CarrierWave::Uploader::Base

def store_dir "uploads / # {model.class.to_s.underscore} / # {mount_as} / # {model.id}" end

Вот рабочий:

класс ImportCsvFileWorker

def self.perform (csv_file_id) csv_file = CsvFile.find (csv_file_id)

csv_file.import!
csv_file.send_report!   end

конец

Я использую службу синтаксического анализа smarecsv

def process_csv parser = :: ImportData :: SmartCsvParser.new (csv_file.file_url)

parser.each do |smart_row|
  csv_file.increment!(:total_parsed_records)
  begin
    self.contact = process_row(smart_row)
  rescue => e
    row_parse_error(smart_row, e)
  end
end   rescue => e # parser error or unexpected error
csv_file.save_import_error(e)   end

Блокирует ли delayed_job базу данных для пользователя / контакта, поэтому я не могу добавлять контакты через приложение?

Локально приложение заморожено / зависает или кажется заблокированным до тех пор, пока не будет завершена фоновая delayed_job (кстати, если я запускаю на Heroku, это вызывает ошибки H12, но, думаю, мне нужно сначала исправить проблему локально). Просто пытаюсь понять - что заставляет его блокироваться? Должен ли он делать это? Это код (бизнес-логика файла CSV и представление добавления контакта работают независимо)? Но сторона приложения не будет работать, если запущено фоновое задание или это способ обработки Active Record. Это можно обойти?

Я не изолировал его, но подозреваю, что если выполняется какое-либо фоновое задание, приложение становится недоступным.

Я попытался включить все относящиеся к делу факты - дайте мне знать, если потребуются какие-либо дополнительные подробности. большое спасибо за помощь.

ОБНОВЛЕНИЕ - я обнаружил, что у меня есть ContactMergingService, который, кажется, блокирует все контакты. Если я закомментирую эту услугу ниже, мое приложение не зависнет.

Итак, мой вопрос в том, какие есть другие варианты = Перед добавлением контакта я пытаюсь найти все существующие одинаковые адреса электронной почты (если я его найду, я добавляю контактные данные). как мне это сделать без блокировки базы данных?

это потому, что я использую метод поиска? Есть ли способ лучше?

> class ContactMergingService
> 
>   attr_reader :new_contact, :user
> 
>   def initialize(user, new_contact, _existing_records)
>     @user = user
>     @new_contact = new_contact
>     @found_records = matching_emails_and_phone_numbers   
>   end
> 
>   def perform
>     Rails.logger.info "[CSV.merging] Checking if new contact matches existing contact..."
>     if (existing_contact = existing_contact())
>       Rails.logger.info "[CSV.merging] Contact match found."
>       merge(existing_contact, new_contact)
>       existing_contact
>     else
>       Rails.logger.info "[CSV.merging] No contact match found."
>       new_contact
>     end   end
> 
>   private
> 
>   def existing_contact
>     Rails.logger.info "[CSV.merging] Found records: #{@found_records.inspect}"
>     if @found_records.present?
>       @user.contacts.find @found_records.first.owner_id # Fetch first owner
>     end   end
> 
>   def merge(existing_contact, new_contact)
>     Rails.logger.info "[CSV.merging] Merging with existing contact (ID: #{existing_contact.id})..."
>     merge_records(existing_contact, new_contact)   end
> 
>   def merge_records(existing_relation, new_relation)
>     existing_relation.attributes do |field, value|
>       if value.blank? && new_relation[field].present?
>         existing_relation[field] = new_relation[field]
>       end
>     end
>     new_relation.email_addresses.each do |email_address|
>       Rails.logger.info "[CSV.merging.emails] Email: #{email_address.inspect}"
>       if existing_relation.email_addresses.find_by(email: email_address.email)
>         Rails.logger.info "[CSV.merging.emails] Email address exists."
>       else
>         Rails.logger.info "[CSV.merging.emails] Email does not already exist. Saving..."
>         email_address.owner = existing_relation
>         email_address.save!
>       end
>     end
>     new_relation.phone_numbers.each do |phone_number|
>       Rails.logger.info "[CSV.merging.phone] Phone Number: #{phone_number.inspect}"
>       if existing_relation.phone_numbers.find_by(number: phone_number.number)
>         Rails.logger.info "[CSV.merging.phone] Phone number exists."
>       else
>         Rails.logger.info "[CSV.merging.phone] Phone Number does not already exist. Saving..."
>         phone_number.owner = existing_relation
>         phone_number.save!
>       end
>     end   end
> 
>   def matching_emails_and_phone_numbers
>     records = []
>     if @user
>       records << matching_emails
>       records << matching_phone_numbers
>       Rails.logger.info "[CSV.merging] merged records: #{records.inspect}"
>       records.flatten!
>       Rails.logger.info "[CSV.merging] flattened records: #{records.inspect}"
>       records.compact!
>       Rails.logger.info "[CSV.merging] compacted records: #{records.inspect}"
>     end
>     records   end
> 
>   def matching_emails
>     existing_emails = []
>     new_contact_emails = @new_contact.email_addresses
>     Rails.logger.info "[CSV.merging] new_contact_emails: #{new_contact_emails.inspect}"
>     new_contact_emails.each do |email|
>       Rails.logger.info "[CSV.merging] Checking for a match on email: #{email.inspect}..."
>       if existing_email = @user.contact_email_addresses.find_by(email: email.email, primary: email.primary)
>         Rails.logger.info "[CSV.merging] Found a matching email"
>         existing_emails << existing_email
>       else
>         Rails.logger.info "[CSV.merging] No match found"
>         false
>       end
>     end
>     existing_emails   end
> 
>   def matching_phone_numbers
>     existing_phone_numbers = []
>     @new_contact.phone_numbers.each do |phone_number|
>       Rails.logger.info "[CSV.merging] Checking for a match on phone_number: #{phone_number.inspect}..."
>       if existing_phone_number = @user.contact_phone_numbers.find_by(number: phone_number.number)
>         Rails.logger.info "[CSV.merging] Found a matching phone number"
>         existing_phone_numbers << existing_phone_number
>       else
>         Rails.logger.info "[CSV.merging] No match found"
>         false
>       end
>     end
>     existing_phone_numbers   end
> 
>   def clean_phone_number(number)
>     number.gsub(/[\s\-\(\)]+/, "")   end
> 
> end

person user2970050    schedule 04.04.2016    source источник
comment
Можете ли вы загрузить модель csv_file?   -  person Patrick O'Grady    schedule 08.04.2016


Ответы (2)


Вы можете попробовать что-то вроде:

 Thread.new do
    ActiveRecord::Base.transaction do   
      User.import(user_data)
    end
    ActiveRecord::Base.connection.close
 end

В вашем коде импорта CVS.

person aarkerio    schedule 04.04.2016
comment
спасибо за ваше предложение. Я понял, комментируя ContactMergeService, проблема исчезла. Итак, теперь мой вопрос: что вызывает «блокировку» всех контактов и пользователей в указанной выше службе? - person user2970050; 04.04.2016
comment
@aakerio - отдал вам должное, поскольку это открыло нам путь к размышлениям .... в то время как добавление «ветки» не решило проблему. Вы упомянули ActiveRecord :: Base .transaction do ..... заставили нас понять основную причину, приведенную ниже. огромное спасибо. - person user2970050; 14.04.2016

Мы пришли к выводу, что причина проблемы заключалась в том, что когда CsvParsingService # выполняет запуски, он помещает AccessShareLocks в определенные таблицы в базе данных (мы думаем, что контакты, адреса электронной почты, номера телефонов и, возможно, пользователи).

Блокировки сохраняются до завершения метода. Любой другой запрос, который пытался получить доступ к одной из этих заблокированных таблиц, просто будет ждать, пока база данных не будет разблокирована. Поскольку метод анализирует каждую строку данного загруженного файла csv_file, его выполнение занимает до 90 минут.

Любой запрос к приложению, которое пытается получить доступ к одной из этих заблокированных таблиц, будет остановлен и будет ждать, пока таблицы не будут разблокированы. Поскольку Heroku отключит запрос через 30 секунд, именно это вызывало ошибки H12 (на стороне приложения).

Причина проблемы заключалась в том, что запись gem state-machine_active по умолчанию обертывает каждый переход состояния внутри транзакции.

Рабочий вызывал службу синтаксического анализа, запустив csv_file.import !, который инициировал переход на конечном автомате csv_file, который затем вызвал CsvParsingService и проанализировал каждую строку. Поскольку конечный автомат оборачивал все внутри транзакции, ничего не фиксировалось, пока переход состояния не был завершен.

Обновив гем до версии 0.4.0pre и добавив параметр use_transactions: false в конечный автомат в модели CsvFile, он больше не блокирует базу данных при вызове .import! и обработка.

person user2970050    schedule 09.04.2016