在Heroku上配置Puma集群

19

我需要关于在我的RoR4 Heroku应用上配置Puma(多线程+多核服务器)的帮助。

Heroku文档并不是最新的。我按照这篇文章:Concurrency and Database Connections 进行了配置,但是它没有提到如何为集群进行配置,所以我不得不同时使用两种类型(线程和多核)。

我的当前配置:

./Procfile

web: bundle exec puma -p $PORT -C config/puma.rb

./config/puma.rb

environment production
threads 0,16

workers 4
preload_app!

on_worker_boot do
  ActiveRecord::Base.connection_pool.disconnect!

  ActiveSupport.on_load(:active_record) do
    config = Rails.application.config.database_configuration[Rails.env]
    config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
    config['pool']              = ENV['DB_POOL'] || 5
    ActiveRecord::Base.establish_connection
  end
end

问题:

a) 由于 Cluster 工作进程是被 fork 的,我是否需要类似 Unicorn 中的 before_fork / after_fork 配置?
b) 如何根据我的应用程序调整线程数量 - 降低线程数量的原因是什么?在什么情况下会有所不同? 0:16 不已经优化了吗?
c) Heroku 数据库允许 500 个连接。根据线程、工作进程和 dyno 数量,DB_POOL 的适当值是多少?每个线程、工作进程和 dyno 是否都需要单独的 DB 连接来并行工作?

总体而言,如何配置并发性和性能?


当涉及到调整线程数时,我读了一篇关于独角兽工作进程调优的教程,建议运行ab并增加工作进程数(在您的情况下是线程),直到性能下降(请求需要更长时间才能完成)。最好先使用相对动态的页面,观察不同请求/并发比例的表现(还要记住,如果您发送了许多请求,Heroku 可能会中断您的连接,怀疑您正在进行 DoS 攻击)。 - Mike Szyndel
@MichaelSzyndel 所以我基本上必须先遍历每个工作线程,检查性能,然后再遍历线程并再次检查吗?这不取决于具体请求了吗? - Miiller
1
据我在某处读到的信息,Heroku每个dyno有两个核心(4个虚拟核心)。最佳实践是每个dyno运行一个进程,然后由您决定每个进程运行多少线程。我会用ab进行测试。还要注意,如果您使用了521MB的RAM,Heroku将发送警报,并且在>1GB时进行交换(请参阅Heroku文档以确认)。 - Mike Szyndel
你使用哪种Dyno类型?你提到了:“Multi-Thread+Multi-Core Server”,这是否意味着使用PX Dyno(每月$500)? - nothing-special-here
2个回答

28

a) 在 Cluster 工作进程被 fork 后,是否需要像 Unicorn 那样使用 before_fork / after_fork 配置呢?preload_app 的情况下是需要的。预加载应用程序会启动实例,并为工作进程 fork 出内存空间;结果是你的初始化器只会运行一次(可能分配数据库连接等)。在这种情况下,你的 on_worker_boot 代码很适合。如果没有使用 preload_app,那么每个 worker 将自己启动,这种情况下使用初始化器来设置自定义连接是理想的,就像你正在做的那样。事实上,如果没有 preload_app,你的 on_worker_boot 块将出错,因为此时 ActiveRecord 等还没有被加载。

b) 如何根据我的应用程序调整线程计数?降低线程计数的原因是什么?在什么情况下会有影响?0:16 已经优化了吗?

0:16 表示 Puma 的线程数和 Worker 数。优化取决于应用的性质。对于 CPU 密集型任务(如密码哈希),使用与核心数相同的线程数通常是最佳选择。对于 I/O 密集型任务(如大量读取文件),使用较少的线程数可能更好,因为线程可能会在等待 I/O 时处于空闲状态。

在Heroku(和我的测试)中,您最好将min/max线程与DB_POOL设置相匹配,其中max<=DB_POOLmin线程允许应用程序在没有负载时旋转资源,这通常很好地释放了服务器上的资源,但在Heroku上可能不太需要;那个dyno已经专门用于服务Web请求,因此最好让它们处于启动状态。虽然不需要将max线程<= DB_POOL环境变量设置,但您有可能会消耗池中所有数据库连接,然后您有一个线程想要连接但无法获取它,您就会得到旧的“ActiveRecord :: ConnectionTimeoutError - 在5秒内无法获得数据库连接”错误。这取决于您的应用程序,您的max > DB_POOL也完全可以正常运行。我建议将您的DB_POOL设置为至少与您的min线程值相同,即使您的连接没有被急切加载(5:5线程如果您的应用程序从未访问数据库,则不会打开5个连接)。

c)Heroku数据库允许500个连接。根据线程,工作进程和dyno计数,DB_POOL的良好值是多少?-每个线程/工作程序/ Dyno是否都需要在并行工作时拥有独立的DB连接?

为清楚起见,Production Tier允许500个。

每个工作者的每个线程可能会消耗一个连接,这取决于它们是否都在同时尝试访问数据库。通常,一旦完成操作,连接就会被重用,但正如我在b)中提到的那样,如果您的线程数大于池的大小,可能会遇到问题。这些连接将被重用,这一切都由ActiveRecord处理,但有时并不理想。有时连接会变为空闲状态或死亡,这就是为什么建议启用Reaper来检测和回收死掉的连接。


我理解的是 workers 2 意味着为 Puma 添加了两个工作进程,因此总共有 3 个进程,这样对吗? - gaussblurinc

4

您不希望DB连接数少于线程数。请记住,每个独立的进程都有自己的连接池,因此,如果您的DB支持20个连接,并且要运行2个进程,则在不冒超时风险的情况下可以运行的线程最多为10个,每个线程具有10个连接池。

您需要为rails控制台会话保留一些连接。还要注意后台工作者及其是否线程化。

如果您的工作者位于单独的进程中(例如sidekiq),它们将拥有自己的连接池。如果您的工作者线程是从Web进程(例如girl_friday或sucker_punch)中生成的,则您需要将DB_POOL设置为大于Web线程的最大数量,因为它们将共享一个连接池。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接