Александр Шарак, "Одноклассники"

30
Подсчет уникальных комбинаций на примере статистики групп в Одноклассниках Александр Шарак Руководитель отдела статистики Одноклассников

description

HighLoad++ 2013

Transcript of Александр Шарак, "Одноклассники"

Page 1: Александр Шарак, "Одноклассники"

Подсчет уникальных комбинаций на примере

статистики групп в Одноклассниках

Александр Шарак

Руководитель

отдела статистики

Одноклассников

Page 2: Александр Шарак, "Одноклассники"

Задача

• Необходимо посчитать «уники» для отображения на графиках в разделе «Статистика» для администраторов групп

Page 3: Александр Шарак, "Одноклассники"

Задача • Надо посчитать, сколько у нас уникальных комбинаций такого

типа: • группа, пол, возраст

• группа, тип активности

• Каждый тип комбинаций надо посчитать за разные периоды: • час

• 6 часов

• сутки

• текущие 24 часа

• последние 7 дней

• календарный месяц

• последние 365 дней

Page 4: Александр Шарак, "Одноклассники"

Типичные ошибки подсчетов • Данные не должны дублироваться:

• меняется возраст

• меняется место жительства

• меняется пол

• Ну и конечно нельзя просто суммировать данные по дням, чтобы получить данные за неделю.

Page 5: Александр Шарак, "Одноклассники"

Выбор платформы • Статистика должна быть точной.

• Подсчёты «задним числом» другим алгоритмом.

• Подсчёты должны быть быстрыми, не обязательно в realtime: • Статистика за текущие 24 часа должна быть готова раз в час (для начала).

• Статистика за закрытые периоды, например 1 астрономический час, должна быть готова за 15 минут.

• Статистика за последние 365 дней должна быть готова «утром».

• Должна быть возможность запросить новые данные по всем группам или все данные за любую дату по отдельно взятой группе.

• Подсчёты должны быть экономными.

• Разработка системы должна быть быстрой.

• Данные должны быть доступны для внутреннего анализа.

Page 6: Александр Шарак, "Одноклассники"

Наш выбор

• Учитывая: • требования

• доступные человеческие ресурсы и их компетенцию

• Выбрали: • MS SQL 2012 Always On availability group

• Некоторые сомнения у нас были, особенно про подсчет статистики за последние 365 дней.

Page 7: Александр Шарак, "Одноклассники"

В SQL всё просто! • Select groupid, country, count(distinct userid)

from table where timestamp between @datefrom and @dateto group by groupid, country

Page 8: Александр Шарак, "Одноклассники"

Однако объёмы не маленькие

• Более 400 000 активных групп в день.

• Более 1 000 000 активных групп в год.

• Более 800 000 000 действий в день.

• Для подсчета «уников» за последние 365 дней «тупым подходом» необходимо обработать более 200 000 000 000 записей каждый день.

Page 9: Александр Шарак, "Одноклассники"

Как SQL считает «уники» • Select groupid, country, gender, count(distinct userid)

from table where timestamp between @datefrom and @dateto group by groupid, country, gender

• Алгоритм:

• Считываются данные по кластерному индексу.

• Данные делятся на столько частей, сколько доступно ядер.

• Каждый поток создает хеш-таблицу: (hash key, (groupid, country, gender, user), count).

• Каждый поток из предыдущей хеш-таблицы создает новую: (hash key, (groupid, county, gender), count).

• Хеш-таблицы объединяются и выдается результат.

• Хеш-таблица для первоначального агрегирования может получиться огромной и в памяти не уложится.

Page 10: Александр Шарак, "Одноклассники"

Как SQL считает «уники» при не достаточной памяти • Подсчеты делятся на более мелкие части и промежуточные результаты (хеш -таблицы

скидываются в temp db на диск).

• SQL-сервер делает следующие итерации: • считываются данные по кластерному индексу;

• данные делятся на столько частей, сколько доступно ядер;

• каждый поток создает хеш-таблицу;

• если памяти не хватает, то хеш-таблица скидывается на диск и берётся следующая пачка данных;

• потом хеш-таблицы считываются с диска и объединяются;

• если опять не хватает памяти, то опять всё скидывается на диск;

• и так до тех пор, пока не получается результат.

• «Со стороны» это выглядит так: • сначала наблюдаются бешеные процессы чтения и высокая нагрузка на CPU;

• процессы чтения прекращаются, но нагрузка на CPU все еще высокая;

• нагрузка на CPU падает и начинается интенсивная запись на диск;

• эти три шага повторяются много раз.

• В результате: • подсчеты очень медленные;

• IO-система так нагружена, что параллельные процессы «проседают».

Page 11: Александр Шарак, "Одноклассники"

Упрощаем задачу для SQL • Необходимо, чтобы селекты укладывались в память

• Самое простое - делим все группы на несколько частей и считаем «уники» для каждой части отдельно.

• Данные надо хранить с кластеризацией по ID групп, что вызовет проблемы при загрузке – сплошные вставки и фрагментация. Исторические данные нельзя было бы удалить.

• Если данные кластеризовать по дате, то для каждой части придется делать full scan за весь период

• Делается много лишних операций, описанных ниже

• Для «уников» за большие периоды надо использовать результаты подсчетов за меньшие периоды, посчитанные ранее, например:

• для подсчета часа использовать минутные «уники»;

• для подсчета месяца использовать суточные «уники».

Page 12: Александр Шарак, "Одноклассники"

Доставка логов

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

• Поставили «буферную» базу для принятия логов и передачи дальше системе статистики

Буфер Система

групп Система

статистики

Page 13: Александр Шарак, "Одноклассники"

Выкачка логов из «буфера» • С большой периодичностью в одном потоке выкачиваем данные

из «буфера».

• Обработку строковых типов не делаем, так как это крайне неэффективно.

• Преобразование строк в цифровые значение (нормализация данных) тоже, соответственно, не делаем.

• Получаем только целые числа (ID сущностей) и даты.

COLUMN_NAME DATA_TYPE

Registered smalldatetime

ID_Group bigint

ID_User bigint

ActionType tinyint

VisitType tinyint

MemberType tinyint

Page 14: Александр Шарак, "Одноклассники"

Выкачка логов из «буфера» • Сделали кластерный индекс по времени события с партициями

по дням.

• Такой индекс нам позволяет быстро записать и прочитать данные с постепенно возрастающим временем события.

• Время сброса данных растет пропорционально росту активности в группах, но не пропорционально росту размера всей таблицы.

• Однако данные за один квартал мы храним в отдельной базе: • чтобы бэкапы происходили «быстро»;

• чтобы «старые» данные можно было эффективно убрать в архив.

Page 15: Александр Шарак, "Одноклассники"

Как передать результаты обратно на сайт • Full dump:

• легко реализовать;

• годится только для небольшого объёма данных.

• По changetime:

• трудно отследить удаления (например, когда в группе всего один пользователь и он стал старше);

• если изменилась одна запись в группе, то надо пометить все связанные;

• постоянный апдейт колонки changetime и деградация индекса.

• Лента изменений:

• в ленту идут только инсерты;

• удаления отслеживать не надо;

• сайт получает полный комплект измененных статистик, которым можно заменить старый комплект без дополнительно обработки.

Page 16: Александр Шарак, "Одноклассники"

Топология

• MS SQL Server

• 80 GB RAM

• 2 CPU (6 ядер) (мощные)

• Temp db – SAS-диски

• Данные – массив из SATA-дисков

Буфер Система

групп MS SQL Primary

MS SQL Secondary

(mirror)

Буфер резерв

Page 17: Александр Шарак, "Одноклассники"

Big Fail • Сначала всё, как обычно, работало достаточно быстро.

• Но со временем система начала все больше тормозить:

• группы стали популярнее;

• расчеты стали немного сложнее;

• данных за большие периоды накапливалось все больше (особенно для подсчета последних 365 дней);

• подсчеты «уников» за большие периоды так начали нагружать IO, что начали проседать параллельные процессы загрузки данных и подсчеты маленьких периодов.

• Поэтому мы решили, что MS SQL secondary (mirror), который доступен в режиме read only, должен заняться полезным делом:

• Сделали так, чтобы сервис забирал результаты не с primary, а с secondary сервера.

• В результате:

• нагрузка на IO систему сильно снизилась.

Page 18: Александр Шарак, "Одноклассники"

Big Fail

• И всё равно надо было предпринимать шаги по масштабированию, так как мы понимали, что долго так не протянем.

Page 19: Александр Шарак, "Одноклассники"

О масштабируемости • Масштабируется MS SQL AlwaysOn HA group легко:

• можно добавить в кластер secondary сервера для подсчетов.

• Но это дорого стоит.

• Мы начали думать: можем ли мы посчитать «уники» за большие периоды эффективней, чем MS SQL?

• Тогда мы бы могли в MS SQL оставить только то, что хорошо и быстро работает:

• хранилище для исходных данных;

• хранилище для результатов;

• подсчеты «уников» за маленькие периоды – до часа.

Page 20: Александр Шарак, "Одноклассники"

Эффективный алгоритм • Мы придумали быстрый алгоритм, как при помощи Merge Sorta файлов

посчитать «уники», нагружая IO и CPU по максимуму, при этом делая минимум лишних операций и используя минимум памяти.

• Далее рассмотрим частный случай, когда мы из семи дневных результатов высчитываем недельные «уники».

Page 21: Александр Шарак, "Одноклассники"

Выгрузка данных в файлы • Из базы данных в память выкачиваем данные за последний закрытый период

(например, день), отсортированные по (groupid, userid).

• Записываем эти данные в 100 файлов, распределяя по groupid mod 100, чтобы все записи одной группы попали только в один файл. Это необходимо для распараллеливания.

• Формат файлов – бинарный, чтобы мы могли четко знать. где именно начинается и заканчивать одна запись.

