Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

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

Всім привіт! Мене звуть Олексій, я роблю ClickHouse.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

По-перше, відразу поспішаю вас порадувати, я не сьогодні вам розповідатиму, що таке ClickHouse. Якщо чесно, мені це набридло. Я щоразу розповідаю, що це. І, мабуть, уже всі знають.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Натомість я розповідатиму, які є можливі граблі, тобто як можна неправильно використовувати ClickHouse. Насправді боятися не варто, тому що ми розробляємо ClickHouse як систему, яка проста, зручна, працює із коробки. Поставив і все, жодних проблем.

Але все-таки треба враховувати, що ця система спеціалізована і можна легко натрапити на незвичайний сценарій використання, який виведе цю систему із зони комфорту.

Отже, які граблі? Здебільшого я говоритиму про очевидні речі. Всім очевидно, всі розуміють і можуть порадіти, що вони такі розумні, а хто не розуміють, ті дізнаються щось нове.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Перший найпростіший приклад, який, на жаль, часто зустрічається, це велика кількість inserts з маленькими batches, тобто велика кількість маленьких inserts.

Якщо розглядати, як ClickHouse виконує insert, то можна за один запит відправити хоч потік даних на терабайт. Це не проблема.

І подивимося, яка типова буде продуктивність. Наприклад, таблиця у нас із даних Яндекс.Метрики. Хіти. 105 якихось стовпців. 700 байт у стиснутому вигляді. І вставлятимемо батчами по одному мільйону рядків.

Вставляємо в таблицю MergeTree, виходить півмільйона рядків на секунду. Чудово. У репліковану таблицю – трохи менше буде приблизно 400 000 рядків на секунду.

І якщо включити кворумну вставку, виходить трохи менше, але все одно пристойна продуктивність, 250 000 термін в секунду. Кворумна вставка – це недокументована можливість ClickHouse*.

* станом на 2020 рік, вже документовано.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Що буде, якщо погано робити? Вставляємо по одному рядку в таблицю MergeTree і виходить 59 рядків за секунду. Це у 10 000 разів повільно. У ReplicatedMergeTree – 6 рядків на секунду. А якщо ще кворум увімкнеться, то виходить 2 рядки на секунду. На мою думку, це якийсь відмінний відстій. Як можна так гальмувати? У мене навіть на футболці написано, що ClickHouse не повинен гальмувати. Проте буває іноді.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Насправді це наш недолік. Ми могли б зробити так, щоб усе працювало нормально, але не зробили. І ми це не зробили, тому що для нашого сценарію це не потрібно. У нас і так були батчі. Просто до нас на вхід надходили батчі, і жодних проблем. Вставляємо і все нормально працює. Але, звісно, ​​можливі будь-які сценарії. Наприклад, коли ви маєте купу серверів, на яких дані генеруються. І вони вставляють дані не так часто, але все одно виходять часті вставки. І треба цього якось уникнути.

З технічної точки зору суть у тому, що коли ви робите insert в ClickHouse, то дані не потрапляють ні в який момент. У нас навіть не справжній log structure MergeTree, а просто MergeTree, тому що немає ні log'а, ні memTable. Ми просто відразу записуємо дані у файлову систему, вже розкладені по стовпчикам. І якщо у вас 100 стовпців, більше 200 файлів треба буде записати в окрему директорію. Все це дуже громіздко.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

І виникає питання: "Як робити правильно?", якщо така ситуація, що потрібно все ж таки якось записувати дані в ClickHouse.

Спосіб 1. Це найпростіший спосіб. Використовувати якусь розподілену чергу. Наприклад, Kafka. Просто виймаєте дані з Kafka, бачимо раз на секунду. І все буде нормально, ви записуєте, все нормально працює.

Недоліки в тому, що Kafka це ще одна громіздка розподілена система. Я ще розумію, якщо у вас у компанії вже є Kafka. Це добре, це зручно. Але якщо її немає, то варто тричі подумати, перш ніж тягнути ще одну розподілену систему собі в проект. І тому варто розглянути альтернативу.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Спосіб 2. Ось така олдскульна альтернатива і при цьому дуже проста. Є у вас якийсь сервер, який генерує ваші логи. І він просто записує ваші логі у файл. І раз на секунду, наприклад, цей файл перейменовуємо, відриваємо новий. І окремий скрипт або по cron, або якийсь daemon бере найстаріший файл і записує в ClickHouse. Якщо записувати логи раз на секунду, то все буде чудово.

Але недолік цього способу в тому, що якщо у вас сервер, на якому генеруються логи кудись зник, то дані теж зникнуть.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Спосіб 3. Є ще один цікавий спосіб, що взагалі без тимчасових файлів. Наприклад, у вас якась рекламна крутилка або ще якийсь цікавий daemon, який генерує дані. І ви можете накопичувати пачку даних прямо в оперативній пам'яті, в буфері. І коли проходить достатня кількість часу, ви цей буфер відкладаєте убік, створюєте новий, а в окремому потоці те, що вже накопичилося, вставляєте в ClickHouse.

