LINUX.ORG.RU

Лавина бесполезных изменений в кеше - хорошо ли это?

 , шторм


0

1

Предположим, over 128 запущенных на машине процессов, обслуживающих REST-запросы, практически одновременно, независимо друг от друга, получили запросы на сборку дорогостоящего JSON'а. Они все поняли, что та версия, которая ныне лежит в кеше, устарела, и начали вытаскивать данные. Считаем, что процессы никак не взаимодействуют, поскольку это именно процессы, а не потоки - им затруднительно узнать что-то вроде «данные находятся на пересчёте, обождите и приходите в кеш снова, когда данные посчитаются».

Теперь все эти процессы через некоторое время получаю огромный такой текстовый блоб -и хотят его засунуть в кеш обратно. И вот все 128 процессов идут в кеш и засовывают туда этот свой JSON. Вот только... wait, oh shi... да они ведь одно и то же засовывают в один и тот же кеш! Т.е. буквально: все 128 процессов бомбардируют кеш тем, что надо было бы записать всего один раз.

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

а) Дорогой Кеш, вот у меня есть данные для ключа «ААА», которые я собрал в (метка времени). Можно ли мне их тебе засунуть или твои данные новее?

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

в) Конец, если то, что мы пытаемся передать, точно совпадает с тем, что уже есть. Если данные всё же нужно записать, говорим: «Дорогой Кеш, вот тебе данные, актуальные на момент времени X, прими эти данные, если у тебя там запись для ключа ААА древнее моей».

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

Теперь внимание вопрос: а есть ли кеш-движки, которые умеют всё-таки не только хранить гигабайты в спуле, но умеют быть достаточно «ленивыми» для того, чтобы не принимать лишнее? Я вот написал подобную обвязку с протоколом обмена для Redis на Perl'е, но хотелось бы конечно, чтобы подобные вещи были встроенными.

Объясняется это очень просто: производительность кеша, который сам по себе является критичной компонентой системы, не должна бы «проваливаться» в случае подобного рода «лавинных» записей. Каждый процесс моет внутри себя творить всё, что угодно: в конце-концов у него всё равно отберут его квант времени и передадут управление другим процессам, а вот напрягать без веских на то оснований разделяемые ресурсы типа кешей - это нездорово, потому что мы никогда не знаем, насколько сейчас тому же Redis'у и без нас хреново.

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

★★★★★

Если версия в кэше устаревает по таймауту, то есть стандартное решение:

Каждый поток «кидает кубик» на обновление кэша. Чем ближе деадлайн таймаута, тем больше берётся вероятность. В итоге работа с кэшами размазывается по времени.

Если обновление нужно «прямо срочно вот», то хорошее универсальное решение мне не известно.

Можно предложить такое:

Для каждой записи кэша хранится поле update_started типа timestamp («начал подготовливать данные в... (время)»).

  1. Обнаружив устаревший кэш, поток проверяет поле update_started.
  2. Если update_started отличается от текущего времени меньше, чем на N миллисекунд, засыпаем на M миллисекунд. Проснувшись, идём в п.1 и проверяем состояние кэша.
  3. Если update_started отличается от текущего времени больше чем на N миллисекунд, записываем в update_started текущее время и начинаем обновлять данные. Обновленные данные записываем в кэш.

N и M подбираем экспериментально под нагрузку.

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

Deleted
()
Последнее исправление: Deleted (всего исправлений: 4)

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

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

Deleted
()
Ответ на: комментарий от Deleted

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

Ничего не понял. А если каким-то более гуманоидным языком? :)

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от Deleted

Это не избавляет от записи одного и того же. И чревато ситуациями, когда после процесса, записавшего более новые данные, приходит застрявший где-то на полпути процесс с устаревшими данными: кеш радостно примет устаревшие данные и перетрёт ими более новые.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

А вот не факт, что нужно избавляться от записи одного и того же. Тебе придется делать синхронизацию этих поток какую-то, CAS-м или еще чем, не дай боже с локами, не дай боже твой кэш это микросервис в сетке (latency вступает в игру). Очень даже может быть, что твой кэш это какая-то lock+wait free структура данных и записать тебе туда 128 раз из раздных тредов даёт гораздо меньше оверхэд по производетельности чем мутить все эти вычисления и синхронизацию. Без облкадывания тестами производительности на JMH или чем-то подобном если это не Java, я бы не советовал это менять. В реальной ситуакции, в реальной нагрузке, без JMH и годной статистике с сотнями тысяч прогонов такие изменения - это гадание на кофейной гуще.

Еще раз, если кеш lock+wait free, то скорее всего в него дешевле 128 раз записать, чем производить вычисления необходимые для синхронизации потоков. Хождение в базу 128 раз это уже отдельный вопрос, но и тут нет четкого ответа.

DiKeert ★★
()
Последнее исправление: DiKeert (всего исправлений: 1)
Ответ на: комментарий от DRVTiny

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

DiKeert ★★
()

Предположим, over 128 запущенных на машине процессов, обслуживающих REST-запросы, практически одновременно, независимо друг от друга, получили запросы на сборку дорогостоящего JSON'а. Они все поняли, что та версия, которая ныне лежит в кеше, устарела, и начали вытаскивать данные.

Пусть первый бежит за данными а остальные ждут пока он их принесет. Это будет проще чем чинить последствия.

ya-betmen ★★★★★
()

Такой ситуации не должно быть вообще. Почему у тебя 128 процессор обрабатывают одно и тоже? Для чего придумали очереди?

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

pawnhearts ★★★★★
()
Ответ на: комментарий от ya-betmen

Пусть первый бежит за данными а остальные ждут пока он их принесет.

А если не принесёт?

И это точно не должно быть опросом, должно быть в режиме прерывания/обработки_события. Это значит, что нужно обрабатывать даже не 1 событие, а 2: сначала ставить будильник, чтобы отменить второй обработчик по истечении таймаута. Т.е., предположим, процесс, который начал обновлять данные, ставит исключающую блокировку для того, чтобы другие не смогли обновить эти же данные и «подождали». А потом, когда завершает обновление, например, через publish рассылает на канале «OBJECT_UPDATES» в Redis сообщение «OBJECTID», чтобы все, кто ждёт завершения обновления OBJECTID получили оповещение: дескать, ну теперь качайте новые данные.

А что произойдёт, если в процессе, который обновляет данные, возникнет исключение, если его убьёт нечто снаружи и т.д.? Просто заблокироваться на попытке обновления данных - однозначно нельзя.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от Deleted

По-моему это всё-таки ты не понял. Но без конкретики спор немного идиотский, не находишь?

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

ты ничего не понял

По-моему

Омг. И эти люди еще программируют.

CAS позволяет избежать повторной записи. Сделан он не для этого, а для избежания коллизий, но позволяет. Это всё.

Но твоя проблема не в повторной записи.

Deleted
()
Ответ на: комментарий от Deleted

Месье не соизволит объяснить, каким же таким чудесным образом CAS позволяет избежать повторной записи - прямо во всех-всех случаях?

Насколько я вижу ситуацию, CAS позволяет экслюзивно одному клиент писать данные и отшибать других. Это не защищает никак от записи тех же самых данных. Я имею в виду: не защищает от записи данных, которые бит-в-бит совпадают с тем, что уже есть в кеше. Для реализации логики «не-пиши-то-что-есть» - нужен как минимум 2-х стадийный обмен: сначала передача контрольной суммы, а затем, если контрольная сумма отличается - можно передать данные.

И да, штатного CAS в Redis нет. Есть сторонние модули для реализации этого функционала сбоку.

При этом сам по себе CAS - весьма стрёмная штука, о чём я говорил выше (хотя в реализации https://github.com/kenn/redis-mutex учтён риск блокировки «навсегда»).

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

Нет ответа на вопрос - иди мимо.

DRVTiny ★★★★★
() автор топика
Последнее исправление: DRVTiny (всего исправлений: 3)
Ответ на: комментарий от Deleted

Тебя надо запереть и не давать общаться с нормальными людьми. Профессиональная деформация такой степени глубины уже не лечится.

Я прочитал про «как бы CAS» в Redis. Во-первых я самого начала не говорил вообще ни слова про CAS. Это не тот инструмент, который мне нужен.

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

И да, я уже давно реализовал нужную мне логику, написав сервер на пути от клиента до кеша, который собственно и отшибает штормы, не позволяя фигачить в кеш ненужные там данные. Вопрос был вообше о другом - а именно о том, что современные NoSQL-кеши тупы, как пробка и умеют только делать автоматический expire данных, но не имеют принципиального представления об актуальности данных.

DRVTiny ★★★★★
() автор топика
Ответ на: комментарий от DRVTiny

Во-вторых, я говорил о проверках суммы и метки времени при взаимодействии с движком кеша.

Именно для этого я тебя и тыкаю вторые сутки носом в CAS. Но это сложно, да 8)

современные NoSQL-кеши тупы, как пробка

По себе кеши не судят.

Deleted
()
Ответ на: комментарий от DRVTiny

А если не принесёт?

Это единственная проблема?

Вообще из описания не очень похоже, что тебе нужен «умный» кеш. Чем не устраивает складывать обновленные данные в файл из которого будут читать потоки?

ya-betmen ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.