Кэш каталога можно потерять и заполнить снова: первые посетители подождут чуть дольше, база данных на несколько минут поработает усерднее, и всё вернётся в норму. Счётчик важных операций на той же памяти потерять уже нельзя: заново его не наполнит ни один посетитель. Хранилище при этом одно, память одна, а правила у этих двух видов данных почти противоположные. Поэтому вопрос не в том, «что умеет Valkey», а в том, что должно случиться с каждым ключом, когда что-то пойдёт не так.
Дальше разберём это по порядку: как отличить данные, которые можно потерять, от тех, которые нельзя; что именно делает Valkey при заполнении памяти; почему сохранение на диск не отвечает на вопрос о вытеснении; и когда две роли стоит развести по разным экземплярам или даже по разным серверам.
Один вопрос, который сортирует ключи
Прежде чем открывать конфигурацию, полезно задать каждому типу данных один вопрос: откуда эти данные вернутся, если Valkey завтра окажется пустым? Не «насколько они важны», а именно «откуда». Ответ делит ключи на три группы довольно честно.
- Восстанавливаются из другого источника автоматически. Страница каталога, результат тяжёлого запроса, отрендеренный фрагмент. Источник есть, код обращения к нему уже написан, потеря означает лишь холодный старт.
- Восстанавливаются, но с усилием. Например, счётчик попыток входа или очередь заданий, у которых есть журнал в основной базе. Вернуть можно, но нужен отдельный процесс пересчёта, и на время пересчёта поведение системы будет странным.
- Больше нигде не лежат. Если Valkey — единственное место, где хранится значение, то потеря ключа — это потеря факта. Такое значение не должно жить в хранилище, которое по замыслу допускает вытеснение.
Обратите внимание, что третья группа — это часто не решение, а недосмотр. Разработчик когда-то положил в Valkey «временный» флаг состояния обработки платежа, он прижился, и через год от него зависит бизнес-логика. Проверка вопросом «откуда восстановится» как раз и вскрывает такие ключи.
| Ключ | Источник восстановления | Допустимое последствие потери |
|---|---|---|
| Кэш карточки товара | Основная база, обычный запрос | Временный рост времени ответа |
| Кэш результатов поиска | Пересчёт по запросу пользователя | Нагрузка на поиск после перезапуска |
| Счётчик попыток входа | Журнал событий в базе, если он ведётся | Ненадолго ослабленная защита от перебора |
| Задание в очереди на отправку | Нет, если очередь — единственное место | Потерянное письмо или операция |
Таблица условная, но принцип в ней общий: чем правее колонка и чем короче строка «источник», тем меньше у ключа прав находиться рядом с кэшем.
Что Valkey делает, когда память заканчивается
Для кэша заполнение памяти — не авария, а нормальный режим работы. Он существует для того, чтобы быть заполненным самыми востребованными данными. Именно поэтому в Valkey есть механизм вытеснения, и им управляет параметр maxmemory-policy: он определяет, что произойдёт при достижении лимита памяти (описание вытеснения ключей в Valkey). Сам лимит задаётся отдельно, параметром maxmemory; без него у экземпляра нет границы, при которой политика вообще вступает в дело.
Политики делятся на три семейства по своему поведению.
noeviction: отказать, но ничего не удалять
При такой политике Valkey не освобождает место сам. Когда лимит достигнут, новые записи получают отказ (Valkey: Key eviction). Уже лежащие ключи остаются на месте. Для данных, которые нельзя терять тихо, это ровно то, что нужно: проблема становится видимой. Клиент получает ошибку о нехватке памяти, мониторинг её замечает, приложение либо повторяет попытку, либо сообщает пользователю о неудаче.
Цена этого выбора — запись, которая не состоялась. Если приложение не умеет обрабатывать отказ, вы получите не потерянные данные, а потерянное действие, о котором система хотя бы знает. Это принципиально лучше, чем тихое удаление чужого ключа, но требует, чтобы код обработки ошибки существовал.
allkeys-политики: удалить любой подходящий
Семейство allkeys может удалять любые подходящие ключи, независимо от того, есть ли у них срок жизни (Valkey: Key eviction). Слово «любые» здесь главное. Политика не знает, что один ключ — кэш каталога, а другой — счётчик важных операций. Для неё это просто записи в одном пространстве памяти, и при нехватке места лишние уйдут. Для чистого кэша такое поведение идеально: никаких отказов записи, память всегда обновляется самыми нужными данными. Для смешанного экземпляра это лотерея, в которой счётчик может проиграть.
volatile-политики: только то, у чего есть срок
Семейство volatile выбирает для удаления ключи, у которых задан TTL. Если подходящих ключей нет, поведение такое же, как у noeviction (Valkey: Key eviction). На первый взгляд это изящный компромисс для смешанного экземпляра: кэшу даём TTL, постоянным данным не даём, и вытесняться будет только кэш.
Но компромисс держится на дисциплине. Любой кэш-ключ, записанный без TTL, перестаёт быть кандидатом на удаление и начинает занимать память навсегда. Один забытый вызов в коде, одна новая функция, написанная коллегой, который не знает про договорённость, — и доля защищённых ключей растёт. Когда вытеснять станет нечего, экземпляр незаметно превращается в noeviction, и первыми от этого пострадают как раз записи кэша, а заодно и постоянные данные, которым тоже нужно место.
| Политика | Что происходит с новой записью | Что может исчезнуть | Для кого подходит |
|---|---|---|---|
| noeviction | Отказ | Ничего не удаляется само | Данные, которые нельзя терять тихо |
| allkeys-… | Принимается, место освобождается удалением | Любой подходящий ключ | Чистый кэш |
| volatile-… | Принимается, пока есть ключи с TTL | Только ключи с TTL; при их отсутствии как noeviction | Смешанный экземпляр при строгой дисциплине TTL |
Смешанный экземпляр: три способа обмануться
Разберём, что происходит, когда кэш и постоянные ключи живут в одном экземпляре, при каждой из политик. Допустим условно: лимит памяти достигнут, в экземпляре есть несколько миллионов кэш-записей и небольшой набор счётчиков.
С allkeys. Освобождая место, Valkey выбирает кандидатов по алгоритму политики. Счётчик, к которому давно не обращались, выглядит для алгоритма так же, как давно не запрашиваемая страница каталога. Он может быть удалён. Приложение при следующем обращении увидит отсутствующий ключ и, скорее всего, начнёт счёт с нуля, не догадавшись, что это не новое событие, а потерянная история. Ошибки при этом нет: система ведёт себя «корректно».
С noeviction. Теперь страдает кэш. Новая страница каталога не помещается, запись в кэш отклоняется, и приложение, которое не ожидало отказа от кэша, возвращает пользователю ошибку, хотя данные в основной базе целы. Чтобы защитить несколько счётчиков, вы сломали основную функцию кэша.
С volatile. Всё работает до тех пор, пока ключи без TTL занимают небольшую часть памяти. Как только их становится слишком много или кэш начинает попадать без срока жизни, вытеснять становится нечего, и экземпляр превращается в noeviction. Поведение меняется без единого изменения конфигурации.
Можно подумать, что проблему решит разделение ключей по префиксам: cache: для кэша, data: для постоянных данных. Префиксы удобны для чтения и для мониторинга, но лимитов памяти они не задают. Политика вытеснения и maxmemory относятся ко всему экземпляру, а не к группе имён. Namespace — это договорённость об именах, а не граница ресурса.
Диск отвечает за другое: сохранение не равно вытеснению
Частая путаница звучит так: «Мы включили сохранение на диск, значит, данные в безопасности, а вытеснение уже не страшно». Это два независимых механизма, которые решают разные задачи.
Вытеснение отвечает на вопрос «что освободить, когда не хватает памяти». Сохранение отвечает на вопрос «что останется после перезапуска процесса». Они не связаны так, как кажется. Если ключ вытеснен, он удалён из рабочего набора, и следующее сохранение уже не будет его содержать. Диск не спасает от вытеснения, он лишь фиксирует его последствия.
Теперь посмотрим на последовательность, в которой запись попадает в хранилище и возвращается обратно. Условный пример со счётчиком:
- Приложение увеличивает счётчик операций с 1000 до 1001. Valkey изменяет значение в памяти и отвечает клиенту.
- Если сохранение включено, изменение попадает на диск тем способом и в тот момент, которые определяет настройка: либо при следующем снимке, либо при записи в журнал команд, в зависимости от выбранного режима и параметров синхронизации.
- Процесс или сервер аварийно останавливается. Содержимое памяти исчезает.
- После запуска Valkey читает последнее сохранённое состояние и восстанавливает набор ключей до того момента, который успел попасть на диск.
Всё интересное сосредоточено между пунктами 1 и 2. Если ответ клиенту ушёл, а запись на диск ещё не состоялась, то после сбоя в восстановленном состоянии будет 1000, хотя приложение уверено, что 1001 подтверждено. Размер этого окна зависит от выбранного режима сохранения и его настроек в вашей версии; конкретные гарантии нужно смотреть в справочнике по той версии Valkey, которую вы ставите, и не переносить на неё ожидания от другого продукта или другой конфигурации.
Если же сохранение выполняется снимками раз в определённый период, то после сбоя пропадут все изменения с момента последнего снимка. Для кэша это безразлично. Для счётчика это конкретное число потерянных событий, которое придётся восстанавливать из другого места.
Реплика: вторая копия, но не вторая жизнь
Реплика кажется надёжным ответом, но у неё свои границы. Во-первых, если репликация асинхронная, последние записи основного экземпляра могут не успеть дойти до реплики к моменту сбоя: ситуация та же, что с диском, только окно определяется сетью. Режим и условия вашей репликации нужно проверить в конфигурации и в справочнике по своей версии.
Во-вторых, реплика повторяет то, что происходит на основном экземпляре. Если ключ удалён по политике вытеснения или командой, реплика, как правило, следует за ним. Копия защищает от потери машины, но не от логических ошибок и не от тихого удаления нужного ключа. И в-третьих, если основной и резервный экземпляры живут на одном физическом сервере или даже на одной виртуальной машине, потеря этого сервера означает потерю обоих.
Поэтому ни диск, ни реплика не делают данные неуязвимыми. Они сокращают и сдвигают окно потери, но не обнуляют его. А политика noeviction, которая защищает от тихого удаления, ничего не гарантирует при потере самой машины: ключ остаётся в памяти только до тех пор, пока работает сервер.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Два сбоя: что вернётся и что нет
Проверим систему на двух конкретных ситуациях и посмотрим, откуда берутся данные.
Сбой первый: исчез кэш
Экземпляр с кэшем каталога перезапущен без сохранённого состояния, и в нём пусто. Первый запрос к каждой странице идёт в основную базу, результат записывается в кэш, следующие запросы получают его из памяти. Пока кэш не прогреется, нагрузка на базу выше обычной. Если база к такому не готова, главная опасность не в потере данных, а в том, что одновременный запрос тысяч холодных страниц перегрузит основной источник. Это решается прогревом, ограничением параллельных обращений к базе и разумным TTL, но не защитой самого кэша.
Восстановление здесь автоматическое, а риск измеряется временем. Именно поэтому для кэша выгодна политика allkeys: она позволяет экземпляру жить в заполненном состоянии без отказов и без ручного вмешательства.
Сбой второй: исчезла постоянная запись
Экземпляр со счётчиками перезапущен после аварии, и последнее сохранённое состояние оказалось старше последних операций. Здесь восстановление идёт иначе, в три шага:
- Valkey загружает то, что успело попасть на диск, и счётчики возвращаются к значению на момент последнего сохранения.
- Приложение или отдельный скрипт сравнивает счётчики с журналом событий в основной базе, если такой журнал ведётся, и добирает пропущенные операции.
- Только после выравнивания счётчики снова считаются достоверными.
Если журнала нет, то пропавших изменений просто не из чего восстановить, и последствие потери нужно признать допустимым заранее, на этапе проектирования. Это и есть ответ на вопрос из начала статьи: для каждого ключа нужно знать, чем закрывается пробел между последней сохранённой копией и моментом сбоя. Если закрыть его нечем, значит, ключ либо не должен жить в Valkey, либо его потеря должна быть принята как нормальное событие.
Один сервер или два: где проходит граница
Разделить роли можно на трёх уровнях, и каждый из них решает свою часть проблемы.
Один экземпляр, разные префиксы
Как уже сказано, это организационная мера, а не техническая изоляция. Один лимит памяти, одна политика, один процесс. Подходит только тогда, когда все ключи имеют одинаковую цену потери.
Два экземпляра на одной VPS
Это уже настоящее разделение правил. У каждого экземпляра своя политика вытеснения, свой лимит памяти, свои настройки сохранения и свой порт. Кэш получает allkeys и большой лимит, постоянные данные получают noeviction и более строгое сохранение. Нехватка памяти в кэше больше не диктует судьбу счётчиков: он освобождает место внутри своего лимита, а второй экземпляр этого не замечает.
Но общее у них остаётся: оперативная память машины, процессор, диск и ядро операционной системы. Сумма лимитов maxmemory не должна приближаться к объёму оперативной памяти, потому что Valkey занимает больше, чем указано в лимите: нужен запас на служебные структуры, буферы клиентов и на фоновое сохранение, во время которого процессу может понадобиться дополнительная память. Если машина исчерпает память целиком, ядру придётся принимать решение за вас, и оно не обязано выбрать кэш, а не экземпляр с важными данными.
Условный пример расчёта. Возьмём VPS уровня Heka: 4 vCPU, 8 ГБ RAM, 160 ГБ NVMe. Допустим, мы отдаём кэшу лимит 3 ГБ, экземпляру с постоянными данными 1,5 ГБ, а оставшиеся примерно 3,5 ГБ резервируем под систему, служебные накладные расходы Valkey, фоновое сохранение и запас на всплеск. Эти числа выбраны для иллюстрации: реальные значения зависят от размера ключей, числа клиентов и режима сохранения, и их нужно определять замером на своей нагрузке. Ресурсы тарифа описывают размер машины, а не обещают конкретную ёмкость данных.
Два разных VPS
Здесь разделение максимальное: у кэша и у постоянных данных разные виртуальные машины со своими процессами, дисками и оперативной памятью. Исчерпание памяти или перегрузка процессора кэшем не затрагивает экземпляр с важными данными, а перезапуск или переустановка одной машины не трогает другую. Кэш можно масштабировать или пересоздавать, не опасаясь за счётчики.
Важно не приписывать этому больше, чем есть. Два отдельных VPS — это две отдельные машины с отдельными ресурсами и доступом, но из этого не следует автоматически, что они расположены на разном оборудовании, и сама по себе пара серверов не делает одно приложение отказоустойчивым. Постоянным данным по-прежнему нужны сохранение на диск, продуманная репликация или копии, способ проверки целостности. Разделение убирает влияние кэша на постоянные записи, но не заменяет защиту самих записей.
У схемы с двумя серверами есть и цена: сетевая задержка между приложением и каждым из экземпляров, два набора доступов и обновлений, два объекта мониторинга. Для кэша, к которому приложение обращается на каждый запрос, лучше держать его ближе к приложению; к экземпляру с постоянными данными обращений обычно меньше, и лишние миллисекунды чувствительны слабее. Если нужно разместить машины в разных локациях, в публичном предложении MAATRIX упомянуты UK, US, RU и FR, а доступность конкретной ОС и образа проверяется в форме заказа. Серверы создаются автоматически примерно за пару минут после оплаты картой РФ или через СБП, на NVMe-накопителях, на процессорах AMD EPYC и с root-доступом, который позволяет самим настроить оба экземпляра и политики.
| Размещение | Что разделено | Что общее | Когда уместно |
|---|---|---|---|
| Один экземпляр | Только имена ключей | Лимит, политика, процесс, машина | Все ключи одинаково безвредно терять |
| Два экземпляра на одной VPS | Политика, лимит, настройки сохранения | RAM, CPU, диск, ядро, судьба машины | Небольшой проект, нужен быстрый старт |
| Две VPS | Всё перечисленное и ресурсы машин | Сеть между ними и ваше приложение | Постоянные данные нельзя подвергать риску из-за кэша |
Если вы только выбираете, с какого хранилища начинать, и сомневаетесь, насколько Valkey отличается от привычного Redis, полезно прочитать разбор про переход на Valkey и то самое «почти»: различия в мелочах как раз определяют, какие настройки придётся перепроверить.
Как выглядит нехватка памяти в каждой схеме
Проследим один и тот же рост нагрузки в двух вариантах размещения и посмотрим, кто за что платит. Условный сценарий: магазин проводит распродажу, число просматриваемых товаров и число операций растёт в несколько раз.
Вариант с одним смешанным экземпляром и политикой allkeys. Каталог раздувается, память достигает лимита, Valkey начинает вытеснять. Вытесняет он не по важности, а по правилам политики, и в числе кандидатов оказываются счётчики. Внешне всё нормально: ошибок нет, ответы быстрые. Проблема проявляется позже, когда оказывается, что счётчики стартовали заново, и никто не может сказать, когда это произошло. Единственный косвенный признак — рост числа вытесненных ключей в статистике сервера, но в смешанном экземпляре он не отличает кэш от важных данных.
Вариант с одним экземпляром и noeviction. Память достигает лимита, новые записи кэша отклоняются. В логах приложения появляются ошибки о нехватке памяти, часть страниц перестаёт кэшироваться, нагрузка на базу растёт. Счётчики целы, но продажи в самый нужный момент замедляются из-за кэша, который перестал выполнять свою работу.
Вариант с двумя экземплярами. Кэш достигает лимита и начинает вытеснять по allkeys, как и положено. Статистика вытеснений на кэш-экземпляре растёт, и это нормальный режим, на который можно смотреть без тревоги: тревожным становится падение доли попаданий в кэш. Экземпляр с постоянными данными с политикой noeviction живёт на своём лимите, и его заполненность растёт только от его собственных данных. Если он приближается к лимиту, это событие для дежурного: либо увеличить лимит, либо понять, что в него попало лишнее. Два разных сигнала приходят из двух разных мест и означают две разные вещи.
Вот что стоит наблюдать на каждом из экземпляров:
- на кэше: долю промахов, число вытесненных ключей, время ответа основной базы при росте промахов;
- на постоянном экземпляре: занятую память относительно лимита, число отказов записи, отставание или состояние сохранения и репликации;
- на обоих: общий запас оперативной памяти машины, чтобы сумма лимитов с накладными расходами не подошла вплотную к объёму RAM.
Эти показатели доступны в статистике сервера, а точные названия полей зависят от версии, поэтому их лучше сверить по справочнику установленной версии, прежде чем строить на них оповещения.
Передача настройки коллеге
Хорошая настройка Valkey должна быть понятна человеку, который не присутствовал при её создании. Для каждого экземпляра достаточно записать четыре вещи рядом с конфигурацией:
- Роль: кэш или постоянные данные.
- Политика и лимит: значение
maxmemory-policyиmaxmemoryс объяснением, почему именно они. - Допустимая потеря: что случится с данными при перезапуске и откуда они восстанавливаются.
- Правило для новых ключей: какой экземпляр выбирать и нужен ли TTL.
Это экономит больше времени, чем подробные инструкции по эксплуатации: когда роль и допустимая потеря записаны, остальные решения выводятся из них.
Правило для нового ключа
Перед тем как положить в Valkey новый тип ключа, назовите вслух, что произойдёт, если завтра его не окажется на месте: чем он заменится, сколько займёт восстановление и кто это заметит. Если ответ звучит как «ничего страшного, придёт из базы», ключу место в кэше с allkeys. Если как «придётся пересчитывать по журналу и это неприятно», ему нужен экземпляр с noeviction и продуманным сохранением. А если ответа нет, то размещать ключ рано: сначала нужно придумать, откуда он вернётся.