Apache Cassandra

Представляю вашій увазі другу статтю із серії, присвяченій процесу запису даних у СУБД Cassandra. Цього разу я планую надати інформацію про наступний компонент — проміжний кеш даних колонкових сімейств.

Відразу після приміщення даних Commit Log, інформація також дублюється у структуру, звану Memtable. Цей компонент має такі властивості:

  • Елементи, що зберігаються в Memtable, відсортовані за ключами записів;
  • Дані всередині записів відсортовані колонками;
  • Для пошуку та вставки елементів використовується алгоритм Java ConcurrentSkipListMap;
  • Для кожного колонкового сімейства існує окрема MemTable (в англійській термінології Memtable описується як per-ColumnFamily structure1 ).

Тепер розглянемо все докладніше. Щоб краще зрозуміти структуру Memtable, доповнимо ілюстрації з першої частини огляду (Apache Cassandra. Запис даних. Частина 1 - Commit Log). Нагадаю, що зображення створені подібно до ілюстрацій з офіційної документації2.

Cassandra
Принцип роботи: процес запису даних починається з логування всіх операцій у Commit Log, це необхідно для відновлення у разі збою роботи вузла. Відразу після цього дані дублюються в Memtable. Однак Memtable не здійснює логування, тобто точне запам'ятовування всіх операцій, а тільки зберігає найсвіжіші дані. Це цілком логічно з тієї точки зору, що місце в оперативній пам'яті значно дорожче, ніж місце на жорсткому диску. Виходячи з того, що дані в Memtable упорядковані за ключом запису, а всередині ключа по колонках, наш малюнок можна ускладнити відповідним чином. Візьмемо розширений варіант схеми також із попередньої частини оглядупроцесу запису даних:

Cassandra

Потрібні невеликі пояснення - оскільки для кожного колонкового сімейства існує окрема структура даних Memtable, маркування CF означає Column Family. Всередині кожного CF дані упорядковані за ключом запису. Column Data означає корисні дані, подані у вигляді колонок. Різна довжина сегментів даних зумовлена ​​тим, що кількість та обсяг колонок усередині запису може значно відрізнятися навіть у рамках одного колонкового сімейства. Цю особливість архітектури я описав у статті Apache Cassandra. Первинні ключі та особливості зберігання записів.

Для пошуку та вставки нових елементів у Memtable використовується алгоритм Skip List3, вірніше його реалізація на Java - ConcurrentSkipListMap4 5. Розуміння принципу його роботи не зовсім обов'язкове в контексті вивчення Memtable, проте цей алгоритм робить і без того швидкий пошук оперативної пам'яті ще швидше і тому я коротко постараюсь пояснити його основи.

Більш детально розглянути алгоритм по кроках можна у статті на Хабрі7 та деяких інших джерелах8, а ми йдемо далі.

До даних, що знаходяться в Memtable, згодом можна отримувати доступ як до кешу при запитах читання. Це також одна з відмінностей від Commit Log, до якої не можуть здійснюватись клієнтські запити.

Коли Memtable досягає певного обсягу, відбувається скидання (flush) даних у структуру, звану SSTable, яка розташовується на жорсткому диску. Регулювати максимальний обсяг Memtable можна за допомогою параметраmemtable_total_space_in_mb, який за умовчанням з версії Cassandra 2.0.29 став дорівнює чверті (раніше третина) від об'єму пам'яті, що виділяється для динамічної пам'яті Java (Java heapsize).

У свою чергу керування пам'яттю Java здійснює Cassandra самостійно і в залежності від обсягу RAM встановлюються наступні значення10 :

RAM Heap Size
Менш 2GB1/2 RAM
Від 2GB до 4GB1GB
Більше 4GB1/4 RAM, але не більше 8GB

Виставляти обсяг більше 8ГБ не рекомендують через зростаючі до критичного рівня витрати на завдання обслуговування (таких як garbage collection), а також можливість впливу на процеси операційної системи:

Багато користувачів нових в Cassandra є спрямовані на перевірку Java основу розміру вище високої, які consumes majority of underlying system’s RAM. У більшості випадків, удосконалюючи Java heap size є дійсний додатковий для цих умов:

— У більшості випадків, здатність Java до чудового handle garbage collection над 8GB quickly diminishes.

— Modern operating systems maintain the OS page cache for frequently accessed data and are very good at keeping this data in memory, але can be prevented from doing its job by elevated Java heap size.

Якщо ви маєте більше, ніж 2 ГБ системи пам'яті, яка є типовою, зберігати розмір Java application relatively small to allow more memory for page cache.

