SlideShare a Scribd company logo
1 of 82
Как мы заставили Druid
работать в Одноклассниках
Юрий Невиницин
ЗАЧЕМ?
Статистика
Менеджеры
Разработчики
Администраторы
• Цели
• Аномалии
• Мониторинг
• Эксперименты
• Запуски
• Без неё никак
Немного цифр
• 350 таблиц
• 50TB
• 12 млрд событий в сутки
• Отставание на полчаса
• 6 секунд
• Цена в миллионы долларов
Постановка задачи
Постановка задачи
• Быстро
• 24x7x365 ( -1 ЦОД )
• Масштабируемо
• Open-source (Java)
Druid
• Быстро
• 24x7x365 ( -1 ЦОД )
• Масштабируемо
• Open-source (Java)
• Предагрегация
• Timeseries, TopN, GroupBy
Альтернативы
• PostgreSQL
• Influx
• Prometheus
• OpenTSDB
• ClickHouse
• Все довольны
• Сэкономили миллионы долларов
• 1 человек
Druid
Событий/сек
Кластер 500 000
Нода 50 000
Таблица 10 000
Druid
Событий/сек
Кластер 500 000 2 300 000
Нода 50 000 275 000
Таблица 10 000 90 000
Druid
MAX
Внешние компоненты
• Storage (Amazon, HDFS, local)
• Metadata DB (MySQL, Postgres, Derby)
• ZooKeeper
• Cache (memcache, local)
Собственные компоненты
• Realtime
• Historical
• Broker
• Coordinator
• Indexing service
DATA
Realtime Segment
StorageMeta
Coordinator Historical
Historical
QUERY
Broker
subquery1 subquery2 subquery3 subquery4
Historical HistoricalRealtime
Отказ MySQL
• Данные копятся в Realtime-нодах
• Пока не кончатся ресурсы
= предсказуемый запас времени
Отказ MySQL
Storage, Coordinator
• Данные копятся в Realtime-нодах
• Пока не кончатся ресурсы
= предсказуемый запас времени
• Cassandra-based DB
• Свежие данные всегда доступны
Отказ ZooKeeper
• Маленький таймаут
• Много данных
• Лавина трафика
• «Нет данных»
Что делать?
Отказ ZooKeeper
• Удалить данные ZK
• Иметь запас памяти в ZK
• Корректно завершать Historical
• Стартовать Historical с паузой
• Убрать ненужные чтения
• Убрать ненужные данные
Что делать?
Загрузка данных
Realtime
• sss
MMAP MMAP
Искажение данных
Persisted
Data
Source
Position
Загрузка в Realtime
Persisted
Data
Source
Position
Потеря
Дубль
Деградация чтения
Загрузка данных
Time Calls Host
10:45 123 web1
10:45 132 web2
10:45 345 api1
10:45 354 api2
10:50 120 web1
10:50 128 web2
10:50 342 api1
10:50 333 api2
Time Calls Host
10:45 123 web1
10:45 132 web2
10:45 345 api1
10:45 354 api2
10:50 120 web1
10:50 128 web2
10:50 342 api1
10:50 333 api2
long[] long[]
api1 api2 web1 web2
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
int[]
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
1
1
0
0
1
1
0
0
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
1
1
0
0
1
1
0
0
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
1
1
0
0
1
1
0
0
bitmap1
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
bitmap1
bitmap2
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
bitmap1
bitmap2
bitmap3
bitmap4
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
1
1
0
0
1
1
0
0
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
1
1
0
0
1
1
0
0
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
SUM(calls) host = web%, mob%, music%, video%
1
1
0
0
1
1
0
0
5 95
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
Загрузка данных
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
Time Calls Host
10:50 123 2
10:50 132 3
10:50 345 0
10:50 354 1
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
Time Calls Host
10:55 123 2
… … …
api1 api2 web1 web2
0 0 1 0
… … … …
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
Time Calls Host
10:50 123 2
10:50 132 3
10:50 345 0
10:50 354 1
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
Time Calls Host
10:55 123 2
… … …
api1 api2 web1 web2
0 0 1 0
… … … …
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
Time Calls Host
10:50 123 2
10:50 132 3
10:50 345 0
10:50 354 1
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
Time Calls Host
10:55 123 2
… … …
api1 api2 web1 web2
0 0 1 0
… … … …
Деградация чтения
Загрузка данных
• Размер сегмента
• Частота сброса на диск
• Использовать Selector
like.photo.main
like.photo.main.favorites
like.photo.main.favorites.widget
like.photo.main.favorites.widget.banner125
like.photo.main
like.photo.album
like.photo.group
like.video.main
like.video.album
like.video.group
like.music.main
like.music.album
like.music.group
share.photo.main
share.photo.album
share.photo.group
share.video.main
share.video.album
share.video.group
share.music.main
share.music.album
share.music.group
comment.photo.main
comment.photo.album
comment.photo.group
comment.video.main
comment.video.album
comment.video.group
comment.music.main
comment.music.album
comment.music.group
SUM(calls) Event = like.%
like.photo.main
like.photo.album
like.photo.group
like.video.main
like.video.album
like.video.group
like.music.main
like.music.album
like.music.group
share.photo.main
share.photo.album
share.photo.group
share.video.main
share.video.album
share.video.group
share.music.main
share.music.album
share.music.group
comment.photo.main
comment.photo.album
comment.photo.group
comment.video.main
comment.video.album
comment.video.group
comment.music.main
comment.music.album
comment.music.group
SUM(calls) Event = like.%
Action
Like
Share
Comment
Object
Photo
Video
Music
Place
Main
Album
Group
SUM(calls) Action = like
Action
Like
Share
Comment
Object
Photo
Video
Music
Place
Main
Album
Group
SUM(calls) Action = like
Time Calls Host
10:45 123 2
10:45 132 3
10:45 345 0
10:45 354 1
10:50 120 2
10:50 128 3
10:50 342 0
10:50 333 1
long[] long[]
api1 api2 web1 web2
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
0 1 0 0
int[]
1
1
0
0
1
1
0
0
5 95
SUM(calls) Action = like
5 11
100 = +
16 = +
Истинно тяжелый запрос
• 2TB
• 74 секунды
• Приоритеты
• Надо выставлять в запросе
• Работают на уровне очереди
• Отказ MySQL предсказуем
• Для ZooKeeper: запас памяти, корректно
завершать Historical, а стартовать с паузой
• Realtime не гарантирует exactly-once
• Подбор размера сегмента и частоты сброса на
диск
• Использовать Selector
• Разбивать большое измерение на мелкие
• Приоритеты на уровне очереди
Юрий Невиницин
nevinitsin@corp.mail.ru
Юрий Невиницин
nevinitsin@corp.mail.ru
Юрий Невиницин
nevinitsin@corp.mail.ru
Юрий Невиницин
nevinitsin@corp.mail.ru
Деградация чтения
Загрузка данных
t
N rows
Historical
Realtime N parts = 1
Деградация чтения
Загрузка данных
t
N rows
Historical
Realtime N parts = 2
Деградация чтения
Загрузка данных
t
N rows
Historical
Realtime N parts = 3
Деградация чтения
Загрузка данных
t
N rows
Historical
Realtime N parts = 30
1 | Область названия раздела
Данные: название источника данных, 2016
74
Слайд с текстом
Подзаголовок
• Далтон Трамбо, один из самых успешных голливудских сценаристов, автор
«Римских каникул» и «Спартака», не подозревал, что черный список
«Hollywood 10» реально существует, пока сам не попал туда и не был навсегда
выкинут из жизни фабрики грез;
• Премьера «Трамбо» состоялась в программе «специальный показ» на
кинофестивале в Торонто в сентябре 2015 года. Выход картины в широкий
прокат состоялся 6 ноября 2015 года.
Данные: название источника данных, 2016
75
Слайд с цифрой
63
Подпись
в две строчки
%
Данные: название источника данных, 2016
76
Слайд с двумя цифрами
63
Подпись
в две строчки
%
27
Подпись
в две строчки
млн
Данные: название источника данных, 2016
77
Изображение с комментарием
Стиль изображений
Зайдите в Quick Styles.
Выберите стиль с тенью
Данные: название источника данных, 2016
78
Вертикальный скриншот Android с комментарием
Скриншот на экране
мобильного телефона
на платформе Android
Вставьте свой скриншот в черное
поле мобильного устройства
Данные: название источника данных, 2016
79
Вертикальный скриншот iOS с комментарием
Скриншот на экране
мобильного телефона
на платформе iOs
Вставьте свой скриншот в черное
поле мобильного устройства
Данные: название источника данных, 2016
80
Горизонтальный скриншот iOS
Данные: название источника данных, 2016
81
Горизонтальный скриншот Android
Данные: название источника данных, 2016
82
Скриншот на экране ноутбука
Скриншот
на экране ноутбука
Вставьте свой скриншот
в черное поле ноутбука
Данные: название источника данных, 2016
83
Таблица
Размещения CRM (руб.) Значение
Промо-баннер (ТГБ под аватаркой) 10000 10000 показов
Промо-посты c охватом на свою группу 10000 23000 показов
Оповещения для вступления в группу 10000 8000 показов
Услуги
Промо-баннер (ТГБ под аватаркой) 10000 16000 показов
Промо-посты c охватом на свою группу 10000 28000 показов
Оповещения для вступления в группу 10000 14000 показов
Промо-баннер (ТГБ под аватаркой) 10000 23000 показов
Промо-посты c охватом на свою группу 10000 1 пост
Оповещения для вступления в группу 10000 23000 показов
Промо-баннер (ТГБ под аватаркой) 10000 Бонус
84
Контакты и полезная информация
Поддержка
партнеров
partners@ok.ru
Отдел
продаж
sales@corp.mail.ru
Блог ОК с информацией
о запусках, событиях и др.
insideok.ru
Лучшие кейсы на базе ОК
за последние годы
awards.insideok.ru
Продуктовые
обновления
ok.ru/gruppa
Официальная
группа ОК
ok.ru/ok
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)

