Как использовать кэш памяти в контексте, важном для параллелизма - PullRequest
2 голосов
/ 15 июня 2011

Рассмотрим следующие два метода, написанные в псевдокоде, которые выбирают сложную структуру данных и обновляют ее соответственно:

getData(id) {
   if(isInCache(id)) return getFromCache(id)         // already in cache?
   data = fetchComplexDataStructureFromDatabase(id)  // time consuming!
   setCache(id, data)                                // update cache
   return data
}

updateData(id, data) {
   storeDataStructureInDatabase(id, data)
   clearCache(id)
}

В вышеприведенной реализации существует проблема с параллелизмом, и мы можем получить устаревшие данные в кеше: рассмотрим два параллельных выполнения, выполняющих getData() и updateData() соответственно. Если первое выполнение извлекает данные из кэша точно между вызовами другого выполнения к storeDataStructureInDatabase() и clearCache(), то мы получим устаревшую версию данных. Как бы вы справились с этой проблемой параллелизма?

Я рассмотрел следующее решение, в котором кеш становится недействительным непосредственно перед фиксацией данных:

storeDataStructureInDatabase(id, data) {
   executeSql("UPDATE table1 SET...")
   executeSql("UPDATE table2 SET...")
   executeSql("UPDATE table3 SET...")
   clearCache(id)
   executeSql("COMMIT")
}

Но с другой стороны: если одно выполнение считывает кэш между вызовами другого выполнения к clearCache() и COMMIT, то устаревшие данные будут выбраны в кэш. Проблема не решена.

1 Ответ

2 голосов
/ 15 июня 2011

С точки зрения кеша вы не можете предотвратить извлечение устаревших данных.

Например, когда кто-то начинает отправлять HTTP-запрос (если ваше приложение является веб-приложением), который впоследствии сделает кеш недействительным, следуетмы считаем кеш недействительным при запуске запроса POST?когда запрос обрабатывается вашим сервером?когда вы запускаете код контроллера?Ну нет .На самом деле кеш недействителен только после завершения транзакции базы данных .Даже когда транзакция начинается, только в конце, на фазе COMMIT транзакции.И любой рабочий процесс, работающий с предыдущими данными, имеет очень мало шансов узнать, что данные изменились, в веб-приложении, как насчет html-страниц, показывающих устаревшие данные в браузере, вы хотите сбросить эти страницы?

Но давайте просто подумаем, что ваш параллельный процесс предназначен не только для Интернета, но и для реальных важных для параллелизма параллельных заданий.

Одна из проблем заключается в том, что ваш кэш не обрабатывается сервером базы данных, поэтомуэто не в транзакции COMMIT / ROLLBACK.Вы не можете решить сначала очистить кеш, но перестроить его, если сделаете откат.Таким образом, вы можете очистить и перестроить кеш только после совершения транзакции .

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

  • действительно ли важно иметь устаревшую версию кеша?Допустим, ваш параллельный процесс сделал что-то всего за несколько миллисекунд, прежде чем вы извлекли бы эту новую версию (так что она старая) и работали с ней, возможно, в течение 40 мс, а затем строили окончательный отчет об этом, не замечая, что кэш был сброшен за 15 мс доконец работы.Если ваш ответ процесса не может содержать какие-либо устаревшие данные, вам придется проверить достоверность данных перед их выводом (поэтому вы должны еще раз проверить, что все данные, используемые в рабочем процессе, все еще действительны в конце).
  • Итакесли вы не хотите перепроверять достоверность данных, что означает, что ваш процесс должен был установить некоторую блокировку ( семафор ?) при запуске и снять блокировку только в конце работы, вы сериализуете твоя работа.Базы данных могут ускорить сериализацию, работая на уровнях псевдосериализации для транзакций и прерывая транзакцию, если какие-либо изменения делают эту псевдосериализацию опасной.Но здесь вы не только работаете с базой данных, поэтому вы должны выполнять сериализацию самостоятельно.
  • Процесс сериализации идет медленно, но вы можете попробовать сделать то же самое, что и база данных, которая выполняет задания вПараллельно и аннулирование любой работы, выполняемой при изменении данных (таким образом, если что-то, обнаруживающее ваш кэш, очищает и уничтожает и повторно запускает все существующие параллельные задания, подразумевая, что у вас есть что-то, справляющееся со всеми параллельными заданиями)
  • или просто принимаютВы можете иметь небольшие прошлые недействительные устаревшие данные .Если мы говорим о веб-приложении, когда ваш ответ передается по TCP / IP браузеру клиента, он может быть уже недействительным.

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

  • в процессе записи: все важные чтения (те, которые получат некоторые записи) и все записывать вещи в транзакции с высоким уровнем изоляции (уровень 4) и со всеми необходимыми блокировками строк .Работать только с базой данных сложно, совершенно невозможно, если добавить внешний кеш для операций чтения.
  • в параллельном процессе чтения: делайте что хотите (чтение из внешнего кэша), если данные чтения не будут использоваться для операций записи.Если одна из прочитанных данных впоследствии будет использоваться для операции записи, то достоверность этих данных должна быть проверена в транзакции записи (так в процессе записи). Почему бы не добавить отметку времени в данные, чтобы при возврате к операции записи вы могли знать, действительна ли она по-прежнему.
...