Cassandra. Як не померти, якщо знаєш лише Oracle

Привіт Хабр.

Мене звати Михайло Бутримов, я хотів би хотів трохи розповісти про Cassandra. Моя розповідь буде корисною тим, хто ніколи не стикався з NoSQL-базами, — вона має дуже багато особливостей реалізації та підводного каміння, про які потрібно знати. І якщо крім Oracle чи будь-якої іншої реляційної бази ви нічого не бачили, ці речі врятують вам життя.

Чим гарна Cassandra? Це NoSQL база даних, спроектована без єдиної точки відмови, яка добре масштабується. Якщо вам потрібно додати пару терабайт для якоїсь бази, ви просто додаєте ноди в кільце. Розширити її ще на один дата-центр? Додаєте ноди у кластер. Збільшити оброблюваний RPS? Додаєте ноди у кластер. У зворотний бік також працює.

Cassandra. Як не померти, якщо знаєш лише Oracle

У чому ще вона гарна? Для того, щоб обробляти багато запитів. Але багато – це скільки? 10, 20, 30, 40 тисяч запитів на секунду – це небагато. 100 тисяч запитів на секунду на запис теж. Є компанії, які говорили, що вони тримають 2 млн запитів на секунду. Ось їм, мабуть, доведеться повірити.

І в принципі Cassandra має одну велику відмінність від реляційних даних — вона взагалі на них не схожа. І це дуже важливо пам'ятати.

Не все, що виглядає однаково, працює однаково

Якось до мене прийшов колега і запитав: Ось СQL Cassandra query language, і в ньому є select statement, в ньому є where, в ньому є and. Я пишу літери і не працює. Чому?». Якщо ставитися до Cassandra як до реляційної бази даних, це ідеальний спосіб закінчити життя жорстоким самогубством. І я не пропагую, це заборонено в Росії. Ви просто спроектуєте щось неправильно.

Наприклад, до нас приходить замовник і каже: Давайте побудуємо базу даних для серіалів, або базу даних для довідника рецептів. У нас там будуть страви з продуктами чи список серіалів та акторів у ньому». Ми радіємо раді: «Давайте!». Це два байти переслати, пара табличок і все готове, все працюватиме дуже швидко, надійно. І все чудово, поки замовники не приходять і не кажуть, що домогосподарки вирішують ще й зворотне завдання: вони мають список продуктів, і вони хочуть дізнатися, яку страву вони хочуть приготувати. Ви мертві.

Все тому, що Cassandra – гібридна база даних: вона одночасно і key value, і зберігає дані у широких шпальтах. Якщо говорити мовою Java або Kotlin, це можна було б описати так:

Map<RowKey, SortedMap<ColumnKey, ColumnValue>>

Тобто, карта, всередині якої лежить ще й відсортована карта. Першим ключем до цієї карти є Row key або Partition key – ключ партиціонування. Другий ключ, який є ключем до вже відсортованої картки, це Clustering key.

