Кро­ме SQL и NoSQL, сущес­тву­ет еще целый мир NewSQL. Это базы дан­ных, которые взя­ли новые под­ходы рас­пре­делен­ных сис­тем от NoSQL и оста­вили реляци­онную модель пред­став­ления дан­ных и язык зап­росов SQL. Эти БД воз­никли бук­валь­но за пос­ледние нес­коль­ко лет, но уже интенсив­но борют­ся за поль­зовате­лей, оттесняя на рын­ке и ста­рые доб­рые SQL БД, и чуть менее ста­рые NoSQL. Давай поз­накомим­ся с этим загадоч­ным NewSQL поб­лиже.
 

Классификация

Го­воря о NewSQL-базах, мы име­ем в виду базы, удов­летво­ряющие нес­коль­ким кри­тери­ям:

  • под­дер­жка реляци­онной модели и тран­закци­онности;
  • SQL как основной интерфейс дос­тупа к дан­ным;
  • го­ризон­таль­ная мас­шта­биру­емость;
  • со­вер­шенно новый дви­жок, не унас­ледован­ный от клас­сичес­ких СУБД;
  • по­яви­лись в пос­ледние 3–5 лет.

Мож­но выделить нес­коль­ко клас­сов решений в области хра­нения и обра­бот­ки дан­ных, отно­сящих­ся к NewSQL. Во‑пер­вых, к NewSQL при­чис­ляют спо­собы исполь­зования обыч­ных SQL-баз в клас­тере из нес­коль­ких физичес­ких узлов для хра­нения и обра­бот­ки боль­ших объ­емов дан­ных. Сюда мож­но отнести MySQL Cluster, Postgres-XC, Oracle RAC и про­чие. Все про­мыш­ленные СУБД пыта­ются соб­рать­ся в клас­тер. Все эти решения пред­став­ляют собой про­межу­точ­ный слой (middleware), который рас­пре­деля­ет зап­росы меж­ду узла­ми, хра­нящи­ми дан­ные (обыч­ными БД), и объ­еди­няет резуль­таты — собс­твен­но, осу­щест­вля­ет шар­динг. Как пра­вило, на зап­росы нак­ладыва­ются силь­ные огра­ниче­ния на исполь­зование внеш­них клю­чей и джой­нов. Мы не будем рас­смат­ривать эти сис­темы здесь, хотя это инте­рес­ная тема для даль­нейше­го иссле­дова­ния.

Во‑вто­рых, NewSQL — это новые движ­ки хра­нения дан­ных для сущес­тву­ющих БД. Самый популяр­ный сре­ди них TokuDB — дви­жок для MySQL. Он исполь­зует индексы на так называ­емых фрак­таль­ных деревь­ях. Эти индексы зна­читель­но быс­трее B-деревь­ев в слу­чаях, ког­да индексы не помеща­ются в опе­ратив­ной памяти и их при­ходит­ся читать с дис­ка. Так как основная осо­бен­ность новых движ­ков — тон­кие отли­чия в про­изво­дитель­нос­ти в некото­рых экзо­тичес­ких слу­чаях, мы исклю­чаем их из это­го обзо­ра. Нас в пер­вую оче­редь инте­ресу­ет фун­кци­онал.

info

Фрак­таль­ные индексы прик­рутили и к MongoDB — в одном неофи­циаль­ном фор­ке.

В‑треть­их, NewSQL — это совер­шенно новые СУБД, которые под­держи­вают SQL. Тут мы оста­новим­ся под­робнее. Мно­гие NewSQL БД — это in-memory БД. Они хра­нят все дан­ные в опе­ратив­ной памяти. Соз­датели этих БД счи­тают фаталь­ным недос­татком сущес­тву­ющих БД стрем­ление все хра­нить на дис­ке. Если тебе нуж­но обра­баты­вать дан­ные быс­тро, нуж­но хра­нить их в ОЗУ. А отно­ситель­но неболь­шой объ­ем ОЗУ на одной машине мож­но лег­ко ком­пенси­ровать, соб­рав клас­тер из сотен узлов. Хра­нение в ОЗУ не озна­чает, что дан­ные будут потеря­ны при вык­лючении питания. Все эти БД ведут жур­нал опе­раций и пери­оди­чес­ки ски­дыва­ют сним­ки дан­ных на диск. Мы рас­смот­рим VoltDB и MemSQL.

Дру­гие раз­работ­чики NewSQL борют­ся со слож­ностью раз­верты­вания и управле­ния клас­терной СУБД в сов­ремен­ных вир­туаль­ных и облачных сре­дах. Соз­дают­ся кра­сивые веб‑кон­соли для управле­ния и монито­рин­га. Рас­смот­рим в этом качес­тве NuoDB. Третьи раз­работ­чики NewSQL замахи­вают­ся на соз­дание уни­вер­саль­ных плат­форм для хра­нения, пред­став­ления и обра­бот­ки струк­туриро­ван­ных дан­ных, в виде реляци­онной модели, но не толь­ко. Здесь мы поз­накомим­ся с замеча­тель­ной FoundationDB. Итак, что­бы новая СУБД в пол­ной мере мог­ла называть­ся NewSQL-решени­ем, она дол­жна быть SQL и NoSQL одновре­мен­но.

 

VoltDB

VoltDB — это in-memory СУБД, написан­ная на Java. Это самая ста­рая БД в нашем обзо­ре, пер­вый пуб­личный релиз сос­тоял­ся аж в мае 2010 года. VoltDB так­же единс­твен­ная СУБД в обзо­ре, пол­ностью дос­тупная под сво­бод­ной лицен­зией (AGPL). Есть и проп­риетар­ная Enterprise-вер­сия, в которую добав­лены плюш­ки вро­де VoltDB Web Studio — «сре­ды раз­работ­ки» в виде веб‑при­ложе­ния. В отли­чие от дру­гих СУБД, где на одном сер­вере ты можешь соз­дать нес­коль­ко баз дан­ных, в VoltDB один про­цесс обслу­жива­ет одну базу. Точ­нее, мно­жес­тво про­цес­сов на мно­жес­тве хос­тов клас­тера обслу­жива­ют одну базу, и дру­гих баз в этом клас­тере нет. Каж­дый про­цесс одно­пото­чен, асин­хро­нен, име­ет свой каталог для хра­нения снап­шотов, работа­ет на одном CPU и называ­ется пар­тици­ей (partition). Соот­ветс­твен­но, на каж­дом хос­те клас­тера нуж­но запус­кать нес­коль­ко про­цес­сов, сог­ласно чис­лу ядер CPU.

Са­ма БД опи­сыва­ется в виде катало­га. В этом катало­ге находит­ся опи­сание клас­тера: сколь­ко узлов, сколь­ко про­цес­сов на узле, количес­тво копий дан­ных (тут оно называ­ется K-factor). Еще тут есть пол­ное опи­сание схе­мы БД, пря­мо в фай­ле ddl.sql, в виде CREATE TABLE, ALTER TABLE и про­чих конс­трук­ций. А еще есть исходные тек­сты хра­нимых про­цедур на Java (да, в VoltDB хра­нимые про­цеду­ры — толь­ко на Java). Весь этот каталог ком­пилиру­ется (!) в один JAR-архив. И при запус­ке про­цес­са СУБД нуж­но ука­зать этот JAR-файл. Это боль­ше похоже на какой‑нибудь J2EE сер­вер при­ложе­ний, чем на СУБД.

При ком­пиляции для каж­дого зап­роса в хра­нимых про­цеду­рах стро­ится план выпол­нения. Потом этот план не меня­ется. Мож­но дать опти­миза­тору под­сказ­ки о количес­тве записей в таб­лицах, но в целом ста­тич­ный план выпол­нения зап­роса, не исполь­зующий акту­аль­ную ста­тис­тику, выг­лядит анах­рониз­мом. Запус­тив про­цес­сы на всех узлах клас­тера, мож­но под­клю­чать­ся к любому узлу, исполь­зуя воль­тди­биш­ный JDBC-драй­вер. Есть кли­ент­ские биб­лиоте­ки для дру­гих ЯП. А мож­но делать зап­росы к хра­нимым про­цеду­рам через прос­тень­кий HTTP-интерфейс.

Хра­нимые про­цеду­ры очень важ­ны в VoltDB. Это еди­ница тран­закции, вызов каж­дой хра­нимой про­цеду­ры пред­став­ляет собой тран­закцию, и дру­гих тран­закций нет. То есть весь набор дос­тупных тран­закци­онных дей­ствий мы опре­деля­ем на эта­пе ком­пиляции катало­га БД. Хра­нимые про­цеду­ры явля­ются частью сер­вера (вком­пилиро­ваны в сер­вер). «Пятью девят­ками» тут даже не пах­нет: хочешь новую фун­кцию — переком­пилируй­ся и переза­пус­ти базу. Вся опти­миза­ция вер­тится вок­руг хра­нимых про­цедур.

Муль­тивер­сион­ности нет, бло­киро­вок тоже, пос­коль­ку на пар­тицию работа­ет толь­ко один про­цесс и кон­куриро­вать ему не с кем. С одной сто­роны, про­пус­кная спо­соб­ность дей­стви­тель­но воз­раста­ет, и дей­стви­тель­но уро­вень изо­ляции тран­закций serialized. Хотя слож­но говорить об уров­не изо­ляции при фак­тичес­ки одно­поль­зователь­ской работе. Если зап­рос зат­рагива­ет нес­коль­ко пар­тиций, то это будет единс­твен­ный зап­рос, выпол­няющий­ся в базе в этот момент. При таком под­ходе даже неудоб­но упо­минать о раз­делении прав дос­тупа к дан­ным. Их тоже нет.

Вспом­ним про такое свой­ство тран­закций, как durability: здесь оно реали­зова­но через жур­нал тран­закций и пери­оди­чес­кие снап­шоты. Жур­нал хра­нит­ся толь­ко со вре­мени пос­ледне­го снап­шота, поэто­му обра­щение к исто­ричес­ким дан­ным невоз­можно. Надеж­ность хра­нения дос­тига­ется за счет резер­вирова­ния дан­ных на нес­коль­ких узлах клас­тера. Рас­пре­делен­ные тран­закции (да, они здесь воз­можны) фик­сиру­ются двух­фазным ком­митом.

