Удалили ключ — Redis вернул ноль байт. Память на графике наконец проседает через полминуты, и непонятно: всё штатно или мы только что нашли утечку. Скорее всего штатно. Дальше — почему так происходит и что реально смотреть.
Две цифры, которые постоянно путают
У Redis есть внутренняя бухгалтерия и есть реальность. used_memory — сколько данных инстанс считает своими по своей же статистике. used_memory_rss — сколько страниц памяти занимает процесс с точки зрения операционной системы.
Первая цифра может упасть в ту же секунду, когда вы сделали DEL. Вторая — нет, и это не баг. Между «Redis забыл про ключ» и «операционная система получила страницы назад» лежат две прослойки: механизм удаления и аллокатор.
Как именно исчезает ключ
Команда DEL работает синхронно: пока Redis освобождает память от значения, он не обслуживает другие запросы. Для строки в 100 байт это незаметно. Для хеша на 4 миллиона полей — заметная пауза в обработке.
Поэтому появился UNLINK: ключ пропадает из пространства имён мгновенно, а освобождение памяти уходит в фоновый поток. Ту же логику можно включить принудительно для обычных команд — параметры lazyfree-lazy-user-del, lazyfree-lazy-expire, lazyfree-lazy-eviction, lazyfree-lazy-server-del.
Отдельная история — ключи с TTL. Redis ищет просроченные двумя путями: пассивно, когда кто-то обратился к ключу, и активно — фоновым циклом, который выбирает случайные ключи с истекающим сроком и вычищает те, что уже протухли. Отсюда и знакомое ощущение «память вернулась сама через двадцать секунд»: ключ физически лежал в базе до того, как до него дошла очередь.
Аллокатор не спешит делиться
Redis по умолчанию работает на jemalloc. Память он берёт у системы крупными кусками и раздаёт под данные сам. Когда вы освобождаете ключ, байты возвращаются не операционной системе, а обратно в пул jemalloc — страницы помечаются как грязные и отдаются наружу позже, по внутреннему таймеру.
Плюс арены: jemalloc заводит отдельные пулы под потоки, чтобы они не толкались за общим локом. Каждая арена держит свою память. Если потоков много, число арен растёт, и освобождённое в одной арене не помогает другой. Ограничивается переменной окружения MALLOC_ARENA_MAX.
И ещё: возврат памяти идёт фоном active defrag (activedefrag yes), который переупаковывает данные, вытаскивая полезное из разбросанных страниц.
Метрики, по которым видно, что происходит
| Метрика | Что показывает | На что смотреть |
|---|---|---|
| used_memory | Данные по версии самого Redis | Растёт вместе с числом ключей — норма |
| used_memory_rss | Сколько занимает процесс для ОС | Стоит на месте после массового удаления |
| mem_fragmentation_ratio | Отношение rss к used_memory | 1,0–1,3 — норма; выше 1,5 — смотреть фрагментацию |
| allocator_frag_ratio | Фрагментация внутри аллокатора | Высокая вместе с ratio — арены и пулы |
| mem_allocator | Какой аллокатор подключён сборкой | Разные сборки ведут себя по-разному |
Если mem_fragmentation_ratio меньше единицы, часть памяти ушла в swap. Это хуже, чем фрагментация: отзывчивость инстанса при этом падает куда заметнее.
Что проверить руками
- INFO memory — снять used_memory, used_memory_rss, mem_fragmentation_ratio до удаления и через минуту после.
- MEMORY DOCTOR — короткий вердикт от самого Redis, часто указывает на конкретную причину.
- MEMORY USAGE key — сколько весит один ключ со всеми накладными расходами. Полезно перед чисткой больших коллекций.
- redis-cli --bigkeys — находит крупные структуры. Именно они дают паузы при синхронном DEL.
- MEMORY STATS — разбивка по потребителям: клиентские буферы, replication backlog, память под осколки.
Типичные ошибки
Самая частая — перезапуск инстанса. Он «лечит» симптом на один вечер: после старта RSS аккуратный, а через сутки фрагментация возвращается ровно та же, потому что нагрузка не изменилась. Вторая ошибка — смотреть только на used_memory и радоваться, когда внутренняя цифра упала. Третья — ставить maxmemory без политики вытеснения, а потом удивляться, что запись падает с ошибкой.
И ещё одна, совсем обидная: массово удалять ключи обычным DEL в цикле на боевом инстансе. Лучше сразу UNLINK — или включить ленивое освобождение на уровне конфигурации.
Когда чинить нечего
Ориентир простой: если удалённые данные освобождаются в пределах минуты, а отношение RSS к used_memory держится около единицы, трогать ничего не нужно — это нормальное поведение аллокатора, заложенное в его устройство.
Другой разговор, когда RSS стоит на месте час и растёт линейно вместе с числом ключей. Тогда смотрите на крупные коллекции, клиентские буферы и настройки арен, а не на команду удаления.
Комментарии (0)
Войдите, чтобы комментировать.
Пока нет комментариев. Будьте первым.