Доступ к данным

Что же такое базы данных документов?

Джули Лерман

 

Джули ЛерманПочти наверняка вы хотя бы слышали о NoSQL. На эту тему даже были статьи в журнале «MSDN Magazine». Многие люди, к которым я отношусь с глубоким уважением, весьма заинтересованы в NoSQL, и я, выросшая на реляционных базах данных, хотела получше разобраться в ней. Я проделала довольно большую исследовательскую работу и замучила своих друзей, пытаясь вникнуть в этот вопрос. Теперь я намерена поделиться с вами тем, что выяснила о подмножестве баз данных NoSQL, которое называется «базы данных документов» (document databases). Другое подмножество — базы данных пар «ключ-значение». Microsoft Azure Table Storage, о котором я писала в своей рубрике за июль 2010 г. (msdn.microsoft.com/magazine/ff796231), — один из примеров NoSQL-хранилища пар «ключ-значение».

Сначала я должна дать определение NoSQL. Этот термин стал слишком часто использоваться. Им обобщенно называют механизмы хранения данных, не являющиеся реляционными и поэтому не требующими применения SQL для доступа к своим данным. В статье «Addressing the NoSQL Criticism» (bit.ly/rkphh0) Брэдли Холт (Bradley Holt), эксперт по CouchDB, упоминает, что он слышал, как некоторые переопределяют NoSQL как «не только SQL (not only SQL)». Его точка зрения состоит в том, что NoSQL ни в коем случае не следует рассматривать как движение против SQL. Это мнение мне по душе, потому что я предпочитаю использовать для конкретной работы наилучшие инструменты.

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

Из множества доступных баз данных документов я сосредоточусь на двух наиболее популярных — MongoDB (mongodb.org) и CouchDB (couchdb.apache.org), а также на RavenDB (ravendb.net), которая была написана для Microsoft .NET Framework и завоевывает все большую популярность (см. статью «Встраивание RavenDB в приложение ASP.NET MVC 3» в этом номере). Все детали этих баз данных вы можете узнать, посетив их веб-сайты.

За исключением нескольких особенностей (о которых я кратко расскажу в этой статье) эти базы данных предоставляют свои данные в основном по HTTP, хранят их в виде документов JavaScript Object Notation (JSON) и поддерживают API на нескольких языках. Общие цели этих баз данных — простота, скорость и масштабируемость. Не менее важно, что все три базы данных являются проектами с открытым исходным кодом.

От эксперта по MongoDB я услышала, что главная цель этого продукта — производительность. Эксперт по CouchDB указал на простоту и надежность. А Эйенде Рейхен (Ayende Rahien), автор RavenDB, сказал, что проект RavenDB нацелен на «быстрые операции записи, быстрые операции чтения и мир во всем мире». Однако каждая из этих трех баз данных документов может предложить гораздо больше.

Альтернатива реляционным базам данным, а не замена

NoSQL и базы данных документов — это альтернатива реляционным базам данных, а не их замена. У каждой из них своя ниша, и они просто расширяют круг вашего выбора. Но как выбрать нужное? Важный критерий — теорема согласованности, доступности и отказоустойчивости разделов (Consistency, Availability and Partition Tolerance, CAP). Она утверждает, что при работе в распределенных системах можно получить только две из трех гарантий (C, A или P), поэтому вы должны решить, что для вас важнее. Если важнее всего согласованность, тогда нужно использовать реляционную базу данных.

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

Термин, который вы будете часто слышать, — «конечная согласованность» (eventual consistency) или, как выражено на сайте RavenDB: «Лучше устаревшие данные, чем автономные». В ряде предметных областей достаточно конечной согласованности. Там не страшно, если извлекаемые данные не являются актуальными с точностью до миллисекунды.

В каких-то ситуациях, возможно, важнее существование некоей версии данных, а не ожидание фиксации всех транзакций. Это относится к доступности (A) в CAP, что главным образом связано с поддержкой бесперебойной работы сервера. Уверенность в том, что вы всегда можете обратиться к базе данных, приоритетнее, и это позволяет значительно ускорить работу базы данных (т. е. базы данных документов работают быстро!). Вы увидите, что отказоустойчивость разделов (P) тоже важна для баз данных документов, особенно при горизонтальном масштабировании.

RESTful HTTP API — по большей части

Многие из баз данных NoSQL доступны в стиле RESTful, поэтому вы устанавливаете соединение с вашей базой данных по URI, а запросы и команды передаете как HTTP-вызовы. Исключением является MongoDB. По умолчанию она использует TCP для взаимодействий с базой данных, хотя доступен минимум один HTTP API. CouchDB и MongoDB предоставляют специфичные для конкретных языков API, которые позволяют писать и выполнять запросы и обновления на соответствующем языке, обходясь без кодирования HTTP-вызовов. В RavenDB имеется API .NET-клиента, упрощающий взаимодействие с базой данных.

Связанные данные в одной записи

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

Ниже приведен типичный пример того, как может выглядеть запись в базе данных документов (я позаимствовала этот пример со студентом из учебника по MongoDB):

{
  "name" : "Jim",
  "scores" : [ 75, 99, 87.2 ]
}

А вот пример из вводной статьи по CouchDB, где описывается книга:

{
  "Subject": "I like Plankton"
  "Author": "Rusty"
  "PostedDate": "5/23/2006"
  "Tags": ["plankton", "baseball", "decisions"]
  "Body": "I decided today that I don't like baseball.
    I like plankton."
}

Это простые структуры со строковыми данными, числами и массивами. Вы также можете встраивать одни объекты в другие и получать более сложную структуру документа, такую как в этом примере публикации в блоге:

{
  "BlogPostTitle": "LINQ Queries and RavenDB",
  "Date":"\/Date(1266953391687+0200)\/",
  "Content":"Querying RavenDB is very familiar for
    .NET developers who are already using LINQ
    for other purposes",
  "Comments":[
             {
             "CommentorName":"Julie",
             "Date":"\/Date(1266952919510+0200)\/",
             "Text":"Thanks for using something I already
               know how to work with!",
             "UserId":"users/203907"
             },
  ]
}

Уникальные ключи

Ключи нужны в любой базе данных. Если вы не предоставляете их, они создаются за вас на внутреннем уровне. Без ключей невозможна индексация баз данных, но в вашей предметной области могут потребоваться известные ключи (known keys). Обратите внимание в предыдущем примере с публикацией в блоге на ссылку на «users/203907». Именно так RavenDB использует значения ключей и позволяет определять взаимосвязи между документами.

Хранилище в формате JSON

Общее во всех этих записях-примерах — они используют JSON для хранения данных. CouchDB и RavenDB (и многие другие) хранят свои данные в JSON. MongoDB использует разновидность JSON под названием Binary JSON (BSON), которая позволяет выполнять двоичную сериализацию. BSON — это внутреннее представление данных, поэтому, с точки зрения программирования, никакой разницы быть не должно.

Простота JSON облегчает преобразование объектных структур почти любого языка в формат JSON. Поэтому вы можете определить объекты в своем приложении и хранить их непосредственно в базе данных. Это избавляет разработчиков от необходимости использования ORM (object-relational mapper) для постоянной трансляции между схемой базы данных и схемой классов/объектов.

Механизмы полнотекстового поиска, например Lucene (lucene.apache.org), на который опирается RavenDB, обеспечивают высокоскоростной поиск в этих текстовых данных.

Обратите внимание на дату в примере с публикацией в блоге. В JSON нет типа, поддерживающего даты, но каждая база данных предоставляет способ интерпретации типов date из любого языка, на котором вы кодируете. Если вы изучите список Data Types and Conventions для MongoDB BSON API (bit.ly/o87Gnx), то увидите, что в него добавлен тип date наряду с несколькими другими.

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

Наборы типов

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

База данных без схемы

Запись student, описанная выше, содержит свою схему. Каждая запись отвечает за собственную схему, даже если она содержится в единой базе данных или наборе. И одна из записей student не обязательно должна соответствовать другой записи student. Конечно, ваше ПО должно понимать различия между ними. Эту гибкость можно было бы задействовать для повышения эффективности. Например, зачем хранить null-значения? Когда свойство вроде most_repeated_class не имеет значения, вы могли бы сделать следующее:

"name" : "Jim",
"scores" : [ 75, 99, 87.2 ]
"name" : "Julie",
"scores" : [ 50, 40, 65 ],
"most_repeated_class" : "Time Management 101"

Поддержка транзакций

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

Базы данных документов и DDD

Одна из базовых концепций разработки, управляемой предметной областью (domain-driven development, DDD), относится к моделированию предметной области с применением агрегатных корней. При планировании классов предметной области (которые могут стать документами в вашей базе данных) вы можете искать данные, которые чаще всего являются самодостаточными (скажем, заказ с его позициями), и концентрироваться на них как на индивидуальной структуре данных. В системе заказов у вас, вероятно, также будут клиенты и продукты. Но к заказу можно обращаться без необходимости получения информации о клиенте, а продукт можно было бы использовать без обязательного доступа к заказам, в которых он присутствует. То есть, хотя вы обнаружите много возможных самодостаточных структур данных (тот же заказ с его позициями), это не исключает в определенных сценариях необходимости или возможности присоединения данных через внешние ключи.

Ко всем базам данных прилагаются руководства по различным доступным шаблонам, а также указывается, какие из них позволили чаще добиваться успеха их пользователям. Например, в документации MongoDB говорится о шаблоне Array of Ancestors, который ускоряет доступ к связанным данным при присоединении документов.

Если повторение данных в реляционной базе данных считается грехом, то при работе с базами данных NoSQL, особенно распределенными, денормализация данных полезна и вполне приемлема.

Запросы и обновление

В каждой базе данных есть API для запросов и обновлений. Хотя они могут не являться частью базового API, через надстройки предоставляются разнообразные языковые API. Для ввода .NET Framework в мир баз данных документов RavenDB поддерживает запросы через LINQ — прекрасная возможность для .NET-разработчиков.

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

Map/reduce — это шаблон, и в разных базах данных он реализован по-разному. Роб Эштон (Rob Ashton) предоставил интересное сравнение того, как RavenDB и CouchDB выполняют сопоставление/сокращение (bit.ly/94OCME).

Если RavenDB требует наличия предопределенных представлений для запросов, а CouchDB позволяет запрашивать только через сопоставление/сокращение, то MongoDB (также использующая представления и сопоставление/сокращение) дополнительно поддерживает специализированные запросы (ad hoc querying). Однако возможность выполнять специализированные запросы по большей части теряется, когда вы отходите от известных схем и реляционной природы баз данных SQL.

Революция в базах данных

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

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


Джули Лерман (Julie Lerman) — Microsoft MVP, преподаватель и консультант по .NET, живет в Вермонте. Часто выступает на конференциях по всему миру и в группах пользователей по тематике, связанной с доступом к данным и другими технологиями Microsoft .NET. Ведет блог thedatafarm.com/blog и является автором очень популярной книги «Programming Entity Framework» (O’Reilly Media, 2010). Вы также можете читать ее заметки в twitter.com/julielerman.

Выражаю благодарность за рецензирование статьи экспертам Тэду Ньюарду (Ted Neward) и Савасу Парастатидису (Savas Parastatidis).