З іншого боку, дані теж при kill -9 зникають. Якщо ваш сервер впаде, ви втратите ці дані. І ще проблема в тому, що якщо ви не змогли записати в базу, то у вас дані накопичуватимуться в оперативній пам'яті. І або закінчиться оперативна пам'ятка, або просто втратите дані.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Спосіб 4. Ще один цікавий спосіб. Є у вас якийсь серверний процес. І він може надсилати дані в ClickHouse відразу, але робити це в одному з'єднанні. Наприклад, відправив http-запит з transfer-encoding: chunked з insert'ом. І генерує чанки вже не дуже рідко, можна кожен рядок відправляти, хоча буде overhead на кадр цих даних.

Але тим не менш у цьому випадку дані будуть відправлені в ClickHouse відразу. І ClickHouse сам їх буферизуватиме.

Але також виникають проблеми. Тепер ви втратите дані, у тому числі, коли ваш процес уб'ється і, якщо процес ClickHouse уб'ється, тому що це буде незавершений insert. А в ClickHouse вводять атомарні атоми до певного вказаного порога в розмірі рядків. В принципі це цікавий спосіб. Теж можна використовувати.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Спосіб 5. Ось ще один цікавий спосіб. Це якийсь розроблений community – сервер для батчингу даних. Я на нього сам не дивився, тож нічого гарантувати не можу. Втім, і для самого ClickHouse жодних гарантій не надається. Це теж Open Source, але з іншого боку, ви могли звикнути до деякого стандарту якості, який ми намагаємося забезпечувати. А ось для цієї штуки – я не знаю, зайдіть на GitHub, перегляньте код. Можливо, щось нормальне написали.

* станом на 2020 рік, слід також додати на розгляд KittenHouse.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Спосіб 6. Ще один спосіб - це використання Buffer таблиць. Переваги цього в тому, що це дуже просто почати використовувати. Створюєте Buffer таблицю та вставляєте до неї.

А недолік у тому, що проблема вирішується не повністю. Якщо при ставці типу MergeTree ви повинні групувати дані по одному батчу в секунду, то при ставці в buffer таблицю, вам потрібно групувати хоча б до кілька тисяч в секунду. Якщо буде більше 10 000 за секунду, то все одно буде погано. А якщо вставляти батчами, то ви бачили, що там виходить сотня тисяч рядків на секунду. І це вже на досить тяжких даних.

І також buffer таблиці не мають лога. І якщо з вашим сервером щось не так, дані будуть втрачені.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

І як бонус нещодавно у нас в ClickHouse з'явилася можливість забирати дані з Kafka. Існує двигун таблиць - Kafka. Ви просто творите. І на нього можна навісити матеріалізовані уявлення. У цьому випадку він сам вийматиме дані з Kafka і вставлятиме в потрібні вам таблиці.

І особливо тішить у цій можливості те, що її робили не ми. Це community фіча. І коли я говорю «community фича», я говорю без жодної зневаги. Код ми читали, ревю робили, має працювати нормально.

* станом на 2020 рік, з'явилася аналогічна підтримка для RabbitMQ.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Що ще може бути незручним або несподіваним при вставці даних? Якщо ви робите запит insert values ​​і в values ​​пишіть якісь вирази, що обчислюються. Наприклад, now() – це теж обчислюване вираз. І в цьому випадку ClickHouse змушений на кожен рядок запускати інтерпретатор цих виразів, і продуктивність просяде на порядки. Краще цього уникати.

* на даний момент проблема повністю вирішена, регресії продуктивності при використанні виразів в VALUES більше немає.

Інший приклад, коли можуть бути деякі проблеми, коли у вас на одному батчі дані відносяться до купи партицій. За замовчуванням у ClickHouse партиції за місяцями. І якщо ви вставляєте батч із мільйона рядків, а там дані за кілька років, то у вас там буде кілька десятків партицій. І це еквівалентно тому, що будуть батчі в кілька десятків разів меншого розміру, тому що всередині вони завжди спочатку розбиваються партиціями.

* Нещодавно в ClickHouse в експериментальному режимі додано підтримку компактного формату шматків і шматків в оперативній пам'яті з write-ahead log, що майже повністю вирішує проблему.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Тепер розглянемо другий вид проблеми – це типізація даних.

Типізація даних буває жорстка, а буває рядкова. Рядкова – це, коли ви просто взяли та оголосили, що у вас усі поля типу string. Це відстій. Так не треба робити.

Давайте розберемося, як робити правильно у тих випадках, коли хочеться сказати, що якесь поле у ​​нас, рядок, і нехай ClickHouse сам розбереться, а я паритися не буду. Але все-таки варто робити деякі зусилля.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Наприклад, у нас є IP-адреса. В одному випадку ми його зберегли як рядок. Наприклад, 192.168.1.1. А в іншому випадку – це буде число UInt32*. 32 біт достатньо для IPv4 адреси.

По-перше, як не дивно, дані стиснуться приблизно однаково. Там буде різниця, звісно, ​​але не така велика. Тож щодо дискового введення-виводу особливих проблем немає.

Але є серйозна різниця за процесорним часом та за часом виконання запиту.

Порахуємо кількість унікальних IP-адрес, якщо вони зберігаються у вигляді чисел. Виходить 137 мільйонів рядків на секунду. Якщо те саме у вигляді рядків, то 37 мільйонів рядків на секунду. Я не знаю, чому такий збіг вийшов. Я сам виконував ці запити. Проте приблизно в 4 рази повільніше.

