Приложение Rails зависает при отправке события с сервера (SSE), требующего действий базы данных - PullRequest
0 голосов
/ 11 марта 2020

Я впервые учусь и делаю SSE на рельсах! Код моего контроллера:

  def update
    response.headers['Content-Type'] = 'text/event-stream'
    sse = SSE.new(response.stream, event: 'notice')
    begin
      User.listen_to_creation do |user_id|
        sse.write({id: user_id})
      end
    rescue ClientDisconnected
    ensure
      sse.close
    end
  end

Внешний интерфейс:

  var source = new EventSource('/site_update');
  source.addEventListener('notice', function(event) {
    var data = JSON.parse(event.data)
    console.log(data)
  });

Модель pub / sub

class User
  after_commit :notify_creation, on: :create

  def notify_creation
      ActiveRecord::Base.connection_pool.with_connection do |connection|
        self.class.execute_query(connection, ["NOTIFY user_created, '?'", id])
      end
  end

  def self.listen_to_creation
    ActiveRecord::Base.connection_pool.with_connection do |connection|
      begin
        execute_query(connection, ["LISTEN user_created"])
        connection.raw_connection.wait_for_notify do |event, pid, id|
          yield id
        end
      ensure
        execute_query(connection, ["UNLISTEN user_created"])
      end
    end
  end

  def self.clean_sql(query)
    sanitize_sql(query)
  end

  private

  def self.execute_query(connection, query)
    sql = self.clean_sql(query)
    connection.execute(sql)
  end
end

Я заметил, что если я пишу в SSE , что-то тривиальное, как в учебнике, как ... sse.write({time_now: Time.now}), все прекрасно работает. В командной строке CTRL+C успешно завершает работу локального сервера.

Однако всякий раз, когда мне нужно написать что-то, требующее какого-либо действия с базой данных, например, когда я выполняю postgres pub / sub как в этом учебном пособии , тогда CTRL+C не выключает локальный сервер, он просто зависает и зависает и требует от меня ручного уничтожения PID.

при фактическом вращении на сервере, иногда ссылка refre sh также будет зависать вечно. В других случаях выдается ошибка тайм-аута:

ActiveRecord::ConnectionTimeoutError (could not obtain a connection from the pool within 5.000 seconds (waited 5.001 seconds); all pooled connections were in use):

К сожалению, эта проблема сохраняется и в производстве, где я использую Heroku. Я просто получаю много ошибок тайм-аута. Но я думаю, что у меня правильно настроен Heroku, а также локальные настройки ... Насколько я понимаю, мне просто нужен большой пул (у меня есть 5), чтобы получать соединения и разрешать несколько потоков. Ниже вы найдете некоторый код конфигурации.

НЕТ ПЕРЕМЕННЫХ ENV, ПО УМОЛЧАНИЮ ИСПОЛЬЗУЕТСЯ!

# config/database.yml
default: &default
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: proper_development


# config/puma.rb
workers Integer(ENV['WEB_CONCURRENCY'] || 1)

threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

Если это полезно, вот вывод, когда я запускаю rails s

=> Booting Puma
=> Rails 5.0.2 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 4.3.3 (ruby 2.4.0-p0), codename: Mysterious Traveller
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop

1 Ответ

0 голосов
/ 02 апреля 2020

Похоже, проблема здесь заключается в отсутствии согласованности между потоками puma и соединениями с базой данных. Если какое-то соединение было инициировано middleware et c через AR, написанный вами код может привести к тому, что два соединения будут удерживаться в одном цикле запроса до тех пор, пока вы не получите уведомление, и поток не завершит свою работу. AR кэширует соединения на поток , поэтому, если был сделан запрос и соединение было извлечено из пула, он будет удерживаться этим. Посмотрите на эту проблему для более подробной информации. Если вы в конечном итоге используете пул соединений для проверки еще одного соединения и заставите это соединение ждать, пока вы не получите уведомление от Postgres, потенциально два соединения могут удерживаться тем же потоком Puma, который ожидает.

Чтобы увидеть это в действии, запустите новый экземпляр сервера Rails в разработке и сделайте запрос к конечной точке SSE. Если вы получали тайм-ауты раньше, чем вы могли видеть два соединения с Postgres, в то время как вы сделали только один запрос к недавно запущенному серверу. Таким образом, даже несмотря на то, что количество потоков и размер пула соединений были одинаковыми, у вас могут не хватить свободных соединений из пула. Более простым способом может быть просто добавить эту строку в разработку после проверки соединения, чтобы увидеть, сколько кешируемых соединений сейчас удерживается.

def self.listen_to_creation
    ActiveRecord::Base.connection_pool.with_connection do |connection|
      # Print or log this array
      p ActiveRecord::Base.connection_pool.instance_variable_get(:@thread_cached_conns).keys.map(&:object_id)

      begin
        execute_query(connection, ["LISTEN user_created"])
.........
.........

Кроме того, опубликованные вами фрагменты, похоже, указывают, что вы запуск до 16 потоков в пуле соединений размера 5 в среде разработки, так что это другая проблема.

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

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

Редактировать: Кроме того, забыл добавить, что SSE в rails имеет проблему поддержания живых мертвых соединений, если он не знает связь мертва У вас могут быть потоки, которые бесконечно ждут, пока не придут какие-то данные, и они поймут, что соединение больше не является действительным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...