База даних KDB+: від фінансів до Формули 1

KDB+, продукт компанії KX - це широко відома у вузьких колах, виключно швидка, колонкова база даних, призначена для зберігання тимчасових рядів та аналітичних обчислень на їх основі. Спочатку вона користувалася (і користується) великою популярністю в індустрії фінансів — її використовують усі топ-10 інвестиційних банків та багато відомих хедж-фондів, бірж та інших організацій. Останнім часом у KX вирішили розширити клієнтську базу і тепер пропонують рішення і в інших областях, де є велика кількість даних, впорядкованих за часом або іншим чином телеком, біоінформатика, виробництво і т.д. У тому числі вони стали партнером команди Aston Martin Red Bull Racing у «Формулі 1», де допомагають збирати та обробляти дані з датчиків болідів та аналізувати тести в аеродинамічній трубі. У цій статті я хочу розповісти, які особливості KDB+ роблять її надпродуктивною, чому компанії готові витрачати на неї великі гроші, нарешті чому це насправді не база даних.
 
База даних KDB+: від фінансів до Формули 1
 
У цій статті я спробую розповісти загалом, що являє собою KDB+, які можливості та обмеження має, в чому її користь для компаній, які бажають обробляти великі обсяги даних. Я не вдаватимуся в деталі реалізації KDB+ і в деталі її мови програмування Q. Обидві ці теми дуже великі і заслуговують на окремі статті. Багато інформації з цих тем можна знайти на сайті code.kx.com, у тому числі книгу Q-Q For Mortals (див. посилання нижче).

Деякі терміни

  • In-memory база даних. База даних зберігає дані в оперативній пам'яті для прискорення доступу. Переваги такої бази зрозумілі, а недоліки – можливість втрати даних, необхідність мати багато пам'яті на сервері.
  • Колонкова база даних. База даних, де дані зберігаються поколоночно, а чи не запис за записом. Основна перевага такої бази в тому, що дані з однієї колонки зберігаються разом на диску та в пам'яті, що значно прискорює доступ до них. Немає потреби вантажити колонки, які не використовуються у запиті. Основний недолік - складно модифікувати та видаляти записи.
  • Тимчасовий ряд. Дані з колонкою типу дата чи час. Як правило, для таких даних важлива впорядкованість за часом, щоб можна було легко визначити, який запис передує чи слідує за поточним, або щоб застосовувати функції, результат яких залежить від порядку записів. Класичні бази даних побудовані зовсім іншому принципі — уявленні сукупності записів як безлічі, де порядок записів у принципі не визначено.
  • Вектор. У контексті KDB+ це список елементів одного атомарного типу, наприклад, чисел. Інакше кажучи, масив елементів. Масиви, на відміну списків, можна зберігати компактно і обробляти, використовуючи векторні інструкції процесора.

 

Історична довідка

Компанія KX була заснована в 1993 році Артуром Вітні, який до цього працював у банку Морган Стенлі над мовою A+, спадкоємцем APL - дуже оригінальною і свого часу популярною мовою у фінансовому світі. Зрозуміло, в KX Артур продовжив так само і створив векторно-функціональну мову K, керуючись ідеями радикального мінімалізму. Програми на K виглядають як безладний набір розділових знаків і спеціальних символів, сенс знаків і функцій залежить від контексту, і кожна операція несе в собі набагато більше сенсу, ніж це буває у звичних мовах програмування. За рахунок цього програма на K займає мінімум місця – кілька рядків можуть замінити сторінки тексту багатослівної мови типу Java – і є надконцентрованою реалізацією алгоритму.
 
Функція на K, що реалізує більшу частину генератора LL1 парсера за заданою граматикою:

1. pp:{q:{(x;p3(),y)};r:$[-11=@x;$x;11=@x;q[`N;$*x];10=abs@@x;q[`N;x]  
2.   ($)~*x;(`P;p3 x 1);(1=#x)&11=@*x;pp[{(1#x;$[2=#x;;,:]1_x)}@*x]  
3.      (?)~*x;(`Q;pp[x 1]);(*)~*x;(`M;pp[x 1]);(+)~*x;(`MP;pp[x 1]);(!)~*x;(`Y;p3 x 1)  
4.      (2=#x)&(@x 1)in 100 101 107 7 -7h;($[(@x 1)in 100 101 107h;`Ff;`Fi];p3 x 1;pp[*x])  
5.      (|)~*x;`S,(pp'1_x);2=#x;`C,{@[@[x;-1+#x;{x,")"}];0;"(",]}({$[".s.C"~4#x;6_-2_x;x]}'pp'x);'`pp];  
6.   $[@r;r;($[1<#r;".s.";""],$*r),$[1<#r;"[",(";"/:1_r),"]";""]]}  

 Цю філософію екстремальної ефективності при мінімумі рухів тіла Артур втілив і в KDB+, яка з'явилася в 2003 році (думаю, тепер зрозуміло звідки буква K в назві) і є ні що інше, як інтерпретатор четвертої версії мови K. Поверх K додана більш приємна погляду користувача версія K під назвою Q. Q також додана підтримка специфічного діалекту SQL - QSQL, а в інтерпретатор - підтримка таблиць, як системного типу даних, засобів роботи з таблицями в пам'яті і на диску і т.п.
 
Таким чином, з точки зору користувача KDB+ це просто інтерпретатор мови Q з підтримкою таблиць і схожих на SQL виразів у стилі LINQ з C#. Це найважливіша відмінність KDB+ від інших баз даних та головна її конкурентна перевага, яку часто не беруть до уваги. Це не база даних + допоміжна мова-інвалід, а повноцінна потужна мова програмування + вбудована підтримка функцій бази даних. Ця різниця відіграватиме визначальну роль при перерахуванні всіх переваг KDB+. Наприклад…
 

Розмір

За сучасними мірками, KDB+ має просто мікроскопічний розмір. Це буквально один виконуваний файл розміром менше мегабайта і один невеликий текстовий файл з деякими системними функціями. Реально менше одного мегабайта і за цю програму компанії платять десятки тисяч доларів на рік за один процесор на сервері.

  • Такий розмір дозволяє KDB+ чудово почуватися на будь-якому залізі — від мікрокомп'ютера Pi до серверів з терабайтами пам'яті. На функціональність це ніяк не впливає, навіть стартує Q миттєво, що дозволяє його використовувати навіть як скриптову мову.
  • При такому розмірі інтерпретатор Q повністю міститься в кеш процесора, що прискорює виконання програм.
  • При такому розмірі файлу процес Q займає мізерно мало місця в пам'яті, можна запускати їх сотнями. При цьому при необхідності Q може оперувати десятками-сотнями гігабайт пам'яті в рамках одного процесу.

Універсальність

Q чудово підходить для різних завдань. Процес Q може виконувати роль історичної бази даних та надавати швидкий доступ до терабайт інформації. У нас, наприклад, є десятки історичних баз, у деяких з яких один стислий день даних займає понад 100 гігабайт. Проте при розумних обмеженнях запит до бази буде виконаний за десятки-сотні мілісекунд. Загалом на запити користувачів у нас є універсальний тайм-аут – 30 секунд – і він спрацьовує дуже рідко.
 
З тією ж легкістю Q може бути in-memory базою даних. Додавання нових даних до таблиць у пам'яті відбувається настільки швидко, що лімітуючим фактором є запити користувачів. Дані в таблицях зберігаються по колонках, а отже будь-яка операція по колонці використовуватиме кеш процесора на повну потужність. На додаток до цього KX постаралися реалізувати всі базові операції типу арифметичних через векторні інструкції процесора, максимізуючи їх швидкість. Q може виконувати і завдання не властиві баз даних — наприклад, обробляти потокові дані та обчислювати в «реальному часі» (із затримкою від десятків мілісекунд до кількох секунд в залежності від завдання) різні агрегуючі функції для фінансових інструментів для різних часових інтервалів або будувати модель впливу досконалої угоди ринку і проводити її профіль практично відразу після її вчинення. У таких завданнях найчастіше основну тимчасову затримку вносить не Q, а синхронізації даних із різних джерел. Висока швидкість досягається завдяки тому, що дані та функції, що їх обробляють, знаходяться в одному процесі, а обробка зводиться до виконання кількох QSQL виразів та джойнів, які не інтерпретуються, а виконуються бінарним кодом.
 
Нарешті, Q можна писати і будь-які сервісні процеси. Наприклад, Gateway процеси, які автоматично розподіляють запити користувачів за потрібними базами та серверами. Програміст має повну свободу реалізувати будь-який алгоритм для балансування, пріорітизації, відмовостійкості, прав доступу, квот і взагалі душі завгодно. Головна проблема тут, що доведеться все це реалізовувати самому.
 
Наприклад, я перерахую, які типи процесів є у нас. Всі вони активно використовуються і працюють спільно, об'єднуючи в одне ціле десятки різних баз, обробляючи дані з безлічі джерел та обслуговуючи сотні користувачів та додатків.

  • Конектори (feedhandler) до джерел даних. Ці процеси використовують як правило зовнішні бібліотеки, які завантажуються в Q. С-інтерфейс в Q виключно простий і дозволяє легко створити проксі функції для будь-якої С/C++ бібліотеки. Q досить швидкий, щоб впоратися, наприклад, з обробкою потоку FIX повідомлень з усіх європейських стокових бірж одночасно.
  • Розподільники даних (tickerplant), які служать проміжною ланкою між конекторами та споживачами. Одночасно вони пишуть вхідні дані в спеціальний бінарний лог, забезпечуючи стійкість для споживачів до втрат з'єднання або перезапуску.
  • In-memory бази даних (rdb). Ці бази забезпечують максимально швидкий доступ до сирих свіжих даних, зберігаючи в пам'яті. Як правило, вони накопичують дані в таблицях протягом дня та обнулюють їх уночі.
  • Persist бази даних (pdb). Ці бази забезпечують збереження даних у історичну базу. Як правило, на відміну від rdb, вони не зберігають дані в пам'яті, а використовують спеціальний кеш на диску протягом дня і копіюють дані опівночі на історичну базу.
  • Історичні основи (hdb). Ці бази забезпечують доступ до даних за попередні дні, місяці та роки. Розмір їх (днями) обмежений лише розміром жорстких дисків. Дані можуть розміщуватися будь-де, зокрема на різних дисках для прискорення доступу. Є можливість стискати дані, використовуючи кілька алгоритмів на вибір. Структура бази добре документована і проста, дані зберігаються поколоночно у звичайних файлах, отже їх можна обробляти зокрема засобами ОС.
  • Основи з агрегованою інформацією. Зберігають різні агрегації, як правило, груповані за назвою інструменту та інтервалу часу. In-memory бази оновлюють свій стан при кожному повідомленні, а історичні зберігають передраховані дані для прискорення доступу до історичних даних.
  • Нарешті, gateway процеси, обслуговуючі програми та користувачів. Q дозволяє реалізувати повністю асинхронну обробку вхідних повідомлень, розподіл їх за базами, перевірку прав доступу тощо. Зауважу, що повідомлення не обмежуються і найчастіше не є виразами SQL, як це буває в інших базах даних. Найчастіше SQL вираз прихований у спеціальній функції і конструюється виходячи з параметрів, запрошених користувачем - проводиться конвертація часу, фільтрація, дані нормалізуються (наприклад, ціна акцій вирівнюється, якщо виплата дивідендів) і т.п.

Типова архітектура для одного типу даних:

База даних KDB+: від фінансів до Формули 1

Швидкість

Хоча Q є мовою, що інтерпретується, це одночасно векторна мова. Це означає, що багато вбудованих функцій, зокрема, арифметичні, набувають аргументів будь-якої форми — числа, вектора, матриці, списки, а від програміста очікується, що він реалізовуватиме програму як операції над масивами. У такій мові, якщо ви складаєте два вектори по мільйону елементів, вже не відіграє ролі, що мова інтерпретована, додавання буде здійснюватися супероптимізованою бінарною функцією. Оскільки левова частка часу в програмах на Q йде на операції з таблицями, що використовують ці базові векторизовані функції, то на виході маємо дуже пристойну швидкість роботи, що дозволяє обробляти величезний обсяг даних навіть в одному процесі. Це схоже на математичні бібліотеки в пітоні — хоча сам пітон мова дуже нешвидка, в ньому є багато прекрасних бібліотек типу numpy, які дозволяють обробляти числові дані зі швидкістю мови, що компілюється (до речі, numpy ідеологічно близька до Q).
 
Крім цього в KX дуже ретельно підійшли до проектування таблиць та оптимізації роботи з ними. По-перше, підтримується кілька видів індексів, які підтримуються вбудованими функціями і можуть бути застосовані не тільки до колонок таблиць, але й до будь-яких векторів - угруповання, сортування, атрибут унікальності та спеціальне угруповання для історичних баз. Індекс накладається елементарно та автоматично коригується при додаванні елементів у колонку/вектор. Індекси однаково успішно можуть накладатися на колонки таблиць як пам'яті, і на диску. При виконанні запиту QSQL індекси використовуються автоматично, якщо це можливо. По-друге, роботу з історичними даними зроблено через механізм відображення файлів ОС (memory map). Великі таблиці ніколи не вантажаться в пам'ять, натомість потрібні колонки відображаються безпосередньо в пам'ять і реально вантажиться тільки та їх частина (тут допомагають зокрема індекси), яка необхідна. Для програміста немає різниці, знаходяться дані у пам'яті чи ні, механізм роботи з mmap повністю прихований у надрах Q.
 
KDB+ не є реляційною базою даних, таблиці можуть містити довільні дані, при цьому порядок рядків у таблиці не змінюється при додаванні нових елементів і може і повинен використовуватися при написанні запитів. Ця особливість гостро необхідна для роботи з тимчасовими рядами (дані з бірж, телеметрія, логи подій), тому що якщо дані відсортовані за часом, то користувачу не потрібно застосовувати жодних SQL трюків, щоб знайти в таблиці перший або останній за часом рядок або рядків N , визначити який рядок слідує за N-м рядком і т.п. Ще більше спрощуються джойни таблиць, наприклад знайти для 16000 угод VOD.L (Водафон) останнє котирування в таблиці з 500 мільйонів елементів займає близько секунди на диску та десяток мілісекунд у пам'яті.
 
Приклад джойна за часом - quote таблиця відображається в пам'ять, тому немає необхідності вказувати VOD.L у where, неявно використовуються індекс на sym колонці і той факт, що дані відсортовані за часом. Майже всі джойни в Q - це звичайні функції, а не частина виразу select:

1. aj[`sym`time;select from trade where date=2019.03.26, sym=`VOD.L;select from quote where date=2019.03.26]  

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

Підсумок

KDB+ популярна у бізнесу в першу чергу завдяки своїй винятковій універсальності — вона однаково добре служить як і in-memory база, і як база для зберігання терабайт історичних даних, і як платформа для аналізу даних. Завдяки тому, що обробка даних відбувається безпосередньо в базі, досягається висока швидкість роботи та економія ресурсів. Повноцінна мова програмування, інтегрована з функціями бази даних, дозволяє реалізувати на одній платформі весь стек необхідних процесів - від отримання даних до обробки запитів користувачів.
 

Додаткові відомості

Недоліки

Істотним недоліком KDB+/Q є високий поріг входження. Мова має дивний синтаксис, деякі функції перевантажені (value, наприклад, має близько 11 варіантів використання). Найголовніше він вимагає радикально іншого підходу до написання програм. У векторній мові необхідно весь час мислити в термінах перетворень масивів, всі цикли реалізовувати через кілька варіантів функцій map/reduce (які називаються adverbs Q), ніколи не намагатися заощадити, замінюючи векторні операції атомарними. Наприклад, для знаходження індексу N-го входження елемента масив слід писати:

1. (where element=vector)[N]  

хоча це виглядає дуже неефективно за мірками C/Java (= створює булевий вектор, де повертає індекси true елементів у ньому). Але такий запис робить сенс виразу зрозумілішим і ви використовуєте швидкі векторні операції замість повільних атомарних. Концептуальна різниця між векторною мовою та іншими можна порівняти з різницею між імперативним та функціональним підходами до програмування, і до цього треба бути готовим.
 
Деякі користувачі також бувають незадоволені QSQL. Справа в тому, що він схожий на справжній SQL. Насправді це просто інтерпретатор SQL-подібних висловів, який підтримує оптимізацію запитів. Користувач повинен сам писати оптимальні запити, причому на Q, до чого багато хто не готовий. З іншого боку, звичайно, завжди можна самому написати оптимальний запит, а не покладатися на чорний оптимізатор.
 
Як плюс книга з Q - Q For Mortals доступна безкоштовно на сайті компаніїТакож там зібрано багато інших корисних матеріалів.
 
Ще один великий мінус – вартість ліцензії. Це десятки тисяч доларів на рік за один CPU. Тільки великі фірми можуть дозволити собі такі витрати. Останнім часом KX зробила ліцензійну політику більш гнучкою і надає можливість платити лише за час використання або орендувати KDB+ у хмарах Google та Амазон. Також KX пропонує для скачування безкоштовну версію для некомерційних цілей (32 бітна версія або 64-бітна на запит).
 

конкуренти

Існує чимало спеціалізованих баз, побудованих на схожих принципах — колонкові, in-memory, орієнтовані дуже великі обсяги даних. Проблема в тому, що це спеціалізовані бази даних. Яскравий приклад – Clickhouse. У цій базі даних дуже схожий на KDB+ принцип зберігання даних на диску та будови індексу, деякі запити вона виконує швидше за KDB+, хоча і не суттєво. Але навіть як база даних Clickhouse є більш спеціалізованою, ніж KDB+ — web аналітика vs довільні тимчасові ряди (ця відмінність дуже важлива — через нього, наприклад, у Clickhouse немає можливості використовувати впорядкованість записів). Але, головне, у Clickhouse немає універсальності KDB+, мови, яка дозволила б обробляти дані безпосередньо в базі, а не вантажити їх попередньо в окремий додаток, будувати довільні SQL вирази, застосовувати довільні функції у запиті, створювати процеси не пов'язані з виконанням функцій історичної бази . Тому складно порівнювати KDB+ з іншими базами, вони можуть бути краще в окремих сценаріях використання або просто краще, якщо йдеться про завдання класичних баз даних, але мені невідомий інший настільки ефективний і універсальний інструмент для обробки тимчасових даних.
 

Інтеграція з Python

Щоб спростити роботу з KDB+ для людей, які не знайомі з технологією, KX створили бібліотеки для тісної інтеграції з Python в рамках одного процесу. Можна викликати будь-яку python-функцію з Q, так і навпаки - викликати будь-яку Q функцію з Python (зокрема QSQL вирази). Бібліотеки конвертують у разі потреби (заради ефективності не завжди) дані з формату однієї мови у формат іншої. У результаті Q і Python живуть у такому тісному симбіозі, що межі між ними стираються. В результаті програміст, з одного боку, має повний доступ до численних корисних бібліотек Python, з іншого боку він отримує інтегровану в Python швидку базу для роботи з великими даними, що особливо корисно тим, хто займається машинним навчанням або моделюванням.
 
Робота з Q в Python:

1. >>> q()  
2.q)trade:([]date:();sym:();qty:())  
3. q)  
4. >>> q.insert('trade', (date(2006,10,6), 'IBM', 200))  
5. k(',0')  
6. >>> q.insert('trade', (date(2006,10,6), 'MSFT', 100))  
7. k(',1')  

Посилання

Сайт компанії - https://kx.com/
Сайт для розробників https://code.kx.com/v2/
Книга Q For Mortals (англійською) https://code.kx.com/q4m3/
Статті на тему застосувань KDB+/Q від співробітників kx https://code.kx.com/v2/wp/

Джерело: habr.com

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