А якщо порахувати різницю у місці на диску, то різниця теж є. І різниця десь на одну чверть, бо унікальних IP-адрес досить багато. І якби тут були рядки з невеликою кількістю різних значень, то вони спокійно стиснулися б за словником приблизно в однаковий обсяг.

І чотириразова різниця у часі на дорозі не валяється. Можливо, вам, звичайно, на фіг, але коли я бачу таку різницю, мені стає сумно.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Розглянемо різні випадки.

1. Один випадок, коли у вас різних унікальних значень небагато. У цьому випадку використовуємо просту практику, яку ви, напевно, знаєте та можете використовувати для будь-яких СУБД. Це все має сенс не тільки для ClickHouse. Просто записуєте до бази числові ідентифікатори. А конвертувати в рядки та назад можна вже на стороні вашої програми.

Ось, наприклад, у вас є регіон. І ви його намагаєтесь зберегти у вигляді рядка. І там написано буде: Москва та МО. І коли я бачу, що там написано «Москва», то це ще нічого, а коли й МО, то зовсім сумно стає. Це скільки байт.

Натомість ми просто записуємо число Ulnt32 і 250. У нас 250 в Яндексі, а у вас, можливо, по-іншому. Про всяк випадок скажу, що в ClickHouse є вбудована можливість роботи з геобазою. Ви просто записуєте довідник з регіонами, в тому числі ієрархічний, тобто там буде і Москва, і МО, і все, що вам потрібно. І можна конвертувати лише на рівні запиту.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Другий варіант приблизно такий самий, але вже з підтримкою всередині ClickHouse. Це тип даних Enum. Ви просто всередині Enum прописуєте всі необхідні вам значення. Наприклад, тип пристрою там пишіть: десктоп, мобільний, планшет, телевізор. Усього 4 варіанти.

Недолік у тому, що потрібно періодично вказувати. Додали лише один варіант. Робимо alter table. Насправді alter table в ClickHouse безкоштовний. Особливо безкоштовний для Enum, тому що дані на диску не змінюються. Проте alter захоплює блокування* на таблицю і повинен почекати, поки виконуються всі selects. І тільки після цього alter виконається, тобто деякі незручності присутні.

* У нових версіях ClickHouse, ALTER зроблений повністю неблокуючим.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ще один варіант досить унікальний для ClickHouse - підключення зовнішніх словників. Ви можете писати в ClickHouse числа, а ваші довідники тримати в будь-якій зручній для вас системі. Наприклад, можна використовувати: MySQL, Mongo, Postgres. Можна навіть свій мікросервіс запиляти, який по http віддаватиме ці дані. І на рівні ClickHouse ви пишете функцію, яка буде ці дані перетворювати з чисел на рядки.

Це спеціалізований, але дуже ефективний спосіб виконати join із зовнішньою таблицею. Причому є два варіанти. В одному варіанті ці дані будуть повністю закешовані, будуть повністю присутні в оперативній пам'яті і оновлюватися з деякою періодичністю. А в іншому варіанті, якщо ці дані в оперативну пам'ять не поміщаються, то можна їх частково кешувати.

Ось приклад. Є Яндекс.Дірект. І там є рекламна компанія та банери. Рекламних компаній, напевно, близько десятка мільйонів. І приблизно поміщаються в оперативку. А банерів – мільярди, вони не містяться. І ми використовуємо словник, що кешується з MySQL.

Єдина проблема в тому, що словник, що кешується, буде працювати нормально, якщо hit rate близький до 100%. Якщо менше, то при обробці запитів на кожну пачку даних треба буде реально брати відсутні ключі і ходити забирати дані з MySQL. Про ClickHouse я ще можу заручитися, що так, не гальмує, про інші системи говорити не буду.

А як бонус те, що словники - це дуже простий спосіб оновлювати дані в ClickHouse заднім числом. Т. е. був у вас звіт по рекламній компаніям, користувач просто змінив рекламну компанію і у всіх старих даних, у всіх звітах ці дані теж змінилися. Якщо писати рядки безпосередньо в таблицю, оновлювати їх буде неможливо.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ще один спосіб, коли ви не знаєте, звідки взяти ідентифікатори для ваших рядків. можна просто захешувати. Причому найпростіший варіант - це брати 64 бітний хеш.

Єдина проблема в тому, що якщо хеш 64-бітний, то колізії у вас обов'язково будуть майже напевно. Бо якщо там мільярд рядків, то ймовірність вже стає відчутною.

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

І є простий трюк. Правда, для серйозних даних теж не дуже підходить, але якщо щось не дуже серйозне, просто додайте в ключ словника ще ідентифікатор клієнта. І тоді колізії у вас будуть, але лише в межах одного клієнта. І такий спосіб у нас використовується для мапи посилань в Яндекс.Метриці. Є у нас там урли, ми бережемо хеші. І ми знаємо, що колізії, звісно, ​​є. Але коли відображається сторінка, то ймовірність того, що саме на одній сторінці в одного користувача якісь урли злиплися і це ще помітять, це можна знехтувати.

