Нереляционные СУБД издавна существовали бок о бок с СУБД SQL, оставаясь, однако, в тени последних — ориентированные на SQL системы были вне конкуренции, применялись для решения большинства задач в области управления данными и успешно с ними справлялись. Однако недавно ситуация изменилась: стремительное развитие веб-технологий вывело бизнес на глобальный уровень и привело к возникновению новых проблем и задач, среди которых обеспечение масштабируемости и доступности систем в условиях возможных сбоев, постоянно растущие объемы информации и нагрузки, необходимость работать с разнообразными данными и т. д. Как оказалось, ориентированные на SQL системы в новых условиях работают не лучшим образом: будучи по своей природе односерверными, они плохо масштабируются в распределенной среде, наличие фиксированной схемы не всегда удобно, а мощный язык запросов, строгая согласованность данных и транзакции часто не требуются — и от них можно отказаться в пользу других преимуществ. Таким образом, сложилась благоприятная ситуация для возникновения новой волны нереляционных систем управления данными, нацеленных на решение актуальных сегодня задач. Это движение, объединяющее в себе множество самых разных систем, получило название «NoSQL» (Not Only SQL), хотя полной замены традиционных SQL-ориентированных систем не произошло — SQL и NoSQL оказались разными инструментами, решающими каждый свою задачу.

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

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

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

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

Что может предложить NoSQL?

Большинство систем NoSQL создавались распределенными с расчетом на легкое масштабирование и работу на кластерах из недорогих машин и в облаках, а это означает, что в работу с данными привносятся дополнительные сложности, не свойственные традиционным односерверным СУБД. Например, поддержка транзакций и строгой согласованности данных (означающей, что все клиенты наблюдают одно и то же состояние базы) в условиях распределенной системы требует большого числа синхронных сетевых взаимодействий, что не только повышает время отклика системы, но и снижает ее устойчивость к сбоям. Если при этом система значительно распределена географически, то ситуация еще больше усложняется. Отражением этих особенностей стало эмпирическое утверждение Эрика Брюэра, известное под названием «теорема CAP» [1]: «Распределенная система не может гарантировать одновременно доступность при сбоях узлов, согласованность данных и устойчивость к разделению сети». Кроме того, существуют уточнения, принимающие во внимание такую важную характеристику системы, как время отклика. Руководствуясь этими рассуждениями, разработчики новых систем стараются искать компромиссы — при этом общей тенденцией для NoSQL является ослабление гарантий согласованности данных и отказ от транзакций в пользу большей гибкости, производительности, масштабируемости и устойчивости к сбоям.

При работе с большинством систем NoSQL приходится иметь дело с так называемой согласованностью «в конечном счете» (eventual consistency), гарантирующей, что при отсутствии обновлений данные на всех узлах (реплики) в конце концов станут согласованными. Это означает не только то, что в течение некоторого промежутка времени клиенты будут получать устаревшие данные, но и потенциальную возможность возникновения конфликтующих версий объектов (если операции модификации разрешены на более чем одной реплике). Приложение, таким образом, должно быть способно справляться с подобными ситуациями. Возможны также модификации согласованности «в конечном счете» (зависящие в основном от реализации взаимодействия клиента с системой), которые гарантируют, например, чтение собственных записей, монотонность чтений и т. д. [2].

