menu_links表引起的性能问题

在我们网站运营过程中,有的时候发现网站渐渐的或者突然慢下来,这个时候一定是出了什么问题,但我们不能马上知道原因。我前不久就在工作中遇到了一次这样的事情。

我们的网站每周都有一次发布,可能会上线一些新功能,或者修复一些BUG或者缺陷。我们的发布人员主要使用Drush来进行发布操作。在将最新代码部署到生产服务器以后。我们一般会做以下几个Drush操作:

drush fr release-related-feature;
drush updb
drush cc all

每次这几个操作都比较慢,有时甚至会使网站Down掉,而今天我要说的网站性能变慢也与这些部署有关。我们直观的感受主要有三点:

  1. 网站监控显示平均响应时间翻倍。
  2. 网站主导航出现了重复,一些禁用了的菜单又重新出现,去后台发现禁用的还在那里,出现的是新加入的菜单。
  3. 通过SQL查询,发现menu_links里的数据也将近多出来一倍

经过分析之后,觉得menu_links是一切的根源。所以我们采取的第一个措施是删除重复的菜单链接。然后清Drupal缓存。(这里多说一句,Drupal的很多诡异问题都可以通过清缓存修复,虽然这么做会让网站短暂的变慢以重建缓存),之后监控就显示响应时间回到原来的水平,页面上的重复链接也都没有了。

故事讲到这里还没有结束,因为后来的几次发布又出现了这样的问题,甚至是menu_links表多出几倍的数据,而网站也同样慢了几倍,这时我们首先用之前的方法解决了问题,然后进行了进一步的分析,发现Drupal会在做page callback路由时尝试去做menu_rebuild。

// Rebuild if we know it's needed, or if the menu masks are missing which
// occurs rarely, likely due to a race condition of multiple rebuilds.
if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
  menu_rebuild();
}

为了验证猜想,我们在settings.php里,将menu_rebuild_needed设置成TRUE,这样每次用户访问都会进行menu_rebuild,结果我们重现了这一问题,本来menu_rebuild执行时是有Lock的,但还是会重复向menu_links插入数据,就像注释中写的那样,系统遇到了race condition。 因此这次我们采取的方案是将settings.php里的menu_rebuild_needed设置成FALSE:

$conf['menu_rebuild_needed'] = FALSE;

这样,在用户访问网站时就不会触发menu_rebuild,而我们每周发布时会从命令行在清缓存的时候去rebuild menu,我们希望这样做能够解决这一问题。

后记:经过分析,这种情况出现于Pressflow版的Drupal 6,由于menu_rebuild采用了事务方式提交,而在使用drush updb时,处于维护模式,并且有一次伴随的menu_rebuild,会触发menu_rebuild_needed,而此时前一次的还没有COMMIT。