От SQL к NoSQL и обратно

От SQL к NoSQL и обратно

 

Константин Селезнев

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

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

За многолетнюю историю развития СУБД было выработано несколько моделей представления структурированной информации: иерархическая, сетевая, реляционная, объектно-реляционная и т. д. Наибольшее распространение получила реляционная модель, предложенная Эдгаром Коддом в 1970 году, которая надолго стала стандартом представления структурированной информации. Причины этого вытекают из сформулированных требований к СУБД. Во-первых, реляционная модель оказалась достаточно простой, с точки зрения прикладного программиста. Во-вторых, она обладает достаточной гибкостью и позволяет представлять информацию из самых разных предметных областей. В-третьих, в рамках этой модели используется мощный и удобный подход к манипулированию данными (реляционная алгебра), впоследствии оформленный в виде языка SQL. В-четвертых, простота и естественность реляционной алгебры позволили создать высокопроизводительные и универсальные алгоритмы выполнения запросов, удовлетворяющие большую часть потребностей разработчиков прикладных систем. Наконец, к сегодняшнему дню создано множество инструментальных средств, ориентированных на обработку реляционных данных, что дает еще одно преимущество реляционных СУБД.

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

Реляционная модель предполагает оперирование только атомарными данными и исключает обработку какой-либо неструктурированной информации, а это приводит к тому, что хранение графических, аудио- или видеоданных становится невозможным. Более того, невозможно даже хранение текстовых документов произвольной длины. Поэтому, первым отступлением от классической реляционной модели в сторону удобства разработки прикладного ПО можно считать введение типа BLOB (Binary Large Object — «бинарные данные большого объема»), который сегодня поддерживается большинством современных СУБД и закреплен в стандартах языка SQL. Каждая СУБД, помимо BLOB, может иметь свои собственные «дополнительные» типы данных, которые обычно требуют введения дополнительных поисковых операций (например, полнотекстовый поиск по BLOB). Это еще дальше уводит от классической реляционной модели.

По мере роста объемов хранимой информации и сложности ее внутренних взаимосвязей стали возникать новые проблемы. Увеличение сложности SQL-запросов привело к тому, что СУБД уже далеко не всегда способны выработать оптимальный план выполнения поступившего запроса. На практике это производится двумя способами. Первый — в тексте SQL указываются «подсказки», помогающие оптимизатору запросов выработать наиболее эффективный план. Второй — вместо запроса используется хранимая процедура. Причина хорошей применимости обоих способов кроется в том, что встроенные в СУБД оптимизаторы запросов располагают только статистической информацией о содержимом базы и строят планы выполнения запросов только на ее основе, а разработчик прикладного ПО всегда обладает гораздо большими сведениями.

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

Допустим, требуется организовать хранение большого множества чисел и быстро выполнять операции поиска, добавления и удаления числа. Для этого в реляционной базе создается таблица с одним числовым полем, а по этому полю формируется поисковый индекс на основе B-дерева. Такое решение работает быстро, но предполагает дублирование информации (числа хранятся и в самой таблице, и в поисковом индексе). Более эффективным было бы использование только B-дерева, но реляционные СУБД не всегда способны это определить.

Данный пример может показаться слишком простым, но похожая ситуация реализована в Triple Storages (хранение графов семантических сетей в семантическом Web), отвечающих за хранение и обработку графов семантических связей. Каждая дуга графа представляет собой три числа (два кода вершин и один код окраски). Множество всех дуг можно было бы хранить в виде реляционной таблицы с необходимыми поисковыми индексами, но вместо этого хранят только поисковые индексы, поскольку каждый из них содержит всю информацию о каждой дуге.

Третий пример — полнотекстовые поисковые системы Интернета. Их база данных должна содержать поисковые слова и коды документов, в которых эти слова являются ключевыми. Это несложно сделать с помощью реляционной таблицы, состоящей из двух полей — поисковое слово и код документа. Если слово встречается в нескольких документах или документ имеет несколько ключевых слов, то таблица содержит несколько записей.