Другой особенностью, отделяющей NoSQL от SQL, является собственно отказ от SQL. Модели данных и методы работы с данными в рамках NoSQL очень разнообразны и ориентированы на решение достаточно узких классов задач — например, системы, построенные на основе модели ключ-значение, такие как Redis, Riak и Project Voldemort, позволяют добиться очень высокой производительности и могут легко масштабироваться, но эффективный поиск объектов в таких системах возможен только по уникальному ключу. Сам объект может быть как простой последовательностью байтов, так и структурой, позволяющей запрашивать и обновлять отдельные поля. Если же необходима более функциональная и гибкая модель данных, то стоит обратить внимание на документные системы (например, MongoDB и CouchDB), не имеющие строгой схемы данных и оперирующие JSON-документами (JavaScript Object Notation, текстовый формат для представления объектов, допускающий атрибуты простых типов, массивы, а также вложенные объекты). JSON более компактен, чем, например, XML, однако для целей хранения данных могут применяться еще более оптимизированные варианты, такие как BSON (Binary JSON). В терминологии документных СУБД хранимые и представляемые в JSON объекты называют документами. Эти системы обычно поддерживают индексы на полях документов и позволяют выполнять сложные запросы. По своим возможностям такие СУБД ближе всего к SQL, хотя полноценных ACID-транзакций и ограничений целостности в них нет. Тем не менее операции обновления обычно являются атомарными на уровне одного документа, как и некоторые специальные операции. Наконец, существует ряд более узкоспециализированных систем — например, на основе семейств столбцов (по образцу Google BigTable), XML-ориентированные СУБД, графовые системы, объектно-ориентированные базы данных и т. д. Многие системы NoSQL поддерживают параллельную обработку данных в рамках популярной парадигмы MapReduce.

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

Итак, налицо не только большое разнообразие систем NoSQL, но и множество вариантов их конфигурации, заметно влияющих на их свойства.

Новый SQL

Для масштабирования и повышения производительности систем SQL применяют разделение данных между серверами (на уровне приложения либо с помощью связующего слоя ПО — например, dbShards), репликацию (в основном по схеме главный-подчиненный), кэширование данных в оперативной памяти (используя, например, Memcached), денормализацию и другие подходы, часто сводящие на нет преимущества SQL. Так, при разделении данных между узлами теряются возможности контроля ссылочной целостности и глобальных транзакций, а при чтении данных из кэша нет гарантии, что они актуальны. Однако и в мире SQL появился ряд OLTP-систем, основанных на новых архитектурах, обеспечивающих высокую производительность и масштабируемость (например, VoltDB, H-Store, Clustrix и т. д.), — это так называемые представители поколения NewSQL. Данные системы могут эффективно справляться с большими потоками «небольших» транзакций, поддерживают семантику ACID в полном объеме, как и SQL. Транзакции, затрагивающие несколько узлов, выполняются менее эффективно, а также может потребоваться достаточно трудоемкий процесс разработки схемы и написания хранимых процедур для работы с данными (например, в VoltDB и H-Store). Другие же системы, такие как Clustrix и MySQL Cluster, сохраняют совместимость с MySQL. Увеличение производительности в системах NewSQL может достигаться за счет работы целиком в оперативной памяти (периодическое сохранение снимков состояния базы на дисках и репликация позволяют решить проблемы надежности), а также за счет применения SSD.

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

Использование SQL-интерфейса для NoSQL-систем может быть заманчиво с точки зрения облегчения миграции существующих приложений, однако SQL плохо подходит для манипулирования квазиструктурированными и неструктурированными данными. Тем не менее предпринимаются попытки создания схожих с SQL языков для унификации работы с системами NoSQL (например, UnQL — SQL-подобный язык, ориентированный на работу с неструктурированными данными), хотя ввиду многообразия моделей данных и особенностей конкретных систем это является сложной и вряд ли выполнимой в ближайшее время задачей.

Задача и инструмент

Для любых приложений наличие строгой согласованности данных и транзакций желательно, а часто и необходимо, однако в ряде случаев решающим фактором могут оказаться возможности масштабирования, производительность или модель данных. Рассмотрим, например, моделирование объектов с динамическими атрибутами с возможностью поиска и сортировки. Имеется несколько решений на базе СУБД SQL, но все они имеют серьезные недостатки и ограничения: единая таблица, содержащая все возможные атрибуты в качестве столбцов, базовая таблица и несколько таблиц для подтипов (если возможно выделить подтипы с более или менее одинаковыми наборами атрибутов) либо использование схемы «сущность — атрибут — значение» (Entity-Attribute-Value). Дело в том, что SQL был изначально предназначен для работы со структурированными данными, а неструктурированные плохо вписываются в эту модель. Стоит заметить, что некоторые СУБД, например Microsoft SQL Server, предоставляют возможность оптимизации хранения разреженных столбцов (содержащих большое количество неопределенных значений), однако всех проблем это не решает.

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

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