Як бонус – для багатьох операцій достатньо одних хешів і самі рядки можна ніде не зберігати.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Інший приклад, якщо рядки короткі, наприклад домени сайтів. Їх можна зберігати як є. Або, наприклад, мова браузера ru - 2 байти. Мені звичайно, дуже шкода байтики, але не турбуйтеся, 2 байти не шкода. Будь ласка, зберігайте як є, не парьтесь.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Інший випадок, коли, навпаки, рядків дуже багато і в них при цьому дуже багато унікальних та ще й безліч потенційно не обмежена. Типовий приклад – це пошукові фрази чи урли. Пошукові фрази, у тому числі через помилки. Подивимося, скільки унікальних пошукових фраз на добу. І виходить, що їх майже половина від усіх подій. І в цьому випадку ви могли б подумати, що потрібно нормалізувати дані, вважати ідентифікатори, складати в окрему таблицю. Але робити так не треба. Просто зберігайте ці рядки як є.

Краще - нічого не вигадуйте, тому що якщо окремо зберігати, то потрібно робити join. А цей join - це в кращому разі випадковий доступ до пам'яті, якщо ще пам'ять поміститься. Якщо не поміститься, то взагалі будуть проблеми.

А якщо дані зберігаються в in place, то вони просто вичитуються в потрібному порядку із файлової системи і все нормально.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

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

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

Давайте подивимося, яка виходить різниця. У ClickHouse є спеціалізована функція, яка обчислює домен. Вона дуже швидка, ми її оптимізували. І, чесно скажу, вона навіть не відповідає RFC, але тим не менш вважає все, що нам потрібно.

І в одному випадку ми просто діставатимемо урли і обчислюватимемо домен. Виходить 166 мілісекунд. А якщо взяти готовий домен, то виходить лише 67 мілісекунд, тобто майже втричі швидше. Причому швидше не через те, що нам потрібно робити якісь обчислення, а через те, що ми читаємо менше даних.

Ось чомусь у одного запиту, який повільніше, виходить більша швидкість гігабайтів на секунду. Тому що він читає більше гігабайт. Це зайві дані. Запит працює швидше, але виконується за більш тривалий час.

А якщо подивитися обсяг даних на диску, то виходить, що урл 126 мегабайтів, а домен лише 5 мегабайтів. Виходить у 25 разів менше. Проте запит виконується лише в 4 рази швидше. Але це тому, що дані гарячі. А якби були холодні, то напевно було в 25 разів швидше через дискове введення-виведення.

До речі, якщо оцінити, наскільки домен менше ніж урл, то виходить десь у 4 рази. Але чомусь на диску дані займають у 25 разів менше. Чому? Через стиснення. І урл стискається, і домен стискується. Але часто урл містить купу сміття.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

І, звичайно, варто використовувати правильні типи даних, які призначені спеціально для потрібних значень або які підходять. Якщо ви в IPv4, зберігайте UInt32*. Якщо IPv6, то FixedString(16), оскільки IPv6 адреса – це 128 біт, т. е. зберігайте у бінарному форматі.

А що робити, якщо у вас іноді IPv4 адреси, а іноді IPv6? Так, можна зберігати обидва. Один стовпець для IPv4, інший для IPv6. Звичайно, є варіант IPv4 відображати IPv6. Це теж працюватиме, але якщо вам у запитах часто потрібна саме IPv4 адреса, то непогано б засунути в окремий стовпець.

* тепер у ClickHouse є окремі типи даних IPv4, IPv6, які зберігають дані так само ефективно, як числа, але представляють їх так само зручно, як рядки.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ще важливо зауважити, що варто дані заздалегідь обробити. Наприклад, надходять до вас якісь сирі логи. І, можливо, варто не відразу їх засовувати в ClickHouse, хоча дуже привабливо нічого не робити і все буде працювати. Але варто таки провести ті обчислення, які можна.

Наприклад, версія браузера. У деякому сусідньому відділі, який мені не хочеться показувати пальцем, там версія браузера зберігається так, т. е. як рядок: 12.3. А потім, щоб зробити звіт, вони беруть цей рядок і ділять на масив, а потім перший елемент масиву. Звичайно, все гальмує. Я питав, чому вони так роблять. Вони мені відповіли, що не люблять передчасної оптимізації. А я не люблю передчасної песимізації.

Так що в цьому випадку правильніше буде розділити на 4 стовпці. Тут не бійтеся, тому що це ClickHouse. ClickHouse – це стовпцева база даних. І що більше акуратних маленьких стовпців, то краще. Буде 5 BrowserVersion, робіть 5 стовпців. Це нормально.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Тепер розглянемо, що робити, якщо у вас багато довгих рядків, дуже довгих масивів. Їх не потрібно зберігати в ClickHouse взагалі. Натомість ви можете зберегти в ClickHouse тільки якийсь ідентифікатор. А ці довгі рядки засуньте їх у якусь іншу систему.

Наприклад, в одному із наших аналітичних сервісів є деякі параметри подій. І якщо на події приходить багато параметрів, ми просто зберігаємо перші 512, що попалися. Тому що 512 – не шкода.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

