«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
Олег Анастасьев "Ближе к Cassandra". Выступление на Cassandra Conf 2013
1. Ближе к Cassandra
Олег Анастасьев
ведущий разработчик,
команда платформы
odnoklassniki.ru
2. 7 M онлайн
40M в день, 80M в месяц
!
~ 300 000 www страниц/сек,
20 ms на страницу
>280 ГБит/сек
!
> 6 600 серверов в 5 ЦОД
99.9% java
3. Cassandra @
* Начинали в 2010
- своя версия, основана на 0.6
- целились в:
выживаемость при отказе ЦОД, масштабируемость,
простота эксплуатации, адаптируемость
* Сейчас
- 23 разных кластера
- 418 всего нод
- 240 TB данных
!
- пережили несколько отказов ЦОД
6. Виджет Класс!
* Он везде
- На каждой странице по несколько
- В ленте активности
- На сайтах в Интернетах
!
* Он на всем
- Картинки и альбомы
- Видео
- Посты и комментарии
- Ссылки на внешние сайты
Класс! 103 927
7. Виджет Класс!
* Высоко нагруженный
- 1 000 000 чтений/сек, 3 000 записей/сек
!
* Сложный профиль
Класс! 103 927
- Много чтений
- Длинный хвост (40% чтений случайны)
- Чувствительна к вариациям скорости
- 3TB данных (9TB с RF) и растет
- ~ 60 млрд класс!ов к ~6 млрд объектов
8. Простое решение
SQL табличка
RefId:long
RefType:byte
UserId:long
Created
9999999999
PICTURE(2)
11111111111
11:00
!
для отрисовки
Вы и 4256
!
SELECT TOP 1 WHERE RefId,RefType,UserId=?,?,?
(98% are NONE)
!
SELECT COUNT (*) WHERE RefId,RefType=?,?
(80% are 0)
!
SELECT TOP N * RefId,RefType=? WHERE IsFriend(?,UserId)
9. Простая проблема
SQL табличка
RefId:long
RefType:byte
UserId:long
Created
9999999999
PICTURE(2)
11111111111
11:00
!
для отрисовки
Вы и 4256
!
SELECT TOP 1 WHERE RefId,RefType,UserId=?,?,?
(98% are NONE)
= N >=1
!
SELECT COUNT (*) WHERE RefId,RefType=?,?
(80% are 0)
= M>N
!
SELECT TOP N * RefId,RefType=? WHERE IsFriend(?,UserId)
= N*140
10. Пробуем Cassandra
LikeByRef (
refType byte,
refId bigint,
userId bigint,
!
PRIMARY KEY ( (RefType,RefId), UserId)
для отрисовки
LikeCount (
refType byte,
refId bigint,
likers counter,
!
PRIMARY KEY ( (RefType,RefId))
Вы и 4256
!
SELECT FROM LikeCount WHERE RefId,RefType=?,?
(80% are 0)
!
SELECT * FROM LikeByRef WHERE RefId,RefType,UserId=?,?,?
(98% are NONE)
= N*20%
11. >11 M iops
* Пробуем решить по быстрому
LikeByRef (
! refType byte,
refId bigint,
! userId bigint,
!
! PRIMARY KEY ( (RefType,RefId, UserId) )
!
SELECT TOP N * RefId,RefType=? WHERE IsFriend(?,UserId)
- Нужен Order Pres Partitioner
(Random не масштабируется)
- Запросы по диапазону ключей (KeyRange)
- Больше сетевого оверхеда
- Партиций >10x, Размер данных > x2
12. Колоночный блум фильтр
* Что делает
- Хранит пары (PartKey, ColumnKey) в SSTable
*-Filter.db
!
И это хорошо
- Убрали 98 % чтений с диска
- Меньше ложных попаданий
* Но все таки…
- Они стали очень большими
*
Проблемы с GC Promotion Failures
.. фиксим (CASSANDRA-2466)
13. Уже готово ?
1. COUNT()
сервер приложений
> 400
00
2. EXISTS
cassandra
- 2 похода для отрисовки (COUNT+RR)
- THRIFT медленный, когда много клиентов
- EXISTS() дает 200 Gbit/sec (140*8*1Mps*20%)
15. Вместе это хорошо
* Быстрый запрос TOP N друзей
1. Берем друзей из кеша соц графа
2. Проверяем по блум фильтру в памяти
3. Читаем с диска пока получим N
!
* Свои кеши
- Специализированные под приложение
!
* Свое объединение реплик данных
- … можно находить и разрешать кофликты
16. Слушаем мутации
// Implement it
interface StoreApplyListener {
boolean preapply(String key,
ColumnFamily data);
}
// and register with CFS
store=Table.open(..)
.getColumnFamilyStore(..);
store.setListener(myListener);
* Регистрируем слушателя
после применения логов но до госсипа
!
* RowMutation.apply()
и расширяем логику записей
+ На репликах, хинты, ReadRepairs
17. Счетчики с быстрым чтением
* Кеш счетчиков
LikeCount (
refType byte,
refId bigint,
ip inet,
counter int
PRIMARY KEY ( (RefType,RefId),
- Off heap (sun.misc.Unsafe)
- Компакен (30M in 1G RAM)
- Читаем только кеш локальной ноды
!
* Репликацию кеша
-
проблема холодного кеша реплик
делаем (NOP) записи
меньше чтений
учитывает длинный хвост
18. Вариации скорости
* Что делает Cassandra
1. Взять 1 ноду с данным и CL-1 - дайджест
2. Ждем данные и дайджест
3. Сравниваем и возвращаем (или RR)
* Ноды притормаживают
- Изза: SEDA, ротируем commit log , внезапно
много IO, заткнулась сеть или пропала,
внезапно много IO мимо кеша диска…
* И что видят клиенты ?
- Пики времени обработки запросов
- Можно только ждать (до таймаута)
19. Read Latency leveling
* “Параллельная” обработка чтений
1. Запрашиваем данные со всех реплик сразу
2. Ждем сколько надо CL ответов реплик
!
* И это хорошо
- Всегда минимальное время обработки
- Та же нагрузка при отказе ЦОД
!
* что (не так уж) плохо
- “Дополнительная” нагрузка и трафик
20. More tiny tricks
* Для SSD io
- Deadline IO шедулер
- 64k -> 4k размер запроса на чтение
!
* Хинт Лог
- Коммит лог для хинтов
- Ждем доставки всех хинтов на старте
!
* Селективый компактор
- Чаще читаем из CF - чаще компактим
22. * Сообщения в чатах
- Читаем свежую страницу на открытии
- длинный хвост (80%) для остальных
- 150 млрд, 100 TB на дисках
- Чтений больше: 120k в сек; 8k записей
23. Сообщение это структура
Message (
chatId, msgId,
!
created, type,userIndex,deletedBy,...
text
)
MessageCF (
chatId, msgId,
!
data blob,
!
PRIMARY KEY ( chatId, msgId )
- Все сообщения чата в 1 партиции
- Каждое представлено блобом
для уменьшения накладных расходов
!
- Стало плохо
Возможны конфиликтующие изменения
одного сообщения
(пользователи, антиспам, чистка,…)
24. Легкое разрешение конфликтов
get
get
(version:ts1, data:d1)
(version:ts1, data:d1)
write( ts1, data2 )
delete(version:ts1)
insert(version: ts2=now(), data2)
Messages (
chatId, msgId,
version timestamp,
data blob
PRIMARY KEY ( chatId, msgId, version )
write( ts1, data3 )
delete(version:ts1)
insert(version: ts3=now(), data3)
(ts2, data2)
(ts3, data3)
- merged on read
25. Специализированный кеш
* Опять. Потому что можем
- Off-heap (Unsafe)
- Кеширует только свежайшие страницы
- Пишет состояние в локальный (system) CF
И ключи, и значения
последовательное чтение на старте
- Компрессия данных в памяти
В 2 раза больше памяти бесплатно
26. Делим диски
* 4U HDDx24, до 4 TB на сервер
- Size tiered compaction = файл до 4 TB
- RAID10 ? LCS ?
!
* Делим CF на 256 кусочков
* Стало хорошо
- Более частые, но меньшие сбросы мемтаблиц
- Столько же работы для компактора
но более мелкими наборами
- Можно распределять между дисками
27. Политика распределения
* Из коробки
- “Берем наиболее свободный по месту диск”
* И теперь некоторые из них
- слишком нагружены на ввод-вывод
!
* По поколениям
- На каждом диске хранится одинаковое
количество sstables одного поколения
работает лучше всех для ЖД
29. * Список чатов
- мало данных (230GB)
- есть горячий набор, короткий хвост (5%)
- список часто пересортировывается
- 130k чтений/сек, 21k записей/сек
30. Конфликтные изменения
* List<ЧатOverview> в 1 блоб
.. или мы получим много могилок
!
* Много конфликтов
все меняют 1 колонку
!
* Определен алгоритм слияния
* Нужно детектирования конфликтов
31. Векторные часы
* Voldemort
- byte[] ключ -> byte[] значение + ВЧ
- Все клиенты - координаторы
- Заменяемые виды хранилищ
!
* Ну и заменили
- На СС таблицы от CS 0.6
- Со специализированным кеш
кеши. мы знаем как.
32. Скорость
* Кластер из 3 нод, RF = 3
- Intel Xeon CPU E5506 2.13GHz
RAM: 48Gb, 1x HDD, 1x SSD
!
* 8 байт ключ -> 1 KB значение
!
* Дает
- 75 k чтений/сек и 15 k записей
33. Зачем нам C* ?
* Готовые компоненты РБД
быстрое хранилище, gossip,
надежные асинхронные сообщения, детектор сбоев,
топология, последовательная обработка, ...
* Данные могут быть структутированы
сложнее, чем byte[] ключ -> byte[] значение
* Выполнила наши цели
* Сделано на java