Сценарий удаления подстановочных файлов Redis с использованием EVAL, SCAN и DEL возвращает "Команды записи, которые не допускаются после не детерминированных команд",

Таким образом, я нахожусь в поиске сценария lua, который использует SCAN для поиска ключей на основе шаблона и удаления их (атомарно). Сначала я подготовил следующий скрипт

local keys = {};
local done = false;
local cursor = "0"
repeat
    local result = redis.call("SCAN", cursor, "match", ARGV[1], "count", ARGV[2])
    cursor = result[1];
    keys = result[2];
    for i, key in ipairs(keys) do
        redis.call("DEL", key);
    end
    if cursor == "0" then
        done = true;
    end
until done
return true;

Что бы вертело бы следующие "Err: @user_script: 9: писать команды, которые не разрешены после не детерминированных команд". Поэтому я немного подумал об этом и придумал следующий скрипт:

local all_keys = {};
local keys = {};
local done = false;
local cursor = "0"
repeat
    local result = redis.call("SCAN", cursor, "match", ARGV[1], "count", ARGV[2])
    cursor = result[1];
    keys = result[2];
    for i, key in ipairs(keys) do
        all_keys[#all_keys+1] = key;
    end
    if cursor == "0" then
        done = true;
    end
until done
for i, key in ipairs(all_keys) do
    redis.call("DEL", key);
end
return true;

который по-прежнему возвращает ту же ошибку (@user_script: 17: команды записи не допускаются после не детерминированных команд). Это меня насторожило. Есть ли способ обойти эту проблему?

сценарий запускался с использованием phpredis и следующего

$args_arr = [
          0 => 'test*',   //pattern
          1 => 100,     //count for SCAN
  ];
  var_dump($redis->eval($script, $args_arr, 0));

Ответ 1

ОБНОВЛЕНИЕ: ниже применяется версия Redis до 3.2. Из этой версии репликация, основанная на эффекте, снимает запрет на детерминизм, поэтому все ставки не действуют (или, скорее, на).

Вы не можете (и не должны) смешивать команды SCAN с любой командой записи в скрипте, потому что прежний ответ зависит от внутренних структур данных Redis, которые, в свою очередь, уникальны для серверного процесса. Иными словами, два процесса Redis (например, master и slave) не гарантируют возврата тех же ответов (поэтому в контексте репликации Redis [который не является operation-, а основан на утверждении], который может сломать его).

Redis пытается защитить себя от таких случаев, блокируя любую команду записи (например, DEL), если она выполняется после случайной команды (например, SCAN а также TIME, SRANDMEMBER и т.п.). Я уверен, что есть способы обойти это, но хотите ли вы это сделать? Помните, что вы пойдете на неизвестную территорию, где поведение системы не определено.

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

Сначала спросите себя, можете ли вы снять какие-либо требования. Нужно ли быть атомарным? Атоматичность означает, что Redis будет заблокирован на время удаления (независимо от конечной реализации) и что длина операции зависит от размера задания (то есть количество удаленных ключей и их содержимого [удаление большого набора дороже, чем удаление короткой строки, например]).

Если атомарность не обязательна, периодически/лениво SCAN и удалять небольшими партиями. Если это необходимо, поймите, что вы в основном пытаетесь подражать команде злых KEYS :) Но вы можете сделать лучше, если у вас есть предварительное знание шаблона.

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

Тем не менее, самая "трудная" проблема заключается в том, что вам нужно запускать сопоставление ad-hoc, обеспечивая при этом атомарность. Если это так, проблема сводится к получению моментального снимка с фильтром по образцу в пространстве ключей, за которым следует последовательность удалений (повторное подчеркивание: при блокировке базы данных). В этом случае вы можете очень хорошо использовать KEYS в своем сценарии Lua и надеяться на лучшее... (но зная, что вы можете прибегнуть к SHUTDOWN NOSAVE довольно быстро: P).

Последняя оптимизация - индексирование самого пространства ключей. И SCAN и KEYS - это в основном полное сканирование таблицы, так что, если мы должны индексировать эту таблицу? Представьте, что вы храните указатель на имена ключей, которые могут быть запрошены во время транзакции, - вы, вероятно, можете использовать отсортированный набор и лексикографические диапазоны (HT @TwBert), чтобы избавиться от большинства требований к сопоставлению шаблонов. Но при значительных затратах... вы не только будете делать двойную бухгалтерскую книгу (сохраняя затраты на каждое имя ключа в ОЗУ и ЦП), вам придется добавить сложность в ваше приложение. Зачем добавлять сложность? Поскольку для реализации такого индекса вам придется поддерживать его самостоятельно на уровне приложения (и, возможно, во всех других сценариях Lua), тщательно обертывая каждую операцию записи Redis в транзакцию, которая также обновляет индекс.

Предполагая, что вы сделали все это (и учитывая очевидные ловушки, такие как добавленный потенциал сложности для ошибок, как минимум удвоенная нагрузка на запись на Redis, RAM и CPU, ограничения на масштабирование и т.д.), Вы можете погладить себя на плечо и поздравить себя за использование Redis таким образом, чтобы он не был предназначен. Хотя предстоящие версии Redis могут (или не могут) включать лучшие решения для этой задачи (@TwBert - хотите сделать совлокальный RCP/contrib и снова немного взломать Redis?), Прежде чем попробовать это, я действительно призываю вас переосмыслить исходные требования и убедитесь, что вы правильно используете Redis (т.е. проектируете свою "схему" в соответствии с вашими требованиями к доступу к данным).