Ротация данных в многопоточной среде
У меня достаточно долго длилась головоломка на тему, как в многопоточной среде ротировать данные и счётчики. На днях я реализовал одну из своих идей, которой и хочу поделиться.
В одной из систем, которую я развивал, ротация производилась простым способом — начитывались все актуальные данные из базы, данные ротировались через мьютексы, старые удалялись. В такой реализации мне решительно не нравилось то, что при ротации повторно начитывались неизменённые данные. Плюс мьютексы — это зло, их нужно избегать.
Таким образом, у меня было два требования к своей версии ротации: обновлять только изменённые данные, никаких блокировок.
Ротация происходит в несколько шагов. Первый шаг — начитываем изменённые данные. Для этого во всех начитываемых таблицах есть поле modified, которое является timestamp-ом последнего обновления данных. Зная время последнего обновления данных, мы всегда можем выбрать только нужное (WHERE modified > last_rotation_timestamp). В результате в новом плане у нас присутсвуют все новые данные.
На втором шаге мы копируем из старого плана все данные, которые остались неизмененными. На этом же шаге удаляются из плана данные, которые уже не актуальны (завершённые кампании, удалённые сайты и т.п.). В результате мы имеем новый план.
На третьем шаге мы рассылаем каждому из процессов новый план, ждём пока все процессы не обновлят их и преходим к четвёртому шагу.
На четвёртом шаге мы сбрасываем счётчики из старого плана и удаляем его.
Вчера протестировал на своём ноуте как быстро это работает. Получилось, что ротация данных порядка 130.000 кампаний, заняла 8 секунд при обновлении всех кампаниий и 2 секунды в случае, когда новых данных небыло. Считаю это неплохим результатом.
Далее несколько приёмчиков которые я использовал.
Во-первых, я пока отказался от использования пулов памяти потому, что данные часто добавляются/удаляются, а моя реализация пула не позволяет утилизировать освобождённые ресурсы. Позже обязательно займусь разработкой нового пула, потому что основное время из тех 2-х секунд, как я считаю, занимает выделение памяти.
Во-вторых, а храню счётчики отдельно от основных данных, что позволяет легко “прикреплять” счётчики из старых данных к новым без необходимости как-то их синхронизировать, копировать и т.п.
У каждого рабочего процесса есть связь с основным процессом через socketpair по технологии, описанной мною ранее. В такой среде уведомления о новых данных происходит легко: каждому рабочему процессу посылается пакет с ссылкой на новые данные, рабочие процессы подменяют старые данные на новые, увеличивают некоторый счётчик на едниницу и отсылают ответ основному процессу о том что данные обновились. Основной процесс получает ответы от рабочих процессов, и, когда счётчик принимает значение равное количеству рабочих процессов, считает что все процессы обновили данные и переходит к четвёртому шагу ротации данных.
Вот какой я молодец ![]()
Tags: данные, крутилка, оптимизация, программирование


