Как обновить отсортированный набор другим отсортированным набором в Redis? - PullRequest
1 голос
/ 12 февраля 2020

Я новичок в Redis, и теперь мне нужно обновить отсортированный набор, если ключ существует в другом отсортированном наборе.

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

set_1
{key_1:val_1, key_2:val_2, key_3:val_3}

set_2
{key_1:val_new_1, key_3:val_new_3, key_4:val_new_4}

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

set_1
{key_1:val_new_1, key_2:val_2, key_3:val_new_3}

Я уже некоторое время читаю документацию Redis, и кажется, что использование команды SET с опцией XX может помочь:

Команда SET поддерживает набор параметров, которые изменяют его поведение: XX - Устанавливайте ключ только в том случае, если он уже существует.

Но возможно ли избежать этого при каждой записи в первом сете? Может быть, использовать что-то вроде zunionstore?

1 Ответ

0 голосов
/ 12 февраля 2020

Команда SET работает только для обычных ключей, но не для отсортированных наборов.

В отсортированных наборах у вас есть пары очков-членов, поэтому номенклатура пар ключ-значение в вашем примере немного сбивает с толку. Я предполагаю, key_1, key_2, key_3, ... являются членами, а val_1, val_2, ... являются баллами.

Давайте создадим отсортированные наборы следующим образом, чтобы рассмотреть решение:

> ZADD set_1 1 key_1 2 key_2 3 key_3
(integer) 3
> ZADD set_2 1001 key_1 1003 key_3 1004 key_4
(integer) 3

По умолчанию AGGREGATE равно SUM, это то, что мы будем использовать повсюду.

Мы создадим два отсортированных набора с пересечением обоих: один со счетами set_1 и один со счетами set_2.

> ZINTERSTORE intersect_set_1 2 set_1 set_2 WEIGHTS 1 0
(integer) 2
> ZINTERSTORE intersect_set_2 2 set_1 set_2 WEIGHTS 0 1
(integer) 2

Теперь мы создаем набор промежуточных шагов для set_1, где мы устанавливаем нулевое значение для тех, кто находится в set_2:

> ZUNIONSTORE pre_set_1 2 set_1 intersect_set_1 WEIGHTS 1 -1
(integer) 3

Теперь мы готовы обновить set_1, объединяя:

  • pre_set_1: все set_1, но с теми, которые также в set_2 установлены на ноль баллов.
  • intersect_set_2: пересечение set_1 и set_2, со счетами set_2.

Вот последняя команда:

> ZUNIONSTORE set_1 2 pre_set_1 intersect_set_2
(integer) 3

Давайте посмотрим на результат:

> ZRANGE set_1 0 -1 WITHSCORES
1) "key_2"
2) "2"
3) "key_1"
4) "1001"
5) "key_3"
6) "1003"

Не забудьте очистить:

> UNLINK pre_set_1 intersect_set_1 intersect_set_2

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

Оптимальным решением будет Lua script :

local set2 = redis.call('ZRANGE', KEYS[1], '0', '-1', 'WITHSCORES')
local set2length = table.getn(set2)
for i=1,set2length,2 do redis.call('ZADD', KEYS[2], 'XX', set2[i+1], set2[i]) end
return set2length/2

Это перебирает set_2, обновляя set_1. Обратите внимание на использование XX в команде ZADD, чтобы только обновить, если оно существует.

Использовать как:

EVAL "local set2 = redis.call('ZRANGE', KEYS[1], '0', '-1', 'WITHSCORES') \n local set2length = table.getn(set2) \n for i=1,set2length,2 do print(1) redis.call('ZADD', KEYS[2], 'XX', set2[i+1], set2[i]) end \n return set2length/2" 2 set_2 set_1

Сценарий Lua это атом c из-за однопоточной природы Redis.

...