В прошлый раз мы начали рассматривать механизм архивных объектов и рассмотрели его первую часть — поиск объектов, которые нужно перенести в архив. Мы обнаружили возможность ускорения поиска и уцепились за нее. В результате наш механизм научился отсеивать заведомо неархивируемые объекты, чтобы не тратить на них время. В то же время мы понимаем, что объектов, которые нужно архивировать, все равно может найтись предостаточно, и агент архивирования потратит вечность на их обработку. В этот раз предлагаю посмотреть, как можно помочь агенту архивирования справиться с большим объемом данных.
Логичным решением было бы оптимизировать код самого агента, исключив из него неиспользуемый функционал. Но если заглянуть внутрь агента, то становится понятно, что там все гладко и не особо-то сильно наоптимизируешь. Можно, конечно, убрать оттуда разные проверки и обработку вычисления из правила архивирования. Но, во-первых, существенного (и уж тем более кратного) подъема производительности на этом не получить, а во-вторых, сокращать функциональность механизма не входило в наши планы. Может, переписать агент на T-SQL? В машинный код??
А что если вместо одного агента будут работать несколько? Объем работы должен разделиться между агентами, и тогда общее время обработки пропорционально сократится. Но если мы запустим несколько агентов, то они, выполняя одни и те же поиски, непременно начнут хватать одни и те же объекты и наперегонки архивировать их — толкучка, блокировки, жуткие ошибки в логах и неизбежные жертвы.
Совсем не то, что мы хотим получить. При наличии N правил архивирования теоретически можно запустить N агентов, каждый со своим правилом. Уже вполне работоспособная схема, но хочется большего. Наглядный пример красивого разделения общего объема работ между исполнителями у нас перед глазами — служба Workflow с ее очередью задач и рабочими процессами. Клиенты ставят задачи в очередь, а рабочие процессы потихоньку растаскивают ее, обрабатывая каждый свою задачу. Что особенно вкусно: количество процессов можно увеличивать или уменьшать, подстраивая Workflow под свои нужды и возможности сервера. И каждый занят своим делом, никто никому не мешает.
Что нужно сделать, чтобы и агенты архивирования смогли работать совместно? Очевидно, нужна очередь, в которой объекты будут лежать и ждать своей архивной участи. Как ее наполнять, мы уже знаем: у нас есть правила архивирования с запросами поиска объектов. Нужен лишь сценарий, который поочередно выполнит их все и поместит их результаты в очередь — специальную таблицу в базе данных. Чтобы агент, взяв объект из очереди, понимал, что это (документ, запись какого-то справочника или задача), и не забыл выполнить вычисление из соответствующего правила архивирования, вместе со ссылкой на объект в очередь помещаем ссылку на само правило.
Хорошо, у нас есть одна очередь, и мы хотим запустить N=50 агентов архивирования. Как это сделать? Стандартный агент архивирования ничего не знает про очередь, и требуется его доработка. Но если в прошлый раз нам удалось обойтись незначительными правками в агенте, полностью сохранив обратную совместимость, то сейчас вряд ли стоит к этому стремиться, ведь решение работать с очередью принципиально изменит механизм. Поэтому лучше создать копию агента и доработать ее. Главный вопрос, на который предстоит ответить: как сделать так, чтобы агенты, обращаясь к очереди одновременно, не получали одни и те же объекты, провоцируя конфликты. Ответ на вопрос все там же в службе Workflow: нужно получать случайную запись и сразу удалять ее из очереди, чтобы другим не досталась. Ключевое отличие от работы с очередью задач заключается в размере самой очереди, которая в нашем случае легко может вырасти до сотен тысяч или миллионов записей. Поэтому получать случайную запись следует не из всей очереди а только из первых X записей. С другой стороны, X должно быть достаточно большим, чтобы вероятность конфликта при N работающих агентах не была слишком высокой. Подбирать X придется методом научного тыка. Скажем, X = 100*N выглядит вполне подходящим для начала. Таким образом, получение очередной записи из очереди может выглядеть следующим образом:
delete top (1)
ObjectQueue with (nowait)
output
deleted.RuleID,
deleted.ObjectID
where
ObjectID = (select top 1 toprecords.ObjectID
from (select top 5000
ObjectID
from ObjectQueue) [toprecords]
order by checksum(newid()))
ObjectQueue — имя таблицы с очередью, ObjectID и RuleID — поля с ИД объекта и правила, соответственно. Чуть подробнее остановлюсь на двух моментах. Первое: случайность выбранной записи обеспечивается за счет сортировки по checksum(newid()). Но сортировка, да еще по вычисляемому на лету значению — не самая дешевая операция. Именно по этой причине мы ограничиваем выборку первыми X записями, а не сортируем всю очередь. Второй момент — хинт nowait в операторе delete. Нужен он для того чтобы при попытке получения одним агентом записи, которую в этот момент удаляет из очереди другой агент, сервер без раздумий возвращал ошибку блокировки, и потерпевший неудачу агент мог сразу предпринять следующую попытку обращения к очереди. Это рано или поздно произойдет, и будет происходить все чаще, по мере сокращения очереди. В конце архивирования очередь сократится до таких величин, что вероятность конфликтов будет значительно выше вероятности удачной выборки. Без этого хинта агент, пытающийся удалить из очереди уже занятую запись, будет ожидать возможности заблокировать запись. Предсказуемо безуспешно, поэтому и ждать не стоит. В результате обращения к очереди агент получит либо пару ИД (объекта и правила), которые нужно обрабатывать, либо ошибку блокировки записи в очереди, и в этом случае нужно повторить обращение к очереди. Остальная логика агента остается без изменения: по ИД правила архивирования определить, что за объект необходимо архивировать и есть ли в правиле вычисление, которое необходимо выполнить перед архивированием.
В итоге мы получим возможность гибко увеличивать количество агентов, сокращая время архивирования. Процессор холодный? Удваиваем количество агентов! Можно и наоборот — уменьшать количество агентов прямо на ходу, если видим, что очередь почти пустая, и три агента справляются не хуже трехсот.
Авторизуйтесь, чтобы написать комментарий