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