А якщо ви не можете визначитися з вашими типами даних, то ви також можете записати дані в ClickHouse, але в тимчасову таблицю типу Log, спеціальну для тимчасових даних. Після цього ви можете проаналізувати, який там у вас розподіл значень, що взагалі є і скласти правильні типи.

* зараз у ClickHouse є тип даних LowCardinality який дозволяє ефективно зберігати рядки з меншими витратами праці.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Тепер розглянемо ще один цікавий випадок. Іноді у людей все якось дивно працює. Я заходжу та бачу таке. І відразу здається, що це робив якийсь дуже досвідчений, розумний адмін, який має великий досвід налаштування MySQL версії 3.23.

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

У принципі, я поважаю чужий досвід, зокрема розумію, якими стражданнями цей досвід може бути напрацьований.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

І причини більш-менш зрозумілі. Це старі стереотипи, які могли накопичитися під час роботи з іншими системами. Наприклад, у MyISAM таблицях немає кластерного первинного ключа. І такий спосіб поділу даних може бути запеклою спробою отримати ту ж функціональність.

Інша причина – те, що будь-які операції типу alter над великими таблицями робити важко. Все блокуватиметься. Хоча в сучасних версіях MySQL ця проблема вже не така серйозна.

Або, наприклад, мікрошардування, але про це трохи пізніше.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

У ClickHouse так робити не треба, тому що, по-перше, первинний кластерний ключ, дані впорядковані за первинним ключем.

І іноді мене запитують: "Як змінюється продуктивність діапазонних запитів у ClickHouse від розміру таблиці?". Я говорю, що вона ніяк не змінюється. Наприклад, у вас таблиця мільярда рядків і читаєте діапазон один мільйон рядків. Все нормально. Якщо в таблиці трильйон рядків і ви читаєте один мільйон рядків, то буде майже те ж саме.

І, по-друге, будь-яких штук типу ручних партицій не потрібно. Якщо ви зайдете та подивіться, що там на файловій системі, ви побачите, що таблиця – це досить серйозна річ. І там усередині є щось на кшталт партицій. Т. е. ClickHouse все робить за вас і вам не потрібно страждати.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Alter в ClickHouse безкоштовний, якщо alter add/drop column.

І маленькі таблиці робити не варто, тому що якщо у вас у таблиці 10 рядків або 10 000 рядків, то це неважливо. ClickHouse – це система, яка оптимізує черезвихід, а не стійкість, так що 10 рядків обробляти не мають сенсу.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Правильно використати одну велику таблицю. Позбудьтеся старих стереотипів, все буде добре.

А як бонус у нас в останній версії з'явилася можливість робити довільний ключ партиціонування для того, щоб виконувати всякі maintenance операції над окремими партіціями.

Наприклад, вам потрібно багато маленьких таблиць, наприклад коли буває потреба в обробці якихось проміжних даних, вам надходять чанки і вам потрібно виконувати перетворення над ними перед записом у фінальну таблицю. Для цього випадку є чудовий двигун таблиці - StripeLog. Це приблизно як TinyLog, тільки краще.

* зараз у ClickHouse є ще й таблічна функція input.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ще один антипаттерн – це мікрошардінг. Наприклад, вам дані потрібно шардувати і у вас є 5 серверів, а завтра буде 6 серверів. І ви вважаєте, як ці дані перебалансувати. І замість цього ви розбиваєте не на 5 шардів, а на 1 шардів. І далі відображаєте кожен із цих мікрошардів на окремий сервер. І у вас вийде на одному сервері, наприклад, 000 ClickHouse, наприклад. Окремі місця на окремих портах або окремі бази даних.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Але в ClickHouse це не дуже добре. Тому що навіть один instance ClickHouse намагається використовувати всі доступні ресурси сервера для обробки одного запиту. Т. е. є у вас сервер який-небудь і там, наприклад, 56 процесорних ядер. Ви виконуєте запит, який виконується одну секунду, і використовуватиме 56 ядер. А якщо ви розмістили там 200 ClickHouse на одному сервері, виходить, що запуститься 10 000 потоків. Загалом усе буде дуже погано.

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

І ще одна причина в тому, що буде у вас міжпроцесорна взаємодія TCP. Дані доведеться серіалізувати, десеріалізувати і це безліч мікрошардів. Просто неефективно працюватиме.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ще один антипаттерн, хоч його важко назвати антипаттерном. Це велика кількість передагрегації.

Взагалі передагрегація – це добре. Було у вас мільярд рядків, ви його загрегували і стало 1 000 рядків, і тепер запит виконується миттєво. Все чудово. Так можна робити. І для цього навіть у ClickHouse є спеціальний тип таблиці AggregatingMergeTree, який робить інкрементальну агрегацію при вставці даних.

Але бувають випадки, коли ви думаєте, що ми ось так агрегуватимемо дані і ще так агрегуватимемо дані. І в деякому сусідньому відділі, теж не хочеться говорити в якому, використовують таблиці SummingMergeTree для підсумовування за первинним ключем, а як первинний ключ використовують штук 20 якихось стовпців. Я про всяк випадок змінив імена деяких стовпців для конспірації, але так і є.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

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

