当您想要项目或启动的开发速度时,Ruby on Rails是一个很棒的框架。 它开箱即用非常有用,并带有大量幕后魔术,使您的生活更轻松。 但是,就性能而言,它并不是目前最快的框架。 您会发现一些个人和公司脱离Rails偏爱其他事物的例子。 尽管如此,还是有很多公司成功地扩展了Rails并获得了成功-看看Airbnb,Github,Gitlab和Shopify。
因此,在跳船之前,您应该考虑在与Rails一起工作时将性能放在首位,并且也可以成功。 本文旨在列出我多年来积累的最重要的提示和技巧,这些技巧和窍门使Rails能够以极快的速度运行并能够扩展到每分钟数百万个请求。
一般提示
首先,在Rails项目中要实现一些常规技巧,以使自己为成功做好准备。
设置APM
如果不先评估性能,就无法提高性能,因此必须跟踪和监视正确的指标。 您应该跟踪加载时间,请求时间和数据库查询时间等。 就个人而言,我发现New Relic是Rails最好的APM工具之一,但价格偏高。 更实惠的替代方法是SkyLight的免费试用。
保持最新
我个人知道,如果您将其过时的时间太长,为您的项目更新Rails版本会是多么令人恐惧的事情,我同情必须经历这一磨难的任何人。 因此,请帮自己一个忙,并尝试与Ruby和Rails的较新版本保持同步。 这将帮助您跳过跳过多个版本的痛苦,并确保您拥有所有较新的性能增强功能。
帕累托原理(80/20规则)
在开发软件时,这是一个众所周知的规则-帕累托原理。 这也被称为“少数人的法则”,并指出,对于大多数事件,80%的影响是由20%的原因产生的。 其背后的想法是,当您有更大的问题需要解决时,不要浪费时间进行微优化。 如果数据库查询非常慢,那么通过减少序列化的毫秒数将无法完成很多工作。 因此,请谨慎选择战斗。
保持精益
Rails背后有一个了不起的社区,还有一个宝石库,可以轻松地帮助您完成复杂的任务。 但是很容易被添加到项目中的宝石所吸引,从而导致膨胀。 在选择要添加到项目中的gem时要小心,并尝试保持依赖项精简。
懒惰—在后台工作中做
每当您需要执行复杂或长时间运行的操作时,请考虑将其扔给后台工作人员-发送电子邮件,推送通知,上传图片等。 尽量减少主线程中的工作,将确保用户的响应迅速。 对我们而言,好事是Rails可以轻松实现多种选择-Sidekiq , Rescue或ActiveJob 。
更改序列化器
如果您的项目中有API,则很可能会使用ActiveModelSerializers gem来序列化数据。 我强烈建议改用Netflix的fast_jsonapi 。 这个宝石比默认的ActiveModelSerializers快25倍,我可以凭经验亲自证明这一点。
将我缓存到外面
有时,如果您有大量静态数据,或者无法加快处理速度,则另一种选择是使用缓存。 Rails使开箱即用的操作变得异常简单。 这是一个有到期时间的高速缓存的简单示例:
Rails . cache.fetch( "categories" , expires_in: 5. minutes) do <br> Categories . all.map(&:name)<br> end
活动记录(数据库)
ActiveRecord是Rails提供的神奇的ORM(有史以来最好的ORM之一)。 在不了解可能导致将来出现瓶颈的细节的情况下,很容易陷入易用性中。
使用您的数据库
许多开发人员急于在代码中执行所有操作,或者可能由于编写原始SQL而被吓倒了,这一点并未引起人们的注意或实现。 但是,只要有可能或方便,就应该使用您的数据库。 在Ruby中处理和排序数据结构可以减少CPU时间,而您的数据库可以做到这一点而不会费力。
幕后窥视
不要仅仅为活动录音的魅力而着迷,而不必担心幕后发生的事情。 为了消除性能瓶颈,您需要查看触发的实际查询并理解它们。 在开发环境中,Rails将打印出所有正在运行的查询,这使您可以注意到任何不必要的查询。 另外,这里有一些技巧可以帮助您更深入地进行挖掘:
# enable slow query logs in Rails
ActiveRecord::Base.logger = Logger.new(STDOUT)
# Get rails to show you the sql that will run
User.where(id: 1 ).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" = 1"
# Get rails to explain the sql query for more detail
User.where(id: 1 ).explain
# The explain will tell you which index is being used, and what work happens in the back.
=> EXPLAIN for : SELECT "users" .* FROM "users" WHERE "users" . "id" = $ 1 [[ "id" , 1 ]]
QUERY PLAN
--------------------------------------------------------------------------
Index Scan using users_pkey on users (cost= 0.14 . .8 .16 rows= 1 width= 962 )
Index Cond: (id = '1' ::bigint)
( 2 rows)
SQL向导
不用担心,我不建议您在原始SQL查询中编写所有内容。 但是,学习SQL和数据库设计的基础知识将帮助您更好地了解幕后情况,并允许您根据需要优化查询。 作为软件开发人员,这是一项宝贵的技能,它将对您的职业发展起到很大帮助。
小气
提高查询效率的一种方法是仅选择您真正需要的内容。 无需执行SELECT *
,而是指定需要数据库检索的列。 默认情况下,ActiveRecord会选择所有内容,但您可以利用选择或采摘来解决此问题。
# Benchmark both methods because in some cases pluck
# is faster than a select & map.
Category.select(:type)
Category.pluck(:type)
N + 1个查询
这是一个经典的问题。 如果要从数据库加载博客,然后尝试通过遍历记录查找该博客的所有注释,则将强制Rails对每个注释运行查询。 可以通过预加载注释Blog.includes(:comments)消除此问题,并有助于避免N + 1查询问题。
专家提示:看一下Bullet gem,以帮助您发现任何N + 1查询问题。
组合查询
当与具有多个开发人员的较大团队一起工作时,有时您会注意到每个接触代码库的人都可能在整个代码路径中添加查询。 通常,这些查询可以在代码路径的顶部合并为更少的查询。 这样可以确保没有重复的查询,并允许数据库从战略上进行繁重的工作。
索引,索引,索引
这是一个不应被忽略的数据库最佳实践。 如果索引不正确,将会对数据库性能产生负面影响,并导致不必要的表扫描。 构建新功能时,请先考虑项目中将要查询的内容,然后尝试添加正确的索引。 如果您有一个现有项目,则始终可以使用Active Record Doctor或LolDBA嗅出丢失的索引。
移居
大规模运行时,如果表中有数百万条记录,那么不幸的是,在Rails中运行迁移的常规方法会使您失败。 他们会出错或锁定表很长时间,从而使您的网站瘫痪。 过去已经解决了这一问题,有两种工具可以为您解决这一难题:
Gh-ost :这是MySQL的无触发器在线模式迁移解决方案,并且是唯一对我有用的工具。
LHM :这将在您的表在线时迁移它们,而不会锁定您的表。
极端提示:如果您敢,请使用Raw SQL
如果有一个急需性能的端点,则可以考虑将整个功能中的所有Active Record查询组合到顶部的单个大型SQL查询中,以提取实现用例所需的任何记录。
免责声明:是的,很难维护大规模的原始SQL查询,而且可读性也较差。 但是我确实提到这只是在绝望的情况下。
服务器和硬件
部署Rails项目时,有关基础架构和体系结构要记住几件事。
选择可扩展的架构
随着云基础设施走了很长一段路,为了构建应用程序而不得不构建裸机服务器的日子已经一去不复返了。 在决定Rails项目的基础架构时,您应该使用可伸缩的基于云的系统。 例如,您可以使用AWS Fargate或Kubernetes根据需要自动缩放您的dockerized Rails应用程序。
布罗特利压缩
Brotli是基于gzip的另一种压缩算法,具有多项改进和更好的压缩率。 现在大多数Web服务器都支持它,并且添加它是优化压缩速度的简便方法。 而且,谁也不想免费改善网络性能。 (参考: Brotli vs Gzip )
给它更多果汁
Rails因使用大量内存而臭名昭著,尤其是当您有多个运行彪马的工人时。 因此,不要让您的应用程序变得口渴,并从一开始就给它加汁。 您可以在云提供商上利用内存优化的实例来使您成功。 并且不要忘记关注服务器上的交换使用情况。
调音
由于Rails 5,Rails的Web服务器已切换到Puma,默认情况下,它将仅运行一个工作服务器。 一旦设置了Rails部署,请确保增加计算机上可用内核或更多合理内核上的工作线程数量。
HTTP / 2
如果您正在使用像Nginx这样的反向代理,请确保已打开HTTP / 2选项,以使您从中获得的所有性能优势。
顶上的樱桃(其他)
我们快要走到终点了—本节是关于性能的一些额外技巧。
不要使用动态方法
Rails魔术附带价格。 其中一些方法会消耗大量资源,因此最好跳过使用它们。 例如,find_by()和find_all_by()有点慢,因为它们需要遍历method_missing并针对数据库中的列列表解析名称。
节流
在扩展时,势必会在无法缓存的各种端点上遇到恶意尝试,这将导致昂贵的操作消耗资源。 为了解决这个问题,我建议添加诸如机架攻击之类的gem,以在登录,重置密码或注册等端点上实施限制。
知道你的O(n)vs O(1)
随着数据集大小的增加,我们需要注意代码在时间复杂度方面的影响。 在需要处理或循环处理大量数据的情况下,请考虑使用散列而不是默认使用数组。
一律使用CDN
您的所有静态资产都应始终以CDN开头,并确保缓存策略合理。 如果要对缓存失效进行精细控制,则可以使用电子标签和Cache-Control。
高级
在极端情况下,您可以考虑一些高级优化。 我会争论,如果您已经达到了这一点,那么可能是时候为项目中某些计算密集型的部分考虑其他语言了,所以我将在本节中保持简短。
刷C
Ruby的主要实现是用C编写的,它使您可以用C重写代码的慢速部分,例如,如果您正在使用加密算法来生成证书。 如果您对驻留在C代码中的想法不那么兴奋,则可以利用Rails社区使用C语言编写的第三方gem。
更快的后台作业
随着后台工作量的增加,您可以将后台工作人员切换到性能更高的语言。 通过使用Redis或Amazon SQS之类的队列,工作人员可以被分离到他们自己的微服务中。 签出此兼容Sidekiq的go-workers库或用Crystal作为两个选项编写的sidekick版本。
尝试更快的Ruby版本
Ruby的一些实现旨在提高性能。 如果您对这些变化感兴趣,请快速浏览Truffle-Ruby或JRub y。
底线
如果我们看一下使用Rails的公司,我们会发现他们能够利用Rails惊人的开发速度将客户放在第一位,并且在扩展规模时也设法提高了性能。 在本文中,我们讨论了提高性能的多种技巧。 希望本指南对您有所帮助。 直到下次。
喜欢谈论科技或创业公司?
你也很幸运,我也是! 如果您想谈论前沿技术,企业家精神或初创公司创始人的危险,请在Twitter或LinkedIn上找到我。
From: https://hackernoon.com/the-ultimate-guide-to-blazing-fast-performance-in-rails-j24v320k