Для ілюстрації розподілу бази даних намалюємо три ноди. Тепер потрібно зрозуміти, як розкласти дані на ноди. Тому що якщо ми пхатимемо все в одну (їх, до речі, може бути тисяча, дві тисячі, п'ять — скільки завгодно), це не дуже про розподіленість. Тому нам потрібна математична функція, яка повертатиме число. Просто число, довгий int, який потраплятиме в якийсь діапазон. І в нас одна нода відповідатиме за один діапазон, друга – за другий, n-на – за n-ий.

Cassandra. Як не померти, якщо знаєш лише Oracle

Це число береться за допомогою хеш-функції, яка застосовується саме до того, що ми називаємо Partition key. Це стовпець, який вказується в директиві Primary key, і це той стовпець, який буде першим і найголовнішим ключем карти. Він визначає, на яку ноду, які дані потраплять. Таблиця створюється в Cassandra майже з таким же синтаксисом, як у SQL:

CREATE TABLE users (
	user_id uu id,
	name text,
	year int,
	salary float,
	PRIMARY KEY(user_id)

)

Primary key у разі складається з однієї колонки, і вона є ключем партиционирования.

Як у нас ляжуть користувачі? Частина потрапить на одну ноду, частина – на іншу, і частина – на третю. Виходить звичайна хеш-таблиця, вона ж map, вона ж у Python – словник, вона ж – проста Key value-структура, з якої ми можемо читати всі значення, читати та писати за ключом.

Cassandra. Як не померти, якщо знаєш лише Oracle

Select: коли allow filtering перетворюється на full scan, або як не треба робити

Давайте напишемо якийсь select statement: select * from users where, userid = . Виходить як би в Oracle: пишемо select, вказуємо умови і все працює, користувачі дістаються. Але якщо вибрати, наприклад, користувача з певним роком народження, Cassandra лається, що вона не може виконати запит. Тому що вона взагалі нічого не знає про те, як у нас розподіляються дані про рік народження — у неї як ключ вказана лише одна колонка. Тоді вона каже: «Добре, я можу, як і раніше, виконати цей запит. Додайте allow filtering». Ми додаємо директиву, все працює. І в цей момент відбувається страшне.

Коли ми ганяємо на тестових даних, то все чудово. А коли ви виконуємо запит у продакшені, де у нас, наприклад, 4 мільйони записів, то у нас все не дуже добре. Тому що allow filtering - це директива, яка дозволяє Cassandra зібрати всі дані з цієї таблиці з усіх нод, всіх дата-центрів (якщо їх багато в цьому кластері) і лише потім вже відфільтрувати. Це аналог Full Scan і навряд чи від нього хтось у захваті.

Якби нам потрібні були користувачі лише за ідентифікаторами, нас це влаштувало б. Але іноді нам потрібно писати інші запити та накладати інші обмеження на вибірку. Тому згадуємо: це все у нас карта, яка має ключ партиціонування, але всередині неї — відсортована карта.

І вона теж має ключ, який ми називаємо Сlustering Key. Цей ключ, який у свою чергу складається з колонок, які ми виберемо, за допомогою якого Cassandra розуміє, як у неї дані фізично відсортуються і лежатимуть на кожній ноді. Тобто для якогось Partition key Clustering key розповість, як саме дані запхати в це дерево, яке місце вони там займуть.

Це реально дерево, там просто викликається компаратор, в який ми передаємо якийсь набір колонок у вигляді об'єкта, і він задається також у вигляді перерахування колонок.

CREATE TABLE users_by_year_salary_id (
	user_id uuid,
	name text,
	year int,
	salary float,
	PRIMARY KEY((year), salary, user_id)

Зверніть увагу на директиву Primary key, у неї перший аргумент (у нашому випадку рік) завжди йде Partition key. Він може складатися з однієї або кількох стовпчиків, це не важливо. Якщо колонок кілька, його потрібно ще раз у дужки прибрати, щоб препроцесор мови зрозумів, що це саме Primary key, а за ним решта всіх колонок — Clustering key. При цьому вони в компараторі передаватимуться в тому порядку, в якому вони йдуть. Тобто, перша колонка більш значуща, друга менш значуща і так далі. Як ми для data classes пишемо, наприклад, поля equals: перераховуємо поля, і їм пишемо, які більше, а які менше. У Cassandra це, умовно кажучи, поля data class, до якого застосовуватиметься написаний для нього equals.

Задаємо сортування, накладаємо обмеження

Потрібно пам'ятати, що порядок сортування (зменшується, зростаюча, не важливо) задається в той же момент, коли створюється ключ, і змінити його потім буде не можна. Він фізично визначає, як будуть розсортовані дані та як вони лежатимуть. Якщо потрібно буде змінити Clustering key або порядок сортування, доведеться створювати нову таблицю та переливати до неї дані. З уже існуючою так не вийде.

Cassandra. Як не померти, якщо знаєш лише Oracle

Ми заповнили нашу таблицю користувачами і побачили, що вони лягли в кільце спочатку за рік народження, а потім усередині на кожній ноді із зарплати та user ID. Тепер ми можемо селектити, накладаючи обмеження.

З'являється знов наш працюючий where, and, і користувачі дістаються нам, і все знову добре. Але якщо ми спробуємо використовувати тільки частину Clustering key, причому менш значущу, то Cassandra відразу свариться, що не може в нашій карті знайти місце, де цей об'єкт, у якого ось ці поля для компаратора null, а ось цей, який щойно задали , - Де він лежить. Мені доведеться знову підняти всі дані з цієї ноди та відфільтрувати їх. І це аналог Full Scan у рамках ноди, це погано.

У будь-якій незрозумілій ситуації створювай нову таблицю

Якщо ми хочемо мати можливість діставати користувачів за ID або віком, або зарплатою, що робити? Нічого. Просто використати дві таблиці. Якщо треба діставати користувачів трьома різними способами - таблиць буде три. Минули часи, коли ми економили місце на гвинті. Це найдешевший ресурс. Він коштує набагато дешевше, ніж час відповіді, який може бути згубним для користувача. Користувачеві набагато приємніше отримати щось за секунду, ніж за 10 хвилин.

Ми обмінюємо зайве місце, денормалізовані дані на можливість добре масштабуватися, надійно працювати. Адже насправді кластер, який складається з трьох дата-центрів, у кожному з яких по п'ять нід, за прийнятного рівня збереження даних (коли точно нічого не загубиться), здатний пережити загибель одного дата-центру повністю. І ще по дві ноди в кожному з двох, що залишилися. І ось тільки після цього розпочнуться проблеми. Це досить хороше резервування, воно коштує пари-трійки зайвих ssd-накопичувачів та процесорів. Тому для того, щоб використовувати Cassandra, яка ніколи не SQL, в якій немає відносин, зовнішніх ключів, потрібно знати прості правила.

Проектуємо все від запиту. Головними стають не дані, бо, як додаток збирається із нею працювати. Якщо йому потрібно отримувати різні дані різними способами або ті самі дані різними способами, ми повинні покласти їх так, як буде зручно додатку. Інакше ми провалюватимемося в Full Scan і ніякої переваги Cassandra нам не дасть.

Денормалізувати дані – це норма. Забуваємо про нормальні форми, ми більше не реляційні бази. Покладемо щось 100 разів, лежатиме 100 разів. Це все одно дешевше, ніж гальмувати.

Вибираємо ключі для партиціонування так, щоб вони розподілялися нормально. Нам не потрібно щоб хеш від наших ключів потрапляв в один вузький діапазон. Тобто рік народження у прикладі вище – поганий приклад. Вірніше, він хороший, якщо у нас користувачі за роком народження нормально розподілені, і поганий, якщо йдеться про учнів 5-го класу — там не дуже добре партиціонуватиметься.

Сортування вибирається раз на етапі створення Clustering Key. Якщо її потрібно буде змінити, доведеться переливати нашу таблицю з іншим ключем.

І найважливіше: якщо нам потрібно 100 різними способами забрати ті самі дані, значить у нас буде 100 різних таблиць.

Джерело: habr.com

Додати коментар або відгук