Что, собс­твен­но, нового? Эта SQL БД работа­ет в клас­тере. И поэто­му у нас есть два типа таб­лиц: пар­тициро­ван­ные (partitioned) и реп­лициро­ван­ные (replicated). С реп­лициро­ван­ными таб­лицами (такие соз­дают­ся по умол­чанию, обыч­ным CREATE TABLE) все прос­то — они целиком копиру­ются на все узлы клас­тера. Это удоб­но для неболь­ших таб­лиц‑спра­воч­ников, с которым час­то дела­ются джой­ны и которые ред­ко меня­ются. Пар­тициро­ван­ные таб­лицы шар­диру­ются меж­ду узла­ми клас­тера (точ­нее, меж­ду про­цес­сами‑пар­тици­ями). Шар­диру­ются по хешу клю­ча. Клю­чом может быть любой стол­бец таб­лицы с целочис­ленным или стро­ковым зна­чение (но не null). Ключ у таб­лицы может быть толь­ко один. В общем, таб­лица раз­мазыва­ется ров­ным сло­ем по все­му клас­теру (учи­тывая, конеч­но, K-factor).

Партицированные таблицы в VoltDB
Пар­тициро­ван­ные таб­лицы в VoltDB

Со­ответс­твен­но, выбор­ка мно­жес­тва строк из пар­тициро­ван­ной таб­лицы — недеше­вое удо­воль­ствие, к тому же не гаран­тиру­ется порядок выбор­ки в слу­чае отсутс­твия явной сор­тиров­ки. При этом стро­ки с раз­ных узлов будут выданы в том поряд­ке, в каком узлы отве­тили. Обыч­но зап­рос из про­цедур отправ­ляет­ся на все узлы клас­тера, которые могут содер­жать инте­ресу­ющие нас дан­ные, а затем агре­гиру­ются. Одна­ко если про­цеду­ра опе­риру­ет дан­ными по одно­му клю­чу и все задей­ство­ван­ные таб­лицы пар­тициро­ваны по это­му же клю­чу, то вся про­цеду­ра может быть выпол­нена на одном узле. Это, конеч­но же, нам­ного эффектив­нее. Таким обра­зом, при про­екти­рова­нии БД нуж­но тща­тель­но подумать о том, какие дан­ные будут исполь­зовать­ся вмес­те, и пар­тициро­вать их по одно­му и тому же клю­чу.

Пом­ни, что любое изме­нение струк­туры дан­ных тре­бует переком­пиляции катало­га БД, а для некото­рых (к при­меру, добав­ление уни­каль­ного индекса) тре­бует­ся пол­ный переза­пуск базы. Такой под­ход к управле­нию струк­турой ну сов­сем не гиб­кий. Кро­ме того, под­держи­вать нор­мализо­ван­ную реляци­онную схе­му для более‑менее связ­ных дан­ных не пред­став­ляет­ся воз­можным: зап­росы огра­ниче­ны по слож­ности, джой­нить шар­дирован­ные таб­лицы мож­но толь­ко по клю­чу шар­динга и толь­ко на равенс­тво. В update и delete под­запро­сы вооб­ще зап­рещены. Кро­ме того, есть огра­ниче­ния на раз­мер резалт‑сета — не более 50 Мб. Вдо­бавок VoltDB не под­держи­вает внеш­ние клю­чи и check constraint, а уни­каль­ность записей для unique constraint обес­печива­ет толь­ко в пре­делах пар­тиции. При этом заяв­ляет­ся, что СУБД под­держи­вает реляци­онную модель. Кодд перево­рачи­вает­ся в гро­бу.

Эдгар Кодд

Эдгар Кодд
Эд­гар Кодд

Бри­тан­ский уче­ный. Соз­датель реляци­онной модели дан­ных (60–70-е годы ХХ века). Работал в IBM. В 1985 году опуб­ликовал «12 пра­вил Код­да», которые дол­жны выпол­нять­ся в пра­виль­ной реляци­онной СУБД. Ни тог­да, ни, как видим, сей­час ни одна сущес­тву­ющая СУБД не удов­летво­ряет всем этим пра­вилам :).

 

MemSQL

MemSQL очень похожа на VoltDB. Тоже in-memory СУБД, толь­ко написа­на на C++. Пер­вый пуб­личный релиз сос­тоял­ся в июне 2012 года. У MemSQL име­ется толь­ко проп­риетар­ная вер­сия. Бес­плат­ных вер­сий нет, но есть три­алка. В MemSQL узлы клас­тера нерав­нознач­ны. Здесь есть агре­гато­ры и лис­ты (leaf). Агре­гато­ры при­нима­ют зап­росы от поль­зовате­лей, модифи­циру­ют и рас­сыла­ют зап­росы лис­там, агре­гиру­ют резуль­таты. Есть глав­ный агре­гатор, который отве­чает за соз­дание БД и таб­лиц и целос­тность клас­тера в целом. Лис­ты непос­редс­твен­но хра­нят дан­ные и выпол­няют (час­тичные) зап­росы. При падении мас­тер‑агре­гато­ра клас­тер оста­ется работос­пособ­ным, хотя DDL-зап­росы выпол­нять уже нель­зя. Авто­мати­чес­ки новый мас­тер‑агре­гатор не наз­нача­ется.