І що особливо дістає? Те, що ці люди із сусіднього відділу, ходять і просять іноді додати ще один стовпець у первинний ключ. Т. е. ми ось так ось агрегували дані, а тепер хочемо трохи більше. Але в ClickHouse немає іншого первинного ключа. Тому доводиться писати якісь скрипти на C++. А я не люблю скрипти навіть якщо вони на C++.

І якщо подивитися для чого створювався ClickHouse, то не агреговані дані - це просто той сценарій, для якого він народжений. Якщо ви використовуєте ClickHouse для неагрегованих даних, ви все робите правильно. Якщо ви агрегуєте, то це іноді можна пробачити.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ще один цікавий випадок – це запити у нескінченному циклі. Я іноді заходжу на якийсь production сервер і дивлюся там show processlist. І щоразу виявляю, що відбувається щось жахливе.

Наприклад, таке. Тут відразу ясно, що можна було виконати в одному запиті. Просто пишіть там url in та список.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Чому багато таких запитів у нескінченному циклі – це погано? Якщо індекс не використовується, то у вас буде багато проходів за тими самими даними. Але якщо індекс використовується, наприклад, у вас є первинний ключ ru і ви пишете url = чомусь там. І ви думаєте, що буде точково читатися з таблиці один URL, буде все нормально. Але насправді ні. Тому що ClickHouse все робить по пачках.

Коли йому потрібно прочитати якийсь діапазон даних, він читає трохи більше, тому що індекс ClickHouse розріджений. Цей індекс не дозволяє знайти в таблиці один індивідуальний рядок, тільки якийсь діапазон. І дані стискаються блоками. Для того, щоб прочитати один рядок, потрібно взяти цілий блок і розтиснути його. І якщо ви виконуєте купу запитів, у вас буде багато таких перетинів, і купа роботи у вас буде виконуватися знову і знову.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

І як бонус можна помітити, що в ClickHouse не варто боятися передавати навіть мегабайти і навіть сотні мегабайт у секцію IN. Я пам'ятаю з нашої практики, що якщо MySQL передаємо купу значень в секцію IN, наприклад, передаємо туди 100 мегабайт якихось чисел, то MySQL з'їдає 10 гігабайт пам'яті і більше з цим нічого не відбувається, все працює погано.

А друге - це те, що в ClickHouse, якщо у вас запити використовують індекс, то це завжди не повільніше, ніж full scan, тобто якщо прочитати потрібно майже всю таблицю, він йтиме послідовно і читатиме всю таблицю. Загалом сам розбереться.

Але є деякі складності. Наприклад, те, що IN з підзапитом індекс не використовує. Але це наша проблема, і нам треба це виправляти. Нічого фундаментального тут немає. Будемо лагодити*.

І ще одна цікава річ – це те, що якщо у вас дуже довгий запит та розподілена обробка запитів йде, то цей дуже довгий запит буде надіслано на кожен сервер без стиснення. Наприклад, 100 мегабайтів та 500 серверів. І, відповідно, у вас через мережу буде передано 50 гігабайт. Буде передано і потім все успішно здійсниться.

* вже використовує; все полагодили, як обіцяно.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

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

І рішення тут одне. Якщо ви відкрили API, то вам його доведеться різати. Наприклад, вводити квоти якісь. Інших нормальних варіантів немає. Інакше одразу напишуть скрипт і будуть проблеми.

І в ClickHouse є особлива можливість - це підрахунок квот. І можна передавати свій ключ квоти. Це, наприклад, внутрішній ідентифікатор користувача. І квоти рахуватимуть незалежно для кожного з них.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Тепер ще одна цікава річ. Це реплікація на ручному приводі.

Я знаю багато випадків, коли, незважаючи на те, що ClickHouse має вбудовану підтримку реплікації, люди реплікують ClickHouse вручну.

Принцип який? У вас є pipeline обробки даних. І він працює незалежно, наприклад, у різних дата-центрах. Ви однакові дані однаково записуєте в ClickHouse як би. Правда, практика показує, що дані все одно розходитимуться через якісь особливості у вашому коді. Сподіваюся, що у вашому.

І періодично вам все одно доведеться вручну синхронізувати. Наприклад, раз на місяць адміни роблять rsync.

Насправді набагато простіше використовувати вбудовану в ClickHouse реплікацію. Але тут можуть бути деякі протипоказання, тому що для цього потрібно використовувати ZooKeeper. Я нічого поганого про ZooKeeper говорити не буду, в принципі, система робоча, але буває, що люди не використовують його через java-фобію, тому що ClickHouse - це така хороша система, написана на C++, якою можна користуватися і все буде відмінно . А ZooKeeper на java. І якось навіть не хочеться дивитися, але тоді можете використовувати реплікацію на ручному приводі.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

ClickHouse – це практична система. Вона враховує ваші потреби. Якщо у вас реплікація на ручному приводі, ви можете створити Distributed таблицю, яка дивиться на ваші ручні репліки і сама робить між ними failover. І є навіть спеціальна опція, яка дозволяє уникнути флапів навіть якщо ваші репліки систематично розходяться.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Далі можуть бути проблеми, якщо ви використовуєте примітивні table engines. ClickHouse - це такий конструктор, в якому є купа різних движків таблиць. Використовуйте таблиці сімейства MergeTree для всіх серйозних випадків, як написано в документації. А решта – це так, для окремих випадків або для тестів.