Наступний параметр, який відповідає за роботу Memtable -file_cache_size_in_mb. Судячи з опису офіційної документації, використовується як проміжний кеш для читання даних (SSTable-reading buffer) перед їх записом в SSTable.

Здійснювати гнучке управління Memtable можна за допомогою параметрів memtable_flush_writers 11 іmemtable_flush_queue_size 12. Перший параметр відображає кількість (від 2 до 8 за замовчуванням) екземплярів процесу, що відповідає заскидання даних на жорсткий диск. Якщо у вас безліч каталогів даних, великий розмір Java heap або в процесі переміщення даних з Memtable на HDD бере участь багато жорстких дисків, рекомендується збільшити це значення і таким чином розпаралелити процес. Це також актуально при використанні SSD. Другий параметр задає максимальну кількість повних Memtable, які очікують на скидання на жорсткий диск. Його рекомендують встановлювати значення, яке дорівнює максимальній кількості індексів будь-якого колонкового сімейства. Ця рекомендація пов'язана з особливістю зберігання індексів: індекс для колонкового сімейства - це фактично ще одне колонкове сімейство, ключем якого є індексне поле оригінального CF13 :

На стіні літопису, в вторинному indexі є лише одна columnа родини, де key is the value of the indexed column, and the columns contain the row keys of indexed table...

…Cassandra co-locates index entries зі своїми поєднаними оригіналом table keys.

Варто окремо розглянути зміни щодо Memtable, які були введені у версії Cassandra 2.114. Якщо до цієї версії Memtable розташовувалися виключно Java heap, то тепер можна перемістити буфер Memtable у власну пам'ять Cassandra. У зв'язку з цим були додані відповідні змінні параметри конфігурації. Параметрmemtable_allocation_type визначає три15 нових значення (перше я пропущу, оскільки воно відповідає типу зберігання до версії 2.1)16 :

— offheap_buffers переміщує графичну назву і значення до DirectBuffer objects. Це має низький impact on reads — ціни є "живі" Java buffers — але тільки скорочено, що значно спричиняють великі strings or blobs.

Для мене залишаютьсяактуальними питання за що відповідає DirectBuffer і як він працює, адже спочатку йдеться "moves the cell name and value to DirectBuffer", а потім додається "the values ​​are still "live" Java buffers". На Stackoverflow я отримав відповідь17 з цього приводу:

В DirectBuffer є пам'ятник, що розташований безпосередньо за допомогою JNA (probably using malloc), він не може не знаходити «живі» Java об'єкти, вони повинні бути serialized. Yet this memory is not managed by the JVM hence ignored by GC.

Пряме відношення до типу кеша Memtable мають параметри memtable_heap_space_in_mb18 та memtable_offheap_space_in_mb19, які відповідають за розміри буфера при on-heap та off-heap розміщенні відповідно.

Останній новий параметр - memtable_cleanup_threshold20. Задає відношення зайнятого не скидається розміру Memtable до загального допустимого розміру. Високе значення означатиме часте скидання даних у невеликі за обсягом SSTable.

У деяких джерелах можна зустріти дещо застарілу інформацію про memtable. Нижче деякі міркування, які можуть бути актуальними для власників попередніх версій Cassandra, проте не варто все приймати на віру, у багатьох випадках це лише мої власні роздуми.

Відомо, що для одного колонкового сімейства може існувати кілька Memtable - одна поточна, інші, що очікують процесу скидання на диск21 :

Однак залишається питання, що означає «повна Memtable» (full memtable) і чому рекомендується встановлювати значення параметра memtable_flush_queue_size до максимальної кількості індексів для одного колонкового сімейства22 :

Число повних блискавок до повного перетину flush (метальних waiting for a write thread). При мінімально, набір до максимального числаindexes created on a single table.

Можливо, має місце наступний алгоритм: коли одиночний сегмент commit log досягає свого максимального обсягу, створюється новий файл commit log (один для всіх CF) на жорсткому диску і нові Memtable (по одній для кожного CF) в оперативній пам'яті. Одночасно старий commit log позначається бітом 0 (тобто очікує на операції flush). Якщо буде досягнута одна з максимальних меж - commitlog_total_space_in_mb для commit log і memtable_total_space_in_mb для memtable, найстарші повні memtable починають поміщатися в чергу (memtable flush queue) і після їх скидання на диск в SSTable, видаляються (точно також. Це лише моє припущення на основі інформації, яку мені вдалося знайти в мережі.