Команда переносит в Redis условные 100 ГБ бизнес-данных и заказывает сервер с небольшим запасом сверх этого объёма. На тестовой загрузке всё помещается: клиентов мало, набор создан за один проход, репликация и фоновые операции почти не работают. В production те же данные получают другую форму объектов, число соединений растёт, ключи активно создаются и удаляются, а резидентная память процесса подходит к пределу раньше расчёта. После этого появляются два противоположных объяснения. Одни считают, что Redis «съел лишнюю память», другие пытаются вывести универсальный коэффициент между размером файла и RAM. Обе версии скрывают главное: экспорт, внутренний dataset, выделения Redis и физические страницы процесса — разные уровни учёта.
Экспорт измеряет перенос, а не работу Redis
JSON, CSV, дамп приложения или сумма полезных значений описывают сериализованное представление данных. В нём может не быть части имён ключей, сроков жизни, типов, индексов внутренних словарей и служебной информации, необходимой Redis для быстрых операций. Формат также может сжимать повторения или, наоборот, добавлять текстовую разметку. После загрузки Redis хранит не файл, а объекты выбранных типов во внутренних структурах. Строка, hash, set или sorted set имеют разные представления и меняют их в зависимости от содержимого, версии и настроек. Эта статья не ищет цену каждого маленького объекта — это отдельная тема, — но сам факт различия означает, что одинаковый полезный объём способен занять разную память.
Форма нагрузки тоже меняет картину. Постоянное создание и удаление объектов, клиенты, реплики, скрипты и модули оставляют другой служебный расход и историю allocator, чем однократная загрузка для чтения. Поэтому «100 ГБ данных» — бизнес-описание, а не обещание 100 ГБ used_memory или физической RAM.
Dataset — внутренняя оценка, а не исходный payload
В INFO поле used_memory показывает байты, которые Redis выделил через свой allocator — механизм выдачи памяти процессу. В эту сумму входят данные и значительная часть внутренних расходов Redis. Она полезна для понимания того, чем управляет сам сервер, но не равна размеру экспортного файла и не обязана совпадать с физической памятью процесса. used_memory_overhead оценивает выделения на внутренние структуры управления. used_memory_dataset получается после вычитания этого overhead из used_memory. Поэтому название dataset легко прочитать неверно: это не сумма исходных JSON-полей или значений клиента, а внутренний остаток внутри модели учёта Redis.
За пределами бизнес-payload остаются имена и представления ключей, словари, сроки жизни, клиентские и репликационные буферы, AOF-буферы, скрипты и модули. Не всё одинаково отражается в одном поле или растёт вместе с dataset, а некоторые показатели являются частями других. Поэтому один коэффициент «данные × запас» плохо переносится между проектами.
RSS хранит историю allocator и операционной системы
used_memory_rss показывает физические страницы процесса, которые ОС видит в RAM. RSS включает allocator, код, библиотеки, стеки и другие резидентные расходы. RSS может быть выше used_memory, если Redis освободил объекты, но allocator сохранил страницы для повторного использования, либо свободные участки неудобны для новых выделений. Обратная картина также возможна: used_memory выше RSS, если часть выделенной памяти не находится в физической RAM, например была вытеснена в swap. Это не бесплатная экономия. Возврат страниц добавляет задержку, что особенно заметно для Redis с короткими операциями.
Поле mem_fragmentation_ratio сравнивает RSS и used_memory, но в него входят не только потери allocator: также код, библиотеки, стеки и другой RSS-overhead. Для внешней фрагментации allocator есть отдельные allocator_frag_ratio и объём в байтах. Высокий ratio при малой абсолютной разнице может не иметь бизнес-значения, а умеренное отношение на большом процессе — скрывать существенный объём. Сначала важны абсолютные байты и запас до предела, затем происхождение разницы.
Один ключ не объясняет память всего сервера
MEMORY USAGE отвечает на узкий и полезный вопрос: сколько выделений требуют конкретный ключ, его значение и связанный административный overhead. Он помогает сравнить реальные представления объектов, а не гадать по длине строк в выгрузке. Для вложенных типов команда может оценивать размер по выборке элементов. Результат тогда зависит от содержимого объекта и параметра выборки, а не является точным измерением каждого элемента. Кроме того, ключи разного типа и размера распределяются в production не так, как несколько удобных примеров.
Механическое умножение выборки на число ключей пропускает соединения, replication backlog, AOF и общие структуры. Сумма таких измерений не превращается в RSS хоста: MEMORY USAGE описывает выделения объектов, но не код, стеки, страницы allocator и остальную систему. Если форма production отличается от теста, экстраполяция отвечает для другой базы. MEMORY STATS и INFO связывают dataset внутри выделений, overhead Redis и allocator/RSS, а MEMORY USAGE добавляет форму объектов. Вместе они объясняют расхождение, но ни один показатель не задаёт размер сервера.
maxmemory не ограничивает RSS контейнера
maxmemory задаёт внутреннюю границу, после которой Redis применяет выбранную политику. При eviction-политике сервер удаляет ключи, чтобы освободить место; при noeviction команды, которым нужна дополнительная память, могут получить ошибку. Это правило Redis, а не аппаратный потолок процесса. Часть временных буферов репликации и AOF не учитывается при решении об eviction, чтобы удаление ключей не запустило самоусиливающийся цикл новых записей в эти же буферы. INFO показывает эту часть как mem_not_counted_for_evict. Следовательно, достижение или недостижение maxmemory ничего не гарантирует о полном RSS.
У cgroup контейнера и хоста есть собственные пределы физически учитываемой памяти. Процесс может приблизиться к контейнерному лимиту из-за RSS и буферов, хотя сравнение с maxmemory выглядит безопасно. Повышение maxmemory до виртуальной ёмкости лишь уменьшает внешний запас. Eviction говорит о пересечении внутренней границы Redis, а не обязательно о нехватке RAM узла. Для кэша удаление может быть допустимо; для основного хранилища ошибка записи или потеря ключей нарушает сервис. Поведение нужно связывать с политикой и ролью данных. COW при BGSAVE — переходный слой поверх обычного бюджета, рассмотренный отдельно. Такой пик нельзя прятать внутри постоянного коэффициента к dataset.
Бюджет строят по обязательной работе, а не по гигабайтам файла
Решение о RAM связывает модель данных с одновременной нагрузкой: типами и размерами объектов, churn — созданием и удалением, клиентами, репликацией, persistence и историей allocator после пиков. Иначе измеряется лабораторная версия другой системы. Руководителю нужна граница между постоянным расходом, обязательными буферами, переходными пиками и внешним запасом. Затем выбирается компромисс между ценой DRAM, допустимостью eviction или ошибок, задержкой и требованиями к восстановлению.
Прозрачный страничный уровень класса gigaRAM может уменьшать потребность в физической DRAM только там, где действительно есть подходящие менее активные страницы, а горячая часть остаётся в DRAM. Он не меняет used_memory, maxmemory или eviction policy Redis и не делает виртуальную ёмкость безопасным значением внутреннего лимита. Возврат страниц через NVMe имеет собственную задержку, а публичное позиционирование не является обещанием фиксированной экономии или результата для любого Redis. Если весь набор активен или задержка неприемлема, медленный уровень не исправит бюджет. При устойчивой менее активной части результат всё равно оценивают по RSS, внешнему пределу и качеству сервиса, а не по одному полю.
Главный вывод прост: сериализованный объём отвечает, сколько данных переносится; used_memory_dataset — какая часть выделений Redis считается dataset; used_memory — сколько выделил allocator; RSS — сколько резидентной памяти видит ОС. Эти величины связаны, но не взаимозаменяемы. Бюджет RAM становится честным только тогда, когда сохраняет нужную нагрузку и внешний запас, а не совпадает с круглым числом из выгрузки.
Источники. Redis INFO определяет used_memory, dataset, RSS, fragmentation и исключённые из eviction буферы.
MEMORY STATS раскрывает внутренние части выделений и allocator, MEMORY USAGE — оценку ключа и выборку вложенных типов, Key eviction — смысл maxmemory, политик и mem_not_counted_for_evict, а Memory optimization — зависимость расхода от представления данных.
Linux cgroup v2 используется только для внешней границы контейнера.
Meta TMO подтверждает общую механику страничного переноса, продуктовая страница — только позиционирование.