У таблиці MergeTree не обов'язково щоб у вас були якась дата і час. Все одно можете використати. Якщо немає дати та часу, пишіть, що default – 2000 рік. Це працюватиме і ресурсів не вимагатиме.

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

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

З іншого боку, можна використовувати примітивні движки таблиць. Наприклад, залити дані один раз і подивитися, покрутити та видалити. Можете використовувати Log.

Або зберігання невеликих обсягів для проміжної обробки – це StripeLog або TinyLog.

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

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

ClickHouse не дуже любить перенормалізовані дані.

Ось типовий приклад. Це величезна кількість урлів. Ви їх засунули до сусідньої таблиці. А потім вирішили з ними робити JOIN, але це працювати не буде, як правило, тому що ClickHouse підтримує лише Hash JOIN. Якщо оперативної пам'яті не вистачає для безлічі даних, з якими треба з'єднувати, JOIN виконати не вийде*.

Якщо дані великої кардинальності, то не парьте, зберігайте їх у денормалізованому вигляді, урли прямо врозташуванні в основній таблиці.

* а зараз у ClickHouse є і merge join теж, і він працює в умовах, коли проміжні дані не поміщаються в оперативну пам'ять. Але це неефективно і рекомендація залишається чинною.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Ще кілька прикладів, але я вже сумніваюся антипатерні вони чи ні.

У ClickHouse є один відомий недолік. Він не вміє апдейти. У певному сенсі, це навіть добре. Якщо у вас якісь важливі дані, наприклад бухгалтерія, то ніхто їх відправити не зможе, тому що апдейтів немає.

* вже давно додана підтримка update та delete у batch режимі.

Але є деякі особливі способи, які дозволяють апдейти як би на тлі. Наприклад, таблиці типу ReplaceMergeTree. Вони роблять апдейти під час фонових merges. Ви можете форсувати це за допомогою optimize table. Але не робіть це занадто часто, тому що це буде повним перезаписом партиції.

Розподілені JOIN у ClickHouse – це теж погано обробляється планувальником запитів.

Погано, але іноді Ок.

Використання ClickHouse лише для того, щоб прочитати дані назад за допомогою select*.

Я не рекомендував би використовувати ClickHouse для громіздких обчислень. Але це не зовсім так, бо ми вже відходимо від цієї рекомендації. І у нас нещодавно додалася можливість застосовувати моделі машинного навчання у ClickHouse – Catboost. І це мене турбує, бо я думаю: «Який жах. Це скільки ж тактів на байт виходить!». Мені дуже шкода такти на байти пускати.

Ефективне використання ClickHouse. Олексій Міловидов (Яндекс)

Але не бійтеся, ставте ClickHouse, все буде добре. Якщо щось, у нас є спільнота. До речі, спільнота – це ви. І якщо у вас якісь проблеми, ви можете хоча б зайти в наш чат, і сподіваюся, що вам допоможуть.

Питання

Дякую за доповідь! Куди скаржитися на падіння ClickHouse?

Можна скаржитися мені особисто зараз.

Я недавно почав використовувати ClickHouse. Відразу упустив cli інтерфейс.

Вам пощастило.

Трохи пізніше я упустив сервер маленьким select'ом.

У вас талант.

Я відкрив баг GitHub, але його проігнорували.

Подивимося.

Олексій обманом затяг мене на доповідь, пообіцявши розповісти, як ви дані всередині тиснете.

Дуже просто.

Це я ще вчора зрозумів. Більше конкретики.

Там немає жодних хитрощів жахливих. Там просто стиск по блоках. За промовчанням використовується LZ4, можна увімкнути ZSTD*. Блоки від 64 кілобайт до 1 мегабайта.

* також є підтримка спеціалізованих кодеків стиснення, які можна використовувати у ланцюжку з іншими алгоритмами.

У блоках просто сирі дані?

Не зовсім сирі. Там масиви. Якщо у вас стовпець числовий, то там цифри поспіль укладені в масив.

Зрозуміло.

Олексій, приклад, який був з uniqExact над айпишниками, тобто те, що uniqExact за рядками довше вважається, ніж за числами і так далі. А якщо ми застосуємо фінт вухами і каститимемо в момент вичитування? Т. е. ви, як би сказали, що на диску воно у нас не сильно відрізняється. Якщо ми читатимемо з диска рядки, каститимемо, то тоді швидше у нас агрегати будуть чи ні? Чи ми все-таки трохи тут виграємо? Мені здається, що ви це тестували, але чомусь не вказали у бенчмарку.

Я думаю, що воно буде повільніше, ніж без касти. У цьому випадку IP-адресу треба з рядка розпарити. У нас, звичайно, у ClickHouse парсинг IP-адрес теж оптимізований. Ми дуже постаралися, але там у вас числа записані в десятитисячній формі. Дуже незручно. З іншого боку, функція uniqExact буде на рядках працювати повільніше не тільки тому, що це рядки, але ще й тому, що вибирається інша спеціалізація алгоритму. Рядки просто обробляються по-іншому.

А якщо взяти примітивніший тип даних? Наприклад, записали user id, який у нас in, записали його рядком, а потім скостили, буде веселіше чи ні?

