Каким образом кегли Rails 4 Russian doll предотвращают отпечатки?
Я ищу информацию о том, как механизм кэширования в Rails 4 предотвращает попытки нескольких пользователей одновременно восстанавливать ключи кеша, например, кеш-штамп: http://en.wikipedia.org/wiki/Cache_stampede
Я не смог узнать много информации через Google. Если я смотрю на другие системы (например, Drupal), защита от кеш-кеша реализуется через таблицу semaphores
в базе данных.
Ответы
Ответ 1
Rails не имеет встроенного механизма для предотвращения отпечатков кеша.
В соответствии с README для atomic_mem_cache_store
(замена ActiveSupport::Cache::MemCacheStore
, которая уменьшает отметки кеша):
Rails (и любая структура, основанная на активном хранилище кешей поддержки) не предлагать встроенное решение этой проблемы
К сожалению, я предполагаю, что этот камень также не решит вашу проблему. Он поддерживает кэширование фрагментов, но работает только с истечением времени.
Подробнее об этом читайте здесь:
https://github.com/nel/atomic_mem_cache_store
Обновление и возможное решение:
Я подумал об этом немного больше и придумал то, что кажется мне правдоподобным решением. Я не проверял, что это работает, и, вероятно, есть лучшие способы сделать это, но я пытался подумать о самых маленьких изменениях, которые смягчили бы большинство проблем.
Я предполагаю, что вы делаете что-то вроде cache model do
в своих шаблонах, как описано DHH (http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works). Проблема заключается в том, что при изменении столбца модели updated_at
, cache_key
также изменяется, и все ваши серверы пытаются повторно создать шаблон одновременно. Чтобы предотвратить отпечатывание серверов, вам нужно будет сохранить старый cache_key в течение короткого времени.
Возможно, вы сможете сделать это с помощью (dum da dum) кэширования cache_key объекта с коротким сроком действия (скажем, 1 секунду) и race_condition_ttl
.
Вы можете создать такой модуль и включить его в свои модели:
module StampedeAvoider
def cache_key
orig_cache_key = super
Rails.cache.fetch("/cache-keys/#{self.class.table_name}/#{self.id}", expires_in: 1, race_condition_ttl: 2) { orig_cache_key }
end
end
Посмотрите, что произойдет. Существует множество серверов, вызывающих cache model
. Если ваша модель включает StampedeAvoider
, тогда ее cache_key
теперь будет извлекать /cache-keys/models/1
и возвращать что-то вроде /models/1-111
(где 111 - временная метка), которую cache
будет использовать для извлечения скомпилированного фрагмента шаблона.
Когда вы обновляете модель, model.cache_key
начнет возвращать /models/1-222
(предполагая, что 222 - новая временная метка), но в течение первой секунды после этого cache
будет видеть /models/1-111
, так как это то, что возвращается cache_key
. Как только пройдет 1 секунда, все серверы получат кеш-промах на /cache-keys/models/1
и попытаются его восстановить. Если бы все они воссоздали его немедленно, это бы побеждало точку переопределения cache_key
. Но поскольку мы установили race_condition_ttl
в 2, все серверы, кроме первого, будут отложены на 2 секунды, и в течение этого времени они будут продолжать получать старый кешированный шаблон на основе старого ключа кеша. Как только прошло 2 секунды, fetch
начнет возвращать новый ключ кеша (который будет обновлен первым потоком, который попытался прочитать/обновить /cache-keys/models/1
), и они получат хищение кеша, возвращая шаблон, скомпилированный этот первый поток.
Та-да! Stampede предотвращено.
Обратите внимание: если вы это сделаете, вы будете делать в два раза больше чтения кеша, но в зависимости от того, насколько распространены штампы, это может стоить того.
Я не тестировал это. Если вы попробуете, пожалуйста, дайте мне знать, как это происходит:)
Ответ 2
Значение :race_condition_ttl
в ActiveSupport::Cache::Store#fetch
должно помочь избежать этой проблемы. Как сообщает документация:
Настройка: race_condition_ttl очень полезна в ситуациях, когда запись в кеше используется очень часто и находится под большой нагрузкой. Если срок действия кеша истекает, и из-за большой нагрузки семь разных процессов будут пытаться считывать данные изначально, а затем все они попытаются записать в кеш. Чтобы избежать этого случая, первый процесс поиска записи с истекшим кэшем приведет к сокращению времени истечения кеша по значению, установленному в: race_condition_ttl. Да, этот процесс увеличивает время для устаревшего значения еще на несколько секунд. Из-за длительного срока службы предыдущего кэша другие процессы будут продолжать использовать немного устаревшие данные за чуть более длинный период. В то же время первый процесс будет продолжен и будет записывать в кеш новое значение. После этого все процессы начнут получать новое значение. Ключ должен содержать: race_condition_ttl small.
Ответ 3
Отличный вопрос. Частичный ответ, который применяется к одиночным многопоточным серверам Rails, но не к многопроцессорным (или) окружениям (благодаря Нику Урбану для рисования этого различия), заключается в том, что код компиляции шаблона ActionView блокируется на мьютексе, предназначенном для каждого шаблона. См. строка 230 в template.rb здесь. Обратите внимание, что есть проверка завершенной компиляции как до захвата блокировки, так и после.
Эффект заключается в сериализации попыток скомпилировать один и тот же шаблон, где только первый будет делать компиляцию, а остальная часть получит уже завершенный результат.
Ответ 4
Очень интересный вопрос. Я искал в google (вы получаете больше результатов, если ищете "собачью кучу" вместо "stampede" ), но, как и вы, я не получил никаких ответов, кроме этого одного сообщения в блоге: защита от dogpile с помощью memcache.
В основном он хранит фрагмент в двух ключах: key:timestamp
(где timestamp будет updated_at
для активных объектов записи) и key:last
.
def custom_write_dogpile(key, timestamp, fragment, options)
Rails.cache.write(key + ':' + timestamp.to_s, fragment)
Rails.cache.write(key + ':last', fragment)
Rails.cache.delete(key + ':refresh-thread')
fragment
end
Теперь, когда вы читаете из кеша и пытаетесь извлечь не существующий кеш, вместо этого вместо этого попытайтесь заменить фрагмент key:last
:
def custom_read_dogpile(key, timestamp, options)
result = Rails.cache.read(timestamp_key(name, timestamp))
if result.blank?
Rails.cache.write(name + ':refresh-thread', 0, raw: true, unless_exist: true, expires_in: 5.seconds)
if Rails.cache.increment(name + ':refresh-thread') == 1
# The cache didn't exists
result = nil
else
# Fetch the last cache, as the new one has not been created yet
result = Rails.cache.read(name + ':last')
end
end
result
end
Это упрощенное резюме Моше Бергмана, с которым я связан раньше, или вы можете найти здесь.
Ответ 5
Нет защиты от штампов memcache. Это настоящая проблема, когда задействованы несколько машин и несколько процессов на этих нескольких машинах. -Ouch -.
Проблема усугубляется, когда один из ключевых процессов "умер", оставив "блокировку"... заблокированной.
Чтобы предотвратить отпечатки, вам необходимо повторно вычислить данные до истечения срока их действия. Итак, если ваши данные действительны в течение 10 минут, вам необходимо снова восстановить на 5-й минуте и повторно установить данные с новым истечением в течение еще 10 минут. Таким образом, вы не ждете, пока данные истекут, чтобы снова установить его.
Должно также не допустить, чтобы ваши данные истекали с отметкой в 10 минут, но переучитывайте ее каждые 5 минут, и она не должна истекать.:)
Вы можете использовать wget и cron для периодического вызова кода.
Я рекомендую использовать redis, который позволит вам сохранить данные и перезагрузить их в момент сбоя.
-daniel
Ответ 6
Разумная стратегия заключалась бы в следующем:
- используйте
:race_condition_ttl
с по крайней мере ожидаемым временем, необходимым для обновления ресурса. Устанавливать его на меньшее время, чем ожидалось, чтобы выполнить обновление, не рекомендуется, так как сердитая толпа в конечном итоге пытается ее обновить, что приведет к падению.
- используйте время
:expires_in
, рассчитанное как максимальное допустимое время истечения минус :race_condition_ttl
, чтобы разрешить обновление ресурса одним работником и избегать штампа.
Использование вышеописанной стратегии гарантирует, что вы не превысите предельный срок истечения срока действия, а также избегаете паническое бегство. Это работает, потому что только один работник проходит обновление, в то время как сердитая моба удерживается с использованием значения кеша с временем расширения race_condition_ttl
вплоть до первоначально предполагаемого времени истечения срока действия.