Возможна существенная оптимизация рассмотренного примера: скажем, вместо текстового представления слова можно хранить его целочисленный код, а вместо кода документа хранить BLOB, где будут содержаться коды всех документов, имеющих отношение к данному слову. Дальнейшая оптимизация в рамках реляционного подхода невозможна, но ее можно провести посредством так называемого инвертированного индекса. Коды документов сортируются по возрастанию, и в полученном списке вычисляются разницы между кодами соседних документов. Получается последовательность, состоящая из кода первого документа и множества чисел небольшой величины. Она хорошо сжимается алгоритмами энтропийной компрессии (например, алгоритмом Лемпеля — Зива-Велча), в результате чего значительно сокращается объем ввода/вывода, ускоряется загрузка информации с диска и в конечном счете растет скорость полнотекстового поиска.

Задача не для реляционной СУБД

Имеется набор из 10 тыс. датчиков, один раз в секунду отправляющих показатели (вещественные числа). Показатели датчиков стабильны и изменяются плавно. Необходимо в интерактивном режиме анализировать месячные данные и определять, приводит ли резкое изменение одного показателя больше чем на фиксированную пороговую величину к изменению другого показателя больше чем на другую пороговую величину. Желательно использовать обычный ПК, не привлекая специальные решения (RAID-массивы и т. п.).

Если каждый датчик генерирует значение раз в секунду, то за месяц объем данных составит 96 Гбайт, и если использовать РСУБД, то потребуется примерно такой же объем дискового пространства плюс расход на хранение индексов «время -> показания датчиков», количество которых зависит от логической структуры базы. Если потребуется хранить данные за несколько месяцев, то реляционная СУБД уже не подходит.

Показания датчиков можно представить в виде матрицы: строки соответствуют моментам времени, а столбцы — номерам датчиков. Проектирование реляционной базы сводится к представлению этой матрицы в виде реляционных таблиц. Самым простым и логичным с точки зрения реляционного подхода способом организации является использование таблицы с полями «время – код датчика – значение», но это решение неприменимо на практике из-за огромного размера таблицы (2 592 000 секунд x 10 тыс. значений).

Указанную матрицу можно «разворачивать» в реляционные таблицы по горизонтали или по вертикали. В первом случае записи реляционных таблиц будут соответствовать моментам времени, а поля реляционных таблиц — номерам датчиков. Для оценки изменения значения датчика нужно взять запись, соответствующую данному моменту времени, получить запись для следующего момента и вычислить разницу значений в одном и том же поле. Поскольку первичным ключом таблицы является значение времени, то первые два шага будут выполняться достаточно медленно, и даже поиск моментов, когда датчик изменил свои показания больше чем на пороговую величину, на практике работать не будет. Это объясняется тем, что для каждой из 2 592 000 записей нужно будет найти «следующую» из тех же 2 592 000 записей. Если в целях оптимизации таблицы разбить по дням, то каждая из них будет содержать 24 x 60 x 60 = 86 400 записей, и, соответственно, нужно будет делать 30 (количество дней) поисков, каждый из которых будет анализировать 86 400 записей и для каждой искать «следующую» из 86 400 записей.

Если матрицу показаний разворачивать по вертикали, то записи реляционных таблиц будут соответствовать номерам датчиков, а поля — моментам времени. В общей сложности получится совокупность из 2 592 000 полей, разбитых по таблицам. Очевидно, что работать с такой базой очень сложно.

Таким образом, классический реляционный подход не дает эффективного решения задачи.

В рассмотренных примерах информация хотя и представима в терминах реляционной модели, но обладает спецификой, которую можно эффективно использовать для оптимизации работы реляционной СУБД. Однако по мере роста объемов базы рано или поздно наступает предел таких оптимизаций.

Основные концепции NoSQL

Важной вехой для высоконагруженных систем стало развитие Интернета, ряд сервисов которого (DNS-серверы, поисковые машины, социальные сети и т.д.) изначально должны обрабатывать большие массивы информации и отвечать на огромное число запросов. Это требует не только максимального учета любой специфики обрабатываемой информации, но и перехода на распределенные вычисления. Никакой сколь угодно мощный сервер в принципе не способен в одиночку обеспечить нужную производительность. В итоге основными принципами NoSQL стали отказ от реляционной модели для учета специфики обрабатываемых данных, а также хорошая горизонтальная масштабируемость до сотен и тысяч серверов для обеспечения скорости работы. Однако это выявило еще одну проблему, сформулированную в виде теоремы CAP (Consistence, Availability, Partition tolerance — «согласованность, доступность, устойчивость к разделению»): в распределенной вычислительной системе невозможно одновременно выполнить требования по согласованности данных, доступности системы и устойчивости к разделению. Под последним требованием понимается то, что система не распадается на несколько изолированных секций, внутри которых выполняются требования по согласованности и доступности.

