Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как мы храним 75 млн пользователей (Денис Бирюков)
1. Как мы храним 75 млн
пользователей (пишем
неблокируемый сервер)
Бирюков Денис
Компания Каванга
2. Самопиар :)
• Рекламная сеть (много баннеров, много сайтов)
• При показе баннера мы используем таргетинги
– уникальные ограничения
– ретаргетинг
– таргетинг по соцдему
• Нужен сервер
– Помним кому, где и что показывали
– Знаем пол и возраст
– Решаем что именно показать
3. Что было?
Многопоточный сервер
• - Блокировки • + 5% CPU
• - Фрагментация • + Высокая скорость
• - Низкий КПД по ответа
памяти (в 3,5 раза) • + Сохранение на диск
удаление объектов без доп. памяти
раз в час • + Быстрый запуск
4. Задача
• 75 млн пользователей за 3 МЕС, в день
меняется 250 млн объектов их
описывающих (~ 60 ГБ памяти)
• До 3000 запросов/сек на обновление
данных и столько же на их выборку
• Время ответа критично (<= 200 микросек)
• Сервис должен быть масштабируемым
5. Задача
• Периодически нужно делать бекап базы
из памяти на диск
– быстрый подъем при сбое
– анализ данных по файлам без опроса
• Сервис должен быть легко расширяемым
по функционалу
• сервис должен быть масштабируемым
6. Как можно хранить данные
• Memcached?
– Насколько гибка логика удаления? +
– Бэкап базы из памяти на диск? +
– Как обновлять по UDP (различные
клиенты)? +/-
– Кто реализует дополнительную логику
(ретаргетинг)? -
8. В каком виде хранить?
• + гибкость • - гибкость
• - много памяти • + меньше памяти
• - есть доп. • + нет доп.
обработка обработки
• нужна ↑ производительность
Доводы: • требования меняются редко
• бонус: sizeof(struct1) = const
9. Резюмируем требования
• Время ответа (<= 200 микросек)
• Всего храним 67*2 млн юзеров, в день
меняется 250 млн объектов их
описывающих (~ 67 ГБ памяти)
• До 3000 з/сек на обновление данных и
столько же на их выборку
• Бекап базы из памяти на диск
10. Архитектура
• Однопоточный • Свои аллокаторы
– нет блокировок (new/delete)
• Неблокируемый ВВ • Свои определения
'самый старый'
– poll, epoll, kqueue
объект
• Постоянные
• Простая логика
соединения
сохранения (fork)
• UDP и TCP клиенты
11. Сетевой ВВ
• Все сокеты неблок:
– fcntl(sock, F_SETFL, O_NONBLOCK);
• Неблокируемый ВВ (мультиплексирование)
– poll, epoll, kqueue
• Постоянные соединения
• UDP и TCP клиенты
14. Объекты, «устаревание»
• User — есть массив памяти под объекты
– старый тот кто дольше всех не появлялся в
сети (+ память на 2-х связный список)
• IndexHolder (ArrayList) — есть массив объектов
– + 2-х связный список — есть избыток
• ParticularRestrictions (Flight, Banner), Retarget
(Place, Auditory) - циклический массив.
– самый старый по времени создания
15. Как храним user?
• User хранятся std::map<u_int64_t, User*>
– map — не самый быстрый контейнер, но зато
легко выделять память для него
– для убыстрения работы создадим 64 std::map
• Память под std::map выделятся блоками по мере
необходимости. Есть небольшой массив — хранит
ссылки на свободные участки памяти
16. Сохранение на диск
• По специальной 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
17. Емкость памяти
• По прикидкам скорость просмотра: 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 !
18. Логика. 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
30. Резюме по боевой нагрузке
• Кол-во TCP запросов: 37 тыс + 2,6 тыс ~ 39,6 тыс
з/мин
• Кол-во UDP пакетов: 38,3 тыс з/мин
• Кол-во аудиторных UDP пакетов: 4 тыс з/мин.
Итого на сервер сейчас в номинальном режиме:
~ TCP 670 з/сек.
~ UPD 680 з/сек.
А каков предел по нагрузке?
31. Тестовая нагрузка
AMD Opteron 2800.13-MHz 2*4, 16 Gb (memory)
80
70
Время теста, сек
60
50
40 50 потоков
30
100 поток
180 потоков
20
10
0
0,5 млн 1 млн 1,5 млн 2 млн
Количество запросов
32. Тестовая нагрузка
90000
Количество запросов в сек
80000
70000
60000
50000
40000 50 потоков
100 потоков
30000 180 потоков
20000
10000
0
количество запросов в сек