Apache 的 htcacheclean 无法扩展:如何驯服巨大的 Apache disk_cache?

Apache 的 htcacheclean 无法扩展:如何驯服巨大的 Apache disk_cache?

我们有一个 Apache 设置,它有一个巨大的 disk_cache(>500.000 个条目,使用了 >50 GB 的磁盘空间)。缓存每天增长 16 GB。

我的问题是,缓存的增长速度似乎快到可以从缓存文件系统中删除文件和目录的速度。

缓存分区是 iSCSI 存储上的 ext3 文件系统(100GB,“-t news”)。Apache 服务器(充当缓存代理)是虚拟机。disk_cache 配置为 CacheDirLevels=2 和 CacheDirLength=1,并包含变体。典型的文件路径是“/htcache/B/x/i_iGfmmHhxJRheg8NHcQ.header.vary/A/W/oGX3MAV3q0bWl30YmA_A.header”。

当我尝试拨打缓存清理驯服缓存(非守护进程模式,“htcacheclean-t -p/htcache -l15G”),IOwait 持续了几个小时。没有任何可见的操作。几个小时后,htcacheclean 才开始从缓存分区中删除文件,这又需要几个小时。(2009 年 Apache 邮件列表中提出了类似的问题,但没有解决方案:http://www.mail-archive.com/[电子邮件保护]/msg42683.html

高 IOwait 会导致 Web 服务器的稳定性出现问题(到 Tomcat 后端服务器的桥有时会停滞)。

我编写了自己的修剪脚本,该脚本会从缓存的随机子目录中删除文件和目录。结果发现脚本的删除率略高于缓存的增长率. 剧本需要~10 秒读取子目录(例如/htcache/B/x)并释放大约 5 MB磁盘空间。在这 10 秒内,缓存又增长了 2 MB. 与 htcacheclean 一样,IOwait 上升至 25%当连续运行修剪脚本时。

任何想法?

  • 这是 (相当慢的) iSCSI 存储特有的问题吗?

  • 对于巨大的 disk_cache,我是否应该选择不同的文件系统?ext2?ext4?

  • 针对这种情况是否有任何内核参数优化?(我已经尝试了截止期限调度程序和较小的 read_ahead_kb,但没有效果)。

答案1

通过我最近的调查,由 htcacheclean 的类似痛苦引发,我得出结论,清理大型或深层缓存(尤其是涉及 Vary 标头的缓存)的主要问题是实用程序本身的设计问题。

根据对源代码的探究,并观察 strace -e trace=unlink 的输出,一般方法似乎如下:

  1. 遍历所有顶级目录(/htcache/B/x/,上面)
    • 删除已过期条目的所有 .header 和 .data 文件
    • 收集所有嵌套条目的元数据(/htcache/B/x/i_iGfmmHhxJRheg8NHcQ.header.vary/A/W/oGX3MAV3q0bWl30YmA_A.header,上面)
  2. 迭代所有嵌套条目元数据,并清除将来具有响应时间、.header modtime 或 .data modtime 的条目
  3. 遍历所有嵌套条目元数据并清除已过期的元数据
  4. 遍历所有嵌套条目元数据以找到最旧的;清除它;重复

一旦缓存大小降至设定的阈值以下,最后三个步骤中的任何一个都将从清除子程序返回。

因此,对于快速增长和/或已经很大的缓存,即使您进展到步骤#2-#4,步骤#1 所需的延长时间内的增长率也可能很容易被证明是难以克服的。

进一步加剧问题的是,如果在步骤 2 结束时您仍未满足大小限制,那么您必须遍历嵌套条目的所有元数据以找到最旧的条目,以便仅删除该单个条目,然后再做同样的事情,这意味着缓存再次被允许以比您能够修剪的速度更快的速度增长。

/* process remaining entries oldest to newest, the check for an emtpy
 * ring actually isn't necessary except when the compiler does
 * corrupt 64bit arithmetics which happend to me once, so better safe
 * than sorry
 */
while (sum > max && !interrupted && !APR_RING_EMPTY(&root, _entry, link)) {
    oldest = APR_RING_FIRST(&root);

    for (e = APR_RING_NEXT(oldest, link);
         e != APR_RING_SENTINEL(&root, _entry, link);
         e = APR_RING_NEXT(e, link)) {
        if (e->dtime < oldest->dtime) {
            oldest = e;
        }
    }

    delete_entry(path, oldest->basename, pool);
    sum -= oldest->hsize;
    sum -= oldest->dsize;
    entries--;
    APR_RING_REMOVE(oldest, link);
}

解决方案?

显然,更快的磁盘会有所帮助。但我完全不清楚需要将 IO 吞吐量提高多少才能克服 htcacheclean 当前采用的方法中固有的问题。并不是要挖苦创建者或维护者,但看起来这种设计要么没有经过测试,要么从未期望在广泛、深入、快速增长的缓存中表现良好。

但似乎有效的方法是从循环遍历顶级目录的 bash 脚本中触发 htcacheclean,我现在仍在确认。

#!/bin/bash

# desired cache size in integer gigabytes
SIZE=12;
# divide that by the number of top-level directories (4096),
# to get the per-directory limit, in megabytes
LIMIT=$(( $SIZE * 1024 * 1024 * 1024 / 4096 / 1024 / 1024 ))M;

while true;
do
  for i in /htcache/*/*;
  do
    htcacheclean -t -p$i -l$LIMIT;
  done;
done;

基本上,这种方法可以让你更快、更频繁地进入清除步骤(#2-#4),即使只针对一小部分条目。这意味着您有机会以比将内容添加到缓存中更快的速度清除内容。同样,它似乎对我们有效,但我只测试了几天。我们的缓存目标和增长似乎与您的相当,但最终您的里程可能会有所不同。

当然,这篇文章的主要目的是希望它能对那些和我一样偶然遇到这个问题的人有所帮助。

答案2

10 秒的目录读取听起来你可能没有使用 dir_index

检查

/sbin/tune2fs /dev/wherever | grep dir_index

如何开启

tune2fs -O dir_index /dev/wherever

但这只会影响新创建的目录,要重新索引所有内容,请运行

e2fsck -D -f /dev/wherever

相关内容