В отли­чие от VoltDB, в клас­тере мож­но дер­жать нес­коль­ко баз дан­ных. При соз­дании базы сра­зу соз­дает­ся опре­делен­ное чис­ло пар­тишенов (по умол­чанию — по восемь на лист). Любопыт­но, что пар­тишены на лис­тах пред­став­лены отдель­ными базами дан­ных, толь­ко с чис­ловыми суф­фикса­ми. Нап­ример, если ты соз­дал в клас­тере БД под име­нем «test», но на лис­тах будут соз­даны БД‑пар­тиции «test_1», «test_2» и так далее.

В MemSQL, как и в VoltDB, при­сутс­тву­ет два типа таб­лиц: спра­воч­ные (reference) таб­лицы, которые копиру­ются на все узлы клас­тера (вклю­чая агре­гато­ры), и шар­дирован­ные (sharded) таб­лицы, которые раз­мазыва­ются по пар­тици­ям по хешу клю­ча. Клю­чом шар­дирова­ния может быть любой стол­бец таб­лицы, толь­ко этот стол­бец дол­жен вхо­дить в пер­вичный ключ этой таб­лицы. И в уни­каль­ный индекс, если такой понадо­бит­ся. И во внеш­ний ключ, конеч­но, тоже. Так в MemSQL решили проб­лему связ­ности дан­ных, на которую в VoltDB прос­то зак­рыли гла­за.

Распределение ролей и данных в кластере MemSQL
Рас­пре­деле­ние ролей и дан­ных в клас­тере MemSQL

MemSQL может опре­делить, что зап­рос зат­рагива­ет толь­ко одну пар­тицию, и опти­мизи­рует его, отсы­лая толь­ко на один лист. Все зап­росы на изме­нение дан­ных могут выпол­нять­ся толь­ко на узлах‑агре­гато­рах, вне зависи­мос­ти от того, сколь­ко пар­тиций зат­рагива­ет зап­рос. Кро­ме того, явно зап­рещены любые манипу­ляции с пер­вичным клю­чом (для защиты от изме­нения клю­ча шар­динга). Глав­ной осо­бен­ностью MemSQL явля­ется ком­пиляция зап­росов. Любой SQL-зап­рос (все DML и некото­рые DDL) прев­раща­ется в код на C++ (SELECT * FROM TEST прев­раща­ется в две сот­ни строк чего‑то нечита­емо­го). Этот код ком­пилиру­ется обыч­ным GCC в раз­деля­емую биб­лиоте­ку, которая под­клю­чает­ся к сер­веру.

Код парамет­ризован­ный, все зна­чения, встре­чающиеся в WHERE сек­ции зап­роса, явля­ются парамет­рами. В даль­нейшем все подоб­ные зап­росы (с тем же набором парамет­ров) уже выпол­няют­ся в натив­ном коде. Про­явля­ется это в том, что пер­вый зап­рос выпол­няет­ся десят­ки секунд, а пос­леду­ющие — со впол­не нор­маль­ной ско­ростью. Пока не изме­нит­ся струк­тура базы. Пос­ле это­го все зат­ронутые изме­нени­ями зап­росы будут переком­пилиро­ваны заново. Кеширо­вание пла­нов зап­росов само по себе неп­лохо, если при этом сле­дить за акту­аль­ностью ста­тис­тики и струк­туры дан­ных. Ста­тис­тика и селек­тивность выборок в MemSQL игно­риру­ются. Для зап­роса мож­но пос­мотреть план выпол­нения, но там мало информа­ции для опти­миза­ции. Это, ско­рее, дек­ларация решения опти­миза­тора.

В отли­чие от VoltDB, MemSQL не нак­ладыва­ет огра­ниче­ний на слож­ность зап­росов и количес­тво учас­тву­ющих в них пар­тициро­ван­ных таб­лиц, но все про­межу­точ­ные резуль­таты хра­нит во вре­мен­ных таб­лицах на агре­гато­ре и чес­тно пре­дуп­режда­ет о воз­можнос­ти отка­за из‑за нех­ватки памяти на боль­ших зап­росах. Для реп­ликации MemSQL уме­ет соз­давать толь­ко две копии дан­ных: основную и резер­вную. При этом резер­вная копия не учас­тву­ет в опе­раци­ях и дол­жна быть соз­дана на отдель­ном лис­те. Если хочешь вклю­чить резер­вирова­ние, тебе нуж­но в два раза боль­ше лис­тов в клас­тере. Не очень удоб­но.

