SCAN
SCAN
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
- Доступно с версии:
- 2.8.0
- Сложность по времени:
- O(1) для каждого вызова. O(N) для полного итерационного цикла, включая достаточное количество вызовов команды, чтобы курсор вернулся к значению 0. N — количество элементов в коллекции.
- Категории ACL:
-
@keyspace,@read,@slow,
Команда SCAN и тесно связанные с ней команды SSCAN, HSCAN и ZSCAN используются для поэтапной итерации по коллекции элементов.
-
SCANитерирует набор ключей в текущей выбранной базе данных Redis. -
SSCANитерирует элементы типа множеств. -
HSCANитерирует поля типа хэш и связанные с ними значения. -
ZSCANитерирует элементы типа упорядоченного множества и их связанные оценки.
Поскольку эти команды позволяют выполнять поэтапную итерацию, возвращая только небольшое количество элементов за вызов, они могут использоваться в производстве без недостатков команд, таких как KEYS или SMEMBERS, которые могут заблокировать сервер на длительное время (даже несколько секунд) при вызове на больших коллекциях ключей или элементов.
Однако, в то время как блокирующие команды, такие как SMEMBERS, могут предоставить все элементы, входящие в множество в данный момент, семейство команд SCAN предоставляет только ограниченные гарантии относительно возвращаемых элементов, так как коллекция, по которой мы поэтапно итерируем, может меняться во время этого процесса.
Обратите внимание, что SCAN, SSCAN, HSCAN и ZSCAN работают очень похоже, поэтому эта документация охватывает все четыре команды. Однако очевидное различие состоит в том, что в случае SSCAN, HSCAN и ZSCAN первым аргументом является имя ключа, содержащего значение множества, хэша или упорядоченного множества. Команда SCAN не требует имени ключа в качестве аргумента, так как она итерирует ключи в текущей базе данных, поэтому итерируемый объект — сама база данных.
Базовое использование SCAN
SCAN — это итератор на основе курсора. Это означает, что при каждом вызове команды сервер возвращает обновленный курсор, который пользователю необходимо использовать в качестве аргумента курсора при следующем вызове.
Итерация начинается, когда курсор установлен в 0, и завершается, когда сервер возвращает курсор со значением 0. Следующий пример иллюстрирует итерацию SCAN:
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
В примере выше первый вызов использует ноль в качестве курсора, чтобы начать итерацию. Второй вызов использует курсор, возвращённый предыдущим вызовом, в качестве первого элемента ответа, то есть 17.
Как видите, **значение возвращаемое SCAN** — это массив из двух значений: первое значение — новый курсор для использования в следующем вызове, второе значение — массив элементов.
Поскольку во втором вызове возвращённый курсор равен 0, сервер сигнализирует вызывающей стороне о завершении итерации и полном просмотре коллекции. Начало итерации со значением курсора 0 и вызов SCAN до тех пор, пока возвращаемый курсор снова не станет 0, называется **полной итерацией**.
Гарантии SCAN
Команда SCAN и другие команды из семейства SCAN способны предоставить пользователю набор гарантий, связанных с полными итерациями.
- Полная итерация всегда извлекает все элементы, которые были присутствовали в коллекции с начала до конца полной итерации. Это означает, что если данный элемент присутствует в коллекции при запуске итерации и остается там при завершении итерации, то в какой-то момент
SCANвернул его пользователю. - Полная итерация никогда не возвращает элемент, который НЕ был присутствовать в коллекции с начала до конца полной итерации. Таким образом, если элемент был удалён до начала итерации и никогда не добавлялся обратно в коллекцию на протяжении всей итерации,
SCANгарантирует, что этот элемент никогда не будет возвращён.
Однако, поскольку у SCAN очень мало связанного состояния (только курсор), есть следующие недостатки:
- Данный элемент может быть возвращён несколько раз. Приложение должно обрабатывать случай дублирования элементов, например, используя возвращённые элементы только для выполнения операций, которые безопасны при повторном применении.
- Элементы, которые не постоянно присутствовали в коллекции во время полной итерации, могут быть возвращены или нет: это не определено.
Количество элементов, возвращаемых при каждом вызове SCAN
Функции семейства SCAN не гарантируют, что количество элементов, возвращаемых за вызов, находится в определённом диапазоне. Команды также могут вернуть ноль элементов, и клиент не должен считать итерацию завершённой, пока возвращаемый курсор не станет нулём.
Однако количество возвращаемых элементов разумно, то есть в практическом плане SCAN может возвращать максимальное количество элементов порядка нескольких десятков при итерации по большой коллекции или возвращать все элементы коллекции за один вызов, если итерируемая коллекция достаточно мала, чтобы быть представлена как закодированная структура данных (это происходит для небольших множеств, хэшей и упорядоченных множеств).
Однако есть способ настроить порядок величины количества возвращаемых элементов за вызов с помощью опции **COUNT**.
Опция COUNT
Хотя SCAN не предоставляет гарантий относительно количества элементов, возвращаемых при каждой итерации, можно эмпирически настроить поведение SCAN с помощью опции **COUNT**. В принципе, с COUNT пользователь указывает количество работы, которое должно быть выполнено при каждом вызове для извлечения элементов из коллекции. Это **лишь подсказка** для реализации, но, как правило, именно это можно ожидать от реализации в большинстве случаев.
- Значение COUNT по умолчанию равно 10.
- При итерации по пространству ключей или множеству, хэшу или упорядоченному множеству, достаточно большому для представления хеш-таблицей, при условии, что опция **MATCH** не используется, сервер обычно возвращает count или немного больше элементов за вызов. Пожалуйста, ознакомьтесь с разделом почему SCAN может возвращать все элементы сразу позже в этом документе.
- При итерации по множествам, закодированным как intsets (маленькие множества, состоящие только из целых чисел), или хэшам и упорядоченным множествам, закодированным как ziplists (небольшие хэши и множества, состоящие из маленьких индивидуальных значений), обычно все элементы возвращаются в первом
SCANвызове независимо от значения COUNT.
Важно: **нет необходимости использовать одинаковое значение COUNT** для каждой итерации. Вызывающая сторона свободно может изменить значение count от одной итерации к другой по мере необходимости, при условии, что курсор, переданный в следующий вызов, является курсором, полученным в предыдущем вызове команды.
Опция MATCH
Возможна итерация только по элементам, соответствующим заданному шаблону в стиле glob, аналогично поведению команды KEYS, которая принимает шаблон в качестве единственного аргумента.
Для этого просто добавьте аргументы MATCH <pattern> в конец команды SCAN (это работает со всеми командами семейства SCAN).
Это пример итерации с использованием **MATCH**:
redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood (integer) 6 redis 127.0.0.1:6379> sscan myset 0 match f* 1) "0" 2) 1) "foo" 2) "feelsgood" 3) "foobar" redis 127.0.0.1:6379>
Важно отметить, что фильтр **MATCH** применяется после извлечения элементов из коллекции, непосредственно перед возвращением данных клиенту. Это означает, что если шаблон соответствует очень небольшому количеству элементов в коллекции, SCAN, скорее всего, не вернет элементов в большинстве итераций. Пример показан ниже:
redis 127.0.0.1:6379> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
redis 127.0.0.1:6379> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2) 1) "key:611"
2) "key:711"
3) "key:118"
4) "key:117"
5) "key:311"
6) "key:112"
7) "key:111"
8) "key:110"
9) "key:113"
10) "key:211"
11) "key:411"
12) "key:115"
13) "key:116"
14) "key:114"
15) "key:119"
16) "key:811"
17) "key:511"
18) "key:11"
redis 127.0.0.1:6379>
Как видите, большинство вызовов возвращали ноль элементов, но в последнем вызове использовалось значение COUNT 1000, чтобы заставить команду выполнить больше сканирования для этой итерации.
Опция TYPE
Вы можете использовать опцию TYPE чтобы попросить SCAN возвращать только объекты, соответствующие заданному type, что позволит вам итерироваться по базе данных в поисках ключей определённого типа. Опция **TYPE** доступна только для команды итерации по всей базе данных SCAN, а не для HSCAN или ZSCAN и т. д.
Аргумент type — это то же строковое имя, которое возвращает команда TYPE. Обратите внимание на особенность, где некоторые типы Redis, такие как GeoHashes, HyperLogLogs, Bitmaps и Bitfields, могут быть реализованы на сервере с использованием других типов Redis, таких как строка или zset, поэтому их невозможно отличить от других ключей того же типа с помощью SCAN. Например, ZSET и GEOHASH:
redis 127.0.0.1:6379> GEOADD geokey 0 0 value (integer) 1 redis 127.0.0.1:6379> ZADD zkey 1000 value (integer) 1 redis 127.0.0.1:6379> TYPE geokey zset redis 127.0.0.1:6379> TYPE zkey zset redis 127.0.0.1:6379> SCAN 0 TYPE zset 1) "0" 2) 1) "geokey" 2) "zkey"
Важно отметить, что фильтр **TYPE** также применяется после извлечения элементов из базы данных, поэтому опция не уменьшает объёма работы, которую должен выполнить сервер для завершения полной итерации, и для редких типов вы можете не получить элементов во многих итерациях.
Параллельные итерации
Возможно, что бесконечное количество клиентов может итерировать одну и ту же коллекцию одновременно, так как полное состояние итератора хранится в курсоре, который получается и возвращается клиенту при каждом вызове. Никакого состояния на стороне сервера не используется вообще.
Прерывание итераций в середине
Поскольку нет состояния на стороне сервера, но всё состояние захвачено курсором, вызывающая сторона свободна прервать итерацию на половине пути, не сигнализируя об этом серверу каким-либо образом. Бесконечное количество итераций может быть запущено и никогда не завершено без проблем.
Вызов SCAN с повреждённым курсором
Вызов SCAN с повреждённым, отрицательным, выходящим за пределы допустимого диапазона или иным образом недопустимым курсором приведёт к неопределённому поведению, но никогда к аварийному завершению. Неопределённым будет то, что гарантии относительно возвращаемых элементов больше не гарантированы реализацией SCAN.
Единственными допустимыми курсорами являются:
- Значение курсора 0 при запуске итерации.
- Курсор, возвращённый предыдущим вызовом SCAN для продолжения итерации.
Гарантия завершения
Алгоритм SCAN гарантированно завершается только в том случае, если размер итерируемого набора остается ограниченным заданным максимальным размером, в противном случае итерация набора, который всегда увеличивается, может привести к SCAN и никогда не завершить полную итерацию.
Это легко понять интуитивно: если набор увеличивается, то для посещения всех возможных элементов требуется все больше и больше работы, а возможность завершения итерации зависит от количества вызовов SCAN и значения его опции COUNT по сравнению со скоростью роста набора.
Почему SCAN может вернуть все элементы агрегированного типа данных в одном вызове?
В документации опции COUNT указывается, что иногда эта группа команд может возвращать все элементы множества, хэша или упорядоченного множества сразу в одном вызове, независимо от значения опции COUNT. Причина этого в том, что итератор, основанный на курсоре, может быть реализован и полезен только в том случае, если агрегированный тип данных, который мы сканируем, представлен в виде хеш-таблицы. Однако Redis использует оптимизацию памяти, где небольшие агрегированные типы данных, пока они не достигнут заданного количества элементов или максимального размера отдельных элементов, представлены с помощью компактного кодирования с единой областью памяти. В этом случае у SCAN нет значимого курсора для возврата, и он должен проитерировать всю структуру данных сразу, поэтому единственным разумным поведением является возвращение всего в одном вызове.
Однако, когда структуры данных становятся больше и переходят к использованию реальных хеш-таблиц, семейство команд SCAN вернется к обычному поведению. Обратите внимание, что это специальное поведение возвращения всех элементов справедливо только для небольших агрегатов, и не оказывает никакого влияния на сложность команды или задержку. Однако точные пределы для преобразования в реальные хеш-таблицы являются настраиваемыми пользователем, поэтому максимальное количество элементов, которые вы можете увидеть возвращенными в одном вызове, зависит от того, насколько большим может быть агрегированный тип данных, и при этом использовать упакованное представление.
Также обратите внимание, что это поведение специфично для SSCAN, HSCAN и ZSCAN. SCAN само по себе никогда не демонстрирует этого поведения, потому что пространство ключей всегда представлено хеш-таблицами.
Возвращаемое значение
SCAN, SSCAN, HSCAN и ZSCAN возвращают двухэлементный ответ в формате многострочного ответа, где первый элемент — строка, представляющая беззнаковое 64-битное число (курсор), а второй элемент — многострочный ответ с массивом элементов.
-
SCANмассив элементов — это список ключей. -
SSCANмассив элементов — это список членов множества. -
HSCANмассив элементов содержит два элемента, поле и значение, для каждого возвращенного элемента хэша. -
ZSCANмассив элементов содержит два элемента, член и связанное с ним значение, для каждого возвращенного элемента упорядоченного множества.
Дополнительные примеры
Итерация значения хэша.
redis 127.0.0.1:6379> hmset hash name Jack age 33 OK redis 127.0.0.1:6379> hscan hash 0 1) "0" 2) 1) "name" 2) "Jack" 3) "age" 4) "33"
История
- Начиная с версии Redis 6.0.0: добавлена подкоманда
TYPE.
© 2006–2022 Salvatore Sanfilippo
Licensed under the Creative Commons Attribution-ShareAlike License 4.0.
https://redis.io/commands/scan/