Социальный граф Одноклассников в Target Mail.Ru

Post on 20-Jul-2015

369 Views

Category:

Software

1 Downloads

Preview:

Click to see full reader

Transcript

Социальный граф «Одноклассников» в myTarget

Олег Царёв, ведущий разработчик myTarget

myTarget

•  Платформа таргетированной рекламы •  Более 70 таргетингов (пол, возраст, регион, …, установленные приложения, поисковые запросы)

•  «Одноклассники», «Вконтакте», «Мой Мир», …

•  Огромные нагрузки

О чём этот доклад

Социальный граф «Одноклассников»

+ myTarget

О чём этот доклад

•  Социальный граф «Одноклассников» + myTarget

•  Можно ли из этого получить деньги? •  Каким именно образом? •  Какие технические проблемы придётся решить?

Пара слов о докладчике

•  Ведущий разработчик проекта •  Отвечает на вопросы «Как?», «Почему?»,

«Сколько?», «Где?» •  Оптимизирует узкие места •  Обучает коллег, руководит коллегами •  Пилил три разных СУБД (в том числе MySQL) •  Подслушивает в курилке умных людей •  Имеет вредный характер

Постановка задачи

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

Результат

Результат

Результат

Это очень простая задача

#!/usr/bin/env python friends = {1: [2,3], 2: [1], … } games = {1: [game1], 2: [game2], …} wp = defaultdict(defaultdict([])) ok_id = 1 for friend_id in friends[ok_id]: for game in games[friend_id]: wp[ok_id][game].append(friend_id)

Технические требования

•  Десятки* тысяч запросов в секунду •  Максимальное время ответа – единицы миллисекунд

•  Чем меньше требуется ресурсов – тем лучше

Дружба: общая информация

•  Социальный граф •  Вершины: 200 миллионов пользователей •  Рёбра: 13 миллиардов связей •  Граф импортируется в виде лога обновлений •  По логу обновлений можно получить состояние графа в любой момент времени

•  Граф используется для других задач •  Размер лога за всё время: 1.2 терабайт •  Ежедневные обновления: 380 мегабайт

Дружба: как устроены события

События со следующими атрибутами: •  event_id: монотонно возрастает •  event_type:

•  A – связь добавилась •  D – связь удалена

•  ok_id_1: кто дружит •  ok_id_2: с кем дружит •  timestamp: когда связь поменялась •  Есть другие атрибуты (исключил из рассмотрения)

Дружба: актуальный статус связи

Как узнать: дружат два пользователя или нет? 1.  Найти все события (ok_id_1, ok_id_2) 2.  Отсортировать по timestamp 3.  Состояние связи: event_type последнего – состояние связи

•  Может поступить timestamp из прошлого (!) •  События могут дублироваться (!) •  Для обновления графа нужно хранить последнее (по timestamp) обновление и удалённые связи

Дружба: различные представления графа

•  Обновляемое представление: •  живые связи: 360 гигабайт •  удалённые связи: 173 гигабайта •  всего: 533 гигабайта

•  Необновляемое представление: •  Список рёбер: 200 гигабайт •  Списки смежных вершин: 90 гигабайт

Игры: общая информация

•  Исходные данные – лог посещения страниц с игрой пользователями:

(ok_id, game, timestamp) •  Лог за два месяца: 1.5 терабайта •  Если агрегировать: 20 гигабайт

Ключевые вопросы

•  Как хранить граф? •  Как обновлять граф? •  Как хранить игры пользователей? •  Как обновлять игры пользователей? •  Как выбирать игры друзей? •  Как уложиться в таймауты? •  Как выдержать необходимую нагрузку?

Ключевые вопросы

•  Группа вопросов №1 •  Как хранить граф? •  Как обновлять граф? •  Как хранить игры пользователей? •  Как обновлять игры пользователей?

•  Группа вопросов №2 •  Как выбирать игры друзей? •  Как уложиться в таймауты? •  Как выдержать необходимую нагрузку?

Ключевые вопросы

•  Хранение (вычисления) •  Как хранить граф? •  Как обновлять граф? •  Как хранить игры пользователей? •  Как обновлять игры пользователей?

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

Возможные решения

•  Графовые базы данных •  Реляционные СУБД

•  MySQL: join + group by •  PostgreSQL: CTE / WITH … RECURSIVE BY

•  NoSQL: Tarantool •  Собственное решение •  HDFS / Hadoop

Графовые базы данных

•  http://www.highload.ru/2014/abstracts/1611.html •  Чудовищно медленный импорт данных •  Поиск соседей вершины – больше секунды. •  Ни у кого в команде нет опыта эксплуатации и использования.

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

•  Условно годится (?) для хранение •  Не годится для доставка

Реляционные СУБД: PostgreSQL

•  http://www.slideshare.net/quipo/rdbms-in-the-social-networks-age

•  Шикарно! Хочу попробовать. •  Нет опыта эксплуатации PostgreSQL L •  Не рискнул ввязываться. •  Расчёт - всего один запрос. •  Идеально для хранение (вычисления) •  Едва ли подойдет для доставка •  Нужно тестировать

Реляционные СУБД: MySQL (хранение)

•  Если коротко: даже не пытайтесь •  >500 миллионов ключей: падение скорости записи примерно в 20-25 раз

•  За две недели так и не смогли выгрузить •  200 миллионов ключей, 65 партиций... •  Больше 20 партиций => дохнет •  Для хранение не годится вообще L

Реляционные СУБД: MySQL (доставка)

MySQL и HandlerSocket на 1/128 части графа •  Все данные в памяти •  6 CPU (+6 HT) – 100% использование •  MySQL 30 krps •  HandlerSocket 120krps •  MySQL: 99/95/90 frac: 100/41/19 мc •  HandlerSocket: 99/95/90 frac: 100/41/19 мc •  Каши не сваришь L •  Для доставка не годится

Собственное решение

•  Начинающие программисты любят долго писать Очень Универсальные Решения, которые используются ровно в одном проекте ровно до тех пор, пока программист не уволится из проекта (а потом Очень Универсальное Решение с облегчением выпилят).

•  У меня профдеформация: писал OLAP, OLTP и NoSQL СУБД.

•  И оценил срок разработки прототипа в 4 человеко-месяца минимум.

HDFS & Hadoop

•  Широко используется в myTarget •  Группа Data Mining делает умопомрачительные штуки

•  http://www.highload.ru/2014/abstracts/1596.html •  Все данные уже там •  Прямой расчёт данных – почти сутки L •  Я смог это обойти (но про это позже) •  Идеально для хранения (вычисления) •  Не годится вообще для доставка

Tarantool

•  Широко используется в myTarget •  Persistent, In Memory, Low Latency •  http://tarantool.org/ •  Бенчмарк: 1 ядро, 100% загрузка •  120 krps •  99/95/90 frac: 6/3/2 мc •  Если пошардить – ещё меньше •  Идеален для доставка •  Хранение - 533 гигабайта RAM?...

Очевидное решение

•  Считаем граф на hadoop (~90 гигабайт) •  Считаем игры на hadoop (~20 гигабайта) •  Заливаем в tarantool 1.  вытаскиваем друзей пользователя:

•  ok_id è [friend_id] 2.  вытаскиваем игры друзей

•  friend_idè [game] 3.  Вытаскиваем имя и фамилию друга

•  friend_idè {first name, surname}

Анализ и оценка

•  У пользователей несколько* сот друзей в среднем

•  Шаг 1: одно хождение в тарантулы •  Шаг 2: несколько* сотен запросов, одно хождение

•  Шаг 3: единицы запросов, одно хождение •  X * 10 KRPS * 100-500 друзей = в лучшем случае 1..5X миллионов RPS (!)

Проблемы

•  3 хождения (3-4 мс) – слишком долго! •  Десятки* миллионов запросов – слишком много!

•  Не хватит сети •  Не хватит CPU •  Нужно искать другой путь L

Менее очевидное решение

•  Считаем граф на hadoop (~90 Гб) •  Считаем игры на hadoop (~20 Гб) •  Считаем на hadoop игры друзей (~ 400 Гб) •  Заливаем в tarantool 1.  вытаскиваем игры друзей пользователя:

•  ok_id è [{game, [friend_id]}] 2.  вытаскиваем имя и фамилию друга

•  friend_id è {first name, surname}

Анализ и оценка

•  Два хождения – жить можно (1-2 мс) •  400 Гб RAM – многовато L •  Сетевой траффик большой (сеть лагала) •  Но уже работало почти как хотелось •  Почему бы не усечь данные?

Усечение данных – попытка 1

•  «Давайте игнорировать дружелюбных (>1000 друзей) пользователей»

•  Проблема: Иван Ургант дружит с >1200 пользователей

•  «Ургант играет в эту игру? Я тоже хочу» •  На самом деле Иван Ургант не играет в игры на Одноклассники (это просто контпример)

•  И всё равно ничего не усекается L

Усечение данных – попытка 2

•  «Давайте хранить лишь для активных пользователей»

•  Пока проверял – нашёл баг у ОК и два у нас •  Недельная аудитория – десятки* миллионов пользователей

•  Их друзья – почти все пользователи «Одноклассников» è нужно обрабатывать полный граф

•  Так тоже не получится L

Усечение данных – попытка 3

•  «Давайте хранить информацию для рекламируемых игр»

•  Всё равно если нет рекламы è нет показов è не нужна информация

•  Количество игр уменьшилось почти в 20 раз •  И ещё один небольшой трюк, про него позже •  Итоговые данные занимают 70 гигабайт •  … •  Un tequila, por favor!

Выводы

•  Хороший фильтр данных экономит немало ресурсов

•  Искать ответ нужно не в программировании, а в предметной области

•  Данный фильтр уменьшил объём данных J •  Ясно как представлять данные для доставка •  Осталось разобраться с вычислениями

Вычисления: эксперименты

•  Полный граф считался почти 20 часов •  После кучи оптимизаций – в районе 12 часов •  Игры сначала 8 часов, потом 1 час •  Игры друзей – почти 16 часов •  Hadoop постоянно умирал •  Вокруг моего стола висело облако мата и проклятий

•  Решение нашлось, но не сразу •  Решение: инкрементальные вычисления

Концепция поколений

•  Как рассчитывать инкрементально граф? •  Давайте каждый цикл расчёта маркировать

«поколением» •  Считаем лишь новые данные •  Результаты – новое поколение •  Разница между поколениями 1..X и X+1 – дельта

•  По дельте обновляем tarantool •  Sounds like a plan!

Данные в tarantool

message WhoPlay.Game { // название игры

optional string game = 1; // число играющих друзей

optional uint32 friends_count = 2;

// ok_id некоторых играющих друзей repeated uint64 friend_id_list = 3;

} message WhoPlay.Data {

// информация по играм repeated Game games = 1;

}

Обновление данных в tarantool

message WhoPlay.Update { // ok_id пользователя

optional uint64 user_id = 1; // список игр, для которых больше нет who play

repeated string deleted = 2; // обновления / добавление информации по играм

repeated Game games = 3; }

Друзья

Расчёт: ok_id => { GENERATION: X, FRIENDS: [friend_id]}

Дельта: ok_id => { ADDED: [friend_id], DELETED: [friend_id],

KEEP: [friend_id]}

Игры

Расчёт: ok_id => { GENERATION: X, GAMES: [game]}

Дельта: ok_id => { ADDED: [game], DELETED: [game],

KEEP: [game]}

Соединение

ok_id => { ADDED: {[friend_id], [game]} DELETED: {[friend_id], [game]}

KEEP: {[friend_id], [game]}}

Инверсия связей

friend_id => { game: {ADDED: [ok_id],

DELETED: [ok_id], KEEP: [ok_id]}

}

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

Результаты: хранение (вычисление)

•  Порядка 600 гигабайт HDFS •  Порядка 8 часов цикл расчёта •  Порядка 2 часов заливка в tarantool •  Пересчитываем раз в сутки •  Высокая волатильность данных: 20% •  Работает стабильно •  4.5 KLOC python

Результаты: доставка

•  <80 GB RAM •  <1 ms response time •  десятки тысяч запросов в секунду

Результаты: бизнес

•  Живёт на двух серверах •  Повышает CTR •  В confluence осталось куча документов от research

•  Получен бесценный опыт •  Зарабатывает деньги •  Этот доклад

Выводы

•  Начинайте с аналитики •  Делайте бенчмарки •  Сверяйтесь с техническими требованиями

•  Сверяйтесь с бизнес-требованиями •  Не пытайтесь найти серебряную пулю •  Не пытайтесь изобретать велосипед

Вопросы?

Личный: oleg@oleg.sh Рабочий: o.tsarev@corp.mail.ru Facebook: http://facebook.com/oleg.i.tsarev Проект: http://mytarget.my.com/

top related