Сайт Романа ПарпалакаБлог20200820

Кеширование и условие гонки

20 августа 2020 года, 10:57

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

Кеширование не только экономит ресурсы и делает систему более отзывчивой. Без кеширования долгих операций система в принципе не сможет работать в режиме высокой нагрузки. Если вы разрабатываете кеширование в такой системе, важно помнить об одной частой ошибке проектирования — условии гонки (race condition). Расскажу об этой проблеме на выдуманном примере главной страницы интернет-магазина. А в следующий раз поговорим о частном решении на уровне веб-сервера nginx.

Предположим, на главной странице интернет-магазина выводятся карточки наиболее популярных товаров. Товаров и отзывов много, и запрос к базе данных на получение списка выполняется 5 секунд. А остальную часть страницы можно сгенерировать за 100 миллисекунд. Предположим также, что у вас на сервере умеренное количество памяти, и он может одновременно обрабатывать не более 20 запросов (каждый в своем процессе). В таких условиях без кеширования вы в принципе не сможете обработать более 60 / 5,1 · 20 = 235 запросов в минуту к главной странице.

235 запросов в минуту — вроде как не очень мало. Но сайт быстро ляжет, если ссылку разместят на каком-нибудь относительно популярном ресурсе, и на него одновременно перейдут несколько десятков человек. Да и сайт, который не может открыться в течение 5 секунд, в современном вебе никому не составит серьезную конкуренцию.

Если запрос к базе данных выполнить один раз и запомнить список наиболее популярных товаров, скажем, на час, то сервер сможет выдержать до 60 / 0,1 · 20 = 12 000 запросов в минуту. Эта грубая оценка, конечно, уже не отражает реальную возможность сервера. Скорее всего, производительность окажется ниже из-за нехватки ресурсов процессора, сети и т. д. Но оценка показывает, что запрос популярных товаров перестает быть узким бутылочным горлышком в системе.

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

Одновременное выполнение тяжелого запроса — неприятная ситуация по многим причинам:

  1. Несколько конкурентных запросов могут нагружать базу данных и выполняться медленнее, чем один запрос.
  2. Процессы приложения вместо полезной работы будут ждать окончания выполнения долгих запросов (если, конечно, у вас не асинхронная архитектура; хотя я сомневаюсь, что в этом случае вы бы стали сейчас читать в интернете об условии гонки). При достаточном количестве посетителей 20 процессов израсходуются очень быстро, и сайт перестанет открываться, пока, наконец, не завершится выполнение долгих запросов.
  3. Кроме того, если вы еще и допустили ошибку при программировании самого кеша, и система записывает данные в него неатомарно (например, в файл с помощью fopen/fwrite, file_put_contents и т. д.), вы с большой вероятностью получите испорченные данные (записанные вперемешку байты из разных процессов). Если система не готова к некорректным данным в кеше, она может вообще перестать работать, пока не посчитает, что данные в кеше устарели. А если готова — продолжит пытаться выполнить тяжелый запрос в конкурентном режиме и не восстановится до тех пор, пока не посчастливится записать корректные данные в кеш, или пока не упадет нагрузка.

Как избежать условия гонки? Есть два способа.

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

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

Поделиться

Вруны из Теле2, или телефонный маркетинг Ctrl Кеширование в nginx

Читайте также

Оставьте свой комментарий


Формулы на латехе: $$f(x) = x^2-\sqrt{x}$$ превратится в $$f(x) = x^2-\sqrt{x}$$.
Выделение текста: [i]курсивом[/i] или [b]жирным[/b].
Цитату оформляйте так: [q = имя автора]цитата[/q] или [q]еще цитата[/q].
Других команд или HTML-тегов здесь нет.

Записи