Redis-инстанция месяцами работает стабильно: объём данных меняется предсказуемо, used_memory не приближается к пределу, задержка коротких операций укладывается в требования сервиса. Затем Redis запускает создание RDB-снимка в фоне командой BGSAVE, одновременно проходит поток обновлений, и свободный запас на узле быстро сокращается. Ответы в хвосте распределения замедляются, контейнер подходит к пределу памяти, а иногда процесс оказывается рядом с OOM — аварийным завершением из-за нехватки RAM. Такой инцидент часто объясняют фразой «fork удвоил Redis». Она технически неверна: весь набор не копируется физически сразу, а дополнительный расход появляется по мере изменений памяти во время записи снимка.
Fork не удваивает память мгновенно
BGSAVE создаёт дочерний процесс с помощью fork. Дочерний процесс проходит данные и записывает RDB — снимок состояния Redis на определённый момент, а родительский продолжает принимать команды клиентов. Сразу после fork оба процесса в основном ссылаются на одни и те же физические страницы памяти. Это возможно благодаря copy-on-write, или копированию при записи. Пока разделяемая страница только читается, отдельная физическая копия не требуется. Когда родительский процесс меняет содержимое такой страницы, ядро сохраняет для дочернего процесса прежнюю версию и создаёт копию, с которой может продолжить родитель.
Поэтому fork не означает немедленного появления второй полной копии dataset. При коротком снимке и малом числе записей дополнительная физическая память может быть заметно меньше всего набора; при интенсивных обновлениях и долгой работе child копий становится больше. Копирование происходит на уровне страниц, а не логических ключей. Изменение короткого значения способно затронуть страницу с другими данными и служебными структурами. Распределитель памяти, фрагментация, размер страниц и ОС влияют на результат. Поэтому объём COW нельзя приравнять ни к числу изменённых байтов значений, ни автоматически ко всему dataset.
Пауза fork и рост COW — две разные цены
Первая цена возникает в момент fork. Redis выполняет его из основного потока, который обслуживает команды, поэтому подготовка дочернего процесса может дать паузу. Linux использует COW и не копирует сразу все данные, но ему всё равно нужно создать структуры нового процесса и продублировать таблицы страниц — карту виртуальной памяти процесса. Чем больше адресное пространство Redis, тем больше служебной работы. На время fork также влияют процессор, ядро ОС и виртуализация. Прозрачные огромные страницы Linux, или THP, могут сделать последующее копирование крупнее и усилить задержки.
Вторая цена накапливается после начала формирования RDB. Родитель меняет данные, ядро создаёт копии страниц, а child читает старое состояние. Эта память живёт до завершения фоновой работы поверх обычного расхода Redis. Быстрый fork не гарантирует маленького COW при долгом снимке и интенсивной записи. И наоборот, большое адресное пространство способно дать паузу fork при малом последующем COW. У событий разные причины и влияние на p99 — границу задержки для подавляющего большинства операций.
Пик определяет профиль записей и длительность снимка
Размер dataset задаёт масштаб возможной работы, но не определяет пик в одиночку. Redis с большим, почти неизменяемым набором может пройти BGSAVE с умеренным COW. Инстанция меньшего размера, в которой часто обновляются счётчики, очереди или сессии, способна скопировать значительную долю затрагиваемых страниц за то же окно. Важен характер изменений. Повторная запись в уже отделённую страницу не обязана создавать новую COW-копию, а обновления по широкому диапазону затрагивают больше разделяемых страниц. Одинаковое число команд поэтому создаёт разный расход.
Длительность снимка расширяет окно риска. Медленный или занятый накопитель, конкуренция за I/O, нагрузка процессора и размер набора продлевают фазу, а значит, дают рабочему потоку больше времени для COW. Если запас рассчитывался только по used_memory в спокойный час, сервер может сохранить dataset, но потерять предсказуемость задержки во время снимка. Поэтому один Redis проходит ночной BGSAVE безопасно, но испытывает давление днём: меняются темп записей, длительность RDB и конкуренция за накопитель, а не обязательно размер базы.
RSS родителя и дочернего процесса нельзя просто складывать
Внутри Redis показатель used_memory описывает память, которую учитывает распределитель Redis, а dataset — полезную часть, связанную с ключами и значениями. Между ними есть служебные структуры и фрагментация. Но ни один из этих показателей сам по себе не отвечает, сколько физической памяти в момент BGSAVE занято всеми процессами и остальной системой. RSS показывает резидентные страницы отдельного процесса. После fork общие страницы могут входить в RSS и родителя, и дочернего процесса, хотя физически хранятся один раз. Простое сложение двух RSS поэтому завышает расход: оно повторно считает разделяемую часть.
PSS, или пропорциональный резидентный размер, распределяет стоимость каждой общей страницы между использующими её процессами. Он аккуратнее показывает parent и child, но не включает весь бюджет ядра, файлового кэша, других процессов и контейнерных пределов. Redis INFO показывает внутреннюю часть картины: current_cow_size — текущий COW при работающем child, current_cow_peak — его пик, rdb_last_cow_size — COW последнего RDB. Эти значения не заменяют память cgroup или хоста и сами не объясняют задержку накопителя. Стабильный used_memory до BGSAVE не доказывает достаточного резерва, а большая сумма RSS двух процессов — физического удвоения Redis. Нужна общая картина обычного расхода, временного COW, предела среды и качества обслуживания клиентов.
RDB и AOF создают разные обещания
RDB — снимок, а не журнал каждой подтверждённой операции. Если база восстанавливается только из периодических снимков, изменения после последнего успешного RDB могут быть потеряны при аварии. Частота BGSAVE поэтому связана не только с памятью и I/O, но и с допустимым окном потери данных. AOF сохраняет последовательность операций изменения и решает другую задачу устойчивости. Его фоновая перезапись BGREWRITEAOF тоже использует fork и COW, поэтому способна создать похожий класс временного расхода, однако её нельзя называть тем же RDB-снимком.
Redis не запускает тяжёлые фоновые процессы формирования RDB и перезаписи AOF одновременно: одна операция может быть отложена и пойти следом. Однако последовательные операции всё равно способны надолго занять I/O и пересечься с клиентским пиком. Выбор persistence меняет компромисс: более частые снимки сокращают потенциальное окно потери, но увеличивают частоту fork, а AOF меняет профиль записи и восстановления. Ни один вариант не превращает запас памяти в фиксированный процент от dataset.
Снимок меняет профиль активности страниц
У BGSAVE есть особенность, важная для многоуровневой памяти: дочерний процесс проходит данные для сериализации независимо от того, насколько часто к ним обращались клиенты. Страница, которая была малоактивной для обычной нагрузки, во время снимка может понадобиться дочернему процессу. Одновременно родитель продолжает менять активные страницы. Для прозрачного страничного уровня это отдельная фаза нагрузки. В классе решений gigaRAM активные страницы остаются в DRAM, а подходящие менее активные могут обслуживаться через NVMe. Но последовательное чтение дочернего процесса отличается от обычного клиентского профиля, поэтому длительность снимка и общий I/O оценивают отдельно.
Многоуровневая память не заменяет запас на COW. Её экономический смысл появляется, когда у обычной нагрузки есть устойчивая менее активная часть, горячие страницы сохраняются в DRAM, а накопителю хватает ресурса на обязательную работу Redis. Результат оценивают по длительности RDB, памяти хоста и задержке клиентских операций. Бюджет BGSAVE строится вокруг переходного режима.
DRAM нужна не потому, что fork неизбежно копирует весь Redis, а потому, что сочетание адресного пространства, записи, длительности снимка и среды создаёт временный расход. Иногда дешевле изменить расписание или путь persistence, иногда нужен больший запас RAM, а иногда — другая архитектура восстановления. У BGSAVE две зоны риска — пауза fork и растущий COW. Первая зависит от создания child и адресного пространства, вторая — от страниц, изменённых до завершения RDB. Их оценивают раздельно, чтобы сохранить устойчивость данных и задержку сервиса.
Источники. Redis BGSAVE описывает фоновый RDB-снимок;
Redis Persistence — RDB, AOF, fork/COW и порядок фоновых операций;
Diagnosing latency issues — fork, таблицы страниц, виртуализацию и THP;
Redis INFO — показатели COW. Linux fork(2) подтверждает COW и стоимость таблиц страниц, а документация /proc — различие RSS и PSS.
Meta TMO используется только для механики страничного переноса, продуктовая страница — для позиционирования.
