> Что происходит при запросе с использованием звездочки в Redis (Python)
Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы
Компании: Sunlight
Стек: Redis, Python
> Пример ответа
Короткий ответ
SCAN с паттерном * - это не запрос "всех ключей" в классическом смысле, а итеративный обход keyspace без блокировки сервера. В отличие от KEYS *, который блокирует Redis на время выполнения, SCAN возвращает порции ключей (по умолчанию ~10 элементов) и требует повторных вызовов с курсором. Звездочка здесь - glob-паттерн, означающий "любая последовательность символов", поэтому SCAN 0 MATCH * фактически возвращает все ключи, но порциями.
Подробное объяснение
SCAN - это команда для безопасного перебора ключей в production-среде. Её сигнатура: SCAN cursor [MATCH pattern] [COUNT count]. Курсор 0 означает начало итерации; в ответе сервер возвращает новый курсор и массив ключей. Когда курсор снова равен 0 - итерация завершена.
Паттерн * применяется на стороне сервера к каждому ключу в текущей bucket-порции. Важно: MATCH фильтрует уже выбранные элементы, а не управляет выбором bucket'ов. Поэтому при маленьком COUNT и большом keyspace можно получить несколько пустых итераций, пока не найдётся bucket с подходящими ключами.
Ключевые особенности:
- Гарантия: все ключи, существовавшие на момент начала итерации, будут возвращены хотя бы один раз (если не изменялись).
- Нет гарантии уникальности: ключ может вернуться повторно, если он был изменён или перемещён между bucket'ами.
- Итерация не атомарна: параллельные
SET/DELмогут повлиять на результат. - Сложность O(1) на каждый вызов, но общее число вызовов пропорционально количеству bucket'ов (по умолчанию 16384).
В Python-клиенте (redis-py) есть хелпер scan_iter(match='*'), который скрывает управление курсором и возвращает генератор.
На практике
Используйте SCAN * вместо KEYS * в любом коде, который работает с production-данными. KEYS * блокирует весь сервер на время сканирования - при миллионах ключей это секунды простоя. SCAN же отдаёт управление между вызовами, позволяя другим командам выполняться.
Практические рекомендации:
- Устанавливайте
COUNTразумно (100-1000) - слишком большое значение приближает поведение кKEYS. - Не полагайтесь на то, что
SCANвернёт все ключи за один вызов - всегда обрабатывайте курсор в цикле. - Для удаления по паттерну используйте
SCAN+DELпачками (например, через pipeline), чтобы не блокировать сервер. - В кластерном Redis
SCANработает только в рамках одного узла - для полного обхода нуженSCANна каждом master-узле.
Пример кода
PYTHONimport redisr = redis.Redis(host='localhost', port=6379, decode_responses=True)# Правильный способ: итеративный обходdef delete_by_pattern(pattern: str, batch_size: int = 100):cursor = 0while True:cursor, keys = r.scan(cursor=cursor, match=pattern, count=batch_size)if keys:r.delete(*keys)if cursor == 0:break# Или через scan_iter (redis-py)for key in r.scan_iter(match='user:*', count=500):r.delete(key)# Неправильно: блокирующий KEYS# keys = r.keys('user:*') # опасно для production
Как отвечать на собеседовании
Начните с противопоставления KEYS и SCAN: первый - блокирующий, второй - итеративный. Подчеркните, что * - это glob-паттерн, а не спецсимвол команды. Затем объясните механику курсора: почему нельзя получить все ключи одним вызовом и как работает фильтрация MATCH (пост-фильтрация bucket'ов). Упомяните trade-off: SCAN не даёт снапшота, поэтому возможны дубликаты и пропуски при параллельных изменениях.
Для senior-позиции добавьте детали: внутренняя структура keyspace (hash table с bucket'ами), поведение при рехешировании, особенности в кластере. Если спросят про производительность - скажите, что каждый вызов O(1), но общее количество вызовов зависит от числа bucket'ов и плотности данных.
Что проверяет интервьюер
- Понимание разницы между блокирующими и неблокирующими операциями в Redis.
- Знание внутреннего устройства keyspace (hash table, bucket'ы).
- Умение работать с курсором и обрабатывать неполные результаты.
- Осознание ограничений: отсутствие снапшота, возможные дубликаты, поведение в кластере.
- Практический опыт: использование
scan_iterв Python и удаление по паттерну без простоя.
Типичные ошибки
- Утверждение, что
SCAN *возвращает все ключи одним вызовом - это не так, нужен цикл. - Путаница между
MATCHи фильтрацией на клиенте:MATCHприменяется сервером, но к уже выбранным bucket'ам. - Игнорирование повторных ключей: если код предполагает уникальность, нужно использовать
SETдля дедупликации. - Использование
COUNTслишком большим (например, 10000) - это убивает смыслSCANи приближает к блокировке. - Забывают, что в кластере нужно сканировать каждый узел отдельно -
SCANне магически работает по всему кластеру. - Предложение использовать
KEYS *в production "просто для отладки" - это красный флаг для интервьюера.
> Похожие задачи по Python
Использовали ли инструменты для асинхронности в Django, например Celery и Redis
Как работает Redis и почему он быстрый
> Похожие задачи по backend
Как реализовать TTL кэш на Redis без использования таблиц
Работал ли ты с Cassandra, MongoDB, Redis, ElasticSearch, ClickHouse
Работали ли вы с JSON-полями или специфическими расширениями PostgreSQL
Какие особенности и проблемы возникают при работе с JSON в PostgreSQL
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью