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

Попробую-ка я развеять это заблуждение...

Оптимизация скорости

Существует не так много принципиально разных способов софтверно ускорить работу какого-то алгоритма. Можно поменять алгоритм на более быстрый (пузырьковую сортировку на heap, например). Можно разделить алгоритм или данные на независимые куски и обрабатывать их параллельно. Третий же способ — это, грубо говоря, не пересчитывать снова и снова то, что уже посчитано.

Впрочем "не пересчитывать снова" — расплывчатая формулировка. На практике она принимает очень разные формы. Например, среди алгоритмов подсчёта количества установленных битов в байте самый быстрый — написать прямо в коде статическую таблицу с 256-ю возможными ответами задачи. В задаче вывода количества комментариев для выводимых постов в блоге одно из быстрейших решений — денормализовать количество комментариев в отдельное поле таблицы постов, вместо того, чтобы делать select count на каждый пост. Мемоизация функций — ещё один пример того же подхода.

Кеширование

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

Итак, кеширование — это:

Так вот, если разобраться — это всего лишь ещё один способ оптимизации скорости путём запоминания результата и исключения лишних пересчётов. Часто этот термин вообще используют очень общо, и под него попадают и мемоизации, и прочие почти одинаковые вещи.

И вот что, скажите пожалуйста, принципиально в кешировании "грязного"? Напротив, исключение лишних расчётов — это очень прямой и верный способ ускорять вещи, отличающий умного программиста от тупого компьютера, для которого естественный способ поведения — считать, что дали, пока электричество не кончится. Ему не скучно ведь...

Более того, кеширование представления — ещё и один из самых эффективных видов запоминания результатов. Взять для примера тот же вывод пользователю типичной веб-страницы. Кеширование там происходит на куче уровней от нижнего до верхнего. Внизу файловая система кеширует доступ к себе со стороны БД. Дальше БД кеширует результаты повторных одинаковых запросов. ORM кеширует созданные на основе выбранных данных объекты, не делая их повторных выборок. Шаблонная система кеширует отдельные куски шаблонов. На самом верхнем уровне браузер кеширует (если это возможно) всё представление целиком.

Очевидно, самый верхний уровень даёт и самый большой выигрыш в скорости: если страница закеширована, её выдача не стоит ни накладных расходов на проверку актуальности кешей нижних уровней, ни расходов на компоновку значений, закешированных более нижними уровнями. Правда, чем выше уровень кеширования, тем более он и "скоропортящийся", потому что его актуальность зависит от всё большего числа параметров.

Откуда заблуждение

У меня есть теория, откуда берётся отношение к кешированию, как к грязному хаку. Скорее всего от того, что для многих кеширование — это навешивание сверху медленно работающей системы некого универсального кеширующего слоя в надежде на некую магию. Но универсальные кеши работают плохо, потому что не зная специфики приложения, они могут ориентироваться только на самые неточные эвристики. Например, всякие HTTP-прокси отказываются вообще кешировать запросы с GET-параметрами при отсутствии явных указаний в заголовках. Также внешний универсальный кеш обычно не предусматривает инвалидации по событиям изнутри системы, а умеет протухать только по времени. В результате это выливается в периодически проблемы с показом неверных перекешированных данных.

Ещё одно беспокойство, которое я часто слышу — это "а если кеш вдруг отключится, то у вас всё будет работать медленно". Помилуйте, но почему кеш тут чем-то более особенный, чем другие подсистемы моей системы? Если у меня, например, память кончится, диск засбоит или СУБД отвалится, то моя система не только будет работать медленно, она вообще накроется. Так что кеш тут никаким образом не слабое звено, а такая же часть системы, за которой надо следить. Способы делать кеш надежным известны: опциональность работы кеша с точки зрения корректности работы, дублирование на несколько машин.

Кешнехак!

Таким образом, правильная организация кеширования с выбором уровней представления, на которых надо делать кеширование, выбором объектов кеширования и политики инвалидации — это не "хак", а благодарная инженерная задача. И, к слову, довльно сложная. Особенно в части инвалидации.

Комментарии: 13

  1. Andrew

    Очень часто сталкивался с ситуцией когда не очень грамотные программисты вместо того, чтобы правильно проектировать систему, пытались решать проблему ее тормознутости кешированием, в итоге это лишь увеличивало сложность системы и усугубляло проблему.
    Собственно, поэтому я стараюсь добавлять кеширование уже в финальной части разработки приложения, когда уже почти все остальное написано, и есть более-меннее устоявшаяся структура.
    Мне кажется что некоторые программисты считают кэш хаком потому как наблюдают мучения других программистов, которые считают, что кэш решит все их проблемы. :-)
    Так же как и некоторые боятся ORM, потому что наблюдают как другие его abuse сверх всякой меры.

  2. Alexey Kozlov

    Кстати, типичная ситуация для мира софт-разработки. И заблуждение, на самом деле типичное: хороший кешер написать мало кто сумеет, а плохой (ну надо же!) - плохо работает. Напрашивающийся вывод: истинный программист не использует кешеры. С php, например, та же ерунда :)

  3. Михаил Коробов

    Иван, раз речь зашла про кеширование, немного не про то спрошу, можно?) Хотя может и про то, конкретная инженерная задача для примера. Вы как-то обмолвились про то, что можно и не использовать стандартные джанговские cache-декораторы. Почесал я тогда затылок, методом "скопировать-вставить" делать уж очень не хотелось (логика у декоратора все равно ведь примерно та же будет, что и у стандартного), поэтому открыл тикет

    http://code.djangoproject.com/ticket/11269

    и сижу на пропатченной джанге, что не очень радует.

    Так вот вопрос - наверняка же можно было сделать это как-то проще? Задачи, если конкретно, было 2:

    • кешировать страницы с GET-запросами (некоторыми, не всеми)
    • иметь 2 версии страницы в кеше: для зарегистрированных пользователей и для незарегистрированных
  4. Сашка

    Интересно, никогда не считал, что кэш - это хак. Оказывается, что некоторые так считают, но они, вероятно, любую оптимизацию считают хаком :)

  5. Александр Соловьев

    В принципе, я думаю это в некоторой степени камень и в мой огород (мы как-то разговаривали о темплейтах и затрагивали этот момент, ЕМНИП). Но вопрос не в том, что кеш - это хак. Вопрос в том, что какие-то внутренние траблы пытаются решать кешем, что неверно.

     - Рельсы тормозят!
     - Та ну не, возьми и закешируй тормозящую часть.
    

    Та же ситуация с джангой и их темплейтами, например. Когда я говорю о том, что темплейты написаны на самом деле ужасно (как-то до меня это поздно дошло), и что тормозят они нереально, мне говорят "закешируй". В этом случае кеширование — это аллопатия, а так как мы понимаем, что и почему внутри происходит, мы можем заниматься нормальным лечением изнутри, а не припарками. А аллопатией пускай занимаются медики — это им непонятно, как работает система.

    Кеш - это не хак, конечно. Но он просто-напросто не заменяет решение проблемы. Особенно когда еë можно решить.

  6. Sergey Kishchenko

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

  7. Ivan Sagalaev

    Александр Соловьев:

    В принципе, я думаю это в некоторой степени камень и в мой огород

    Не, про тебя я не думал :-). На пост меня сподвиг один разговор с коллегами внутри Яндекса с месяц назад.

    Вопрос в том, что какие-то внутренние траблы пытаются решать кешем, что неверно.

    • Рельсы тормозят!
    • Та ну не, возьми и закешируй тормозящую часть.

    Есть система (Ruby-on-Rails), у которой есть вполне определённый trade-off: скорость и удобство разработки в плюсе и скорость интерпретатора в минусе. Кеш позволяет решить минус и получить все плюсы. Что в этом неверного?

    Попытаюсь сформулировать... Не существует проблем с абсолютной мерой зла, они все измеряются количеством отрицательного эффекта и стоимостью исправления. Торможение интерпретатора можно исправить разными способами: переписать интерпретатор, заменить критичные куски на бинарный код, закешировать. Это всё решает проблему. Просто так вышло, что у кеширования меньше стоимость. И если у него самого нет собственных отрицательных эффектов (или они малы), это очень хорошее сознательно инженерное решение.

    Чем это отличается от костылей? Тем, что костыли — это решение, которое решает одну проблему, но создает несоразмерно много других: например скрывает архитектурную ошибку, которая приведёт к unmaintainable коду через 2 месяца.

    Кеш - это не хак, конечно. Но он просто-напросто не заменяет решение проблемы. Особенно когда еë можно решить.

    В чём смысл решать проблему дорого, если её можно хорошо решить дёшево? Вот именно с этой точкой зрения я и спорю. Кеш — это не хак, не "временное", и не "неправильное" решение. Если незакешированный код работает и так быстро — очень хорошо. Это хороший повод нагрузить его на порядок больше и опять закешировать.

    Sergey Kishchenko:

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

    Я думаю, вряд ли отсутствие такой статьи помешало бы кому-то использовать кеш не по назначению :-).

  8. Ivan Sagalaev

    Иван, раз речь зашла про кеширование, немного не про то спрошу, можно?)

    Можно я это сохраню, как образец логики? :-)

    Ответил в форуме, раз уж это действительно никак не связано с постом: http://softwaremaniacs.org/forum/django/12732/

    А вообще, мне можно написать email'ом :-)

  9. Виктор

    Я отношусь к кешированию как к оптимизации.

    А поскольку существует мнение, что преждевременная оптимизация — корень всех бед, то и к кешированию отношение такое же.

    Сначала делаем по уму, потом смотрим что тормозит и оптимизируем. В случае необходимости — применяем кеширование.

  10. Александр Соловьев

    Чем это отличается от костылей?

    Само собой, что кеш - это не костыли. Я б даже сказал, что кеш — это хорошо. Но отмазки по типу "когда тормозит - используй кеш" — анноят ужасно. Кеш надо применять, когда его надо применять, а не когда люди охотятся за обратной совместимостью (по крайней мере такие доводы писали Армину, когда он предлагал зарефакторить джанговские темплейты). Ну в общем я пытаюсь сказать о том, что когда слишком много [используют кеш/едят шоколад/читают блогов] — это не здраво. Кеш — не панацея, и плохо написанный код им закрывать — моветон.

    В общем, надеюсь моя мысля понята. :-)

  11. Михаил Коробов

    Можно я это сохраню, как образец логики? :-)

    Можно) В 4 утра забавные мысли в голову приходят.

    А за ответ в форуме - спасибо.

  12. Денис Баженов

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

    А кстати сложность, которую добавляет кеширование, - это отдельный разговор. Хорошо, когда мы кешируем данные, которые только читаются. А что если данные меняются? Кеши надо уметь инвалидейтить. Встает вопрос консистентности кеша. Его, как правило, в таком случае делают shared. А когда он shared нужна возможность атомарной работы с отдельными позициями кеша, иначе конкурентные потоки могут все покорруптить. Вот и получается, что хороший кеш написать - задача не из легких.

    Кстати, по-моему опыту у кеша есть еще одно предназначение, которое не было упомянуто. Он в определенной степени обеспечивает масштабируемость системы. Природа многих механизмов кеширования (включая memcached) такова, что они хорошо выдерживают высокую конкуретную нагрузку, чего не скажешь о реляционных базах данных. Я сейчас работаю в проекте, где мы кешируем некоторые данные только для того, чтобы выдерживать пики посещаемости. Если отключить кеш при номинальной нагрузке, система мониторинга даже не увидит разницы во времени отдачи страниц.

  13. Олег Шевелев

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

    Хорошая практика - думать.

Добавить комментарий