More Related Content

More from Ontico

Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Ontico
 

More from Ontico (20)

One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...
One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...
One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...
 
Масштабируя DNS / Артем Гавриченков (Qrator Labs)
Масштабируя DNS / Артем Гавриченков (Qrator Labs)Масштабируя DNS / Артем Гавриченков (Qrator Labs)
Масштабируя DNS / Артем Гавриченков (Qrator Labs)
 
Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)
Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)
Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)
 
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
 
Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...
Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...
Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...
 
PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)
PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)
PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)
 
Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...
Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...
Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...
 
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
 
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
 
MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)
 
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
 
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
 
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
 
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
 
100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...
 
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
 
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
 
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
 
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
 
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
 

Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)

Editor's Notes

  1. Всем привет! Меня зовут Невиницин Юрий, я из Одноклассников, и занимаюсь системой внутренней статистики. Кому нравится мысль о поддержке системы реалтайм аналитики на 50Тб, построеной на MS SQL Server, и в которую логируются миллиарды событий в сутки? Я расскажу о нашем кейсе миграции такой системы на колоночную базу под названием DRUID, а вы узнаете несколько рецептов его использования.
  2. Зачем нам система статистики? Она нужна потому что мы хотим знать всё, о своем сайте. Поэтому мы не только мониторим поведение железа и трафика, нагрузку ЦПУ и на диски, а логируем каждое действие пользователя, все взаимодействия между компонентами сайта, и множество внутрених процессов этих компонентов. Система статистики тесно интегрирована в процесс разработки сайта. Менеджеры оценивают эффективность сайта, устанавливают или корректируют цели, отслеживают как они достигнуты. Администраторы и разработчики следят за работой всех систем, расследуют аномалии. Автоматический мониторинг на ранней стадии выявляют неполадки в работе сайта; а также строят прогнозы по превышению различных лимитов. Кроме этого постоянно идут апдейты, эксперименты, запуск фич, и эффект от всех этих действий отслеживается через систему статистики.
  3. Статистика у нас отображается в основном в виде графиков. Графики у нас бы Обычно мы выводим графики сразу за 5 дней, чтобы сразу визуально понятно, сейчас идет всё как обычно, или хуже, или лучше.
  4. Этот же график можно разложить по параметру.
  5. Второй виз графиков - долгосрочный. Здесь мы смотрим месячные и годовые тренды. Графики у нас не статичны. Мы можем изменить параметры и сразу увидеть результат. Можно выбрать любую дату за последние 2 года, можно задать любые фильтры и группировку. После того, как график настроен, его можно сохранить в дашборд, чтобы в одном месте смотреть графики по какой-то теме.
  6. И это удобно, поэтому редко кто смотрит отдельные графики, чаще открывают сразу дашборд, даже если надо посмотреть всего пару графиков из сотни, что конечно только увеличивает нагрузку на систему статистики. Хочу отдельно отметить, что пользователи не приходят в отдел статистики «создайте мне график с такими-то фильтрами и группировками». Они все графики и дашборды создают сами с произвольными фильтрами и группировками. Нет никаких предагрегатов, всё обсчитывается на лету. Вроде не сложная система. Но это пока данных относительно мало. MS SQL Server справлялся, но по мере роста объема данных, система замедлялась. Замедлялась загрузка данных в неё и увеличивалось время построения графика.
  7. Мы доросли до такого объема, что загрузка некоторых таблиц в час пик отставала на полчаса. А среднее время отдачи одного графика было 6 секунд. То есть кто-то получал график за 2-3 секунды, а кто-то за 20-30 секунд, а кто-то и за минуты. Когда расследуешь аномалию, надо рассмотреть десяток графиков, которые следуют один из другого, их нельзя открыть одновременно, и приходилось 10 раз ждать по 30 секунд. Это бесит. Дико бесит. Менеджеры показывают свои ключевые графики каждую неделю, в том числе и годовые, им приходилось открывать дашборды за 20 минут до митинга. Можно было выжимать еще производительность, добавить серверов и как-то в ручную разносить данные, но майкрософт в то же время изменил политику лицензирования. И продолжи мы использовать SQL, нам пришлось бы отдать миллионы долларов за лицензии. Поэтому мы решили сразу сделать как надо.
  8. А именно, чтобы график открывался за 2 сек, дашборд за 10, статистика не отставала, была всегда доступна и переживала потерю датацентра, масштабируема, с открытым кодом, чтобы затачивать под себя, и желательно на java.
  9. И друид именно это нам и обещал. Это колоночная и распределенная база. Также там есть предагрегация и индескация во время вставки, и те типы запросов которые нам нужны. Получалось, что мы легко сможем заменить SQL server на друид.
  10. Разумеется, мы рассматривали и другие варианты PostgreSQL - не масштабируемо и не высокодоступно influx, vertica - дорого и закрыто (как следствие, высокая доступность под вопросом) prometheus - не масштабируемо и не высокодоступно opentsdb - нет индексов и производительнось под вопросом clickhouse - тогда еще не было
  11. Мы внедрили друид, смигрировали в него исторические данные из MSSQL, и всё у нас залетало. Просмотры графиков увеличились в 5 раз, буквально в день переключения на друид. Стали запускать более тяжелую статистику, которую боялись запускать на старой системе. Сэкономили миллионы долларов И наш друид сопровождает 1 человек.
  12. В час пик наш кластер из 12 машин получает полмиллиона событий в секунду. И у нас есть довольно большой запас прочности.
  13. Это не тесты, это получено на наших реальных данных. всё это здорово и красиво. но что же было на самом деле?
  14. Для начала немного про сам друид. У друида есть несколько внешних зависимостей. Ему нужен сторадж, там друид хранит только хранит данные. Также ему нужна база для метаданных. Там друид хранит сведения о том, где что лежит в сторадже. zk. нужен для дискавери и для объявления, какая нода какие данные обслуживает. и кеш, это или мемкеш или встроеный.
  15. realtime ноды загружают свежие данные и обрабатывают запросы по ним. historical - эти ноды держат всю массу данных и обрабатывают запросы по ним. данные здесь не меняются. broker - отвечает за распределение вычислений между historical и realtime нодами, и за кеширование. Coordinator - распределяет сами данные по historical нодам, на основании метаданных в mysql.
  16. Друид – это распределенная колоночная база данных. Вот наши данные, более-менее упорядоченные по времени. Реалтайм нода берет эти данные, индескирует и нарезает на сегменты по времени, например, по суткам или по часам. В друиде сегмент является минимальной единицей данных. Все операции всегда происходят по-сегментно. Каждый новый сегмент пишется в сторадж и уже никогда не меняется, при этом реалтайм нода по прежнему хранит свою копию этого сегмента. После этого записываются метаданные, что этот сегмент находится в сторадже по такому-то адресу. Координатор периодически перечитывает метадату, и когда находит новый сегмент, через зукипер приказывает некоторым историческим нодам взять этот сегмент, чтобы обрабатывать запросы по нему. исторические ноды обслуживают всю массу данных, кроме самых свежих. Когда мы говорим что у нас кластер на 300ТБ, это это как раз про них. Исторические ноды скачивают сегмент, и через зукипер сообщают об этом всем. Когда реалтайм нода получает это сообщение, она удаляет свою копию сегмента, чтобы освободить ресурсы для новых данных. Брокер тоже получает эти сообщения из зукипера, и узнаёт на каких нодах какой сегмент лежит. И когда приходит запрос, он разделяет его по сегментам и отправляет по нужным нодам. Потом он дождется всех ответов, доагрегирует и отправит клиенту.
  17. Друид – это распределенная колоночная база данных. Вот наши данные, более-менее упорядоченные по времени. Реалтайм нода берет эти данные, индескирует и нарезает на сегменты по времени, например, по суткам или по часам. В друиде сегмент является минимальной единицей данных. Все операции всегда происходят по-сегментно. Каждый новый сегмент пишется в сторадж и уже никогда не меняется, при этом реалтайм нода по прежнему хранит свою копию этого сегмента. После этого записываются метаданные, что этот сегмент находится в сторадже по такому-то адресу. Координатор периодически перечитывает метадату, и когда находит новый сегмент, через зукипер приказывает некоторым историческим нодам взять этот сегмент, чтобы обрабатывать запросы по нему. исторические ноды обслуживают всю массу данных, кроме самых свежих. Когда мы говорим что у нас кластер на 300ТБ, это это как раз про них. Исторические ноды скачивают сегмент, и через зукипер сообщают об этом всем. Когда реалтайм нода получает это сообщение, она удаляет свою копию сегмента, чтобы освободить ресурсы для новых данных. Брокер тоже получает эти сообщения из зукипера, и узнаёт на каких нодах какой сегмент лежит. И когда приходит запрос, он разделяет его по сегментам и отправляет по нужным нодам. Потом он дождется всех ответов, доагрегирует и отправит клиенту.
  18. Когда мы говорим о высокой доступности и видим в списке зависимостей mysql, сразу возникает вопрос Что будет если он упадет? Всё будет работать как раньше, просто данные в realtime начнут копиться. realtime не сможет записать сведения о новых сегментах в mysql, а координатор не сможет прочитать эти сведения и распределить новый сегмент по historical. копиться данные могут безболезненно пока не кончатся ресурсы (место на диске, цпу или память), но это происходит предсказуемо. вы знаете какой у вас поток данных, и можете расчитать, какой есть запас времени, чтобы починить mysql, и никто ничего не заметил. И, кстати, ровно то же самое будет в случае отказа стораджа и координатора.
  19. Но мы всё же решили подстраховаться, и сделали две вещи. Первое, мы заменили mysql на cassandra-based-sql, просто потому что у нас это уже есть, грех не использовать. И второе, ограничили realtime в ресурсах, и когда данных накопиться слишком много, самые старые данные не удаляются, но запросы по ним не обслуживаются. Это сделано исходя из того, что если накопилось так много данных, что realtime не может работать, то скорее всего это глобальная авария, и поэтому свежие данные для нас важнее всего.
  20. С zk, всё не только лучше, но и хуже. Лучше т.к. он сам по себе высокодоступный, с репликацией, и казалось бы что может случиться. Когда мы работаем с zk, мы хотим быстро узнавать об изменениях, например, о зависшей ноде, она должна быстро исчезнуть, чтобы мы не отправляли запросы в никуда. Для этого мы должны выставить небольшой таймаут. Что произойдет когда сессия отвалится по таймауту? "Её" данные будут удалены, и всё остальные клиенты об этом узнают. Сама нода, если она жива, тоже об этом узнает, и создаст новую сессию, и запишет и прочитает все, что нужно. Друид в зукипере держит информацию, какие сегменты на каких нодах. Когда сегментов станет много, да еще и в трех репликах, тогда этот процесс становится болезненным. У нас снапшот zk достигал 6 гигов. И вот одна нода zk притормозила так, что таймауты истекли, часть клиентов повылетала, перешла на другие ноды и начала перевычитывать и перезаписывать данные. Трафик зукиперных нод упирается в потолок, и входящий, и исходящий. Начинают отваливаться другие ноды, и еще добавляют трафика. Эта лавина растет пока все не отвалятся, а зукиперы не потеряют синхронизацию. Как это отразится у пользователя? Когда брокер отключается от зукипера и истекает таймаут сессии, он по факту теряет всё свое знание о том какие данные доступны для запроса и на каких нодах они лежали. И он ничего не показывает, то есть друид не работает на чтение. Это полностью вылечить нельзя.
  21. А пока, когда такое случается, самое простое, что можно сделать, это грохнуть все данные zk и поднять его пустым. Через некоторое время всё заработает само. Кроме того нужно держать запас по памяти двухкратный, потому что если сессия не истечет, а ноды перезапустятся без корректного завершения, они заново запишут свои данные, таким образом, пока не истек таймаут старой сессии, данные в zk будут задублированы. Поэтому обязательно давайте historcal нодам корректно завершиться. И еще. Historical нужно стартовать не одновременно, а с паузой хотя бы в минуту, т.к. при старте они сначала читают с дисков, какие данные у них есть, а потом записывают это всё в зукипер. Если они начнут это делать одновременно, то мы опять рискуем поднять лавину трафика. Чтобы минимизировать вероятность такого сценария, мы сделали так, чтобы полные данные из zk вычитывали только брокеры, координаторы и реалтайм, т.к. historical и всему остальному они не нужны. И мы стали записывать в zk урезанные метаданные, только то что реально нужно (имя таблицы, время, версия, номер шарда, и размер). И это сократило размер данных в zk почти в три раза, а трафик раз в 6-8. В оригинальном друиде эту проблему сейчас решают уносом этих данных из zk
  22. Как происходит загрузка в друид? Посмотрите на схему загрузки данных.
  23. Процесс загрузки данных периодически сбрасывает загруженное на диск, а чтобы по этим данным обслуживать запросы, они подтягиваются mmap-ом. После рестарта эти части так же подтягиваются, и остается дозагрузить небольшой кусочек данных, который не был сброшен на диск. Накапливается много таких частей, и они объединяются в один конечный сегмент, который уже никогда не поменяется. И с этим было два момента.
  24. Первый, -- это искажение данных Realtime нодами. Во время миграции мы конечно же сделали сверку между старой отлаженной системой и друидом. И мы с разочарованием обнаружили, что реалтайм ноды могут терять или дублировать данные. Это происходит при сбоях железа, креша JVM и даже во время корректного завершения. Потому что проиндексированные данные сбрасываются на диск отдельно, а позиция в источнике (с какого места начинать после рестарта) пишется отдельно. И дальше в зависимости, от того, что потеряется, произойдет потеря или дублирование. Избежать эту пролему можно не используя реалтайм ноды. Тогда нужно использовать indexing service, который загружает в друид данные по-сегментно. Если загрузка оборвется по любой причине, то её просто нужно начать заново. И эти воркеры также умеют обрабатывать запросы, поэтому реалтаймовая аналитика тут тоже есть. Это сейчас так. А тогда воркеры не принимали запросов, и нам пришлось методично чинить механизм сброса данных на диск. Справедливости ради, скажу что сейчас на сайте друида отмечено что realtime не гарантирует exactly-once, и починка этого планируется в будущем.
  25. Что можно с этим сделать? Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе. Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму. Он не сканирует словарь, а находит значение бинарным поиском.
  26. Что можно с этим сделать? Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе. Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму. Он не сканирует словарь, а находит значение бинарным поиском.
  27. Что можно с этим сделать? Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе. Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму. Он не сканирует словарь, а находит значение бинарным поиском.
  28. Что можно с этим сделать? Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе. Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму. Он не сканирует словарь, а находит значение бинарным поиском.
  29. Что можно с этим сделать? Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе. Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму. Он не сканирует словарь, а находит значение бинарным поиском.
  30. Но это не всё. Самое важно сейчас будет. История про ленту. Допустим у нас есть 1000 хостов вида "A.B.C", где есть 10 разных A, 10 разных B, 10 разных C. Допустим запрос с регуляркой отфильтрует 500 из них. Получается 1000 проверок регулярки и 500 битмапов нужно объединить. Теперь посмотрим, что будет если измерение хост, разбить на три отдельных измерения A, B и C, по 10 значений в каждом. Тогда наш запрос также будет содержать 3 регулярки A=[xyz] && B=[lmn] && C=[ijk] И отфильтрует те же 500 хостов.
  31. Но это не всё. Самое важно сейчас будет. История про ленту. Допустим у нас есть 1000 хостов вида "A.B.C", где есть 10 разных A, 10 разных B, 10 разных C. Допустим запрос с регуляркой отфильтрует 500 из них. Получается 1000 проверок регулярки и 500 битмапов нужно объединить. Теперь посмотрим, что будет если измерение хост, разбить на три отдельных измерения A, B и C, по 10 значений в каждом. Тогда наш запрос также будет содержать 3 регулярки A=[xyz] && B=[lmn] && C=[ijk] И отфильтрует те же 500 хостов.
  32. Но это не всё. Самое важно сейчас будет. История про ленту. Допустим у нас есть 1000 хостов вида "A.B.C", где есть 10 разных A, 10 разных B, 10 разных C. Допустим запрос с регуляркой отфильтрует 500 из них. Получается 1000 проверок регулярки и 500 битмапов нужно объединить. Теперь посмотрим, что будет если измерение хост, разбить на три отдельных измерения A, B и C, по 10 значений в каждом. Тогда наш запрос также будет содержать 3 регулярки A=[xyz] && B=[lmn] && C=[ijk] И отфильтрует те же 500 хостов.
  33. Но это не всё. Самое важно сейчас будет. История про ленту. Допустим у нас есть 1000 хостов вида "A.B.C", где есть 10 разных A, 10 разных B, 10 разных C. Допустим запрос с регуляркой отфильтрует 500 из них. Получается 1000 проверок регулярки и 500 битмапов нужно объединить. Теперь посмотрим, что будет если измерение хост, разбить на три отдельных измерения A, B и C, по 10 значений в каждом. Тогда наш запрос также будет содержать 3 регулярки A=[xyz] && B=[lmn] && C=[ijk] И отфильтрует те же 500 хостов.
  34. А вот кто-то захотел график за год по вот этим жирным данным. Он знает, что они жирные и готов подождать. За год там 2TB. У нас 18 машин по 10 дисков, тогда с диска нам надо прочитать 11GB, допустим диски читают 150MB/s, 11 гигов они прочитают за 74 секунды. А как вы думаете, что будут делать остальные пользователи эти 74 секунды? Они будут материть друид, меня и весь отдел статистики. И ругаться в чатиках, что ничего не работает. Как жить дальше, зная что такое может произойти? Да, друид поддерживает приоритеты запросов, но сам их не приоритизирует, поэтому приоритетами надо управлять как-то снаружи, что не очень-то удобно. Мы прибили низкий приоритет к самым тяжелым таблицам, раздражения стало меньше, но оно всё равно осталось, потому что приоритеты запросов работают только на уровне очереди, и если часть тяжелого запроса уже попала в обработку, всем всё равно придется ждать, хотя уже не так долго. Поскольку у друида есть все сведения и о запросе, и о данных, по которым он пойдет, мы сделали довольно простую приоритизацию, Мы выставляем запросу приоритет в зависимости от размера данных, по которым этот запрос пройдет. Чем больше надо пройти данных, тем меньше приоритет, независимо от "тяжести" таблицы. И у нас есть 5 очередей и 5 исполнителей с количеством тредов по количеству цпу, каждая из них упорядочивает запросы по приоритету, но имеет при этом свой приоритет на уровне ОС (aka nice), таким образом более быстрые запросы всегда идут вперед, все довольны. Теперь тяжелые запросы вытесняются, и никто их больше не ждет.
  35. Второй момент это деградация производительности по запросам. Это актуально для загрузки через Indexing service тоже. Что я имею в виду. Если у historical время растет линейно с количеством строк в сегменте, то у realtime, оно еще сильно зависит от количества сбросов на диск. При чем это заметно, как правило, на тяжелых таблицах и всяких неудобных запросах.