Назад | Перейти на главную страницу

Apache htcacheclean не масштабируется: как приручить огромный дисковый кэш Apache?

У нас есть установка Apache с огромным disk_cache (> 500 000 записей, используется> 50 ГБ дискового пространства). Кеш увеличивается на 16 ГБ каждый день.

Моя проблема в том, что кеш, кажется, растет почти так же быстро, как это возможно, чтобы удалить файлы и каталоги из файловой системы кеша.!

Раздел кеша представляет собой файловую систему ext3 (100 ГБ, «-t news») в хранилище iSCSI. Сервер Apache (который действует как кэширующий прокси) - это виртуальная машина. Disk_cache настроен с CacheDirLevels = 2 и CacheDirLength = 1 и включает варианты. Типичный путь к файлу - «/htcache/B/x/i_iGfmmHhxJRheg8NHcQ.header.vary/A/W/oGX3MAV3q0bWl30YmA_A.header».

Когда я пытаюсь позвонить htcacheclean для приручения кеша (режим без демона, «htcacheclean-t -p / htcache -l15G»), IOwait идет через крышу несколько часов. Без видимых действий. Только через несколько часов htcacheclean начинает удалять файлы из раздела кеша, что занимает еще пару часов. (Подобная проблема была поднята в списке рассылки Apache в 2009 году, но без решения: http://www.mail-archive.com/dev@httpd.apache.org/msg42683.html)

Высокое значение IOwait приводит к проблемам со стабильностью веб-сервера (иногда останавливается мост к внутреннему серверу Tomcat).

Я придумал собственный сценарий prune, который удаляет файлы и каталоги из случайных подкаталогов кеша. Только чтобы обнаружить, что скорость удаления скрипта чуть выше скорости роста кеша. Сценарий занимает ~ 10 секунд для чтения подкаталога (например, / htcache / B / x) и освобождает около 5 МБ дискового пространства. За эти 10 секунд кеш увеличился еще на 2 МБ. Как и в случае с htcacheclean, IOwait увеличивается до 25% при непрерывном выполнении скрипта обрезки.

Любая идея?

благодаря своим недавним исследованиям, вызванным аналогичными проблемами с 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. перебирать все метаданные вложенных записей, чтобы найти самые старые; очистить его; повторение

и любой из последних трех шагов вернется из подпрограммы очистки, как только размер кэша упадет ниже установленного порога.

поэтому с быстрорастущим и / или уже большим кешем скорость роста в течение длительного времени, необходимого для шага №1, может легко оказаться непреодолимой даже после того, как вы перейдете к шагам №2- №4.

еще больше усугубляет проблему, если вы еще не удовлетворили ограничения размера к концу шага № 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);
}

решение?

очевидно, что быстрые (эр) диски помогли бы. но мне совсем не ясно, насколько увеличится пропускная способность ввода-вывода, чтобы преодолеть проблемы, присущие текущему подходу, принятому htcacheclean. Никаких возражений против создателей или сопровождающих, но похоже, что этот дизайн либо не тестировался, либо никогда не ожидалось, что он будет хорошо работать с широкими, глубокими, быстрорастущими кешами.

но что, похоже, работает, и я все еще подтверждаю это прямо сейчас, - это запускать htcacheclean из сценария bash, который сам перебирает каталоги верхнего уровня.

#!/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) намного быстрее и чаще, даже если это касается небольшого подмножества записей. это означает, что у вас есть шанс очистить контент быстрее, чем он добавляется в кеш. опять же, похоже, это работает для нас, но я тестировал его всего несколько дней. и наши целевые показатели и рост кеш-памяти кажутся на одном уровне с вашими, но в конечном итоге ваш пробег может отличаться.

конечно, основная мысль этой публикации в том, что, возможно, она будет полезна кому-то еще, кто наткнется на этот вопрос так же, как и я.

10 секунд для чтения каталога звучит так, как будто вы не используете dir_index

проверить с

/sbin/tune2fs /dev/wherever | grep dir_index

как включить

tune2fs -O dir_index /dev/wherever

но это повлияет только на вновь созданные каталоги, чтобы переиндексировать все,

e2fsck -D -f /dev/wherever