Я сумніваюся. Я думаю, що буде навіть сумнішим, тому що все-таки парсити числа – це серйозна проблема. Мені здається, що ось у цього колеги була навіть доповідь на тему того, як складно парсити числа у десятитисячній формі, а може й ні.

Олексію, дякую за доповідь! І ще велике спасибі за ClickHouse! У мене питання щодо планів. Чи є у планах фіча для апдейта словників не повністю?

Т. е. часткове перезавантаження?

Так Так. Типу можливість задати там поле MySQL, тобто апдейтіти after, щоб завантажувалися тільки ці дані, якщо словник дуже великий.

Дуже цікава фіча. І, мені здається, якась людина пропонувала її в нашому чаті. Можливо, це були ви.

Не вважаю, що я.

Добре, тепер виходить, що два запити. І можна не поспішаючи починати робити. Але одразу вас хочу попередити, що ця фіча є досить простою для реалізації. Т. е. за ідеєю потрібно просто в таблицю написати номер версії і далі писати: версія менша за таку. А це означає, що швидше за все ми запропонуємо це зробити ентузіастам. Ви ентузіаст?

Так, але, на жаль, не в C++.

Ваші колеги на C++ вміють писати?

Знайду когось.

Відмінно*.

* можливість була додана через два місяці після доповіді – її розробив автор питання та відправив свій запит тягнути.

Спасибо!

Вітаю! Дякую за доповідь! Ви згадували, що ClickHouse дуже добре споживає всі ресурси, доступні йому. І доповідач сусідній із Люксофт розповідав про своє рішення для Пошти Росії. Він сказав, що їм дуже сподобався ClickHouse, але вони не використовували його замість свого основного конкурента саме тому, що він зжирав весь процесор. І вони не змогли встромити його в свою архітектуру, у свій ZooKeeper з докерами. Чи є можливість якось обмежити ClickHouse, щоб він не споживав усе, що йому стає доступним?

Так, можна й дуже легко. Якщо хочете, щоб менше ядер споживав, просто пишіть set max_threads = 1. І все буде в одне ядро ​​виконувати запит. Причому можна різним користувачам вказати різне налаштування. Тож жодних проблем. І колегам із Люксофт передайте, що не добре, що вони не знайшли цього налаштування в документації.

Олексію, привіт! Хотів би поцікавитись таким питанням. Вже не вперше чую, що багато хто починає використовувати ClickHouse як сховище для логів. На доповіді ви говорили, щоби так не робити, тобто не потрібно зберігати довгі рядки. Як ви до цього належите?

По-перше, логи - це, як правило, не довгі рядки. Буває, звісно, ​​винятки. Наприклад, якийсь сервіс, написаний на java, кидають висновок, він логується. І так у нескінченному циклі, і закінчується місце на жорсткому диску. Рішення дуже просте. Якщо рядки дуже довгі, ріжте їх. А що означає довгі? Десятки кілобайт – це погано.

* У нових версіях ClickHouse, включена "адаптивна гранульованість індексу", що знімає проблему зберігання довгих рядків здебільшого.

А кілобайт – це нормально?

Нормально.

Вітаю! Дякую за доповідь! Я вже про це питав у чаті, але не пам'ятаю, чи отримав відповідь. Чи планується якось розширювати секцію WITH на кшталт CTE?

Поки немає. Секція WITH у нас дещо несерйозна. Вона у нас, як маленька фіча.

Я зрозумів. Дякую!

Дякую за доповідь! Дуже цікаво! Світове питання. Чи планується робити, можливо, у вигляді якихось заглушок модифікацію видалення даних?

Обов'язково. Це наше перше завдання у нашій черзі. Ми зараз активно продумали, як усе робити правильно. І варто починати натискати на клавіатуру.

* натискали кнопки на клавіатурі і все зробили.

Чи це вплине якось на продуктивність системи чи ні? Вставка буде така ж швидка, як і зараз?

Можливо, самі deletes, самі updates будуть дуже важкими, але це ніяк не вплине на продуктивність selects і продуктивність inserts.

І ще мале питання. На презентації ви говорили про primary key. Відповідно, у нас є партиціонування, яке за умовчанням місячне, правильне? І коли ми задаємо діапазон дат, який укладається на місяць, то у нас зчитується тільки ця партиція, вірно?

Так.

Таке питання. Якщо ми не можемо виділити якийсь primary key, то чи правильно його робити саме по полі «Дата» для того, щоб у фоновому режимі була менша перебудова цих даних, щоб вони вклалися впорядкованіше? Якщо у вас немає діапазонних запитів і ви навіть не можете вибрати первинний ключ, то чи варто засунути в первинний ключ дату?

Так.

Можливо, є сенс покласти в первинний ключ таке поле, яким дані будуть краще стискатися, якщо вони будуть відсортовані цим полем. Наприклад, ідентифікатор user'а. User, наприклад, ходить на той самий сайт. І тут кладете user id і час. І тоді у вас дані краще стискатимуться. Щодо дати, якщо у вас дійсно немає і ніколи не буває діапазонних запитів щодо дат, то можна не класти дату в первинний ключ.

Добре спасибі велике!

Джерело: habr.com

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