Нестрогое доказательство теоремы CAP основано на простых рассуждениях. Пусть распределенная система состоит из N серверов, каждый из которых обрабатывает запросы некоторого числа клиентских приложений (рис. 1). При обработке запроса сервер должен гарантировать актуальность информации, содержащейся в отсылаемом ответе на запрос, для чего предварительно нужно выполнить синхронизацию содержимого его собственной базы с другими серверами. Таким образом, серверу необходимо ждать полной синхронизации либо генерировать ответ на основе не синхронизированных данных. Возможен и третий вариант, когда по каким-либо причинам синхронизация производится только с частью серверов системы. В первом случае оказывается не выполненным требование по доступности, во втором — по согласованности, в третьем — по устойчивости к разделению.

Иногда распределенные системы классифицируют по видам выполняемых требований CAP: CA — система удовлетворяет требованиям по согласованности и доступности, CP — по согласованности и устойчивости, AP — по доступности и устойчивости. В любом из трех случаев не будет выполнено свойство ACID (Atomicity, Consistency, Isolation, Durability — «атомарность, согласованность, изолированность, долговечность»), обычно строго соблюдаемое в реляционных СУБД, а ему противопоставляется свойство BASE (Basically Available, Soft State, Eventually consistent). При сбое в некоторых узлах системы отказ получает только часть приложений, взаимодействующих с вышедшими из строя узлами. В ходе взаимодействия используются протоколы без состояния, что снижает нагрузку на отдельные узлы и позволяет ее перераспределять. Наконец, допустима временная несогласованность данных в разных узлах системы при условии, что информация будет синхронизирована через некоторый обозримый промежуток времени. BASE используется для наиболее общего описания требований к распределенным NoSQL-системам, подпадающих под утверждение теоремы CAP и не удовлетворяющих требованиям ACID.

Системы NoSQL

Существует множество различных NoSQL-систем обработки данных, в котором можно выделить следующие основные классы: хранение XML-документов, хранение пар «ключ – значение», хранение графов, хранение кортежей произвольной длины, Triple Storages, многомерные данные и т. д.

Хранилища XML-документов (BaseX, eXist) представляют собой средства для работы с большим количеством XML-документов или с документами большого размера. Информация содержится не в текстовом виде, а в некотором внутреннем формате, позволяющем быстро выполнять операции поиска (запросы XPath и XQuery) и изменения документов (XSLT, eXtensible Stylesheet Language Transformations). При загрузке документа проводится его синтаксический анализ, результаты которого помещаются в базу, а при извлечении документа его текст восстанавливается на основе содержимого внутренних структур базы.

Формат XML — не единственный способ текстового представления структурированной информации: по аналогии с XML-хранилищами существуют СУБД (Apache Couch DB и MongoDB) для работы с данными, представленными в виде JSON (Java Script Object Notation ) или BSON (Binary JSON).

Хранение пар «ключ – значение» заключается в реализации двух операций: запись информации по ключу и чтение по ключу — при этом одному ключу может соответствовать сразу несколько значений. Востребованность данной функциональности при построении высоконагруженных систем привела к тому, что появилось целое множество соответствующих СУБД, которые, в зависимости от способа реализации, можно разделить на постоянные (CDB), редко изменяющиеся (Appache Cassandra, membase, MemcacheDB) и часто изменяющиеся (memcached).

Постоянные СУБД не поддерживают изменения информации «на лету» — база данных создается один раз и затем используется продолжительное время в режиме чтения. Это, во-первых, позволяет провести глубокий анализ содержимого базы на этапе ее создания и обеспечить высокую степень компрессии данных, что впоследствии позволит минимизировать ввод/вывод и тем самым ускорить операции поиска. Во-вторых, если база не изменяется, то упрощается и ускоряется многопользовательская работа, поскольку не требуется выполнять никаких блокировок.

Системы для работы с часто изменяющимися данными всю информацию содержат только в оперативной памяти и вообще не используют ввода/вывода. Такой подход популярен при реализации механизмов кэширования, но принципиально не подходит при построении основного хранилища, поскольку любая авария питания приведет к потере данных. Наконец, системы с редко изменяющимися данными являются некоторым промежуточным звеном, обеспечивая возможность корректировки базы, достаточно высокую скорость поиска и сохранение информации на диске.

Часть хранилищ пар «ключ – значение» основаны на хэш-таблицах, а часть — на B-деревьях (BerkleyDB и MemcacheDB), что помимо выполнения операции поиска дает возможность упорядочивать ключи по возрастанию или убыванию.

В общем случае системы рассматривают ключ и значение как массив байтов, причем длина ключа может достигать нескольких килобайтов, а значения — нескольких мегабайтов. Однако часто некоторые СУБД предоставляют дополнительные возможности для структурирования ключей и значений. Так, Apache Cassandra позволяет разбивать значения на несколько колонок и работать с каждой из них отдельно, а BigTable, напротив, использует трехкомпонентные ключи (ключ столбца, ключ строки и временная метка), но значения рассматривает как массив байтов. Такое устройство ключа позволяет использовать BigTable как средство работы с двумерными таблицами большого размера. Примерно по такой же схеме работают хранилища кортежей произвольной длины.

Системы хранения графов и Triple Storages ориентированы на работы с семантическими данными, представленными в виде узлов и дуг. Отличительной чертой Triple Storages является то, что они ориентированы на поддержку стандартов SemanticWeb.

Все эти классы систем NoSQL очень разнородны, и пока отсутствует единый стандарт требований к ним, однако такая стандартизация станет необходима по мере создания новых высоконагруженных систем и повышения требований к скорости обработки и стоимости разработки. Наличие единых стандартов облегчит использование нескольких NoSQL СУБД в одном проекте, упростит интеграцию СУБД между собой, ускорит миграцию на новые СУБД, сделает возможным создание универсальных инструментальных средств NoSQL, позволит заранее осуществлять подготовку специалистов по NoSQL и т. д.

В 2011 году был сделан первый шаг в направлении стандартизации — анонсирован язык запросов UnQL (Unstructured Query Language) для работы с неструктурированной информацией. Для удобства прикладных разработчиков синтаксис и семантика языка во многом схожи с SQL, что вполне естественно — благодаря поддержке UnQL каждая NoSQL-СУБД может стать гибким, удобным и легко используемым инструментом, и она по-прежнему будет основываться на собственных технических решениях, дающих преимущество в каких-либо условиях эксплуатации.

В качестве примера можно указать систему для хранения пар «ключ – значение». Если все СУБД поддерживают UnQL, то прикладной разработчик может подобрать оптимальную, практически не меняя кода приложения. Например, если необходима организация кэширования, то лучше использовать СУБД, ориентированную на работу в памяти (например, membase). Если необходима работа с редко изменяющимися данными, то имеет смысл выбрать СУБД, предназначенную для работы именно в таких условиях (например, CDB). Если помимо поиска необходима сортировка ключей, то подойдет система, основанная на B-деревьях (например, BerkleyDB).

Что лучше?

Реляционные СУБД не спешат мириться со своими недостатками и поддерживают все новые типы данных, используемые в системах NoSQL. Простейшие примеры такого развития (поддержка типов BLOB и полнотекстового поиска) уже упоминались, а более сложный пример — поддержка XML-документов и XPath-запросов. Эта функциональность ключевая для специализированных СУБД (BaseX, eXist), ориентированных на работу с XML, но она же почти полностью присутствует в некоторых реляционных СУБД (например, в Oracle).

Таким образом, SQL и NoSQL движутся навстречу друг другу, а со временем могут слиться в единый подход SQL+NoSQL к разработке СУБД. Возникает вопрос: что лучше использовать — NoSQL или SQL? Широко распространенная и хорошо изученная реляционная СУБД дает массу дополнительных преимуществ: интеграция с уже существующими решениями на базе реляционных СУБД, отказоустойчивость, наличие более богатого инструментария и т. д. Сейчас наблюдается тенденция применения NoSQL вместо SQL, но не исключено, что по мере развития SQL начнется обратное движение. Со временем, чем ближе будут становиться SQL и NoSQL, тем больше будет «метаний» от одного подхода к другому.

При объединении SQL и NoSQL в единое решение из-за многообразия используемых методов хранения информации СУБД может превратиться в огромный черный ящик со сложными и разветвленными настройками, что сильно затруднит работу с ней. Похожая ситуация уже происходила с некоторыми языками программирования (например, Алгол-68, PL/1), когда их семантика оказалась сильно перегружена и вместо удобного инструмента разработчики получили плохо управляемого «монстра». Кроме того, если будет разработан стандарт на системы SQL+NoSQL, то он может оказаться слишком сложным и перегруженным.

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

Современные реляционные СУБД включают в себя все необходимые подсистемы управления данными (накопление, контроль целостности, кэширование, оптимизация и выполнение запросов, репликация и т. д.). После слияния SQL с NoSQL новые СУБД будут предоставлять только «низкоуровневые» модули работы с данными, а все остальное (например, распараллеливание сложных запросов или стратегия кэширования) программист должен либо настроить самостоятельно, либо использовать типовую конфигурацию. В результате для каждого применения будет создаваться целевая СУБД. Уже сейчас для многих практических задач (полнотекстовый поиск, хранение словарей) гораздо эффективнее реализовать собственное узкоспециализированное решение вместо использования СУБД.

NoSQL в работе

Все датчики из задачи предыдущей врезки дают показания раз в секунду, поэтому можно сделать следующее. Во-первых, вместо показаний времени использовать номер секунды, прошедшей с начала месяца. Во-вторых, использовать его не как ключ ассоциативного массива, а как индекс одномерного массива показаний датчика за месяц.

Каждый датчик изменяет свое значение плавно, и последовательность его показаний имеет смысл хранить по аналогии с инвертированными индексами в поисковых системах. Так, история показаний разбивается на дни, а для каждого дня хранится начальное показание датчика и последовательность дельт. В итоге за один день будет накапливаться 24 x 60 x 60 = 86 400 чисел, но 86 399 из них окажутся близки к нулю (так как датчик меняет значения плавно) и будут очень хорошо сжиматься. Если сжатие будет 10-кратным, то для хранения показаний одного датчика за один день понадобится 24 x 60 x 60 = 86 400 чисел, что потребует примерно 350 Мбайт без сжатия и 35 Мбайт со сжатием. Соответственно, вся база будет занимать около 10 Гбайт, при этом ее можно разбить на файлы, содержащие показания набора датчиков за некоторый промежуток времени. Это позволяет подобрать оптимальные параметры разбиения базы на файлы и добиться чтения информации в интерактивном режиме.

Для ускорения скорости анализа нужно хранить дополнительную битовую матрицу, строки которой соответствуют датчикам, а столбцы — моментам времени. Элемент матрицы равен 1, если в данный момент времени данный датчик меняет свое значение больше чем на указанную фиксированную пороговую величину. Суммарно вся матрица будет состоять из 2 592 000 x 10 000 ячеек, что потребует 3 Гбайт памяти, но поскольку датчики меняют свое значение плавно и редко, то матрица будет сильно разреженной и при сжатии займет не более 300 Мбайт.

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

С точки зрения реализации будет рациональным разделить битовые матрицы на блоки (один блок соответствует набору датчиков и некоторому промежутку времени), хранить их в памяти и не производить операции ввода/вывода. Тогда скорость работы данной целевой СУБД будет зависеть только от быстродействия процессора. Поскольку нужные для этого алгоритмы хорошо распараллеливаются, то возможно добиться работы системы в интерактивном режиме.

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

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

Логическим завершением подхода построения целевых СУБД вместо использования классических серверных архитектур является движение в сторону RAD (Rapid Application Development), при котором программист формально описывает качественные и количественные требования к создаваемой СУБД, получает предварительное решение, а затем оптимизирует его отдельные элементы вплоть до самостоятельной реализации отдельных блоков. Созданная таким образом целевая СУБД может быть впоследствии развернута на любой инфраструктуре, включая и облака.

По принципу создания и применения целевые СУБД чем-то напоминают инструменты ETL для загрузки данных в информационные хранилища. Сейчас эти инструменты не готовы для вызова из программ, но позволяют создать, отладить, оптимизировать и многократно использовать саму процедуру ETL. Примерно по той же схеме работают генераторы запросов, и примерно по той же схеме могут работать СУБД типа SQL+NoSQL.

***

Два направления развития СУБД — SQL и NoSQL — во многом противопоставляются, хотя и подчиняются единым законам и движутся навстречу друг другу. Пока это движение почти незаметно, но оно активизируется по мере появления необходимости в средствах построения высоконагруженных систем, в результате чего произойдет слияние этих направлений. Получившиеся таким образом СУБД могут оказаться слишком громоздкими для использования на практике, поэтому, возможно, произойдет отказ от текущей архитектурной концепции СУБД в сторону появления СУБД, представляющих собой набор «кубиков» для построения целевой системы, обладающей характеристиками, необходимыми для конкретных высоконагруженных приложений.