Ни­каких хра­нимых про­цедур в MemSQL нет: база не под­держи­вает даже триг­геры и пред­став­ления. Ну, если не счи­тать ском­пилиро­ван­ные зап­росы за про­цеду­ры :). Хотя MemSQL дек­лариру­ет под­дер­жку ACID-тран­закций, фак­тичес­ки каж­дый зап­рос выпол­няет­ся в отдель­ной тран­закции. А для некото­рых изощ­ренных слу­чаев, таких как ошиб­ка на встав­ке нес­коль­ких записей перечис­лени­ем зна­чений, может заком­митить­ся толь­ко часть изме­нен­ных записей. Вот такая ато­мар­ность. Уро­вень изо­ляции read committed. Но раз­работ­чики приз­нают, что при изме­нении дан­ных на нес­коль­ких пар­тици­ях воз­можно гряз­ное чте­ние. Вот такая изо­лиро­ван­ность.

На­деж­ность фик­сации дан­ных, как и в VoltDB, обес­печива­ется снап­шотами и жур­налом тран­закций. Да вот беда, по умол­чанию жур­нал пишет­ся на диск асин­хрон­но. Недос­таточ­но кис­ло? Нес­мотря на то что MemSQL заяв­ляет­ся как lock-free база, бло­киров­ки исполь­зуют­ся, и на это явно ука­зыва­ется в докумен­тации, нап­ример, уда­ление индекса бло­киру­ет опе­рации изме­нения дан­ных. И да, есть коман­да для уста­нов­ки явной бло­киров­ки на таб­лицу. Реверан­сом в сто­рону клас­сичес­ких баз явля­ется раз­деление прав дос­тупа. База под­держи­вает мно­гополь­зователь­скую работу. Набор при­виле­гий дос­той­ный, но выдавать на отдель­ные объ­екты базы их нель­зя.

 

NuoDB

Соз­датели NuoDB решили сде­лать раз­верты­вание клас­тера в обла­ке при­ятным и необ­ремени­тель­ным заняти­ем. Кажет­ся, уда­лось это им не очень. Зато уро­вень рек­ламно­го бул­лши­та в докумен­тации заш­калива­ет. Это проп­риетар­ная СУБД, написан­ная пре­иму­щес­твен­но на Java. Пер­вый релиз сос­тоял­ся в янва­ре 2013-го. Нач­нем с типов узлов, точ­нее, типов про­цес­сов, которые запус­кают­ся в клас­тере (в тер­миноло­гии NuoDB — домене). Есть один бро­кер — основной про­цесс, который собира­ет домен в еди­ное целое. К нему кли­ент под­клю­чает­ся в пер­вую оче­редь и узна­ет о кон­фигура­ции домена. На каж­дом узле запуще­ны аген­ты, которые обща­ются с бро­кером и док­ладыва­ют о сос­тоянии узлов. База дан­ных соз­дает­ся через бро­кер, в нее вхо­дит один или более движ­ков тран­закций (Transaction Engine) и один или более менед­жеров хра­нения (Storage Manager). К пер­вому под­клю­чают­ся кли­енты для выпол­нения зап­росов, вто­рой отве­чает за хра­нение дан­ных. При соз­дании БД зада­ется, сколь­ко имен­но дол­жно быть соз­дано движ­ков и менед­жеров и на каких имен­но узлах домена они дол­жны быть рас­положе­ны.

Распределение ролей в кластере NuoDB
Рас­пре­деле­ние ролей в клас­тере NuoDB

Все это мож­но нас­тро­ить через доволь­но удоб­ную веб‑админку, с кра­сивым логоти­пом в виде зеленой птич­ки. Одна­ко на прак­тике не все так прос­то. Это единс­твен­ная БД в обзо­ре, которой понадо­бил­ся кор­рек­тно работа­ющий DNS (или пра­виль­ные /etc/hosts). Узел иден­тифици­рует себя по домен­ному име­ни, дру­гие узлы дол­жны иметь воз­можность под­клю­чить­ся к нему по это­му име­ни.

Вне­зап­но — шар­динга здесь нет. Хоть реп­ликация есть, и на том спа­сибо. Все дан­ные в БД дуб­лиру­ются на все менед­жеры хра­нения дан­ной БД. Утвер­жда­ется, что БД оста­ется час­тично работос­пособ­ной при падени­ях бро­кера, аген­та и даже движ­ков тран­закций и менед­жера хра­нения. В качес­тве раз­нооб­разия мож­но нас­тро­ить хра­нение дан­ных в хадупо­вой HDFS (ну хоть тут появ­ляет­ся какой‑то шар­динг).

Ни­какой сов­мести­мос­ти на уров­не про­токо­ла нет, JDBC-драй­вер — свой уни­каль­ный. Было бы понят­но, если бы это была какая‑то кон­соль­ка по раз­верты­ванию клас­тера обыч­ных СУБД (вро­де Amazon RDS). А так, в чем смысл сущес­тво­вания NuoDB? Нес­мотря ни на что, в NuoDB нор­маль­ная реали­зация тран­закций: база под­держи­вает уров­ни изо­ляции от read committed до serializable (по умол­чанию repeatable read), обес­печива­ет ато­мар­ность мно­гоп­роцедур­ных тран­закций.

