Выполняется ли concurrency, даже если в пуле потоков имеется только один поток?

Я использую Rails 5 и Ruby 2.4. Как я могу выяснить, или вы можете сказать, посмотрев ниже, есть ли одновременно несколько потоков?

pool = Concurrent::FixedThreadPool.new(1)
promises = links.map do |link|
  Concurrent::Promise.execute(executor: pool) do
    result = process_link(link)
    if result
      if result.kind_of?(Array)
        result.each do |my_obj|
          my_obj.update_attributes({ :a => a })
          records_processed = records_processed + my_obj.matches.count
        end
      else
        records_processed = records_processed + result.matches.count
        result.update_attributes({ :a => a })
      end
    end
  end
end
promises.map(&:wait).map(&:value!)

Как я установил свой пул в "1", мое предположение заключается в том, что ничто не работает одновременно, но я продолжаю получать эту ошибку...

Error during processing: (ActiveRecord::ConnectionTimeoutError) could not obtain a connection from the pool within 5.000 seconds (waited 5.002 seconds); all pooled connections were in use
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:202:in `block in wait_poll'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:193:in `loop'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:193:in `wait_poll'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:154:in `internal_poll'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:278:in `internal_poll'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:148:in `block in poll'
/Users/nataliab/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/monitor.rb:214:in `mon_synchronize'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:158:in `synchronize'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:148:in `poll'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:717:in    `acquire_connection'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:490:in `checkout'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:364:in `connection'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:883:in `retrieve_connection'
/Users/nataliab/.rvm/gems/[email protected]/gems/activerecord-5.0.1/lib/active_record/connection_handling.rb:128:in `retrieve_connection'

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

links.each do |link|
  result = process_link(link)
  if result
    if result.kind_of?(Array)
      result.each do |race|
        my_obj.update_attributes({ :a => a })
        records_processed = records_processed + my_obj.matches.count
      end
    else
      records_processed = records_processed + result.matches.count
      result.update_attributes({ :a => a })
    end
  end
end

Изменить: Это моя конфигурация базы данных для моей среды разработки. Также обратите внимание, что все это выполняется в консоли rails.

development:
  adapter: postgresql
  encoding: utf8
  database: sims 
  username: postgres
  password: password
  pool: 5
  timeout: 15000
  host: 127.0.0.1

Ответ 1

Ваше предположение, что несколько потоков должны выполняться одновременно только из-за того, что пул подключений исчерпан, неверен. Просто потому, что соединение по-прежнему "проверено" из пула соединений, не означает, что запрос выполняется в проверенном соединении в потоке, это просто означает, что соединение потока еще не было проверено. Нить может сидеть без дела, но все еще удерживать соединение из пула соединений, если оно не было явно завершено.

Поскольку ActiveRecord Connections являются локальными потоками, вы можете исчерпать пул соединений, запустив запросы ActiveRecord для нескольких потоков, как вы это делаете в этом случае. (Каждый раз, когда вызывается Concurrent::FixedThreadPool.new(1), создается новый поток.) ​​Даже если вы выполняете только запросы по одному потоку за раз, по умолчанию соединение будет оставаться открытым на каждом потоке до тех пор, пока они не будут завершены.

Чтобы этого избежать, вы можете вручную проверить соединения после их использования или убедиться, что ваши потоки завершены (убиты), чтобы их соединения могли быть восстановлены (извлечены) пулом.

  • Чтобы вручную проверить соединения, обратитесь к ConnectionPool документации для ваших опций. Самый простой способ - обернуть свой код ActiveRecord в блок with_connection:

    Concurrent::Promise.execute(executor: pool) do
      ActiveRecord::Base.connection_pool.with_connection do
        # update_attributes, etc
      end
    end
    
  • Чтобы все потоки были завершены, вызовите #shutdown, а затем #wait_for_termination. в пуле потоков после того, как вы закончите использовать его:

    values = promises.map(&:value!)
    pool.shutdown
    pool.wait_for_termination
    

Ответ 2

Предполагается, что существует только один поток. Есть два - один в пуле потоков и главный, который породил тот, что в пуле потоков.

Вы можете быть смущены, поскольку вы заставили главный поток ждать, и он не должен обращаться к базе данных. Это не означает, что он все еще не поддерживает соединение, поэтому предотвращает его получение другим.

Как правило, ваш пул соединений с базой данных должен быть установлен как минимум на число порожденных нитей + 1. В этом случае - 2.


Код, который легко воспроизвести:

# migration
class CreateFoos < ActiveRecord::Migration[5.0]
  def change
    create_table :foos do |t|
      t.integer :bar
    end
  end
end

# model
class Foo < ApplicationRecord
end

# rake task
task experiment: :environment do
  Foo.create
  pool = Concurrent::FixedThreadPool.new(1) 
  promise = 
    Concurrent::Promise.execute(executor: pool) do
      Foo.first.update_attributes!(bar: rand(-42..42))
    end
  promise.wait.value!
end

Установите pool в 1 в config/database.yml и запустите задачу. Вы получите сообщение об ошибке. Установите его в 2 - это будет нормально.

Вы можете увеличить количество потоков в пуле и добавить для этого как минимум promises. Вы последовательно откажете пул подключения к базе данных = количество потоков в пуле потоков и преуспеть, если вы добавите еще один элемент в config/database.yml.

Ответ 3

Поскольку вы определяете фиксированный пул потоков, чтобы иметь один поток, я бы предположил, что вы не достигаете каких-либо concurrency. Глядя на вашу ошибку, кажется, что один доступный поток из пула слишком долго занят и вызвал исключение таймаута соединения.

Когда вы изменили реализацию кода, чтобы он не содержал пул потоков, приложение было явно однопоточным без возможности таймаута соединения из-за ожидания потоков из пула. Попытайтесь увеличить размер пула потоков (возможно, до 3 или 5) и посмотреть, все ли вы получаете то же исключение.