В этой презентации мы расскажем о своем опыте применения этого хранилища на примере одной из самых высоконагруженных подсистем — хранилища Класс!ов. В данный момент в системе хранится около 50 миллиардов записей о Класс!, что занимает в сумме около 8 Тб. Для того чтобы реализовать такое хранилище пришлось отойти от классического способа работы с Cassandra. Мы расскажем об этом, а также о том, как Cassandra устроена под капотом, её сильные и слабые стороны, какие решения мы принимали и что мы изменили в Cassandra, чтобы сделать наше хранилище более высокопроизводительным и надежным.
9. Чтение из кластера
0
Данные
resolved
result
Хэш
192 64
Неправильный хэш
Read Repair
128
10. Column Family
Порядок
Таблица “Х”
Ключ name0:byte[] ... nameN:byte[]
byte[] value0:byte[] valueN:byte[]
timestamp0:long timestampN:long
Таблица “Х”
Ключ name0:byte[] ... nameK:byte[]
Порядок
...
11. Запись изнутри
Write (Key, Column) name value ts
Commit Log
Memtable
Flusher Thread
записывает
SSTable 1 SSTable 2 SSTable 3 SSTable 4
Compaction Thread Сортировка слиянием
SSTable 5
Запись на диск всегда последовательная!
12. Чтение изнутри
name value ts
часть данных 1
resolve
Memtable
часть 2
часть 3
SSTable 1 SSTable 4 SSTable 5
- get( Key, columnNames ... )
- slice( Key, from, to, count, direction )
- key_range( fromKey, toKey, count, slice(...) )
13. Анатомия SSTable
SSTable-5-Filter.db SSTable-5-Index.db SSTable-5-Data.db
Данные
Блум - фильтр Ключ => Смещение Строки и Колонки
“Строка, возможно, есть”
По-строчные
Всегда в ОЗУ блум фильтры и
индексы
для
длинных строк
Что дает: - НОЛЬ чтений с диска, если строки нет и вам повезло
- 1 чтение, если строки нет и не повезло
- 2 чтения с диска для маленьких строк
- 3 чтения - для больших
14. Разрешение конфликтов
SSTable “AccountStatements-3456” Memtable “AccountStatements”
RowKey = “Oleg_Anastasyev” RowKey = “Oleg_Anastasyev”
Column=”LV05HABA95142357516” Column=”LV05HABA95142357516”
vs
Value= $1,000,000 Value= $10
Какое состояние верно ?
15. Разрешение конфликтов
SSTable “AccStatements-3456” Memtable
RowKey = “Oleg_Anastasyev” RowKey = “Oleg_Anastasyev”
Column=”LV05HABA95142357516” Column=”LV05HABA95142357516”
vs
Value= $1,000,000 Value= $10
Timestamp = 13:00:05 Timestamp = 13:00:01
С более свежим timestamp.
17. Итог таков
Преимущества: Недостатки:
- Высокая и стабильная скорость записи - Нет ACID, нет откатов
- Очень быстрое чтение отсутсвующего ключа - Нет детектора конфликтов
- Скорость чтения не зависит от объема - NoSQL => нет JOIN
- Сортированные данные на диске О запросах думать зараннее
Денормализация данных
- Высокая доступность
- Масштабирование и восстановление данных на
ходу
- Резервное копирование не нужно
- Эффективная эксплуатация в нескольких ЦОД
22. Классная задачка
таблица
RefId:long RefType:byte UserId:long Created
9999999999 STATUS(2) 11111111111 11:00
запросы
– COUNT ( RefId,RefType=? ): 80% => 0 Вы и 4256
– EXISTS( RefId,RefType,UserId=? ): 98% => Нет
– RefId,RefType=? ORDER BY Created DESC -- кто классил ?
23. Классная задачка
таблица
RefId:long RefType:byte UserId:long Created
9999999999 STATUS(2) 11111111111 11:00
запросы
– COUNT ( RefId,RefType=? ): 80% => 0 Вы и 4256
– EXISTS( RefId,RefType,UserId=? ): 98% => Нет
– RefId,RefType=? ORDER BY Created DESC -- кто классил ?
как то скучно ...
27. Классная проблема
таблица
RefId:long RefType:byte UserId:long Created
9999999999 STATUS(2) 11111111111 11:00
нагрузка 8х
– 16 миллиардов показов в день (~ 300 000/сек)
– 100 M класс!ов в день ( ~ 2500/сек )
– 2TB данных
новый запрос
– RefId,RefType=? ORDER BY ДрузьяСверху
длинный хвост
– 40% EXISTS(RefId,UserId) не кешируются в принципе
28. Классная проблема
уже есть:
– 8 SQL кластеров (без учета резерва)
– 12 кешей (увеличение количества большого эффекта не дает)
– И они близки к пределу по CPU, дисковым операциям
А мы хотим в 8 раз больше
29. Простые решения ?
Добавить больше SQL
– Уже есть 8, доставляем до 32
– Дорого ( железо + лицензии MS)
– Добавление SQL - ручная офлайн работа
– Повторяем раз в полгода ( 64 => 128 =>256 )
– Ненадежно
Добавить кешей
– Много NOT EXISTS + длинный хвост => LRU кеш не работает
– Значит нужно кешировать 100% Классов!
– 2TB ОЗУ не дешево
– ( и надо умножить на 2 или 3 для надежности )
30. Cassandra !
Упираем на хорошее
– Дешевый NOT EXISTS ( отсекается Блум-фильтром )
– Простая структура
– Хвост хранится на дисках
– Удобное масштабирование
– Высокая доступность
Не попадая в плохое
– Нет требований ACID
– Eventual Consistency приемлемо
– Класс!ы никогда не меняются
– У нас есть время для compaction
31. Класс!ная модель данных
LikeByRef Все класс!ы по сущности
LikeCount Счетчики отдельно
LikeByUser Мои класс!ы
32. Класс!ная модель данных
LikeByRef
Key Column Column Value Timestamp
Type+RefId userId:byte[8] <null> Created
– EXISTS ( Type,RefId=?, UserId=?) 98% calls => “NOT EXISTS”
– WHERE Type,RefId=? ORDER BY ДрузьяСверху LIMIT XX
Мы не хотим читать диск на этих запросах
...но Cassandra использует блум-фильтр только для отсечки строк
33. Колоночный блум-фильтр
Что делает
– Хранит пары (Key, Column name) прямо в SSTable *-Filter.db
Хорошо
– Полностью убрали чтения с диска на NOT EXISTS
– ... то есть 98% запросов идут только в память
– больше фильтр => меньше false positives
Плохо
– блум фильтры стали большими - сотни мегабайт
– .. GC Promotion Failures (так как были в одном long[])
– исправили (CASSANDRA-2466) в cassandra 1.0
34. Классная модель
LikeCount
Key Column Column Value Timestamp
Type+RefId nodeIp:byte[4] nodeCounter:int Created
– COUNT ( RefType,RefId=?) 80% calls => “NOT EXISTS”
Мы не хотим делать сетевые запросы если классов нет
...но Cassandra всегда это делает для RR или пострадает консистентность
35. и еще плохо
application server 1. COUNT()
2. EXISTS
cassandra
- DTO <-> hector <-> THRIFT <-> cassandra
- THRIFT медленный и неудобный
- Несконсистентные транзакции
- Дополнительная коммуникация из-за RR
- Кеш только LRU, некомпактный
36. классное решение
application server one-nio
odnoklassniki-like
cassandra
- Бакенд и Cassandra в той же JVM
- Бакенд в том же ринге
- Работает через one-nio транспорт
37. классное решение
Локальный доступ
– запросы COUNT(RefId), EXISTS(RefId,UserId)
проверяются по блум - фильтрам в памяти локальной ноды
Спец кеш счетчиков
– более компактный, off heap
– ... 40M элементов -> 1G RAM
– сохраняется на диск для быстрого старта
– учитывает длинный хвост
42. Кеш счетчиков
Фейковые
0 изменения
TS = TS
m
64
- при изменении
- на втором чтении
128 - повторить раз в 8 ч
43. профит
– 12 cassandra nodes вместо 8 SQLs + резерв + 12 кешей
– более надежная: RF = 3, в каждом ЦОД по реплике
– более производительная: 1M бизнес запросов/сек
– более быстрая: более чем в 10 раз, менее 1.5 мс в среднем
– расширяемая: 12 -> 24 -> 48
– быстрорастущая: 8 TB, + 15 G в день
44. Можно узнать больше!
Odnoklassniki.ru
Интеграция с Odnoklassniki.ru
http://v.ok.ru
http://connect.ok.ru
one-nio
Cassandra
slideshare.net/m0nstermind/presentations
github.com/odnoklassniki/one-nio github.com/odnoklassniki/apache-cassandra
cassandra.apache.org
Олег Анастасьев
oa@odnoklassniki.ru
odnoklassniki.ru/oa