Нет шар­дирован­ных таб­лиц — нет проб­лем с огра­ниче­ниями уни­каль­нос­ти и ссы­лоч­ной целос­тнос­ти. При­сутс­тву­ют все при­выч­ные для реляци­онных баз конс­трей­нты. В целом все как у людей — раз­гра­ниче­ние прав дос­тупа, опти­миза­тор зап­росов на осно­ве ста­тис­тики, B-tree индексы. Обыч­ный SQL двад­цатилет­ней дав­ности. Непонят­но, что здесь нового.

 

FoundationDB

FoundationDB — самый молодой пред­ста­витель нашего обзо­ра. Пер­вый пуб­личный релиз сос­тоял­ся в мае 2013 года. Ребята из FoundationDB пош­ли совер­шенно уни­каль­ным путем. Они решили, что в СУБД механизм хра­нения дан­ных, модель пред­став­ления дан­ных и язык зап­росов дол­жны быть незави­симы. Нап­ример, если вы берете PostgreSQL, то вы вынуж­дены исполь­зовать его MVCC дви­жок хра­нения, реляци­онную модель дан­ных и SQL как язык зап­росов (впро­чем, в MySQL дви­жок хра­нения мож­но выбирать). Еще они ска­зали, что тран­закции — это кру­то, и соз­дали отдель­ную, уни­вер­саль­ную, надеж­ную, рас­пре­делен­ную тех­нологию хра­нения струк­туриро­ван­ных дан­ных.

FoundationDB — это key-value хра­нили­ще, с упо­рядо­чен­ными клю­чами, с под­дер­жкой ACID-тран­закций (в опе­раци­ях на мно­жес­тве клю­чей!), которое раз­верты­вает­ся на клас­тере рав­нознач­ных узлов. Авто­рам неиз­вес­тны ана­логич­ные NoSQL-решения. Тран­закции на key-value под­держи­вают­ся в BerkeleyDB, но это вооб­ще‑то встра­иваемая СУБД. Упо­рядо­чен­ность клю­чей есть в HBase (при осо­бых нас­трой­ках и в Cassandra), но тут ник­то не говорит о тран­закци­ях. Lightweight-тран­закции не в счет. Получа­ется уни­каль­ное и мощ­ное сочета­ние. А поверх key-value находят­ся слои (так и называ­ются: Layers), которые пред­став­ляют более высоко­уров­невую модель хра­нения и язы­ки зап­росов. Самим FoundationDB раз­рабаты­вает­ся SQL Layer, рань­ше он был отдель­ным про­ектом под наз­вани­ем Akiban. Есть слои для хра­нения докумен­тов и гра­фов.

Key-value хра­нили­ще — проп­риетар­ный про­дукт. Есть огра­ничен­ная бес­плат­ная вер­сия. А вот SQL Layer и мно­гие дру­гие слои — уже сво­бод­ное ПО, под AGPL. Хра­нили­ще написа­но на C++. Про­цес­сы одно­поточ­ны, асин­хрон­ны, работа­ют на одном ядре CPU и обслу­жива­ют каж­дый свою пор­цию дан­ных. На каж­дом узле клас­тера нуж­но запус­кать нес­коль­ко про­цес­сов, сог­ласно чис­лу CPU, что дела­ется впол­не проз­рачно. Все узлы клас­тера рав­нознач­ны, одна­ко некото­рые (тре­бует­ся нечет­ное чис­ло) игра­ют роль коор­динато­ров. Коор­динато­ры отве­чают за под­дер­жку тополо­гии клас­тера для реп­ликации дан­ных, опре­деле­ние дос­тупнос­ти сег­мента клас­тера в слу­чае раз­деления.

Реп­ликация проз­рачна. Клас­тер может хра­нить до трех копий дан­ных. Учи­тыва­ется рас­пре­деле­ние узлов меж­ду дата‑цен­тра­ми. Шар­динг клю­чей осу­щест­вля­ется по диапа­зонам (при­мер­но как в MongoDB), что тре­бует пос­тоян­ной переба­лан­сиров­ки клас­тера, которая про­ходит тихо и незамет­но. Наличие и объ­ем фоновой балан­сиров­ки — важ­ный параметр клас­тера. Он виден в выводе стан­дар­тной коман­ды ста­туса. Сетевой про­токол и API — свои собс­твен­ные. Под­держи­вают­ся опе­рации записи и чте­ния по клю­чу, ска­ниро­вания диапа­зона (пом­ним, клю­чи упо­рядо­чены), начало и завер­шения тран­закций. Име­ется два спо­соба хра­нения дан­ных. Либо B-деревья хра­нят­ся пря­мо в фай­лах на дис­ке, что хорошо работа­ет толь­ко на SSD. Либо копия дан­ных живет в ОЗУ, а на диск пишут­ся логи и снап­шоты.

Слои в FoundationDB
Слои в FoundationDB

