Как мы храним 75 млн пользователей (пишем
неблокируемый сервер)
Бирюков ДенисКомпания Каванга
Самопиар :)• Рекламная сеть (много баннеров, много сайтов)
• При показе баннера мы используем таргетинги
– уникальные ограничения
– ретаргетинг
– таргетинг по соцдему
• Нужен сервер
– Помним кому, где и что показывали
– Знаем пол и возраст
– Решаем что именно показать
Что было?
• - Блокировки
• - Фрагментация
• - Низкий КПД по памяти (в 3,5 раза) удаление объектов раз в час
• + 5% CPU
• + Высокая скорость ответа
• + Сохранение на диск без доп. памяти
• + Быстрый запуск
Многопоточный сервер
Задача• 75 млн пользователей за 3 МЕС, в день
меняется 250 млн объектов их описывающих (~ 60 ГБ памяти)
• До 3000 запросов/сек на обновление данных и столько же на их выборку
• Время ответа критично (<= 200 микросек)• Сервис должен быть масштабируемым
Задача• Периодически нужно делать бекап базы
из памяти на диск– быстрый подъем при сбое– анализ данных по файлам без опроса
• Сервис должен быть легко расширяемым по функционалу
• сервис должен быть масштабируемым
Как можно хранить данные• Memcached?
– Насколько гибка логика удаления? +– Бэкап базы из памяти на диск? +– Как обновлять по UDP (различные
клиенты)? +/-– Кто реализует дополнительную логику
(ретаргетинг)? -
В каком виде хранить?• user1
– лог1– лог2
• userN– логM– логM+1
• user1– struct1– struct2
• userN– structM– structM+1
В каком виде хранить?• + гибкость• - много памяти• - есть доп.
обработка
• - гибкость• + меньше памяти• + нет доп.
обработки• нужна ↑ производительность
• требования меняются редко
• бонус: sizeof(struct1) = const
Доводы:
Резюмируем требования• Время ответа (<= 200 микросек)• Всего храним 67*2 млн юзеров, в день
меняется 250 млн объектов их описывающих (~ 67 ГБ памяти)
• До 3000 з/сек на обновление данных и столько же на их выборку
• Бекап базы из памяти на диск
• Однопоточный– нет блокировок
• Неблокируемый ВВ– poll, epoll, kqueue
• Постоянные соединения
• UDP и TCP клиенты
• Свои аллокаторы (new/delete)
• Свои определения 'самый старый' объект
• Простая логика сохранения (fork)
Архитектура
• Все сокеты неблок: – fcntl(sock, F_SETFL, O_NONBLOCK);
• Неблокируемый ВВ (мультиплексирование)– poll, epoll, kqueue
• Постоянные соединения
• UDP и TCP клиенты
Сетевой ВВ
Протокол• TCP, UDP:
– read(...) - заголовок– readv(...) - данные– Action(...) - обработка
• TCP:– writev(...) - заголовок,
данные
struct MSG_TAB { int32_t size; int32_t func;};struct MSG_DATA { MSG_TAB h; char data[h.size];};
ОбъектыUser: 128 bytesint64_t user_id;int64_t time_create;char mask[32];....
IndexHolder flights, banners;
IndexHolder places, auditory;
ParticularRestrictions:идентификаторотгрузкапоказкликсобытие...
Retarget:идентификатор....
Объекты, «устаревание»• User — есть массив памяти под объекты
– старый тот кто дольше всех не появлялся в сети (+ память на 2-х связный список)
• IndexHolder (ArrayList) — есть массив объектов
– + 2-х связный список — есть избыток
• ParticularRestrictions (Flight, Banner), Retarget (Place, Auditory) - циклический массив.
– самый старый по времени создания
Как храним user?• User хранятся std::map<u_int64_t, User*>
– map — не самый быстрый контейнер, но зато легко выделять память для него
– для убыстрения работы создадим 64 std::map
• Память под std::map выделятся блоками по мере необходимости. Есть небольшой массив — хранит ссылки на свободные участки памяти
Сохранение на диск• По специальной UDP датаграме делаем fork
• Дочерний процесс читает и записывает на диск 4 куска памяти: User, ParticularRestrictions, Retarget, IndexHolder. Целостность данных обеспеченна.
• Результаты (0,75+4+1)/(9,375) ~ 61% КПД (> в 1,6 раза):
– User — 2,125 Gb (1,5 Gb (main) + 0,625 Gb (add))
– IndexHolder — 1,5 Gb (1 GB (main) + 0,5 Gb (add))
– ParticularRestrictions — 4 Gb
– Retarget — 1 Gb
– std::map — 0,75 Gb
Емкость памяти• По прикидкам скорость просмотра: 1 ~ 1,5 баннера / 1 мес.
• Время жизни всех объектов ~ 1 мес.
• Потеря активного flight / banner, менее критична чем потеря профиля пользователя.
• Если потеряем старые auditory (place), то охват уменьшится, но качество повысится.
– User — 67 108 864; std::map — сколько нужно
– ParticularRestrictions — 134 216 960
– Retarget — 134 217 728
– IndexHolder — 268 435 456 > 134 216 960 + 134 217 728 !
Логика. Action().• switch(readheader.func) {
– case (SET_USER_EVT): юзер сделал хит
– сase (SET_USER_MASK): юзер изменил профиль
– сase (SET_USER_ADT): юзер добавлен в аудиторию
– сase (SET_RETARGETS): обновили ретаргетинги
– сase (SET_CAMPAIGN): обновили уникальность по РК
– сase (GET_USER_EXP): описание юзера, выдаем банер
– сase (GET_USER_EVT): описание юзера, проверяем клик
– сase (UU_CONTROL): служ: fork, fork_info, mem_info
Архитектура
uuserver1
uuserver2
uuserver3
uuserver4
uuserver1
uuserver2
uuserver3
uuserver4
front1
front2
Кол-во запросов в сек от Exp
Время ответа для Exp
Время ответа для Exp
Кол-во запросов в сек от Evt
Время ответа для Evt
Время ответа для Evt
Кол-во обновлений
Время для обновлений
Кол-во добавлений в аудит.
Время добавлений в аудит.
Резюме по боевой нагрузке• Кол-во TCP запросов: 37 тыс + 2,6 тыс ~ 39,6 тыс
з/мин
• Кол-во UDP пакетов: 38,3 тыс з/мин
• Кол-во аудиторных UDP пакетов: 4 тыс з/мин.
Итого на сервер сейчас в номинальном режиме:~ TCP 670 з/сек.~ UPD 680 з/сек.А каков предел по нагрузке?
0,5 млн 1 млн 1,5 млн 2 млн0
10
20
30
40
50
60
70
80
50 потоков100 поток180 потоков
Тестовая нагрузка AMD Opteron 2800.13-MHz 2*4, 16 Gb (memory)
Количество запросов
Вре
мя
тест
а, с
ек
Тестовая нагрузка
количество запросов в сек0
10000
20000
30000
40000
50000
60000
70000
80000
90000
50 потоков100 потоков180 потоков
Кол
иче
ство
зап
росо
в в
сек
Вопросы?