最近,我们为一个复杂的企业级Drupal网站进行了性能优化。许多可能的性能改进值得分别讨论,每一项都有很多细节。在这篇博客文章中,我们从更广、更深入的角度分享Drupal网站的性能经验。对于一些改进,Drupal生态系统已经在核心和贡献空间提供了运行良好的解决方案。Drupal网站的性能其他一些变化在纸面上听起来很容易,但实施需要付出很多努力。这就是为什么我们需要缩小范围,以便在软件架构级别做出正确的决策。以下是我们在实施过程中得出的部分经验:
一、PHPStan
静态分析本身并不能改善Drupal网站的性能。为了使代码库更具可操作性,引入PHPStan仍然是必要的。有了它的支持,开发者可以自信地重构。刚刚开始会花费不少时间。然而,随着代码的快速发展,效果逐渐显现。此外,开发人员的对话从不重要的技术细节提升到有意义的决策。通过及早发现弃用和实施最佳实践,代码变得更易于维护。
二、路线图、实体架构和数据质量
改进后端的整体架构是一个长期运行的过程。对于许多方面,更改需要迁移和仔细规划,以解决依赖关系并与正在进行的功能开发相匹配。在这个项目中,我们面临着更复杂的内容,因为代码库在多年的时间里随着不同的维护者而增长。例如,外部服务提供格式化数据,预处理挂钩分散在主题、模块文件之间,模板承载业务逻辑,Twig过滤器承担后端责任。
然而,我们并不总是能清楚地看到一个庞大系统的全貌。因此,最好制定总体思路,比如:
外部服务提供纯数据,无法控制下游处理。在这里,达成严格的实施和执行标准对于未来的可维护性至关重要。
通过减少嵌套和流氓引用来扁平化实体架构。此外,每个实体都有正确的类型。对于某些情况,我们引入了与相应编辑实体分离的指定数据实体,以简化结构。
前端和后端之间的集成保持清洁,不要太多依赖。在这个系统中,它主要是一个具有一对一映射的接口模板文件。
将逻辑从预处理转移到构建过程,以更好地掌握缓存和翻译。我们引入了多种新的格式化程序来实现这一点。
扩大测试覆盖范围,特别是对于集成。我们引入了多个新的测试特性,以允许在前端和后端进行无忧的代码移动。
通过使代码更易于管理来改善开发人员体验。更改包括引入更具体的模块,改进类和方法命名,加强服务和接口,以及严格打字。
三、依赖关系更新
我们可能需要更好地维护Drupal网站的依赖关系。我们总是试图保持与原始Drupal网站的实现方式一致,并利用原有Drupal网站的模块、主题等,但有时不兼容的模块是不可避免的。升级这些不兼容的模块虽然有点痛苦,但好处是非常明显的,我们得到了一个完美的网站,最终让客户满意的东西。
升级PHP和Drupal时,几乎可以免费获得许多改进。根据基准测试,PHP 8.3与Drupal结合在一起,在语言级别上比PHP 8.1快1.5倍!此外,Drupal最近的许多改进都是为了提高Drupal网站的性能,即使是在次要版本中也是如此。举几个例子:
1、渲染过程中支持PHP Fibers
2、新的变体缓存API(替换渲染缓存)
3、POST请求现在被渲染缓存
4、状态服务现在使用缓存收集器
5、CSS和JavaScript聚合性能改进
此外,已经有许多努力使Drupal核心性能可被测试。性能测试框架Gander现在是Drupal核心的一部分。它不仅在核心中使用,而且是开放的,它提供了一个新的性能测试基础和一个性能测试特性。Gander与Open Telemetry和Grafana集成用于监控。所有这些都可以通过DDEV插件方便地进行设置,以便本地开发。Drupal核心中的StandardPerformanceTest已经取得了很好的效果,它可以在标准安装中控制所有数据库查询。该测试已经带来了多项后续改进,是在引入新功能时防止潜在性能下降的保障。
回到我们的项目,我们不得不改变依赖树并改进我们的Renovate Bot配置,这使得跟上更新变得更加容易管理。然而,技术工具并不能单独解决所有问题。开发人员和项目管理人员也重申了他们保持接近前沿的承诺。
四、减少有效载荷
在优化Drupal网站的性能时,应考虑发送给用户的数据量。通过移动数据提供商访问具有大型DOM的网站会影响用户体验。对于这个网站,有四个值得注意的步骤大大减少了有效载荷:
一个快速的胜利是使用Minify HTML,它可以从HTML响应中去除空格和注释。
我们分析了图像处理,以确保所有图像都使用正确的源集交付,以响应WebP图像格式。尽管如此,仍有更多的改进空间,例如在创建图像衍生品时使用Avif格式和优化处理管道。请参阅Peter Pónya的这篇博客文章,其中有很好的介绍。
确保只附加已使用的字体子集可以显著减少有效载荷。
我们重构了通过HTML数据属性传递的数据结构。通常,这不是问题,但特别是对于列表页面,DOM可能会迅速膨胀。
五、服务优化
接下来,我们必须对自定义实现进行放大。对于这个项目,通过火焰图进行可视化成为检测性能瓶颈的可靠工具。在XDebug中使用DDEV时,可以使用speedscope将轨迹渲染为火焰图。下面,我们将介绍一些示例性改进。
一如既往,缓存是我们的朋友。策略性地利用静态变量、类属性作为静态缓存或缓存后端(如内存缓存后端)可以显著减少冗余计算。例如,我们改进了自定义别名管理器中语言回退链的静态缓存。在查看实现时,这种变化并不明显,但在看到火焰图中的传播后,它变得清晰起来。
有时,自定义查询可能是值得的。虽然建议尽可能多地利用实体查询和各个实体存储上的方法,但完全加载实体可能会有些过头。例如,我们有一个使用率很高的服务,它决定了给定节点上段落的结构。结构是通过段落的位置和捆绑来计算的。在引入自定义查询和适当的缓存后,此服务的执行时间变得不可测量。
对于看似无害的核心服务方法,必须提出另一个警告。例如,对于大型网站来说,将路径与URL匹配器服务进行匹配或从路由提供者处获取请求的路由集合可能非常昂贵。由于这些只是代码库中的一行代码,因此有必要提高开发人员对这些情况的认识。
我们了解到的一个反模式是构造函数负担过重。在构造函数中设置属性值以确保整个类的可用性对开发人员来说非常有吸引力。然而,在执行此操作时,还需要考虑相应类的实例化。在Drupal中,服务可以被实例化为另一个服务的依赖项。此外,插件可以由其各自的插件管理器实例化——有时甚至在发现时。因此,将昂贵的执行卸载到各自的方法中是至关重要的。
例如,在我们的案例中,我们有一个服务被实例化为许多编辑页面的依赖项,这些页面向构造函数中的外部API发出请求。此类错误可能很难解决,因为后续请求之间的Drupal网站的性能可能会因每个用户的位置而异。火焰图分析有助于揭示错误。
六、基础设施变更
基础设施变更的主要重点是改善内容交付网络和源服务器之间的相互作用。特别是在到达源服务器之前正确重定向用户可以显著改善浏览体验。CDN可以在几毫秒内有效地预测重定向位置,而不是昂贵的往返源服务器来计算请求的目的地。在我们的设置中,源服务器仍然对响应拥有主权,因为CDN根据信息有限的启发式逻辑进行操作。如上所述,将语言国家谈判作为其中的一部分是一个微妙的话题。这就是为什么它是该网站测试最多的功能之一。接下来,我们改进了HTTP缓存控制响应头。在这里,我们将参考HTTP缓存控制模块和Lagoon关于正确配置它们的概述。
我们做的另一个调整是针对Redis内存缓存。我们增加了容量,并将最大内存清除策略更改为allkeys lfu。这意味着当内存已满时,最不常用的项目将从缓存中清除。这是有道理的,因为最常用的缓存项,如引导、发现和配置,应该始终保持不变。Redis状态报告提供了一个信息丰富的概述,以评估不同缓存项的行为方式。
七、前端集成和缓存变化
由于不同的团队在后端和前端工作,与集成的一些摩擦几乎是不可避免的。本质上,两者在实现新功能时都有不同的动机。特别是对于这个网站,我们面临的问题是,一方面,我们在前端有复杂的可重用组件。另一方面,我们希望在多语言和多市场环境中确保高知名度。当然,在具有挑战性的整合中,我们做出了一些妥协。由此,出现了一些不良做法,例如:
使用广泛的段落嵌套并将块放置在块中。
引入组件的耦合,特别是将父属性传递给子属性。
在预处理过程中管理缓存指令。
使用补丁来渲染稍后单独放置在区域模板中的块,有助于我们更好地处理集成。这一变化可能需要很长时间才能进入Drupal核心,因为它存在向后兼容性问题。然而,我们没有遇到补丁的问题,它提供了更大的灵活性。
这一变化也使我们能够更好地控制缓存。块是延迟构建的,当放置在区域中时,缓存信息可以正确地通过不同的组件。Drupal核心在渲染时有一种调试可缓存元数据的好方法。有关更多详细信息,请参阅Matt Glaman的博客文章。此外,Renderviz模块允许用户可视化渲染器如何看到网站的不同组件。这些工具可以更容易地在正确的位置发出可缓存的元数据,并消除冗余或虚假的缓存信息。
八、缓存
Drupal网站最长的响应时间通常是由冷缓存引起的。对于讨论的网站,这变得相关,因为我们经常部署新功能和维护修复。尽管内容交付网络通过持久化静态文件接管了一些繁重的工作,但在大多数情况下,计算量大的页面构建和交付新的JavaScript仍然是必要的。这就是为什么我们决定引入缓存预热。同样,Drupal贡献空间已经为实现这一目标提供了一个坚实的框架。Warmer模块及其子模块使我们能够轻松配置实体缓存预热和CDN预热,这利用了站点地图。将各个作业合并到部署管道中也很简单,因为可以使用Drush命令。
九、Drupal bootstrap
通常,前端在初始响应传递后异步获取一些用户信息。我们意识到,从Drupal CMS提供REST端点的直接方法并不总是最快的。从本质上讲,Drupal引导程序相对昂贵,在某些情况下是不必要的。例如,从第三方系统获取数据或懒惰构建内容时。当然,这种方法并不总是可行的,但如果可能的话,可以很容易地缩短500毫秒的响应时间,并显著降低频繁访问的端点的服务器负载。服务器配置的一些注意事项是必要的。在我们的例子中,我们在nginx服务器上执行普通PHP文件,在内容传递网络上传递缓存,并传递一个设置为private的缓存控制响应头。
十、最后的想法和进一步的阅读
最终,我们必须认识到,客户在他们的网站上投入了大量资金、精力和时间,让他们以适合自己公司的方式展示。我们共同创建了一个系统,现在,随着Drupal网站的性能的提高,创作过程将更加愉快。
随着越来越多的人使用该网站并获得良好的体验,该网站存活更长时间的可能性也在增加。这个网站的长寿不仅归功于我们团队的工作,也归功于整个社区,因为每个人都在共同努力,提供更令人兴奋的系统。