SQL Layer написан на Java. Это совер­шенно отдель­ное сетевое при­ложе­ние. Кли­енты под­клю­чают­ся к SQL Layer по сети, SQL Layer под­клю­чает­ся к key-value хра­нили­щу тоже по сети. Это уве­личи­вает вре­мя обра­бот­ки зап­роса, но раз­работ­чики и не утвер­жда­ют, что latency у них луч­ше всех. Слой SQL не хра­нит сос­тояния, все дан­ные записы­вают­ся в хра­нили­ще, поэто­му раз­мещать этот про­цесс мож­но где угод­но и в любом количес­тве. Раз­работ­чики рекомен­дуют запус­кать SQL Layer на каж­дом кли­ент­ском узле (на бэкен­де тво­его веб‑при­ложе­ния, нап­ример). Но мож­но раз­ворачи­вать и на узлах с хра­нили­щем, лишь бы памяти хва­тило. C SQL Layer мож­но общать­ся по про­токо­лу PostgreSQL, сов­мести­мость поч­ти пол­ная.

У key-value хра­нили­ща име­ется ряд инте­рес­ных огра­ниче­ний. Раз­мер клю­ча — не более 10 Кб. Раз­мер зна­чения — не более 100 Кб. Дли­тель­ность тран­закции — не более 5 с. Одна тран­закция может изме­нить не более 10 Мб дан­ных. Вооб­ще, к тран­закци­ям у FoundationDB отно­шение тре­пет­ное. Пер­вым пун­ктом в white paper идет ода тран­закци­ям: они удоб­ные, понят­ные, полез­ные и не такие уж дорогие. Пос­ле столь­ких гру­бых слов, ска­зан­ных о тран­закци­ях раз­работ­чиками NoSQL-решений, на гла­за навора­чива­ется ску­пая муж­ская сле­за. Тран­закции чес­тные. Ато­мар­ность рас­простра­няет­ся на нес­коль­ко опе­рато­ров, укла­дыва­ющих­ся в ука­зан­ные выше огра­ниче­ния по вре­мени и объ­ему тран­закции.

Изо­ляция обес­печива­ется муль­тивер­сион­ностью пред­став­ления дан­ных, кро­ме того, если две тран­закции кон­флик­туют (нап­ример, одна изме­няет те же клю­чи, которые чита­ет дру­гая), то более поз­дняя завер­шает­ся с ошиб­кой. На кли­енте рекомен­дует­ся пов­торять тран­закции до дос­тижения успе­ха. Зато обес­печива­ется уро­вень изо­ляции serialized для мно­гополь­зователь­ско­го дос­тупа. Есть опе­ратор явно­го начала тран­закции.

SQL Layer рас­ширя­ет реляци­онную модель груп­пами таб­лиц. Цель та же, которой руководс­тво­вались соз­датели докумен­то‑ори­енти­рован­ных NoSQL, — уско­рение дос­тупа к свя­зан­ным дан­ным. Ты находишь в схе­ме дан­ных тес­но свя­зан­ные таб­лицы (типич­ный при­мер: заказ и товары в нем) и объ­еди­няешь их с помощью спе­циаль­ного внеш­него клю­ча в груп­пу таб­лиц. Не в документ, а в груп­пу таб­лиц. В одну груп­пу вхо­дят иерар­хичес­ки свя­зан­ные таб­лицы, так же как в документ объ­еди­няют­ся иерар­хичес­ки под­чинен­ные дан­ные.

Груп­па таб­лиц хра­нит­ся целиком, все клю­чи в груп­пе обра­зуют неп­рерыв­ный диапа­зон. Это зна­чит, что с боль­шой веро­ятностью груп­па таб­лиц хра­нит­ся на одном узле клас­тера и все опе­рации над ней могут быть выпол­нены на этом узле. Поэто­му джой­ны внут­ри груп­пы более эффектив­ны. Тем не менее таб­лицы внут­ри груп­пы оста­ются пол­ноцен­ными SQL-таб­лицами, их мож­но спо­кой­но джой­нить с таб­лицами из дру­гих групп. Жаль, что нет воз­можнос­ти делать реп­лициру­емые таб­лицы‑спра­воч­ники, пос­коль­ку добав­лять их в груп­пы не всег­да воз­можно (в кор­не дерева таб­лиц может быть дру­гой объ­ект), а джой­нить дан­ные с раз­ных пар­тиций — дорого.

Порядок хранения записей группы таблиц в SQL Layer
По­рядок хра­нения записей груп­пы таб­лиц в SQL Layer

Ог­ромный плюс групп таб­лиц перед докумен­тами — иерар­хию под­чинения мож­но поменять, прос­то перес­тавив внеш­ние клю­чи, нес­коль­кими ALTER TABLE. SQL-слой сам оза­ботит­ся пересоз­дани­ем клю­чей и перерас­пре­деле­нием дан­ных. Кро­ме обыч­ных B-tree индексов, FoundationDB пре­дос­тавля­ет spatial-индексы. При­чем, в отли­чие от боль­шинс­тва СУБД (Oracle, PostgreSQL, MySQL), это не R-tree, а z-order индексы. Объ­ясня­ется этот выбор тем, что под SQL-сло­ем лежит key-value хра­нили­ще. Кро­ме того, FoundationDB поз­воля­ет делать индексы на груп­пу таб­лиц. Ана­логом в тра­дици­онных базах могут слу­жить materialized view для Oracle и PostgreSQL или join-indexes для Teradata.

