Понимание пространства ключей Redis: команды удаления и проверки

Раскройте возможности управления пространством ключей Redis с помощью этого подробного руководства. Научитесь безопасно проверять данные с помощью `SCAN` (и почему стоит избегать `KEYS` в продакшене) и эффективно удалять ключи с помощью `DEL` и неблокирующего `UNLINK`. Поймите разрушительную природу `FLUSHDB` и `FLUSHALL` и откройте для себя лучшие практики поддержания здорового и высокопроизводительного экземпляра Redis.

Понимание пространства ключей Redis: команды удаления и проверки

Пространство ключей Redis — это просто набор ключей в текущей выбранной базе данных, но то, как вы проверяете и удаляете эти ключи, может решить, будет ли очистка скучной или ваше приложение зависнет в середине трафика.

Большинство команд учатся этому на горьком опыте. Кому-то нужно удалить ключи session:* из промежуточного кэша, он запускает KEYS session:*, видит список, а затем пытается сделать то же самое в продакшене. В крошечной базе данных это кажется нормальным. В загруженном экземпляре с миллионами ключей команда может удерживать сервер достаточно долго, чтобы несвязанные запросы выстроились в очередь за ней. Redis обрабатывает команды очень быстро, но команда, которая обходит всё пространство ключей, всё равно должна выполнить работу.

Для повседневных операций думайте о двух отдельных шагах: безопасно найти ключи, затем безопасно их удалить. Не относитесь к проверке ключей как к безвредному чтению. Команда чтения всё ещё может быть дорогостоящей.

Начните с базы данных, которую вы действительно используете

Прежде чем что-либо удалять, подтвердите конечную точку и логическую базу данных.

redis-cli -h redis.example.internal -p 6379 INFO keyspace

Вывод выглядит так:

# Keyspace
db0:keys=154233,expires=129900,avg_ttl=2851412
db2:keys=32,expires=32,avg_ttl=60000

Это говорит вам, какие логические базы данных содержат ключи. Многие развертывания Redis используют только базу данных 0. Redis Cluster поддерживает только базу данных 0, поэтому FLUSHDB и FLUSHALL требуют особой осторожности, поскольку обычная ментальная модель "выбранной базы данных" здесь не так полезна.

Если ваше приложение использует нумерованные базы данных в автономном Redis, выберите правильную явно:

redis-cli -n 2 DBSIZE

DBSIZE возвращает количество ключей в выбранной базе данных. Он не показывает имена и не заменяет проверку, но это хорошая проверка работоспособности до и после очистки.

Используйте KEYS только когда пространство ключей мало и одноразово

KEYS pattern возвращает каждый ключ, соответствующий шаблону в стиле glob:

KEYS user:*
KEYS cache:product:???
KEYS *

Правила шаблонов удобны: * соответствует любой последовательности, ? соответствует одному символу, а диапазоны в скобках, такие как [0-9], соответствуют одному символу из диапазона.

Проблема не в корректности. Проблема в том, что KEYS сканирует всё пространство ключей одной командой. В локальном Redis для разработки с несколькими сотнями ключей это удобно. В общем кэше это может добавить задержку для всех остальных клиентов, пока Redis занят формированием результата. Команда документирована как команда пространства ключей с линейной сложностью, поэтому она не должна быть частью обычных сценариев очистки в продакшене.

Я всё ещё использую KEYS в двух местах:

  • Одноразовый локальный Redis при написании тестов.
  • Небольшая промежуточная база данных, где я уже проверил DBSIZE и знаю, что команда не может меня удивить.

Везде используйте SCAN.

Используйте SCAN для проверки в продакшене

SCAN основан на курсоре:

SCAN 0 MATCH user:* COUNT 100

Redis возвращает две вещи: следующий курсор и пакет ключей. Начните с курсора 0. Продолжайте сканирование с возвращенным курсором. Остановитесь, когда Redis снова вернет курсор 0.

1) "24576"
2) 1) "user:100"
   2) "user:101"

COUNT — это подсказка, а не обещание. Redis может вернуть больше ключей, меньше ключей или даже ни одного ключа за данную итерацию. MATCH фильтрует то, что возвращается, но Redis всё равно продвигается по пространству ключей. Узкий шаблон полезен для уменьшения работы на стороне клиента, но он не делает каждое сканирование бесплатным.

Для работы в оболочке предпочитайте redis-cli --scan, поскольку он скрывает цикл курсора:

redis-cli --scan --pattern 'session:*'

Чтобы подсчитать количество совпадающих ключей без их вывода:

redis-cli --scan --pattern 'session:*' | wc -l

Чтобы проверить типы перед удалением:

redis-cli --scan --pattern 'session:*' | head
redis-cli TYPE session:abc123
redis-cli TTL session:abc123
redis-cli MEMORY USAGE session:abc123

TTL особенно полезен для принятия решений об очистке. Если у пространства имен кэша уже есть разумные сроки действия, возможно, вам вообще не нужно массовое удаление. Позволить ключам истечь естественным образом обычно менее рискованно, чем принудительное массовое удаление в рабочее время.

Удаление известных ключей с помощью DEL

DEL удаляет один или несколько ключей и возвращает количество существовавших:

DEL session:abc123
DEL session:abc123 session:def456 session:ghi789

Для небольших ключей DEL обычно подходит. Удаление строкового ключа или небольшого хеша — это не страшный случай. Страшен случай, когда вы удаляете большое агрегированное значение, такое как список с огромным количеством элементов, набор, используемый в качестве индекса, или хеш, который вырос далеко за пределы своего первоначального назначения. Redis удаляет ключ из пространства ключей, но освобождение большого значения может занять время на основном пути.

Если вы удаляете один ключ, который, как вы знаете, мал, используйте DEL. Если вы удаляете много ключей или не уверены, насколько они велики, используйте UNLINK.

Предпочитайте UNLINK для массового удаления или удаления больших значений

UNLINK имеет ту же форму, что и DEL:

UNLINK session:abc123
UNLINK cache:old:1 cache:old:2 cache:old:3

Важное отличие — освобождение памяти. UNLINK немедленно удаляет ключи из пространства ключей, а затем асинхронно освобождает память. Это делает его более безопасным выбором по умолчанию, когда вы очищаете ключи, которые могут содержать большие значения.

Это не означает, что UNLINK — это магия. Вы всё ещё можете создать нагрузку, если ваш скрипт обнаружит и отключит миллионы ключей как можно быстрее. Redis всё равно должен обрабатывать команды, реплики всё равно должны получать изменения, и память всё равно должна быть освобождена. Регулируйте массовую очистку, чтобы вы могли наблюдать за задержкой и памятью во время её выполнения.

Практический цикл очистки выглядит так:

redis-cli --scan --pattern 'session:*' |
  xargs -r -L 100 redis-cli UNLINK

Это удаляет пакетами по 100 ключей. Настройте размер пакета для вашей среды. В тихом окне обслуживания вы можете использовать пакеты большего размера. В горячем кэше, используемом пользовательским трафиком, могут быть более щадящими пакеты меньшего размера с короткой паузой между ними:

redis-cli --scan --pattern 'session:*' |
while read -r key; do
  redis-cli UNLINK "$key" >/dev/null
  sleep 0.005
done

Эта версия медленнее, но её легко остановить и легко понять.

Будьте осторожны с удалением по шаблону

Redis намеренно не предоставляет DEL user:*. Вам нужно комбинировать сканирование и удаление самостоятельно. Это трение полезно, потому что именно здесь происходят несчастные случаи с удалением по шаблону.

Перед удалением:

redis-cli --scan --pattern 'user:*' | head -50
redis-cli --scan --pattern 'user:*' | wc -l

Посмотрите на первую выборку. Подсчитайте целевой размер. Если ваша ожидаемая очистка была "несколько тысяч заброшенных сессий", а количество составляет "большую часть базы данных", остановитесь и исправьте шаблон.

Используйте соглашения об именах, которые делают очистку скучной:

app:prod:session:<id>
app:prod:rate-limit:<user-id>
app:prod:cache:product:<id>

Это более многословно, чем session:<id>, но позволяет точно нацелиться на пространство имен. В Redis Cluster имена ключей также могут включать хэш-теги, такие как cart:{user123}:items, для размещения слотов. Учитывайте эти соглашения, прежде чем писать широкие шаблоны.

FLUSHDB и FLUSHALL — это кнопки сброса

FLUSHDB удаляет все ключи из выбранной базы данных:

FLUSHDB
FLUSHDB ASYNC

FLUSHALL удаляет все ключи из всех логических баз данных:

FLUSHALL
FLUSHALL ASYNC

Современный Redis поддерживает модификаторы ASYNC и SYNC для команд сброса. ASYNC планирует освобождение в фоновом режиме, что помогает избежать большой синхронной паузы освобождения памяти. Это не делает операцию обратимой. Как только ключи исчезли из пространства ключей, ваше приложение видит их как исчезнувшие.

Прежде чем использовать любую из этих команд, я хочу выполнить три проверки:

redis-cli ROLE
redis-cli INFO keyspace
redis-cli CONFIG GET dir

ROLE помогает подтвердить, подключены ли вы к основному или реплике. INFO keyspace показывает, что будет затронуто. CONFIG GET dir часто даёт ещё одну подсказку о том, на каком экземпляре вы находитесь, поскольку каталоги данных обычно содержат пути, специфичные для среды.

Для сценариев сброса разработки будьте явными:

redis-cli -h 127.0.0.1 -p 6379 -n 0 FLUSHDB ASYNC

Избегайте сценариев, которые запускают redis-cli FLUSHALL со значениями по умолчанию. Значения по умолчанию меняются, когда сценарий запускается на другом хосте, в другом контейнере или от исполнителя CI с другими переменными среды.

Проверка после удаления

После очистки проверьте как количество, так и поведение приложения:

redis-cli --scan --pattern 'session:*' | wc -l
redis-cli INFO memory
redis-cli INFO stats | grep expired_keys

Память может не упасть мгновенно после UNLINK или асинхронного сброса, потому что освобождение происходит в фоновом режиме, и аллокаторы могут резервировать память для повторного использования. Это не обязательно утечка. Следите за used_memory, задержкой и тем, движется ли количество ключей в ожидаемом направлении.

Для изменений в продакшене запишите точную команду и шаблон перед её выполнением. Безопасная очистка Redis — это не просто правильная команда. Это правильная команда, против правильного экземпляра, с шаблоном, который вы проверили, в окне, где вы можете наблюдать за результатом.

Более безопасный сценарий очистки в продакшене

Для реальной очистки я предпочитаю превратить команду в небольшой сценарий, а не в однострочник, набранный по памяти. Сценарий не должен быть сложным. Он должен отвечать на четыре вопроса: какой экземпляр, какой шаблон, сколько ключей и как быстро.

Начните с проверок только для чтения:

redis-cli -h redis.example.internal -p 6379 ROLE
redis-cli -h redis.example.internal -p 6379 INFO keyspace
redis-cli -h redis.example.internal -p 6379 --scan --pattern 'app:prod:session:*' | head -20
redis-cli -h redis.example.internal -p 6379 --scan --pattern 'app:prod:session:*' | wc -l

Затем проверьте несколько репрезентативных ключей:

redis-cli TYPE app:prod:session:sample
redis-cli TTL app:prod:session:sample
redis-cli MEMORY USAGE app:prod:session:sample

Если выборка показывает ключи, которые должны естественным образом истечь через несколько минут, подождите вместо удаления. Если у ключей нет TTL и они явно принадлежат заброшенным данным, приступайте к регулируемой очистке. Держите открытым терминал с задержкой или памятью:

redis-cli --latency
redis-cli INFO memory

Для общего продакшен-Redis я предпочитаю скрипт, который можно остановить без потери намерения:

redis-cli --scan --pattern 'app:prod:session:*' |
while read -r key; do
  redis-cli UNLINK "$key" >/dev/null
  sleep 0.002
done

Это не самая быстрая версия. Это версия, которая даёт вам шанс заметить, если задержка изменится, клиенты пожалуются или шаблон окажется шире, чем ожидалось. Когда экземпляр тихий, а количество скромное, пакетная обработка с xargs -L 100 подходит. Суть в том, чтобы сознательно выбирать темп.

Ещё одна полезная привычка: сохраняйте количество до и после в тикете инцидента или заметке о развертывании. "Удалены ключи сессий" — недостаточно. "Удалено 48 213 ключей, соответствующих app:prod:session:* из db0 на redis-cache-01 с помощью UNLINK, увеличения задержки не наблюдалось" — это та заметка, которая сэкономит время позже.