Подтвердить что ты не робот

Каким образом кегли Rails 4 Russian doll предотвращают отпечатки?

Я ищу информацию о том, как механизм кэширования в Rails 4 предотвращает попытки нескольких пользователей одновременно восстанавливать ключи кеша, например, кеш-штамп: http://en.wikipedia.org/wiki/Cache_stampede

Я не смог узнать много информации через Google. Если я смотрю на другие системы (например, Drupal), защита от кеш-кеша реализуется через таблицу semaphores в базе данных.

4b9b3361

Ответ 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 вплоть до первоначально предполагаемого времени истечения срока действия.