Отчего ж? Аж двойная.
1. Спец. флаг в Redis (защищает при первом генерировании в основном)
2. Контрольная проверка наличия кеш-значения с t < TTL1 в начале процедуры перегенерации. Это если очередь долго разгребалась.
Совершенно верно. И придя к тем же выводам, я убрал «фоновую актуализацию» и внедрил «2-х диапазонные кеши», которые обновляются только в том случае, если востребованы.
Да, видимо оно самое. Было бы странно, если бы такую, в общем-то несложную, вещь я бы придумал первым. Но тем не менее, информации на русском — найти не удалось, на английском — очень сложно (и то только благодаря комментариям к этой статье).
Не совсем. Подобную технологию я использовал ранее. Ее цель — убрать случаи пиковой нагрузки.
Например, если на странице нужны 10 кеш-значений, с TTL=100, то по истечении этих 100 секунд все 10 блоков будут перегенерироваться в рамках одного запроса. Чтобы этого избежать, у меня автоматом добавлялась вариативность времен в пределах +-20%. Что гарантировало «постепенное» обновление всего набора.
Получается, что идеи хоть и очень схожи, но все-таки разные. Моя основная цель — «ленивое» обновление, что как часть включает в себя и «защиту сервера».
Идея как раз в том, чтобы по-возможности вообще никогда не заставлять ждать (самое первое генерирование не в счет). А большая «навороченность» обусловлена далеко не столько идеей 2-диапазонного кеширования, сколько размером системы — у много-серверных продуктов свои особенности и требования.
О, с редисом все нормально — ставим довольно короткий TTL для записи самого флага, так что дедлоков не случается. Да и новый консюмер поднимается в течение 10 секунд максимум, если что.
А вот на счет мемкеш — спасибо, учтем.
Да, концепция не дает. Но указанную проблему мы решили. Дело в том, что задачи на обновление у нас идут через Rabbit. Соответственно, поток, занимающийся пересчетом, получая задачу, просто проверяет наличие в кеше соответствующего значения с t < TTL1. Если так и есть, значит задача — дубль, пропускаем. Если t > TTL1 — работаем. И есть отдельный поток — он занимается принудительным пересчетом (иногда надо обновить срочно и вне общей очереди).
PS На самом деле немного обманул — дополнительно еще ставится/проверяется/удаляется спец. флаг, означающий «задача есть в очереди». Для этого мы используем Redis.
По второму замечанию — тут сложнее определять, что значит «порядочно» в каждом конкретном случае. Фиксация в секундах или процентах наверняка где-то будет препядствием. Да и в плане разработки — посложнее. При появлении нового набора данных для кеширования, надо внести правки и в общую процедуру «дай кеш-значение».
Согласен, тоже хорошее решение. Я пробовал фоновый поток, обходящий кеши. Но очень быстро вышло, что слишком много надо было пересчитывать. Когда пришлось ввести 2 фоновых потока, постоянно без пауз что-то считающих, я пошел «в другую сторону».
Ну я вроде и не говорил, что очередь прямо в воркере сделана. Согласен, что подход с «отдельной очередью» более привлекателен, чем «очередь в самом воркере». Поэтому я использую RabbitMQ.
Вообще, я хотел написать целый цикл статей, т.к. за время развития проекта накопилась масса интересных вещей. А тут почти каждый из коментариев что-то да затрагивает из этого.
«Бизнес-логика в монолите». О, такие задачи уже были. И применено два типа решений — конвеер и обратное обращение. Но подробно я опишу это отдельно.
Все верно. В чем суть вашего вопроса? Почему просто монолит на новой ноде не подняли?
Отвечаю: чтобы поднять полноценную ноду с монолитом, надо дополнительно установить и настроить десяток утилит; при запуске монолит устанавливает кучу коннектов и производит много действий по инициализации — надо существенно его переработать, чтобы делал это не при каждом обращении, а один раз при старте; дополнительные работы по настройке обновлений; сохранение всех зависимостей монолита; многократно выше требования к железу. Это — большой пласт работ по подготовке к «run as node» + дополнительные сложности в сопровождении. Зачем? Когда можно сделать маленький сервис без лишних подключений, монструозного кода и прочих проблем.
C «копированием всего себя» хорошо работают системы, которые изначально ориентированы на подобный тип масштабирования. Моя — нет.
Поэтому вместо «возить Белазом по одному кирпичу» я сконструировал быструю «тележку с выделенной полосой движения».
Полностью согласен с аспектом критичности данных. Поэтому все важные вещи пишутся, как раньше — сразу в БД без всяких очередей. А вот менее важные — через очередь, хотя сама по себе она обеспечивает очень хороший уровень безопасности, сохраняя данные «для сервиса» у себя локально на диск до тех пор, пока сервис не подтвердит, что он полностью все обработал.
Да, использовать посредника для записи в БД субъективно страшнова-то. Но объективно, риск в этом случае всего лишь удваивается, по сравнению с риском в варианте «пишем сразу». Тут много надо говорить, так что, наверное, напишу об этом отдельную статью потом.
Предмет разговора — сайт на PHP. Я не нашел эффективного способа организовать очередь запросов в монолите. В приложении на C++/Delphi — запросто. А тут — нет. Как ее сделать? Эмулировать через ту же БД, записывая в нее сами запросы? Или Redis задействовать? А как выполнять? И вот продумывая варианты, я пришел к выводу, что самый простой способ и с точки зрения реализации и с точки зрения логики — просто вынести функционал записи «во вне», переложив всю организацию очереди на специализированный софт.
По производительности, вот с чем я боролся:
1. Запускается один процесс обработки данных. Он делает «тяжелые» запросы на выборку, а потом много «легких» на запись.
2. Запускается второй процесс, которому наоборот надо много выбрать и редко, но «сильно» записать.
3. Еще один процесс делает частые-частые маленькие запросы на запись. Логирует.
Что в итоге: создается значительная нагрузка на HDD сервера с СУБД. И он полностью начинает тормозить. Просто потому, что HDD с такой интенсивностью не справляется. А это сказывается на всей системе в целом.
Варианты: вложиться в железо или ограничить нагрузку. Я пошел по второму пути.
В идеале — должны храниться отдельно и заправшиваться через «обращение к внешнему сервису». На практике — поскольку с железом не богато и для всех БД все-равно один сервер используется, смысла отделять полностью не было. Хранятся в той же. Поэтому, с одной стороны, с точки зрения чистоты подхода — это не совсем внешний сервис, но с точки зрения «работает вне ядра, отдельно» — все-таки да.
В идеале — должны храниться отдельно и заправшиваться через «обращение к внешнему сервису». На практике — поскольку с железом не богато и для всех БД все-равно один сервер используется, смысла отделять полностью не было. Хранятся в той же. Поэтому, с одной стороны, с точки зрения чистоты подхода — это не совсем внешний сервис, но с точки зрения «работает вне ядра, отдельно» — все-таки да.
VolCh ответил совершенно точно, но хочу дополнительно пояснить на примере: допустим надо писать что-то в БД, при этом данные не критичны — пересчет каких-то кешей по истории действий пользователя. Чуть раньше они пересчитаются или чуть позже — не важно. И таких процессов пересчета несколько и организовать их последовательное выполнение одним cron-ом затруднительно. По факту имеем периодически «проседающую» под нагрузкой БД. Решение в таком случае — отдельный сервис, который занимается пересчетом. А по cron-у только постановка задач этому сервису. Результат: все пересчеты идут строго последовательного по одному, БД не «напрягается».
В моей системе реализован сервис чуть более низкого уровня — «Отложенная запись в БД». Я в него отправляю непосредственно запросы. Очень маленькая по размеру, но важная по смыслу штука получилась. И эффект дает огромный.
Да, смысл заголовка не совсем «склеился» с текстом. Но ничего точнее не придумалось. Зачем вообще взялся: много материалов про монолиты, много — про микросервисы. Каждый агитирует за свое. И разработчики часто (все, кого я опрашивал) начинают противопоставлять подходы. И никто не пытается взять из обоих лучшее. Хотелось обратить внимание на то, что однобокий подход, хоть и дает иногда очень хорошие результаты, но именно лишь иногда. А на практике очень часто лучший результат достигается «сбалансированной смесью» подходов и технологий.
1. Спец. флаг в Redis (защищает при первом генерировании в основном)
2. Контрольная проверка наличия кеш-значения с t < TTL1 в начале процедуры перегенерации. Это если очередь долго разгребалась.
Например, если на странице нужны 10 кеш-значений, с TTL=100, то по истечении этих 100 секунд все 10 блоков будут перегенерироваться в рамках одного запроса. Чтобы этого избежать, у меня автоматом добавлялась вариативность времен в пределах +-20%. Что гарантировало «постепенное» обновление всего набора.
Получается, что идеи хоть и очень схожи, но все-таки разные. Моя основная цель — «ленивое» обновление, что как часть включает в себя и «защиту сервера».
А вот на счет мемкеш — спасибо, учтем.
PS На самом деле немного обманул — дополнительно еще ставится/проверяется/удаляется спец. флаг, означающий «задача есть в очереди». Для этого мы используем Redis.
По второму замечанию — тут сложнее определять, что значит «порядочно» в каждом конкретном случае. Фиксация в секундах или процентах наверняка где-то будет препядствием. Да и в плане разработки — посложнее. При появлении нового набора данных для кеширования, надо внести правки и в общую процедуру «дай кеш-значение».
Вообще, я хотел написать целый цикл статей, т.к. за время развития проекта накопилась масса интересных вещей. А тут почти каждый из коментариев что-то да затрагивает из этого.
«Бизнес-логика в монолите». О, такие задачи уже были. И применено два типа решений — конвеер и обратное обращение. Но подробно я опишу это отдельно.
Отвечаю: чтобы поднять полноценную ноду с монолитом, надо дополнительно установить и настроить десяток утилит; при запуске монолит устанавливает кучу коннектов и производит много действий по инициализации — надо существенно его переработать, чтобы делал это не при каждом обращении, а один раз при старте; дополнительные работы по настройке обновлений; сохранение всех зависимостей монолита; многократно выше требования к железу. Это — большой пласт работ по подготовке к «run as node» + дополнительные сложности в сопровождении. Зачем? Когда можно сделать маленький сервис без лишних подключений, монструозного кода и прочих проблем.
C «копированием всего себя» хорошо работают системы, которые изначально ориентированы на подобный тип масштабирования. Моя — нет.
Поэтому вместо «возить Белазом по одному кирпичу» я сконструировал быструю «тележку с выделенной полосой движения».
Да, использовать посредника для записи в БД субъективно страшнова-то. Но объективно, риск в этом случае всего лишь удваивается, по сравнению с риском в варианте «пишем сразу». Тут много надо говорить, так что, наверное, напишу об этом отдельную статью потом.
По производительности, вот с чем я боролся:
1. Запускается один процесс обработки данных. Он делает «тяжелые» запросы на выборку, а потом много «легких» на запись.
2. Запускается второй процесс, которому наоборот надо много выбрать и редко, но «сильно» записать.
3. Еще один процесс делает частые-частые маленькие запросы на запись. Логирует.
Что в итоге: создается значительная нагрузка на HDD сервера с СУБД. И он полностью начинает тормозить. Просто потому, что HDD с такой интенсивностью не справляется. А это сказывается на всей системе в целом.
Варианты: вложиться в железо или ограничить нагрузку. Я пошел по второму пути.
В моей системе реализован сервис чуть более низкого уровня — «Отложенная запись в БД». Я в него отправляю непосредственно запросы. Очень маленькая по размеру, но важная по смыслу штука получилась. И эффект дает огромный.