• Мы пробовали использовать json и csv форматы но сразу от них отказались, потому что на парсинг этих файлов уходила львиная доля мощностей CPU.

• Исходные данные в таком формате:

• За один день – 3 Gb

• За 7 дней – 21 Gb

• За 365 дней – 1.1 Tb

Page 22: Александр Шарак, "Одноклассники"

день#1

день#2

день#3

день#4

день#5

день#6

день#7

каждый файл отдельно уже отсортирован по ID_Group, ID_User

(1x вначале) сортируем список открытых файлов по ID_Group, ID_User первой считанной записи (дальше в цикле) отдаем дальше запись с наименьшим ID_Group, ID_User

берем следующую запись из того файла, чья запись «ушла» перемещаем файл в списке файлов на место, соответствующее следующей считанной записи

Отсортированный список всех данных по ID_Group, ID_User

ID_Group ID_User ActionType

G1 U1 7

G1 U1 7

G1 U2 5

G2 U2

G2 U3

Накопленные данные за предыдущую группу можно считать готовыми, когда начинаются данные про следующую группу

данные про поведение конкретного пользователя в рамках конкретной групы

Page 23: Александр Шарак, "Одноклассники"

Распараллеливаем • Данный алгоритм работает только в одном потоке, то есть, на одном ядре.

• Поэтому мы и создавали не 7 файлов, а 100 комплектов по 7 файлов, распределяя по groupid mod 100.

• И запустили подсчеты в столько потоков, сколько было ядер на сервере – 24.

• Каждый поток обрабатывал отдельный комплект файлов.

• После завершения подсчетов все результаты записываем (bulk insert) в MS SQL для передачи далее в «прод».

• А выгруженные файлы не удаляем, а оставляем для подсчетов следующих периодов, но не дольше 365 дней.

• «Уники» за 7 дней высчитываем за 3 минуты.

Page 24: Александр Шарак, "Одноклассники"

Топология

• Два .NET сервера для «уников» - для HA. Оба делают одно и то же.

Буфер Система

групп MS SQL Primary

MS SQL Secondary

(mirror)

Буфер резерв .NET

«уники» .NET

«уники»

Page 25: Александр Шарак, "Одноклассники"

Нагрузка на CPU

• CPU не нагружен на 100% потому, что есть дисковая очередь.

Page 26: Александр Шарак, "Одноклассники"

Ускоряем ещё больше • Днем, когда сервер отдыхает, можем высчитывать агрегат за 364 дня. Потом

после полуночи надо будет сделать Merge Sort всего двум файлам. Ускорим в 5 раз.

• Для подсчетов «уников» за последние 364 дня можно использовать комбинацию дневных и месячных агрегатов. Ускорим на 25%.

• Для подсчетов можем использовать оба .NET сервера (первый сначала подсчитывает чётные группы, второй – нечётные). Ускорим в два раза.

Page 27: Александр Шарак, "Одноклассники"

Tips and Tricks • Для подсчета «уников» за 365 дней нам надо открыть много файлов – 365x24

(число ядер). Необходимо под каждый FileHandle выделять такой размер буфера, чтобы он не превышал некую MagicConstant (~1Gb). Иначе Windows Server начинает делать swap памяти.

• Файлы, в которых храним числовую (IDs) информацию в бинарном виде, не стоит пытаться зиповать.

• Если шедулируете подсчеты, используя Windows Task Scheduler, то по умолчанию у тасков стоит низкий приоритет на ресурсы. Через UI его повысить нельзя – надо экспортировать XML дефиницию таска, повысить в нем соответствующую настройку и импортировать обратно.

Page 28: Александр Шарак, "Одноклассники"

Итоги • Мы вынесли подсчеты из MS SQL и используем его только как хранилище. К тому

же используем не оптимально, так как нам надо, чтобы данные можно было бы хранить с кластеризацией по (groupid, userid).

• То есть использование MS SQL не обосновано. Вместо него мы будем использовать или Open Source базы, или переделаем всё на обработку файлов в .NET.

• .NET и Windows Server 2008 R2 показал себя как хорошая платформа для обработки данных и файлов

• быстрая и стабильная

• с хорошим файл-кешем

• удобный язык программирования для аппликации параллельной обработки информации.

Page 29: Александр Шарак, "Одноклассники"

Итоги • «Уники» за 365 дней мы можем посчитать за 4 часа на одном сервере, а

потенциально можем посчитать менее чем за час.

• Это в десятки раз быстрее, чем может MS SQL.

• Подсчеты за меньшие периоды занимают несущественное время.

• Количество серверов, необходимых для подсчетов – 2 (+2 для HA).

• Количество трудозатрат – 3 человеко-месяца.

• А если делать сразу всё правильно – 1 человеко-месяц.

Page 30: Александр Шарак, "Одноклассники"

Спасибо за внимание!

Александр Шарак

Руководитель отдела статистики

Одноклассники

[email protected]