Однако применение NoSQL там, где требуются строгая согласованность и транзакционная семантика, неоправданно — подобная функциональность должна быть реализована на уровне приложения, что трудоемко и не всегда возможно. Для таких задач (например, работа с финансовой информацией, обработка заказов и т. д.) наиболее подходящим вариантом был и остается SQL (возможно, в виде одной из NewSQL-систем).

SQL и NoSQL: репликация и кэширование

Репликация между системами SQL достаточно распространена и используется как для повышения надежности и масштабирования, так и, например, по соображениям дальнейшей обработки данных в автономном режиме. Большинство решений NoSQL также поддерживают репликацию, ограничиваясь обычно только собственными экземплярами, однако весьма интересна гетерогенная репликация между системами SQL и NoSQL (c использованием дополнительного программного обеспечения). Например, можно копировать данные из системы SQL в NoSQL для применения MapReduce. Кроме того, можно воспользоваться преимуществом NoSQL в скорости чтения, а SQL СУБД использовать для выполнения относительного редких операций записи или NoSQL для масштабирования чтений. Реализация репликации между столь различными системами в общем случае нетривиальна, однако такие попытки осуществляются (например, репликация между MySQL и MongoDB при помощи Tungsten Replicator).

Применение систем NoSQL, основанных на модели ключ-значение, в качестве кэша расширяет возможности традиционного кэширования, обеспечивая долговременное хранение данных и дополнительный функционал. Кроме того, интерфейс некоторых систем совместим с Memcached, что повышает удобство их применения.

Одно приложение — много СУБД

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

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

Пример построения платформы электронной коммерции на основе независимых сервисов
Пример построения платформы электронной коммерции на основе независимых сервисов

 

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

Интеграция SQL и NoSQL

Объектно-реляционное отображение (Object-Relational Mapping, ORM) давно стало стандартной практикой при разработке объектно-ориентированных приложений, использующих SQL. Являясь промежуточным слоем для преодоления несоответствия между SQL и объектной моделью приложения, ORM обеспечивает управление жизненным циклом объектов, абстракцию от деталей хранения, выполнение запросов в терминах объектной модели, генерацию схемы базы данных и т. д. Тем не менее классические ORM-библиотеки, несмотря на развитый инструментарий и богатую функциональность, мало подходят для высоконагруженных систем. Наличие высокого уровня абстракции мешает применять низкоуровневые оптимизации и требует слишком больших накладных расходов (например, преобразование «объектного» диалекта SQL в реальный запрос, который будет выполнен на целевой СУБД). Однако неоспоримым преимуществом применения ORM-библиотек является упрощение и ускорение разработки. С развитием документной модели данных появились также и средства объектно-документного отображения (Object-Document Mapping, ODM). Эти библиотеки обычно более легковесны, чем их аналоги в ORM, однако тоже не спешат разрешать пользователю вмешиваться в процесс отображения на низком уровне.

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

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

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

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

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

***

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

Литература

  1. Brewer E. Towards Robust Distributed Systems. ACM Symposium on the Principles of Distributed Computing, Portland, Oregon. 2000.
  2. Vogels W., Eventually Consistent, ACM Queue, Т. 6, № 6, 2008.

Сергей Кузнецов (kuzloc@ispras.ru) — главный научный сотрудник, Институт системного программирования РАН. Андрей Посконин (aposk@yandex.ru) — аспирант, факультет вычислительной математики и кибернетики МГУ им. М. В. Ломоносова (Москва).