Кро­ме раз­нооб­разных индексов, в FoundationDB есть прод­винутый опти­миза­тор, который уме­ет не толь­ко сочетать нес­коль­ко раз­личных индексов для одной таб­лицы / груп­пы таб­лиц, но и исполь­зовать пок­рыва­ющие индексы. Опти­миза­тор перес­тра­ивает план выпол­нения зап­роса при уста­рева­нии ста­тис­тики, в отли­чие от всех рас­смот­ренных выше СУБД. Ста­тис­тика собира­ется авто­мати­чес­ки, но может быть обновле­на и вруч­ную соот­ветс­тву­ющей коман­дой ALTER TABLE ... UPDATE STATISTICS. Сам план не содер­жит всей жела­емой информа­ции (ожи­даемая мощ­ность выбор­ки, вре­мя CPU), но поз­воля­ет понять, что же про­исхо­дит.

Сов­мести­мость с ANSI SQL пот­ряса­ющая даже для реляци­онной СУБД. Син­таксис не толь­ко соот­ветс­тву­ет стан­дарту даже в мелочах (GROUP BY и RETURNING), но и име­ет свои спе­цифи­чес­кие рас­ширения. К при­меру, во внеш­нем клю­че дос­таточ­но ука­зать, на какую таб­лицу идет ссыл­ка, стол­бцы ука­зывать необя­затель­но. Есть воз­можность получать стро­ки резуль­тиру­ющей выбор­ки в JSON, дос­таточ­но написать SELECT ** FROM ... (еще один реверанс в сто­рону докумен­тов). В индексе по таб­личной груп­пе мож­но ука­зывать порядок соеди­нения таб­лиц.

Нет явно­го огра­ниче­ния на слож­ность зап­роса и под­запро­сов: их мож­но исполь­зовать как в сек­циях FROM и WHERE, так и в SELECT и SET. Апо­феозом ста­ло то, что фраг­мент кода живого про­екта на PostgreSQL (око­ло тысячи строк), выб­ранный для тес­тирова­ния осо­бен­ностей диалек­та, выпол­нился без еди­ной син­такси­чес­кой ошиб­ки.

 

Заключение

Как видим, вари­антов горизон­таль­ного мас­шта­биро­вания реляци­онной модели на клас­тер не так уж и мно­го. Весь­ма популяр­но шар­дирова­ние по хешу клю­ча. Но это вно­сит замет­ные огра­ниче­ния на эффектив­ные джой­ны. Фак­тичес­ки с тем же удобс­твом, что и на одном ста­ром доб­ром SQL-сер­вере, мож­но опе­риро­вать дан­ными толь­ко в одной пар­тиции. А в таком слу­чае это дан­ные, шар­дирован­ные по одно­му‑единс­твен­ному зна­чению клю­ча.

Го­раз­до инте­рес­нее смот­рится под­ход FoundationDB/Akiban. Все‑таки объ­еди­нять таб­лицы в груп­пы‑докумен­ты — это гораз­до естес­твен­нее, чем исполь­зовать для их хра­нения спе­циаль­ные типы колонок (нап­ример, JSON) внут­ри таб­лиц, как дела­ют клас­сичес­кие СУБД. Опе­рации уже внут­ри это­го агре­гата ста­новят­ся более эффектив­ными, как в докумен­то‑ори­енти­рован­ных NoSQL, а самое при­ятное — сос­тав агре­гата мож­но менять.

Ра­дует, что СУБД перехо­дят на одно­поточ­ную асин­хрон­ную (событий­ную, акторную, ...) архи­тек­туру. Ту самую, которая может быть тебе извес­тна по Node.js или Erlang. Один поток, занима­ющий одно ядро CPU, может обслу­живать в таком вари­анте десят­ки тысяч одновре­мен­ных зап­росов. Это дей­стви­тель­но помога­ет сущес­твен­но повысить throughput, то есть количес­тво обра­баты­ваемых зап­росов в еди­ницу вре­мени.

Пе­чалит, что поч­ти все новей­шие СУБД — проп­риетар­ные. Оста­ется толь­ко наде­ять­ся, что это болезнь рос­та и вско­ре мы уви­дим их и под сво­бод­ными лицен­зиями. Похоже, что рынок NewSQL еще более перег­рет, чем NoSQL. Мно­жес­тво решений кон­куриру­ют друг с дру­гом. Каж­дый обе­щает совер­шенно уни­каль­ную, а по фак­ту — впол­не извес­тную тех­нологию. Под­дер­жка реляци­онной модели изрядно хро­мает. Тран­закци­онность сно­ва вос­при­нима­ется как кон­курен­тное пре­иму­щес­тво, но реали­зовать ее под силу далеко не всем. Дей­стви­тель­но сто­ящих и про­рыв­ных решений — еди­ницы. Будем ждать, ког­да недос­той­ные вым­рут, а дос­той­ные ста­нут еще луч­ше. Мы будем болеть за FoundationDB ;).

Структура рынка СУБД
Струк­тура рын­ка СУБД

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии