Successfully reported this slideshow.
Your SlideShare is downloading. ×

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

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad

Check these out next

1 of 82 Ad

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

Download to read offline

HighLoad++ 2017

Зал «Мумбай», 8 ноября, 10:00

Тезисы:
http://www.highload.ru/2017/abstracts/3045.html

Как мы заставили Druid работать в Одноклассниках.

«Druid is a high-performance, column-oriented, distributed data store» http://druid.io.

Мы расскажем о том, как, внедрив Druid, мы справились с ситуацией, когда MSSQL-based система статистики на 50 терабайт стала:
- медленной: средняя скорость ответа была в разы меньше требуемой (и увеличилась в 20 раз);
- нестабильной: в час пик статистика отставала до получаса (теперь ничего не отстает);
- дорогой: изменилась политика лицензирования Microsoft, расходы на лицензии могли составить миллионы долларов.
...

HighLoad++ 2017

Зал «Мумбай», 8 ноября, 10:00

Тезисы:
http://www.highload.ru/2017/abstracts/3045.html

Как мы заставили Druid работать в Одноклассниках.

«Druid is a high-performance, column-oriented, distributed data store» http://druid.io.

Мы расскажем о том, как, внедрив Druid, мы справились с ситуацией, когда MSSQL-based система статистики на 50 терабайт стала:
- медленной: средняя скорость ответа была в разы меньше требуемой (и увеличилась в 20 раз);
- нестабильной: в час пик статистика отставала до получаса (теперь ничего не отстает);
- дорогой: изменилась политика лицензирования Microsoft, расходы на лицензии могли составить миллионы долларов.
...

Advertisement
Advertisement

More Related Content

More from Ontico (20)

Recently uploaded (20)

Advertisement

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

  1. 1. Как мы заставили Druid работать в Одноклассниках Юрий Невиницин
  2. 2. ЗАЧЕМ? Статистика Менеджеры Разработчики Администраторы • Цели • Аномалии • Мониторинг • Эксперименты • Запуски • Без неё никак
  3. 3. Немного цифр • 350 таблиц • 50TB • 12 млрд событий в сутки • Отставание на полчаса • 6 секунд • Цена в миллионы долларов
  4. 4. Постановка задачи
  5. 5. Постановка задачи • Быстро • 24x7x365 ( -1 ЦОД ) • Масштабируемо • Open-source (Java)
  6. 6. Druid • Быстро • 24x7x365 ( -1 ЦОД ) • Масштабируемо • Open-source (Java) • Предагрегация • Timeseries, TopN, GroupBy
  7. 7. Альтернативы • PostgreSQL • Influx • Prometheus • OpenTSDB • ClickHouse
  8. 8. • Все довольны • Сэкономили миллионы долларов • 1 человек Druid
  9. 9. Событий/сек Кластер 500 000 Нода 50 000 Таблица 10 000 Druid
  10. 10. Событий/сек Кластер 500 000 2 300 000 Нода 50 000 275 000 Таблица 10 000 90 000 Druid MAX
  11. 11. Внешние компоненты • Storage (Amazon, HDFS, local) • Metadata DB (MySQL, Postgres, Derby) • ZooKeeper • Cache (memcache, local)
  12. 12. Собственные компоненты • Realtime • Historical • Broker • Coordinator • Indexing service
  13. 13. DATA Realtime Segment StorageMeta Coordinator Historical
  14. 14. Historical QUERY Broker subquery1 subquery2 subquery3 subquery4 Historical HistoricalRealtime
  15. 15. Отказ MySQL • Данные копятся в Realtime-нодах • Пока не кончатся ресурсы = предсказуемый запас времени
  16. 16. Отказ MySQL Storage, Coordinator • Данные копятся в Realtime-нодах • Пока не кончатся ресурсы = предсказуемый запас времени • Cassandra-based DB • Свежие данные всегда доступны
  17. 17. Отказ ZooKeeper • Маленький таймаут • Много данных • Лавина трафика • «Нет данных» Что делать?
  18. 18. Отказ ZooKeeper • Удалить данные ZK • Иметь запас памяти в ZK • Корректно завершать Historical • Стартовать Historical с паузой • Убрать ненужные чтения • Убрать ненужные данные Что делать?
  19. 19. Загрузка данных
  20. 20. Realtime • sss MMAP MMAP
  21. 21. Искажение данных Persisted Data Source Position Загрузка в Realtime Persisted Data Source Position Потеря Дубль
  22. 22. Деградация чтения Загрузка данных
  23. 23. 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
  24. 24. 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
  25. 25. 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[]
  26. 26. 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[]
  27. 27. 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[]
  28. 28. 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[]
  29. 29. 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[]
  30. 30. 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[]
  31. 31. 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
  32. 32. 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
  33. 33. 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%
  34. 34. 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%
  35. 35. 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%
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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
  40. 40. 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
  41. 41. 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
  42. 42. 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[] Загрузка данных
  43. 43. 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 … … … …
  44. 44. 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 … … … …
  45. 45. 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 … … … …
  46. 46. Деградация чтения Загрузка данных • Размер сегмента • Частота сброса на диск • Использовать Selector
  47. 47. like.photo.main
  48. 48. like.photo.main.favorites
  49. 49. like.photo.main.favorites.widget
  50. 50. like.photo.main.favorites.widget.banner125
  51. 51. 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.%
  52. 52. 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.%
  53. 53. Action Like Share Comment Object Photo Video Music Place Main Album Group SUM(calls) Action = like
  54. 54. Action Like Share Comment Object Photo Video Music Place Main Album Group SUM(calls) Action = like
  55. 55. 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 = +
  56. 56. Истинно тяжелый запрос • 2TB • 74 секунды • Приоритеты • Надо выставлять в запросе • Работают на уровне очереди
  57. 57. • Отказ MySQL предсказуем • Для ZooKeeper: запас памяти, корректно завершать Historical, а стартовать с паузой • Realtime не гарантирует exactly-once • Подбор размера сегмента и частоты сброса на диск • Использовать Selector • Разбивать большое измерение на мелкие • Приоритеты на уровне очереди
  58. 58. Юрий Невиницин nevinitsin@corp.mail.ru
  59. 59. Юрий Невиницин nevinitsin@corp.mail.ru
  60. 60. Юрий Невиницин nevinitsin@corp.mail.ru
  61. 61. Юрий Невиницин nevinitsin@corp.mail.ru
  62. 62. Деградация чтения Загрузка данных t N rows Historical Realtime N parts = 1
  63. 63. Деградация чтения Загрузка данных t N rows Historical Realtime N parts = 2
  64. 64. Деградация чтения Загрузка данных t N rows Historical Realtime N parts = 3
  65. 65. Деградация чтения Загрузка данных t N rows Historical Realtime N parts = 30
  66. 66. 1 | Область названия раздела
  67. 67. Данные: название источника данных, 2016 74 Слайд с текстом Подзаголовок • Далтон Трамбо, один из самых успешных голливудских сценаристов, автор «Римских каникул» и «Спартака», не подозревал, что черный список «Hollywood 10» реально существует, пока сам не попал туда и не был навсегда выкинут из жизни фабрики грез; • Премьера «Трамбо» состоялась в программе «специальный показ» на кинофестивале в Торонто в сентябре 2015 года. Выход картины в широкий прокат состоялся 6 ноября 2015 года.
  68. 68. Данные: название источника данных, 2016 75 Слайд с цифрой 63 Подпись в две строчки %
  69. 69. Данные: название источника данных, 2016 76 Слайд с двумя цифрами 63 Подпись в две строчки % 27 Подпись в две строчки млн
  70. 70. Данные: название источника данных, 2016 77 Изображение с комментарием Стиль изображений Зайдите в Quick Styles. Выберите стиль с тенью
  71. 71. Данные: название источника данных, 2016 78 Вертикальный скриншот Android с комментарием Скриншот на экране мобильного телефона на платформе Android Вставьте свой скриншот в черное поле мобильного устройства
  72. 72. Данные: название источника данных, 2016 79 Вертикальный скриншот iOS с комментарием Скриншот на экране мобильного телефона на платформе iOs Вставьте свой скриншот в черное поле мобильного устройства
  73. 73. Данные: название источника данных, 2016 80 Горизонтальный скриншот iOS
  74. 74. Данные: название источника данных, 2016 81 Горизонтальный скриншот Android
  75. 75. Данные: название источника данных, 2016 82 Скриншот на экране ноутбука Скриншот на экране ноутбука Вставьте свой скриншот в черное поле ноутбука
  76. 76. Данные: название источника данных, 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 Бонус
  77. 77. 84 Контакты и полезная информация Поддержка партнеров partners@ok.ru Отдел продаж sales@corp.mail.ru Блог ОК с информацией о запусках, событиях и др. insideok.ru Лучшие кейсы на базе ОК за последние годы awards.insideok.ru Продуктовые обновления ok.ru/gruppa Официальная группа ОК ok.ru/ok

Editor's Notes

  • Всем привет! Меня зовут Невиницин Юрий, я из Одноклассников, и занимаюсь системой внутренней статистики.

    Кому нравится мысль о поддержке системы реалтайм аналитики на 50Тб, построеной на MS SQL Server, и в которую логируются миллиарды событий в сутки?

    Я расскажу о нашем кейсе миграции такой системы на колоночную базу под названием DRUID, а вы узнаете несколько рецептов его использования.
  • Зачем нам система статистики?
    Она нужна потому что мы хотим знать всё, о своем сайте.
    Поэтому мы не только мониторим поведение железа и трафика, нагрузку ЦПУ и на диски, а логируем каждое действие пользователя, все взаимодействия между компонентами сайта, и множество внутрених процессов этих компонентов.
    Система статистики тесно интегрирована в процесс разработки сайта.
    Менеджеры оценивают эффективность сайта, устанавливают или корректируют цели, отслеживают как они достигнуты.
    Администраторы и разработчики следят за работой всех систем, расследуют аномалии.
    Автоматический мониторинг на ранней стадии выявляют неполадки в работе сайта; а также строят прогнозы по превышению различных лимитов.
    Кроме этого постоянно идут апдейты, эксперименты, запуск фич, и эффект от всех этих действий отслеживается через систему статистики.
  • Статистика у нас отображается в основном в виде графиков.
    Графики у нас бы
    Обычно мы выводим графики сразу за 5 дней, чтобы сразу визуально понятно, сейчас идет всё как обычно, или хуже, или лучше.
  • Этот же график можно разложить по параметру.
  • Второй виз графиков - долгосрочный. Здесь мы смотрим месячные и годовые тренды.

    Графики у нас не статичны.
    Мы можем изменить параметры и сразу увидеть результат.
    Можно выбрать любую дату за последние 2 года, можно задать любые фильтры и группировку.
    После того, как график настроен, его можно сохранить в дашборд, чтобы в одном месте смотреть графики по какой-то теме.
  • И это удобно, поэтому редко кто смотрит отдельные графики, чаще открывают сразу дашборд, даже если надо посмотреть всего пару графиков из сотни, что конечно только увеличивает нагрузку на систему статистики.

    Хочу отдельно отметить, что пользователи не приходят в отдел статистики «создайте мне график с такими-то фильтрами и группировками».

    Они все графики и дашборды создают сами с произвольными фильтрами и группировками.
    Нет никаких предагрегатов, всё обсчитывается на лету.

    Вроде не сложная система.
    Но это пока данных относительно мало.

    MS SQL Server справлялся, но по мере роста объема данных, система замедлялась.
    Замедлялась загрузка данных в неё и увеличивалось время построения графика.
  • Мы доросли до такого объема, что загрузка некоторых таблиц в час пик отставала на полчаса.

    А среднее время отдачи одного графика было 6 секунд. То есть кто-то получал график за 2-3 секунды, а кто-то за 20-30 секунд, а кто-то и за минуты.

    Когда расследуешь аномалию, надо рассмотреть десяток графиков, которые следуют один из другого, их нельзя открыть одновременно, и приходилось 10 раз ждать по 30 секунд. Это бесит. Дико бесит.

    Менеджеры показывают свои ключевые графики каждую неделю, в том числе и годовые, им приходилось открывать дашборды за 20 минут до митинга.

    Можно было выжимать еще производительность, добавить серверов и как-то в ручную разносить данные, но майкрософт в то же время изменил политику лицензирования.
    И продолжи мы использовать SQL, нам пришлось бы отдать миллионы долларов за лицензии.

    Поэтому мы решили сразу сделать как надо.
  • А именно,
    чтобы график открывался за 2 сек,
    дашборд за 10,
    статистика не отставала, была всегда доступна и переживала потерю датацентра,
    масштабируема,
    с открытым кодом, чтобы затачивать под себя, и желательно на java.
  • И друид именно это нам и обещал.
    Это колоночная и распределенная база.
    Также там есть предагрегация и индескация во время вставки, и те типы запросов которые нам нужны.
    Получалось, что мы легко сможем заменить SQL server на друид.
  • Разумеется, мы рассматривали и другие варианты
    PostgreSQL - не масштабируемо и не высокодоступно
    influx, vertica - дорого и закрыто (как следствие, высокая доступность под вопросом)
    prometheus - не масштабируемо и не высокодоступно
    opentsdb - нет индексов и производительнось под вопросом
    clickhouse - тогда еще не было
  • Мы внедрили друид, смигрировали в него исторические данные из MSSQL, и всё у нас залетало.
    Просмотры графиков увеличились в 5 раз, буквально в день переключения на друид.
    Стали запускать более тяжелую статистику, которую боялись запускать на старой системе.
    Сэкономили миллионы долларов
    И наш друид сопровождает 1 человек.
  • В час пик наш кластер из 12 машин получает полмиллиона событий в секунду.
    И у нас есть довольно большой запас прочности.
  • Это не тесты, это получено на наших реальных данных.
    всё это здорово и красиво. но что же было на самом деле?
  • Для начала немного про сам друид.
    У друида есть несколько внешних зависимостей.
    Ему нужен сторадж, там друид хранит только хранит данные.
    Также ему нужна база для метаданных. Там друид хранит сведения о том, где что лежит в сторадже.
    zk. нужен для дискавери и для объявления, какая нода какие данные обслуживает.
    и кеш, это или мемкеш или встроеный.
  • realtime ноды загружают свежие данные и обрабатывают запросы по ним.

    historical - эти ноды держат всю массу данных и обрабатывают запросы по ним. данные здесь не меняются.

    broker - отвечает за распределение вычислений между historical и realtime нодами, и за кеширование.

    Coordinator - распределяет сами данные по historical нодам, на основании метаданных в mysql.
  • Друид – это распределенная колоночная база данных.
    Вот наши данные, более-менее упорядоченные по времени.
    Реалтайм нода берет эти данные, индескирует и нарезает на сегменты по времени, например, по суткам или по часам.
    В друиде сегмент является минимальной единицей данных. Все операции всегда происходят по-сегментно.
    Каждый новый сегмент пишется в сторадж и уже никогда не меняется, при этом реалтайм нода по прежнему хранит свою копию этого сегмента.
    После этого записываются метаданные, что этот сегмент находится в сторадже по такому-то адресу.
    Координатор периодически перечитывает метадату, и когда находит новый сегмент, через зукипер приказывает некоторым историческим нодам взять этот сегмент, чтобы обрабатывать запросы по нему.
    исторические ноды обслуживают всю массу данных, кроме самых свежих. Когда мы говорим что у нас кластер на 300ТБ, это это как раз про них.
    Исторические ноды скачивают сегмент, и через зукипер сообщают об этом всем.
    Когда реалтайм нода получает это сообщение, она удаляет свою копию сегмента, чтобы освободить ресурсы для новых данных.
    Брокер тоже получает эти сообщения из зукипера, и узнаёт на каких нодах какой сегмент лежит. И когда приходит запрос, он разделяет его по сегментам и отправляет по нужным нодам. Потом он дождется всех ответов, доагрегирует и отправит клиенту.
  • Друид – это распределенная колоночная база данных.
    Вот наши данные, более-менее упорядоченные по времени.
    Реалтайм нода берет эти данные, индескирует и нарезает на сегменты по времени, например, по суткам или по часам.
    В друиде сегмент является минимальной единицей данных. Все операции всегда происходят по-сегментно.
    Каждый новый сегмент пишется в сторадж и уже никогда не меняется, при этом реалтайм нода по прежнему хранит свою копию этого сегмента.
    После этого записываются метаданные, что этот сегмент находится в сторадже по такому-то адресу.
    Координатор периодически перечитывает метадату, и когда находит новый сегмент, через зукипер приказывает некоторым историческим нодам взять этот сегмент, чтобы обрабатывать запросы по нему.
    исторические ноды обслуживают всю массу данных, кроме самых свежих. Когда мы говорим что у нас кластер на 300ТБ, это это как раз про них.
    Исторические ноды скачивают сегмент, и через зукипер сообщают об этом всем.
    Когда реалтайм нода получает это сообщение, она удаляет свою копию сегмента, чтобы освободить ресурсы для новых данных.
    Брокер тоже получает эти сообщения из зукипера, и узнаёт на каких нодах какой сегмент лежит. И когда приходит запрос, он разделяет его по сегментам и отправляет по нужным нодам. Потом он дождется всех ответов, доагрегирует и отправит клиенту.
  • Когда мы говорим о высокой доступности и видим в списке зависимостей mysql, сразу возникает вопрос

    Что будет если он упадет?

    Всё будет работать как раньше, просто данные в realtime начнут копиться.
    realtime не сможет записать сведения о новых сегментах в mysql, а координатор не сможет прочитать эти сведения и распределить новый сегмент по historical.

    копиться данные могут безболезненно пока не кончатся ресурсы (место на диске, цпу или память), но это происходит предсказуемо.
    вы знаете какой у вас поток данных, и можете расчитать, какой есть запас времени, чтобы починить mysql, и никто ничего не заметил.

    И, кстати, ровно то же самое будет в случае отказа стораджа и координатора.

  • Но мы всё же решили подстраховаться, и сделали две вещи.
    Первое, мы заменили mysql на cassandra-based-sql, просто потому что у нас это уже есть, грех не использовать.
    И второе, ограничили realtime в ресурсах, и когда данных накопиться слишком много, самые старые данные не удаляются, но запросы по ним не обслуживаются.
    Это сделано исходя из того, что если накопилось так много данных, что realtime не может работать, то скорее всего это глобальная авария, и поэтому свежие данные для нас важнее всего.
  • С zk, всё не только лучше, но и хуже.
    Лучше т.к. он сам по себе высокодоступный, с репликацией, и казалось бы что может случиться.

    Когда мы работаем с zk, мы хотим быстро узнавать об изменениях, например, о зависшей ноде, она должна быстро исчезнуть, чтобы мы не отправляли запросы в никуда. Для этого мы должны выставить небольшой таймаут.

    Что произойдет когда сессия отвалится по таймауту? "Её" данные будут удалены, и всё остальные клиенты об этом узнают.

    Сама нода, если она жива, тоже об этом узнает, и создаст новую сессию, и запишет и прочитает все, что нужно.

    Друид в зукипере держит информацию, какие сегменты на каких нодах. Когда сегментов станет много, да еще и в трех репликах, тогда этот процесс становится болезненным. У нас снапшот zk достигал 6 гигов.

    И вот одна нода zk притормозила так, что таймауты истекли, часть клиентов повылетала, перешла на другие ноды и начала перевычитывать и перезаписывать данные.

    Трафик зукиперных нод упирается в потолок, и входящий, и исходящий.
    Начинают отваливаться другие ноды, и еще добавляют трафика.
    Эта лавина растет пока все не отвалятся, а зукиперы не потеряют синхронизацию.

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

    Это полностью вылечить нельзя.
  • А пока, когда такое случается, самое простое, что можно сделать, это грохнуть все данные zk и поднять его пустым.
    Через некоторое время всё заработает само.

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

    Поэтому обязательно давайте historcal нодам корректно завершиться.

    И еще. Historical нужно стартовать не одновременно, а с паузой хотя бы в минуту, т.к. при старте они сначала читают с дисков, какие данные у них есть, а потом записывают это всё в зукипер.
    Если они начнут это делать одновременно, то мы опять рискуем поднять лавину трафика.

    Чтобы минимизировать вероятность такого сценария, мы сделали так, чтобы полные данные из zk вычитывали только брокеры, координаторы и реалтайм, т.к. historical и всему остальному они не нужны.

    И мы стали записывать в zk урезанные метаданные, только то что реально нужно (имя таблицы, время, версия, номер шарда, и размер). И это сократило размер данных в zk почти в три раза, а трафик раз в 6-8.

    В оригинальном друиде эту проблему сейчас решают уносом этих данных из zk
  • Как происходит загрузка в друид?

    Посмотрите на схему загрузки данных.
  • Процесс загрузки данных периодически сбрасывает загруженное на диск, а чтобы по этим данным обслуживать запросы, они подтягиваются mmap-ом.
    После рестарта эти части так же подтягиваются, и остается дозагрузить небольшой кусочек данных, который не был сброшен на диск.

    Накапливается много таких частей, и они объединяются в один конечный сегмент, который уже никогда не поменяется.

    И с этим было два момента.
  • Первый, -- это искажение данных Realtime нодами.

    Во время миграции мы конечно же сделали сверку между старой отлаженной системой и друидом.
    И мы с разочарованием обнаружили, что реалтайм ноды могут терять или дублировать данные.
    Это происходит при сбоях железа, креша JVM и даже во время корректного завершения.

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

    Избежать эту пролему можно не используя реалтайм ноды.
    Тогда нужно использовать indexing service, который загружает в друид данные по-сегментно. Если загрузка оборвется по любой причине, то её просто нужно начать заново. И эти воркеры также умеют обрабатывать запросы, поэтому реалтаймовая аналитика тут тоже есть.
    Это сейчас так. А тогда воркеры не принимали запросов, и нам пришлось методично чинить механизм сброса данных на диск.
    Справедливости ради, скажу что сейчас на сайте друида отмечено что realtime не гарантирует exactly-once, и починка этого планируется в будущем.
  • Что можно с этим сделать?

    Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе.

    Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму.

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

    Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе.

    Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму.

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

    Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе.

    Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму.

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

    Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе.

    Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму.

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

    Можно влиять на количество этих частей с помощью настройки размера сегмента, интервала сброса на диск, и максимальное кол-во строк в хипе.

    Кроме этого, мы, не зная эти особенности, при миграции все наши фильтры сделали типа regex, так делать не надо, а надо сразу учитавыть какой фильтр указал пользователь, и использовать фильтр типа селектор по максимуму.

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

    История про ленту.

    Допустим у нас есть 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 хостов.
  • Но это не всё. Самое важно сейчас будет.

    История про ленту.

    Допустим у нас есть 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 хостов.
  • Но это не всё. Самое важно сейчас будет.

    История про ленту.

    Допустим у нас есть 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 хостов.
  • Но это не всё. Самое важно сейчас будет.

    История про ленту.

    Допустим у нас есть 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 хостов.
  • А вот кто-то захотел график за год по вот этим жирным данным.

    Он знает, что они жирные и готов подождать.
    За год там 2TB.
    У нас 18 машин по 10 дисков, тогда с диска нам надо прочитать 11GB, допустим диски читают 150MB/s, 11 гигов они прочитают за 74 секунды.

    А как вы думаете, что будут делать остальные пользователи эти 74 секунды?
    Они будут материть друид, меня и весь отдел статистики.
    И ругаться в чатиках, что ничего не работает.

    Как жить дальше, зная что такое может произойти?
    Да, друид поддерживает приоритеты запросов, но сам их не приоритизирует, поэтому приоритетами надо управлять как-то снаружи, что не очень-то удобно.
    Мы прибили низкий приоритет к самым тяжелым таблицам, раздражения стало меньше, но оно всё равно осталось,

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

    Поскольку у друида есть все сведения и о запросе, и о данных, по которым он пойдет, мы сделали довольно простую приоритизацию,

    Мы выставляем запросу приоритет в зависимости от размера данных, по которым этот запрос пройдет.
    Чем больше надо пройти данных, тем меньше приоритет, независимо от "тяжести" таблицы.

    И у нас есть 5 очередей и 5 исполнителей с количеством тредов по количеству цпу, каждая из них упорядочивает запросы по приоритету, но имеет при этом свой приоритет на уровне ОС (aka nice), таким образом более быстрые запросы всегда идут вперед, все довольны.

    Теперь тяжелые запросы вытесняются, и никто их больше не ждет.


  • Второй момент это деградация производительности по запросам. Это актуально для загрузки через Indexing service тоже.

    Что я имею в виду. Если у historical время растет линейно с количеством строк в сегменте,
    то у realtime, оно еще сильно зависит от количества сбросов на диск.

    При чем это заметно, как правило, на тяжелых таблицах и всяких неудобных запросах.

×