2007 г
Еще раз об архитектурах ООСУБД
Адриан Мариотт, ведущий консультант компании Progress Software
Перевод: Сергей Кузнецов
Оригинал: OODBMS Architectures Revisited
Эта статья является ответом на статью вице-президента компании Versant Роберта Грина
Архитектуры ООСУБД. Анализ реализаций
Аннотация
Цель этой статьи состоит в том, чтобы рассмотреть анализ, проведенный Грином в его статье Архитектуры ООСУБД. Анализ реализаций, и внести коррективы в некоторые неверные, с моей точки зрения, утверждения, которые относятся к серверным архитектурам, основанным на страницах. Я рассматриваю вопросы, обсуждаемые в статье Грина, в том же порядке, в котором они упоминаются в этой статье. Для удобства читателей в статье приводятся заголовки статьи Грина и задевшие меня части текста до моего разбора этих утверждений.
Введение
«При выборе правильной архитектуры СУБД показатели производительности и масштабируемости могут повышаться на порядки величин, а не на проценты, как в случае реляционных реализаций.»
Для достижения максимальной производительности и масштабируемости наиболее важно выбрать правильную архитектуру приложения. ООСУБД дают приложениям более полный доступ к персистентным данным, и поэтому архитектура приложений оказывает большее влияние на производительность, чем при использовании РСУБД. Другими словами, при использовании ООСУБД у архитектора приложения имеется намного больше возможностей оптимизации производительности, чем при использовании РСУБД. Следовательно, архитектура приложения оказывает гораздо большее влияние на производительность и масштабируемость, чем выбор конкретного продукта ООСУБД.
Для эффективного использования ООСУБД рекомендуется подход, управляемый сценариями использования, поскольку это позволяет наполнить содержанием весь проект приложения. При проектировании процессной архитектуры следует учитывать разделение ответственности процессов за поддержку сценариев использования. Разделение набора данных должно способствовать определению того, к каким данным будет обращаться каждый из этих процессов. С помощью традиционного анализа следует устанавливать транзакционные требования каждого сценария использования и определять объекты, к которым будет осуществляться доступ в каждой транзакции. В этом отношении полезны диаграммы взаимодействий. В фазу объектно-ориентированного проектирования следует включать проектирование структур оптимального доступа и индексов для поддержки навигационных путей наиболее важных сценариев использования. Характеристики параллельного доступа следует изучать с использованием таких методов, как CRUD-диаграммы. Коротко говоря, при разработке крупных, высокопроизводительных, масштабируемых и надежных приложений, основанных на использовании ООСУБД, важно применять стандартные методы объектно-ориентированного анализа и проектирования для обеспечения корректной архитектуры приложения.
Без выполнения этой работы трудно ожидать, что выбор продукта ООСУБД приведет к изменению характеристик приложения на порядки величин.
Архитектура, основанная на страницах
«Требуется реализация специальных стратегий размещения объектов.»
Это утверждение неверно. В самих по себе архитектурах, основанных на страницах, от программиста не требуется анализ локальности ссылок. Возможность близкого расположения нескольких совместно используемых объектов является средством, обеспечивающим программисту очень эффективный способ оптимизации обработки на основе физических характеристик системы. Разработчики приложений могут оптимизировать производительность путем применения стратегий размещения объектов, при которых физически близко размещаются объекты, используемые совместно, и в разных страницах размещаются объектов, совместно не используемые. Использование стратегий размещения является не обязательным, хотя и рекомендуемым; архитектуры, основанные на страницах, могут работать и без применения стратегий размещения объектов.
Архитектура, основанная на объектах
«Архитектура, «основанная на объектах», является сбалансированной конструкцией, в которой кэширование и поддержка функционирования осуществляется как в процессах приложений, так и в серверных процессах.»
Здесь понятие «сбалансированности» используется для субъективной эстетической оценки. Можно сказать, что все зависит от собственного восприятия. Более важно то, что серверный процесс в архитектуре, основанной на страницах, такой как в ObjectStore, может производить кэширование дисковых страниц, и в ObjectStore это кэширование действительно производится. В ObjectStore механизм кэширования страниц разработан с целью эффективного использования возможности операционных систем кэшировать в основной памяти страницы файлов, к которым недавно осуществлялся доступ. Кроме того, сервер в архитектуре, основанной на страницах, выступает в качестве посредника при всех обращениях к базе данных, отслеживая права доступа клиентов к страницам, управляет транзакциями на стороне сервера и поддерживает жизненный цикл страниц. Так что говорить о «сбалансированности» серверной архитектуры, основанной на страницах, настолько же оправданно и настолько же бессмысленно, как и по отношению к архитектуре, основанной на объектах: в обоих случаях сервер и клиент взаимодействуют для достижения некоторых целей.
Вопрос о том, следует ли выполнять запросы и обрабатывать индексы на клиенте или на сервере, является сложным. Совсем не обязательно более «сбалансированным» является подход, при котором все это делается на сервере. Клиенты базы данных не обязаны выполняться на той же машине, что и сервер, и не обязательно все клиенты работают на одной машине, так что потенциально у клиентов может иметься гораздо большие процессорная мощность и объем основной памяти для кэширования, чем на стороне сервера. Если индексы размещаются в кэшах клиентов и не слишком часто изменяются, то выгоднее использовать эту компьютерную мощность, выполняя запросы на стороне клиентов. Стратегией ObjectStore является перемещение данных ближе к приложению. Но если для запросов нет подходящих индексов, позволяющих оптимизировать их выполнение, или если индексы невозможно эффективно кэшировать в клиентах, то может оказаться лучше выполнять запросы как можно ближе к дискам, т.е. на стороне сервера. Какая архитектура в этом случае сработает лучше, зависит от детальных характеристик данных, приложения и развертывания ООСУБД.
Модель параллельности
«В любой архитектуре имеются способы ослабления блокировок для некоторых типов приложений, для которых не требуется полный набор свойств ACID (Atomicy – атомарность, Consistency – согласованность, Isolation – изоляция, Durability – долговечность).»
В ObjectStore никогда не ослабляются свойства ACID транзакций. В действительности кэш в ObjectStore всегда является транзакционно согласованным. Это одна из лучших черт ObjectStore. Программисты на самом деле не обязаны заботиться о синхронизации кэша, поскольку кэшем управляет ObjectStore, которая также автоматически считывает и блокирует страницы по мере потребности. Эффективность этого процесса показывает, что свойства ACID никогда не следует дискредитировать в угоду производительности.
«…обновления всегда приводят к выполнению операций координации блокировок и согласования кэшей.»
При определенных условиях обновления в ObjectStore не приводят к выполнению операций координации блокировок и согласования кэшей именно потому, что блокировки делаются на уровне страниц. Обновления объектов в страницах, уже заблокированных для обновления, происходят с нулевыми накладными расходами. Это означает, что стоимость блокировки разделяется между всеми объектами, находящимися в этой странице.
В отличие от этого, если каждый объект блокируется индивидуально, то накладные расходы на блокировку становятся значительными, особенно, когда приходится иметь дело с тысячами или миллионами небольших объектов.
«При использовании архитектуры, основанной на … страницах, размещение объектов и блокировки тесно связаны, а архитектура, основанная на объектах, позволяет ортогонализировать эти аспекты.»
Здесь Грин подчеркивает то, что при использовании блокировок на уровне объектов установка блокировки на объект гарантирует, что блокируется только этот объект. Это означает, что при блокировании любого объекта не может быть непредумышленно заблокирован какой-либо другой объект. В этом смысле блокировки на уровне объектов являются разъединенными, или независимыми одна от другой.
Однако Грин не упоминает о том, что в системах реального мира блокировки на уровне объектов требуют дорогостоящего управления и часто бывают избыточными с точки зрения реализуемого сценария использования. В большинстве алгоритмов до успешной фиксации транзакции требуется доступ к ряду объектов, так что наличие гибкой возможности заблокировать их при надобности одновременно, а при другой потребности – независимо один от другого может быть выигрышным с точки зрения производительности.
В системах с архитектурой, основанной на страницах, следствием размещения объектов являются одновременная блокировка объектов, расположенных в одной и той же странице, но являются ли эти блокировки паразитными, или вредными в каком-либо отношении, зависит от деталей сценария использования. Если имеются два объекта O1 и O2, расположенные в одной и той же странице, и некоторая программа выполняет транзакцию, в которой обновляются оба эти объекта, то одновременная блокировка не создает никаких проблем. На самом деле, от нее исходит преимущество, поскольку сокращаются накладные расходы на управление блокировками.
Грин также не упоминает о том, что в системах с архитектурой, основанной на страницах, при наличии реальной потребности в блокировке на уровне объекта можно просто поместить объект на отдельную страницу. Если требуется одновременный и независимый доступ к объектам O1 и O2 от разных программ, при использовании ObjectStore программист может позаботиться об их размещении в разных страницах, чтобы они блокировались независимо.
В ObjectStore обеспечивается очень простой и гибкий API, делающий возможным расположение в одном наборе страниц объектов, используемых совместно в одном и том же сценарии использования, и расположение в других страницах объектов, для которых требуются индивидуальные блокировки. Поэтому в течение проектирования и реализации системы, основанной на ObjectStore, программист может сгруппировать персистентные данные в разных областях базы данных, называемых кластерами, таким образом, что данные занимают минимально возможное число страниц. Когда программа производит доступ к объектам в этих страницах, они отсылаются из сервера в минимальном числе страниц. Это может обеспечить гигантский выигрыш в производительности. В конце концов, все персистентные данные хранятся на магнитных дисках, и на физическом уровне диски являются блочно-ориентированными устройствами с очень большим различием в производительности между случайным доступом и доступом к тому же блоку или последовательным доступом. Видимость этой физической структуры диска на уровне приложения позволяет прикладному программисту радикально оптимизировать производительность.
Однако при некорректном использовании это мощное средство может порождать проблемы. Например, если некоторый программист распределяет объекты, совместно используемые в одной транзакции, по тысячам страниц, то будет дискредитирована производительность как чтения, так и записи. Поэтому более высокий уровень воздействия приложения на физическую организацию базы данных в ООСУБД по сравнению с РСУБД приводит к повышенному уровню возможности проектировщика приложения к повышению или уменьшению производительности.
Параллельность в архитектуре, основанной на страницах
«Блокировки и права получаются на уровне страниц, и в каждой странице может содержаться много объектов, так что возможны ложные ожидания и ложные синхронизационные тупики.»
Действительно, при размещении двух объектов в одной и той же странице попытка одновременного обновления по одному из них в разных клиентах, выполняющих раздельные транзакции, или обновления одного объекта в одной такой транзакции и чтения другого объекта в другой транзакции приведет к конфликту. Заметим, что это не приводит к какой-либо логической проблеме, повреждению базы данных или не транзакционному представлению данных. Грин утверждает, что это представляет собой потенциальную проблему. Насколько серьезно это в действительности?
По существу, имеются три операции над базами данных, которые мы должны здесь проанализировать: чтение, создание и обновление объектов. В системах реального мира большая часть паттернов доступа соответствует сценарию «в основном чтение». Если оба объекта одновременно читаются, и не происходит никаких обновлений их общей страницы, то не возникает никакого конфликта, и оба клиента могут законным образом производить доступ к объектам.
А что насчет создания объектов? Клиенты ObjectStore управляют «локальностью ссылок» путем использования сущностей базы данных, называемых кластерами. Кластеры представляют собой «физические» коллекции страниц. Кластер может содержать одну или большее число страниц, но каждая страница содержится только в одном кластере. При использовании API программисты ObjectStore размещают объекты в кластерах, а не в конкретных страницах. По мере размещения объектов в кластерах к последним добавляются новые страницы, и их размер растет. Если два клиента одновременно создают объекты в одном и том же кластере, то по умолчанию ObjectStore размещает эти объекты в разных страницах этого кластера. Коротко говоря, создание объектов редко приводит к конфликтам.
Так что аргумент Грина касается только обновлений объектов на страницах, которые одновременно читаются или обновляются другими клиентами, обращающимися к разным объектам, размещенным на одном и том же наборе страниц. В этой ситуации применимы два основных подхода. Наиболее эффективным способом избежать этой проблемы является использование стратегии размещения объектов там, где это требуется. Это может полностью устранить избыточные ожидания и синхронизационные тупики.
Однако возникает сложность при попытке кластеризации объектов в расчете на несколько сценариев использования. У разных сценариев использования могут иметься конкурирующие или противоречивые требования к кластеризации, когда один сценарий использования лучше всего оптимизируется при одном способе кластеризации объектов, а для другого сценария предпочтителен другой способ. Эти сложности действительно возникают в реальных проектах, и имеется несколько подходов к их разрешению.
- Один из подходов состоит в игнорировании наименее важного сценария использования и концентрации усилий на наиболее важном сценарии. Этот подход работает, например, в тех случаях, когда основной сценарий использования системы конфликтует с менее важным вторичным сценарием использования, который проигрывается редко.
- Другой подход заключается в повторном обращении к объектной модели, в особенности, к структурам доступа, и ее изменении с целью оптимизации кластеризации для двух (или более) важных сценариев использования. В этом случае разработчик явно настраивает структуры данных и кластеризацию, чтобы они «содействовали» друг другу во время выполнения. Такая настройка может, например, включать хранение дополнительных структур данных для выполнения второго сценария использования, чтобы при этом не затрагивались объекты с неоптимальной кластеризацией. Имеется много вариантов этого подхода.
- В третьем подходе нарушается другое неприкосновенное правило проектирования баз данных «отсутствия избыточного хранения данных» (store-it-once), и оптимизируются два важных сценария использования с режимом «только чтения» в ущерб сценариям использования, обновляющим базу данных. В этом случае одни и те же данные кластеризуются двумя разными способами для максимальной оптимизации читающих сценариев использования за счет введения требования записи в две разные структуры данных при выполнении обновления.
В ObjectStore также обеспечивается специальный режим доступа, называемый MVCC (MultiVersion Concurrency Control), который позволяет клиентам читать страницы из базы данных с гарантией того, что эти чтения не заблокируют обновления, никогда не приведут к синхронизационному тупику и обеспечат транзакционно согласованные данные, но, возможно, не самые свежие. Это также может полностью устранить проблемы конфликтов и синхронизационных тупиков.
В проектах реального мира проектирование модели данных, поддерживающей стратегии размещения, которые оптимизируют несколько сценариев использования, является одним из наиболее творческих аспектов использования ObjectStore и может принести наибольшие дивиденды в терминах общесистемной производительности. Этот подход в сочетании с MVCC позволяет грамотным разработчикам устранить синхронизационные тупики и свести почти к нулю вероятность конфликтов.
Еще одно соображение состоит в том, что проблемы порождаются не паразитными блокировками. Паразитизм блокировок означает, что не было никаких реальных оснований для размещения соответствующих объектов в одной и той же странице, это произошло просто случайно, и все трудности разрешаются путем простого переноса объектов в разные кластеры. Более сложную проблему представляют истинные конфликты по блокировкам, когда двум клиентам одновременно требуется в точности одни и те же объекты с конфликтующими блокировками, что часто случается при одновременном обновлении объектных экстентов, индексов и структур доступа, которые являются корнями в графах объектов. В этой области механизмам блокировки в архитектурах, основанных на объектах и страницах, свойственны одни и те же трудности.
Следствия модели параллельности
«Кроме потенциальных ловушек ложных ожиданий и тупиков, гранулированность объектов, вовлекаемых в транзакции приложений, может оказывать в одной архитектуре большее влияние, чем в другой. В архитектуре, основанной на страницах, из-за возвратных вызовов по поводу блокировок требуется намного больше сетевых взаимодействий при работе сильно гранулированной обновляющей системы.»
Я не совсем понимаю, что Грин здесь имеет в виду. Почему меньшее число блокировок должно приводить к увеличению сетевого трафика, требуемого для управления блокировками? Я думаю, что он хотел сказать, что паразитные конфликты по блокировкам (он называет их ложными конфликтами) могут привести к возрастанию объема трешинга кэшированных блокировок и, следовательно, увеличению сетевого трафика. Это действительно так, но об этом не стоит говорить. Конечно, трешинг по причине паразитных конфликтов по блокировкам может возникнуть, если действительно имеются паразитные конфликты по блокировкам, но, как уже отмечалось, есть эффективные стратегии их устранения, и тогда никакого трешинга не будет.
Хотя для серверов, основанных на страницах, в некоторых сценариях действительно возможен «трешинг страниц», когда два клиента непрерывно обращаются к одним и тем же страницам, обычно это является симптомом проблемы проектирования на уровне клиентского процесса. Ее можно избежать путем реализации N-звенной архитектуры процессов, в которой все запросы обновления некоторого набора объектов направляются единому процессу обновлений, ответственному за эти объекты. Дополнительным преимуществом такого подхода является то, что эти объекты, вероятно, будут находиться в кэше клиента, и к ним будет возможен доступ в основной памяти.
Кроме того, Грин забыл упомянуть о том, что тот же самый трешинг может возникнуть на уровне объектов в системах, в которых используются блокировки на уровне объектов. Когда для двух клиентов требуется некоторая группа объектов с несовместимыми блокировками (например, оба клиента желают обновлять объекты), эти объекты будут метаться между клиентами. Поскольку все блокировки устанавливаются на уровне объектов, накладные расходы на управление блокировками могут быть выше, чем в архитектуре, основанной на страницах. Так что трешинг может проявляться в обеих архитектурах.
«В большинстве случаев архитектура, основанная на объектах, лучше всего подходит при большом числе одновременно работающих пользователей и/или процессов, особенно в системах, которые не могут быть хорошо сегментированы для изоляции.»
Здесь Грин не упоминает о том, что в сервер-ориентированных системах, таких как Versant, по мере возрастания числа пользователей могут быстро проявиться проблемы масштабируемости, поскольку большая часть обработки должна производиться на сервере. В клиент-ориентированной архитектуре, где основная алгоритмическая обработка имеет место на стороне клиента, нагрузка распределяется по всем клиентским машинам, практически устраняя узкое место сервера. Моя точка зрения состоит в том, что преимущества каждой из архитектур будут сильно зависеть от контекста, от того, какие сценарии использования реализуются в приложениях, и от деталей динамического поведения конкретной системы. Грин чрезмерно упрощает суть проблемы.
Для обеспечения масштабируемости может потребоваться осведомленность о характеристиках производительности аппаратуры магнитных дисков, независимо от того, являются ли единицами блокировки объекты или страницы. Предположение, что случайный доступ к данным обеспечивается за то же время, что и случайный доступ, перестает работать, когда база данных достигает таких размеров, что перестает помещаться в основной памяти. Поэтому, вне зависимости от того, какая архитектура ООСУБД используется, для достижения удовлетворительной производительности приложению может потребоваться осведомленность о размещении объектов.
Следствия сетевой модели
«Наибольшей потерей здесь является пропускная способность сети, поскольку при обновлении единственного объекта из нескольких байт потребуется обновить в кэше потенциально большого числа клиентов несколько тысяч байт – страницу …»
Это верно с математической точки зрения, но не слишком существенно. В большей части современной сетевой технологии производительность гораздо больше ограничивается временем задержки, чем пропускной способностью. Пропускная способность сети часто сопоставима с пропускной способностью диска или превосходит ее. Но время задержки операции обращения по локальной сети к серверному процессу с получением ответа может легко превзойти время, требующееся на пересылку мегабайта или большего объема данных по сети.
Посылка сетевого пакета, содержащего несколько байт, и посылка сетевого пакета, содержащего 4096 байт (одну страницу), одинаково эффективны во всех отношениях. Проблему составляет число пакетов, а не их размер. Пересылка данных становится проблемой только в тех случаях, когда в базе данных выполняются тысячи мелких обновлений над многими разными страницами, но снова эту проблему часто удается разрешить путем использования стратегий размещения, которые могут обеспечить расположение всех этих мелких объектов в одной и той же странице. Тогда для всех обновлений фактически будет посылаться один и тот же сетевой пакет.
Во многих случаях ObjectStore демонстрирует, что последовательные обновления могут записываться на диск со скоростью, ограничиваемой только скоростью записи на диск. При обновлении небольших объектов с размером в пределах 100 байт это, например, означает возможность обновления сотен тысяч объектов в секунду. Это достигается за счет разумного размещения объектов и использования развитых индексных структур, которые преобразуют обновления объектов в создание объектов, изменяя как можно меньше страниц. Эта техника часто комбинируется с применением механизма управления буферным пулом C++ для еще большего удешевления операции конструирования индивидуального объекта путем создания в базе данных персистентных массивов C++.
Читая Грина, можно подумать, что он подразумевает обязательность «выталкивания» обновленных страниц в кэши клиентов. Хотя потенциально в серверной архитектуре, основанной на страницах, можно реализовать и такой подход, в ObjectStore это делается совершенно не так. В этой системе страницы «втягиваются» в кэши клиентов по мере необходимости, так что эти обновления не стали бы немедленно посылаться многим клиентам, а попадали бы только к тем из них, которые обращаются к данной странице и, предположительно, нуждаются в обновленных объектах.
«… если модели не являются хорошо сегментированными, и/или связи между объектами соединяют страницы …, которые содержат всего несколько объектов, участвующих в связи, то потенциально потребуется обновлять в кэше многие страницы или контейнеры, содержащие многочисленные избыточные объекты. Это означает, что пропускная способность сети будет использоваться для пересылки избыточных объектов наряду с теми, которые действительно должны быть обновлены в кэше.»
Это утверждение является верным лишь частично. Эта проблема возникает только в том случае, когда используется некорректная стратегия размещения. Конечно, решение состоит в корректной сегментации модели, т.е. приведении в соответствие размещения объектов с особенностями доступа к данным в соответствующих сценариях использования. Тогда этих избыточных пересылок не будет. Как отмечалось выше, путем тщательного размещения экземпляров объектов можно добиться того, чтобы в архитектуре, основанной на страницах, все объекты, необходимые и достаточные для выполнения заданного сценария использования, располагались в минимальном числе страниц. Эти страницы будут пересылаться в минимальном числе сетевых пакетов. Такая конфигурация является предельно эффективной.
«Вообще говоря, архитектура, основанная на объектах, лучше всего подходит для обслуживания большого количества пользователей или процессов, особенно в системах, которые не являются хорошо сегментированными для изоляции, или в которых требуется гибкость для обеспечения в будущем новых паттернов доступа.»
Это обобщение нельзя признать верным, если принять во внимание проблемы узких мест на стороне сервера и накладные расходы на управление блокировками небольших объектов, легко игнорируемые Грином. Проблемы использования данных в будущем и эволюции схемы подробно обсуждаются ниже.
Реализация запросов в архитектуре, основанной на страницах
«После загрузки коллекции объектов и выполнения запросов результатом являются ссылки на объекты, удовлетворяющие предикату запроса, и неявно уже загруженные по сети и подсоединенные к виртуальной памяти клиента страницы, которые содержат эти объекты. Тем самым, с точки зрения передачи по сети и блокировки результат может содержать много объектов, которые в действительности не удовлетворяют предикату запроса.»
Это утверждение ложно. Кажется, Грин думает, что в ООСУБД с архитектурой, основанной на страницах, во время выполнения запросов не используются индексы, но это неверно. В ObjectStore индексы разработаны таким образом, что при выполнении запроса клиенту передаются только страницы индексов. Структуры данных индексов являются очень компактными, и они всегда размещаются в собственных страницах базы данных, поэтому они могут эффективно передаваться с сервера, и параллельно выполняемые запросы от разных клиентов могут одновременно обрабатываться на разных машинах.
Если сетевые расходы на пересылку страниц являются слишком обременительными, то путем выполнения клиентов ObjectStore на общей машине с сервером можно полностью устранить сетевые накладные расходы. Сервер по умолчанию отображает в свою виртуальную память кэши всех локальных клиентов, так что пересылка страниц происходит действительно быстро, и все это фактически работает, как единый процесс. Так что путем декомпозиции системы в N-звенную архитектуру можно выполнять клиентские части ObjectStore либо на машинах, удаленных от серверного процесса, либо на той же машине, что и серверный процесс, устраняя, тем самым, сетевые накладные расходы или распределяя обработку по разным машинам в зависимости от особенностей и потребностей конкретного контекста.
Размещение клиентского процесса на серверной машине может устранить накладные расходы на сетевые коммуникации, но не устраняет трешинг страниц, если, например, имеется большое число параллельно выполняемых обновлений или отсутствует индекс для оптимизации запросов. Так что ObjectStore обладает некоторой гибкостью, но не демонстрирует преимущества обработки запросов на стороне сервера во всех возможных случаях.
Однако следует сравнить это с сервер-ориентированной архитектурой, где все запросы должны выполняться на серверной машине, имеется ограниченная возможность распределения этих запросов и использования истинного параллелизма нескольких машин, когда это имеет смысл. Сервер-ориентированная организация может порождать узкие места на стороне сервера при возрастании числа одновременно работающих пользователей, и единственным выходом из положения является приобретение более крупных и мощных серверных машин.
Реализация запросов в архитектуре, основанной на объектах
«В реализации архитектуры, основанной на объектах, используется процессор выполнения запросов, который выполняется в процессе сервера баз данных.»
С теоретической точки зрения использование архитектуры, основанной на страницах, не исключает возможности выполнения запросов в процессе сервера баз данных. В статье Грина делается заключение, что в этом проявляется некоторая ограниченность архитектур, основанных на страницах, и его можно преодолеть, только обратившись к архитектуре, основанной на объектах. Но в действительности тот факт, что в некотором продукте ООСУБД обеспечивается обработка запросов на стороне сервера, характеризует только этот продукт и совсем не обязательно связан с тем, реализуется ли в этом продукте архитектура, основанная на страницах или объектах.
Как отмечалось выше, при использовании ObjectStore можно выбрать, где хотелось бы выполнять запросы – на клиентской машине или на сервере. Это значит, что некоторые проблемы узких мест сервера, в частности, те, которые проистекают из конкуренции за центральный процессор, могут быть смягчены путем размещения процессов на других машинах и использования истинного параллелизма.
«Любой объект базы данных достижим через запрос, даже если у него отсутствует связь с другими объектами.»
Прежде всего, хочу сделать небольшое логическое замечание относительно этого утверждения: использование архитектуры, основанной на страницах, не исключает возможности запросов в базе данных произвольных объектов. Имеется теоретическая и практическая возможность нахождения и обработки любого объекта в любом файле системы баз данных. Такую возможность можно, например, реализовать, хотя и не эффективно, путем последовательного перебора объектов всей базы данных. Обеспечивается ли такая возможность, и, если обеспечивается, то насколько эффективно,– это всего лишь характеристика конкретного продукта.
Хотя на первый взгляд у возможности произвольных запросов не видно недостатков, при обеспечении средств, позволяющих запрашивать все что угодно в любой точке программы, возникает ряд проблем.
Одним из наиболее важных свойств объектно-ориентированной парадигмы является инкапсуляция, сокрытие за интерфейсом деталей реализации. По существу, объекты представляют собой неделимые вычислительные «атомы», у каждого из которых имеется контракт с другими объектами или клиентами. Для поддержания этой инкапсуляции состояние элементов данных объекта является или должно являться частным, и при вызове метода, обновляющего объект, реализация объекта действует в соответствии с контрактом, изменяя частное внутреннее состояние таким образом, чтобы постусловия и инварианты, а также другие свойства контракта соблюдались к моменту возврата из метода. Часть этого частного состояния образуется связями между объектами. Поскольку весь код, реализующий контракт, содержится внутри соответствующего класса, и только у этого кода имеется доступ к частным элементам данных, поддержка инкапсуляции в компиляторе в значительной степени способствует соблюдению контакта путем автоматического устранения возможности повреждения внутреннего состояния объекта какой-либо внешней программой.
Такая организация означает, что если требуется что-то изменить, или если происходит что-либо неверное, то достаточно просто обнаружить и устранить ошибку, поскольку возможность доступа к внутреннему состоянию объекта имеется только из кода, локального по отношению к этому объекту. Следует сравнить это со случаем, когда имеются общедоступные элементы данных, доступ к которым возможен из любого места внешней программы. Тогда отсутствует инкапсуляция в упомянутом выше смысле, и легко видеть, что эта поддерживаемая компиляторами инкапсуляция является фундаментальной чертой объектно-ориентированной технологии – очень мощным средством контроля сложности, – а не просто выдумкой борцов за чистоту идеи.
При персистентном хранении объектов на диск записывается именно это частное внутреннее состояние. Разрешение доступа из произвольного кода к внутреннему состоянию любого объекта подрывает инкапсуляцию, позволяя коду, внешнему по отношению к объектному классу, изменять состояние объектов. Это означает, что состояние любого объекта может быть изменено любой программой. Другими словами, больше не гарантируется, что только из кода методов класса допускается доступ к этому частному состоянию, и, следовательно, компилятор больше не может обеспечивать выполнение контракта объекта. Практическим выводом является то, что возрастает общая связность всей системы, она становится более сложной, усложняется ее поддержка и тестирование. В системе, вероятно, потребуются проверки времени выполнения, вредно влияющие на производительность.
Поэтому, хотя наличие произвольного доступа к любому персистентному объекту может показаться полностью здравой идеей, с теоретических и практических позиций объектно-ориентированного подхода ситуация является более сложной – имеются потенциальные негативные стороны. Следует заметить, что эта проблема идентична случаю, когда объекты сохраняются в реляционных таблицах, что, по существу, и делается во многих реализациях архитектуры, основанной на объектах.
«Запрос производится путем посылки серверу некоторого оператора, который выполняется на сервере с использованием оптимизатора и механизма индексации, и клиенту возвращается результирующий набор объектов.»
Прежде всего, из помещения этого утверждения в подраздел «Реализация запросов в архитектуре, основанной на объектах» и отсутствия упоминания оптимизации запросов в подразделе «Реализация в архитектуре, основанной на страницах» следует, что в системах объектных баз данных с архитектурой, основанной на страницах, во время выполнения запросов не может производиться оптимизация. Конечно, это не так.
В ObjectStore запросы обрабатываются на стороне клиента, и если имеются пригодные индексы, они используются для оптимизации обработки запроса. Само по себе наличие индексов не изменяет способ раздачи страниц базы данных, используемый серверным процессом. В ObjectStore индексная структура данных трактуется точно так же, как и любая другая персистентная структура данных. Когда индекс используется для оптимизации запроса при его обработке на стороне клиента, в серверный процесс посылаются запросы страниц частей самого индекса. Таким образом, в клиентский процесс доставляются только те части индекса, которые требуются для обработки запроса. Поскольку индексы всегда размещаются в своем собственном специальном наборе страниц базы данных, при использовании индекса клиенту передается минимальное число страниц базы данных – избегаются паразитные и избыточные страничные конфликты. При правильном выборе индексов и наличии должного контекста этот процесс оптимизации может сильно сократить число страниц, запрашиваемых у серверного процесса ObjectStore.
При вызове метода запроса возвращается коллекция указателей на результирующий набор – набор персистентных объектов внутри базы данных. К этим объектам не производится доступ при обработке запроса; поэтому страницы, содержащие эти объекты, не передаются в клиентский процесс.
Здесь нужно отметить еще одну отличительную особенность ObjectStore, повышающую ее гибкость и общую производительность. Эта особенность основывается на идее специальной индексации. В архитектурах, в которых запросы выполняются на стороне сервера, индексы, естественным образом, используются на стороне сервера, но это обычно означает, что реальная структура этих индексов определяется поставщиком базы данных, а не пользователем. Обработка запросов на стороне сервера ограничивает возможность определения структур данных пользователем, реально использующим индекс. Поскольку в ObjectStore разрешается хранение реальных указателей и истинных массивов C++, программист может реализовать эффективные специальные индексные структуры, которые оптимально поддерживают конкретный сценарий использования. Если имеется несколько сценариев использования, для которых требуется максимальная производительность, то на целевые объекты могут ссылаться специальные индексы, специфичные и оптимизированные для каждого сценария использования. При этом подходе в базе данных различаются две основные группы объектов: бизнес объекты, в которых сохраняются реальные данные, соответствующие моделируемой предметной области, и заключается весь смысл системы; и структуры доступа, или индексы, которые поддерживают навигацию к бизнес-объектам, соответствующим конкретным вызовам сценариев использования, или запросам. Программисты могут реализовывать оптимальные структуры данных для обработки объектов в базе данных, используя например, даже STL (Standard Template Library), точно так же, как если бы они являлись структурами данных в «куче». При выполнении запроса на стороне клиента эти структуры доступа считываются постранично, точно так же, как если бы они являлись индексами коллекций ObjectStore, и поэтому специальные структуры доступа полностью эквивалентны индексам на коллекциях, поскольку и те, и другие структуры оптимизируют доступ к бизнес-объектам, и одним и тем же образом. Я не думаю, что подобные специальные индексы можно реализовать, если не опираться на архитектуру запросов на стороне клиента, или если не компоновать каким-то образом пользовательский код на C++ в процесс на стороне сервера, что, как показывает опыт, порождает проблемы безопасности и надежности сервера.
«Эффективность выполнения запросов может обеспечиваться путем использования общесистемных идентификаторов, многопотоковой, удаленной и распределенной обработки, агрегирования.»
В архитектуре, основанной на страницах, могут использоваться и на самом деле используются общесистемные идентификаторы. Точно так же в ООСУБД с архитектурой, основанной на страницах, возможны и параллельные и распределенные запросы, поскольку клиенты могут быть многопотоковыми, и они могут в одно и то же время использовать персистентные объекты более чем из одной базы данных. По причине поддержки отображения адресов клиентский указатель на персистентный объект эквивалентен общесистемному идентификатору; для интерпретации указателя не требуется контекст базы данных.
В ObjectStore также обеспечиваются общесистемные идентификаторы, называемые «мягкими указателями» («soft pointer»), которые уникально идентифицируют любой персистентный объект во всех базах данных ObjectStore, глобально, в любой области видимости и независимо от контекста любого существующего процесса. Мягкие указатели на уровне схемы совместимы с обычными указателями C++ в 32- и 64-разрядных архитектурах. Они также очень эффективны в том отношении, что после первого использования работают с той же скоростью, что и прямые указатели C++.
В архитектурах, основанных на страницах, может использоваться и реально используется многопотоковость как в реализации продукта, так и на уровне программистов. В ObjectStore пользователи могут писать многопотоковый код с использованием соответствующих библиотек поддержки безопасного многопотокового программирования.
В архитектурах, основанных на страницах, и, в частности, в ObjectStore можно напрямую поддерживать параллельные распределенные запросы. На самом деле, как описывалось выше, ObjectStore отличается тем, что позволяет разработчикам системы распределять обработку запросов по машинам клиентов, избегая узких мест на сервере, или выполнять запросы локально на сервере. При разработке ObjectStore эта возможность имелась в виду с самого начала.
Следствия модели запросов
«Реализации ООСУБД всегда фокусируются на навигационном доступе, как основном способе выборки информации из базы данных. Здесь снова не ставится цель проанализировать все возможности механизма запросов, такие как проецирование, агрегация, представления, математические вычисления, курсоры, составные индексы и т.д., и т.п.»
Интересно, что здесь теме навигационного доступа уделяется недостаточное внимание, хотя именно это, вероятно, является одной из основных возможностей любой системы объектных баз данных, и, как утверждает Грин, навигационный доступ является основным средством извлечения информации из объектной базы данных.
Тот факт, что навигационный доступ является основным, приводит к вопросу о следствиях модели запросов в целом. Если первичные средства доступа к данным являются навигационными, то в каких случаях для общей производительности ООСУБД могут быть критичными эффективность выполнения запросов или характеристики подсистемы запросов? Конечно, так может быть только в том случае, когда пользователи решают основывать свои системы на запросах, а не на навигационном доступе, а именно на нем, как говорит Грин, фокусируется большинство поставщиков ООСУБД. Так почему же тема навигационного доступа так быстро отбрасывается?
Ответ, видимо, кроется не в теоретических различиях между архитектурами, основанными на страницах и объектах, а скорее в различиях между продуктами ObjectStore и Versant.
С этой точки зрения навигационный доступ является одной из сильных сторон ObjectStore. ObjectStore может сохранять и использовать в базе данных указатели C++. Почему это является преимуществом? Потому что в принципе это позволяет персистентно хранить любую произвольную объектную модель C++. На практике могут иметься другие ограничения, не позволяющие это сделать, или позволяющие сделать это не идеальным образом, но факт остается фактом: в ObjectStore можно сохранить любую модель C++.
Массивы C++ поддерживают разработку и реализацию многих сильно оптимизированных и эффективных структур данных, таких, какие используются в программе распределения памяти в буферном пуле. Массивы C++ позволяют программисту сократить существенные накладные расходы на конструирование одиночных объектов путем использования одного конструирования массива для всех входящих в него объектов. В условиях персистентности возможность сохранять реальные массивы C++ означает, что запись в базу данных выполняется исключительно быстро. При комбинировании возможностей персистентного хранения в базе данных реальных указателей и массивов C++ и наличии средств эффективной навигации по этим указателям программисты могут разрабатывать абсолютно оптимальные индексные структуры для любого заданного сценария использования, как это описывалось ранее. Насколько мне известно, это очень трудно сделать изнутри C++ при использовании любой другой технологии.
Если все объекты находятся в кэше клиента, навигационный доступ, вероятно, является более быстрым в системе с архитектурой, основанной на страницах, чем в системе с архитектурой, основанной на объектах, потому что в первом случае используются поддерживаемые аппаратурой указатели, и не требуется какая-либо трансляция указателей. Теоретически возможны системы с архитектурой, основанной на объектах, в которых производится свизлинг указателей при помещении объектов в кэш, хотя сделать это труднее, чем в системе с архитектурой, основанной на страницах. Так что теоретически между этими двумя подходами может не быть реальной разницы в производительности, и степень важности этого различия зависит от деталей сценариев использования системы.
Здесь следует подчеркнуть, что навигационный доступ является основным почти для всех приложений ООБД, хотя эта тема едва упомянута в статье Грина.
«В особенности в этом отношении уязвима архитектура, основанная на страницах, поскольку запросы могут адресоваться только к объектам, входящим в специальные коллекции, и индексы применимы только к этим коллекциям. Для выполнения запроса по сети должны загружаться все страницы, содержащие объекты в этих коллекциях или индексы для этих коллекций. Эти страницы, безусловно, будут содержать много лишних объектов, что приводит к неэффективному расходованию пропускной способности сети и падению производительности, поскольку на пересылку страниц может тратиться больше времени, чем на реальную обработку запроса.»
В этой выдержке содержится много неточностей и полуправды. Во-первых, серверные архитектуры не являются автоматически уязвимыми к требованию перемещения страниц в адресные пространства других процессов при обработке запросов. Конкретный продукт можно реализовать таким образом, что он будет уязвимым к этому требованию, но в серверных архитектурах, основанных на страницах, как таковых можно реализовать обработку запросов на стороне сервера, если такой подход является желательным. Перемещать или не перемещать страницы в адресные пространства других процессов – это проблема, зависящая от многих аспектов, среди которых не последними являются сценарий использования и эффективность реализации продукта.
Как подробно описывалось выше, в случае ObjectStore при наличии соответствующих индексов при обработке запроса требуется перемещение на клиента только страниц, содержащих индексные структуры. Не буду развивать здесь тему о смягчении проблемы узких мест сервера.
Во-вторых, в серверных архитектурах, основанных на страницах, в принципе не навязывается то ограничение, что можно запрашивать только объекты, существующие в особых коллекциях, для которых могут иметься или не иметься индексы. В конкретных продуктах может реализовываться библиотека коллекций, но эта особенность ортогональна к специфике серверной архитектуры, основанной на страницах.
В ObjectStore запросы производятся путем вызова метода запросов на его классах коллекций, и он возвращает поднабор элементов коллекции на основе сложного предиката; эти запросы могут индексироваться и эффективно обрабатываются. В ObjectStore также допускается доступ к произвольным объектам двумя способами: с использованием объектного курсора и на основе динамических экстентов. Так что в ObjectStore достижим любой объект без родителей. Причина того, что произвольные запросы к глобальным объектам не рекомендуются в качестве основного режима доступа, кроется в проблемах инкапсуляции.
Так что замечание Грина о том, что «запросы могут адресоваться только к объектам, входящим в специальные коллекции», в действительности является довольно бессмысленным; аналогично можно было жаловаться на то, что в реляционных базах данных запросы могут адресоваться только к таблицам. Кроме того, это в любом случае неверно, поскольку в ObjectStore поддерживаются произвольные запросы через динамические экстенты.
Интересным побочным аспектом является смысл объектов, не имеющих родителей. Объекты-сироты, присутствующие в базе данных ObjectStore, – это объекты, на которые отсутствуют ссылки из других объектов. Это значит, что никакая программа не может получить доступ к этим объектам, кроме как путем использования объектного курсора. Если считать, что используется навигационный доступ, как это и рекомендуется, то наличие этих осиротелых объектов утечки персистентной памяти (memory leak); клиентский код не может удалить более не нужный объект.
«Можно представить себе ситуацию, в которой имеется миллион объектов Foo, среди которых ищется всего один объект. Вдобавок к этому при каждой вставке требуется поддержка индексов, а поскольку потенциально они могут быть распределены между многими клиентскими процессами, может возникнуть много недействительных страниц, и понадобятся сетевые обновления коллекций и индексов, дополнительно нагружающие сеть.»
В правильно спроектированной системе, основанной на ObjectStore, нахождение единственного объекта Foo в коллекции из миллиона объектов обычно является очень быстрым вызовом. Для этого не требуется какая-либо работа программистов!
Здесь Грин рисует очень неточную картину поддержки индексов в ObjectStore. При вставке объекта в индексируемую коллекцию инициируется автоматическая поддержка индекса. В представленном сценарии, в котором одиночный объект Foo вставляется в коллекцию из миллиона объектов, будет обновляться одна страница индекса, или, может быть, несколько страниц. При фиксации транзакции ObjectStore (в предположении, что фиксация выполняется) страницы, содержащие индексные структуры данных, возвращаются на сервер и записываются на диск, как и любые другие страницы. Если к индексу обратится другой клиент, эти несколько страниц будут выдаваться обычным образом страница за страницей. Измененные индексные страницы пересылаются только в тех случаях, когда они используются другими клиентами, и никакие другие страницы, например, те, которые содержат миллион объектов Foo, не пересылаются в результате обновления этого индекса.
«Кроме того, многопотоковая организация клиентского процесса позволят выполнять запросы к нескольким физическим базам данных в параллель.»
Здесь Грин в действительности не различает серверные архитектуры, основанные на объектах и страницах. Из контекста этого предложения следует, что эта черта возможна только при выполнении запросов на стороне сервера и, следовательно, только в архитектуре, основанной на объектах. Истина здесь состоит в том, что серверные архитектуры, основанные на страницах, сами по себе не навязывают какую-нибудь единую модель многопотоковости и не препятствуют параллельному выполнению запросов над несколькими физическими базами данных. Это доказывается существованием реализации серверной архитектуры, обеспечивающей эти возможности. В ObjectStore, типичной реализации серверной архитектуры, основанной на страницах, активно поддерживается и поощряется многопотоковое программирование на стороне клиента, и допускается транзакционный доступ клиентов к нескольким физическим базам данных, распределенным по сети и расположенным на разных платформах под управлением разных серверных процессов ObjectStore. В том, что говорит здесь Грин, не дифференцируются архитектуры, основанные на страницах и объектах, и даже не дифференцируются продукты на каком-либо содержательном уровне.
«При использовании систем с архитектурой, основанной на страницах …, управление индексами обычно интегрируется с кодом приложений. Это влияет на сопровождение приложений. При использовании систем с архитектурой, основанной на объектах, индексы управляются сервером. Новые индексы могут определяться без изменения приложений.»
Тот факт, что управление индексами интегрируется или не интегрируется с кодом приложений, не связан с тем, что система имеет серверную архитектуру, основанную на страницах. В ObjectStore имеется несколько способов поддержки индексов, для некоторых из которых, как отмечает Грин, требуются незначительные изменения кода приложений. Но этот код не является сложным, и в любом случае его написание не представляет собой проблему, это всего лишь код, выполняющий некоторую полезную работу. С точки зрения сопровождения кода индексы ObjectStore также обычно не порождают проблем. Во многих ситуациях необходима явная поддержка индексов, которая производится автоматически во время выполнения ObjectStore.
Неверно также и то, что в ObjectStore нельзя определять индексы без изменения приложения, поскольку эта функциональная возможность может быть обеспечена путем написания дополнительного специального приложения поддержки индексов. API для добавления индексов является очень простым и ориентированным на программистов C++. Определить новый индекс в ObjectStore не сложнее, чем написать операторы DDL для добавления индексов над реляционными таблицами. Поэтому относительно просто написать автономное приложение для добавления или уничтожения индексов над любой коллекцией базы данных, даже если система используется другими приложениями, и эти приложения смогут начать использовать заново созданные индексы сразу после фиксации транзакции, в рамках которой они были созданы. Так что индексы в ObjectStore можно создавать и удалять динамически, «на лету» без изменения кода основных приложений.
Управление идентификационной информацией
«Кроме того, детали реализации физической идентификационной информацией влияют на масштабируемость данных для систем, требующих хранения терабайт информации.»
64-разрядный указатель может адресовать 17,179,869,184 гигабайт, или 16 эксабайт основной памяти. Какой размер имеют сегодняшние базы данных? Если говорить серьезно, то наличие 64-разрядного персистентного адресного пространства трудно считать практическим ограничением размера набора данных, так что, если это замечание Грина адресовано к серверным архитектурам, основанным на страницах, он ошибается.
В ObjectStore действительно имеется ограничением в два терабайта на размер физического файла базы данных, но для преодоления этого ограничения можно использовать несколько физических баз данных. В большинстве реальных проектов большие наборы данных обычно разбиваются на несколько физических баз данных, размер каждой из которых существенно меньше этого предельного значения, и эти физические базы данных распределяются между многими серверными машинами, так что практически это ограничение никогда не представляет проблему.
Физическая идентификационная информация
«Характеристиками физической идентификации являются изменчивость, повторная используемость, стационарность, жесткость.»
Здесь Грин пренебрегает наиболее позитивным аспектом физической идентификационной информации. Физические идентификаторы обычно очень быстро разыменовываются – намного быстрее, чем логические идентификаторы, для которых приходится использовать доступ через некоторую разновидность индексной структуры. Характеристиками, которые делают физические ссылки такими быстрыми, являются «стационарность» и «жесткость».
Я не отрицаю, что в принципе можно было бы реализовать доступ к объектам с использованием физической идентификации как в архитектуре, основанной на объектах, так и в архитектуре, основанной на страницах, но на практике эта идея может сравнительно легко использоваться в серверах с архитектурой, основанной на страницах. В случае ObjectStore обычные указатели C++ обеспечивают исключительно быстрый доступ к данным, а интуитивный стиль программирования C++ делает очень продуктивной работу с библиотеками базы данных.
«Нужно найти все объекты, существующие в системе, которые содержат ссылку на данный объект, и сделать так, чтобы в полях связи скрывалось новое физическое местоположение ...»
Здесь Грин прав. У адресации, основанной на Object-ID, имеется некоторое преимущество над «физической», основанной на местоположении адресацией, которое проявляется при эволюции схемы. При использовании Object-ID эволюцию схемы можно производить в режиме он-лайн и инкрементно. Объекты могут изменяться по мере того, как к ним происходит доступ, поскольку они могут перемещаться без изменения своих адресов, если перестают помещаться в предыдущем месте хранения. Безусловно, это является преимуществом адресации на основе Object-ID.
Однако за это преимущество приходится платить, у него имеется и обратная сторона. Клиенты расплачиваются за эту дополнительную гибкость потребностью в трансляции идентификаторов объектов в физические адреса при каждом доступе к объекту. Компьютеры не могут использовать Object ID, для доступа к объекту в некоторый момент им требуется физический адрес. Конечно, в Versant реализовано некоторое кэширование для предотвращения недопустимого уровня накладных расходов, но на некотором уровне имеется архитектурный компромисс между эффективностью и гибкостью. При использовании адресации на основе Object ID за преимущества эволюции схемы приходится платить постоянно при функционировании системы, в то время как при использовании ссылок на основе местоположения приходится платить только один раз при использовании ссылки.
Так что остаются вопросы. Насколько затруднительна эволюция схемы в базах данных под управлением ObjectStore? Насколько быстро происходит эволюция схемы?
Сложность эволюции схемы
С инженерных позиций этот процесс является чрезвычайно сложным, поскольку, в отличие от реляционной базы данных, в которой персистентная схема является известной и постоянной – все данные располагаются в таблицах, столбцах и строках,– персистентная ООБД является полностью произвольной, определяемой в реальном пользовательском коде C++. Поэтому у любой утилиты общего назначения, поддерживающей эволюцию схемы, должная иметься возможность управления буквально любой объектной моделью C++, которая может встретиться, и возможность преобразования ее в любую другую модель. Для снижения уровня сложности и по соображениям эффективности в ObjectStore утилита поддержки эволюции схем функционирует в режиме оф-лайн, а для уменьшения сложности возможных изменений схемы среди программистов распространен прием написания специального кода эволюции схемы на C++ и его компоновки с библиотекой эволюции схемы ObjectStore для выполнения процесса эволюции схемы.
Так что с позиций пользователя эволюция схемы также представляет некоторую сложность. Рассмотрим пару примеров.
Инициализация заново создаваемого элемента данных на основе существующего элемента
В C++ запрещается наличие в одном адресном пространстве двух одноименных классов с разными схемами организации памяти. Однако в данном случае оказывается необходимым именно это, нужен доступ к старым данным при создании нового объекта. Имеется несколько способов достижения желаемого результата, один из которых используется в инструментальном средстве эволюции схем ObjectStore и состоит из трех шагов.
- Произвести эволюцию схемы для добавления нового элемента данных. Инициализировать его по умолчанию нулем. На этом шаге не удалять старый элемент данных.
- Запустить относительно простую пользовательскую программу, которая находит в базе данных все экземпляры с измененной схемой и инициализирует новый элемент данных значением старого элемента данных. Здесь имеется несколько вариантов:
- использовать объектный курсор;
- производить навигацию по специализированным структурам данных приложения;
- выполнять это действие инкрементно, при первом обновлении объекта;
- объединить действие с шагом 1 процесса эволюции путем использования функции преобразования, которая будет автоматически вызываться в процессе эволюции схемы для каждого встреченного кандидата на эволюцию.
- Последний шаг заключается в запуске процесса очистки, удаляющего старый элемент данных; эта очистка может производиться в любое удобное время, поскольку с логической точки зрения это не требуется, а лишь позволяет освободить память.
Изменение классификации экземпляров
Изменение классификации означает, что некоторый существующий листовой класс (не имеющий подклассов) подвергается рефакторингу путем введения нескольких подклассов. Каждый существующий экземпляр назначается в один из подклассов на основе критерия, задаваемого приложением. Этот процесс снова состоит из трех шагов.
- Во-первых, пользователь добавляет в свою схему новые подклассы. Каждый новый подкласс просто включает базовый класс без добавления к нему чего бы то ни было. Определение базового класса не изменяется.
- Далее пользователь пишет и запускает программу, которая анализирует каждый экземпляр базового класса, выбирает некоторый подкласс и изменяет тип экземпляра, используя вызов функции ObjectStore изменения типа; в этот момент не производятся никакие изменения персистентной схемы объекта, так что все указатели, ссылающиеся на объект с измененным типом, остаются действующими.
- Наконец, базовый класс и подклассы изменяются в соответствии с их целевыми определениями. Этот шаг может включать удаление элементов данных в базовом классе и добавление некоторых из них в качестве элементов данных подклассов. После компиляции новой схемы пользователь запускает процесс эволюции схемы.
Многие сценарии эволюции схемы могут быть обслужены путем непосредственного вызова инструментального средства эволюции схем ObjectStore. Это средство обеспечивает нахождение всех объектов, их изменение, перемещение их при необходимости в другие страницы и исправление значений и типов указателей. Подробности сценариев, для которых не требуется писать код на C++ для эволюции схемы, лучше всего описаны в руководстве ObjectStore Advanced C++ API User Guide [2], к которому отсылаются заинтересованные читатели.
В тех случаях, когда от пользователей требуется написание некоторого кода на C++, этот код является относительно простым и обычно включает нахождение в базе данных всех эволюционирующих объектов и вызов некоторой функции ObjectStore или инициализацию элементов данных. В любом случае обычно код является достаточно прозаичным. Перемещение эволюционирующих объектов в новые области памяти, если у них изменяется размер, и исправление значений и типов указателей обеспечивается средствами библиотеки эволюции схем.
Здесь фокус состоит в том, что эволюция схемы в ObjectStore действительно является сложной с инженерных позиций, но большая часть этой сложности скрывается от пользователя в утилите эволюции схемы базы данных, поставляемой вместе с продуктов.
Скорость эволюции схемы
Этот вопрос является вариантов классического вопроса «насколько длинна часть строки?». Это зависит от контекста, деталей эволюции схемы, скорости дисковой системы, размера базы данных, объема физической основной памяти и числа процессоров в машине, производящей эволюцию схемы.
Средство эволюции схем ObjectStore разработано и оптимизировано для наиболее эффективной работы в среде высокопроизводительных серверов, на которых обычно развертываются базы данных ObjectStore.
Имеются три основных фазы эволюции схемы:
- На первой фазе выполняются предэволюционные преобразования, детали которых здесь неважны.
- На второй фазе выясняется местоположение всех кандидатов на эволюцию, и вычисляются их целевые местоположения; эта информация сохраняется в очень сжатой, но быстро работающей таблице отображения из старых адресов в новые адреса.
- Наконец, на последней фазе читаются все страницы базы данных, производятся изменения объектов, они перемещаются из старых местоположений в новые местоположения, исправляются значения всех указателей, участвующих в ссылках. Необходимо прочитать все страницы, поскольку было бы слишком дорого предварительно вычислять, в каких страницах содержатся указатели на кандидатов на эволюцию. Кроме того, поскольку порядок объектов в постоянной памяти не изменяется, могут перемещаться не только эволюционирующие объекты.
Однако при этом отсутствует троекратное чтение всех страниц базы данных. Первые две фазы могут выполняться без чтения и отображения страниц, содержащих реальные объекты. С каждой страницей в базе данных ObjectStore ассоциируются метаданные, которые задают местоположение и тип каждого объекта внутри этой страницы, а также указатель на начало страницы. Эта информация сильно сжата, и обычно ее размер гораздо меньше размера страницы. На первых двух фазах требуется прочитать только эту метаинформацию, и поэтому они выполняются на порядки величин быстрее, чем если бы требовалось считывать и отображать страницы целиком. Так что в терминах считывания страниц выполняются не три прохода по базе данных, а скорее 1.2 проходов.
Повышенная производительность также достигается за счет продуманного использования многопотоковости. В большей части всех трех фаз при эволюции применяется несколько потоков исполнения, что позволяет эффективно использовать мультипроцессорые установки, типичные для высокопроизводительных серверов. Накладные расходы на синхронизацию потоков сокращаются за счет назначения отдельного потока каждому кластеру базы данных, так что в потоке может быть обработан целый кластер, а после завершения этой работы поток переходит к обработке следующего кластера.
На последней фазе, на которой страницы реально считываются, не требуется реально отображать эти страницы, потребляя адресное пространство. Эволюция может производиться над страницей, не привязанной к свойственным ей адресам. Это означает, что процесс эволюции может масштабироваться до довольно значительных баз данных размером до 20 гигабайт. Более крупные наборы данных в ObjectStore обычно расщепляются на несколько отдельных физических баз данных, распределенных между несколькими машинами. Эволюция каждой из этих баз данных может происходить по отдельности и в параллель, так что этот размер является более чем достаточным в большинстве ситуаций.
На последней фазе, на которой выполняется значительный объем дискового ввода-вывода (по очевидным причинам подразумевается, что обработка эволюции НЕ выполняется в сетевом режиме), ввод-вывод осуществляется большими последовательными блоками, а не путем случайного доступа, который был бы гораздо медленнее.
Все эти средства объединяются с целью обеспечения пропускной способности, равной скорости записи на диск (или близкой к этому) на машине с четырьмя ЦП; так что обеспечивается пропускная способность около семи гигабайт в час. Измерения показывают приближенно линейную зависимость производительность от размеров базы данных. Этот показатель не зависит от числа эволюционирующих объектов, кроме случая, когда их вовсе нет, и третья фаза не выполняется.
Остается еще один вопрос. Если процесс эволюции схемы является настолько эффективным частично из-за того, что в нем НЕ считываются и не отображаются страницы, что делает серверную архитектуру, основанную на страницах, хорошо подходящей для других программ? Не свидетельствуют ли эти данные в пользу какой-нибудь другой архитектуры?
Ответ состоит в том, что серверная архитектура, основанная на страницах, позволяет писать C++-клиентов таким образом, как если бы они работали со структурами данных в основной памяти, вместо того, чтобы заставлять программистов использовать API для навигации по базе данных. Для программиста, использующего ObjectStore, наилучшей метафорой является не «база данных», а «замороженная транзакционная куча» («frozen transactional heap»). Это снижает сложность приложений и повышает скорость приложений, выполняющих большую работу над подсетью объектов, которая может быть кэширована.
При эволюции схемы имеется в точности противоположный сценарий использования. В этом сценарии считываются все страницы базы данных, но над каждой страницей выполняется очень небольшой объем работ.
«Ключевое отличие в реализации систем с физической идентификационной информацией приводит к существенному отличию по части масштабируемости при управлении «сырыми» данными на дисках. Применение в архитектуре, основанной на страницах, подхода трансляции адресов делает невозможным истинно интегрированное распределение данных на диске. Можно сегментировать данные внутри одной базы данных, чтобы приложения работали с подобластями этой базы данных в адресных пространствах своих процессов, но невозможно обеспечить в дисковой подсистеме распределенную (федеративную) базу данных, доступную клиентам в виде логического источника данных. Это означает, что в реализации архитектуры, основанной на контейнерах, возможности масштабируемости распространяются до терабайтных и даже петабайтных диапазонов, но в реализации архитектуры, основанной на страницах, они ограничены диапазонами мегабайт или гигабайт.»
Это просто неверно. Кажется, Грин полагает, что вся база данных должна помещаться в адресное пространство клиента. Конечно, это не так. Виртуальное адресное пространство расходуется только для действительно затрагиваемых страниц (на самом деле, групп страниц общим размеров в 64 Кб) и для страниц, на которые ссылаются «жесткие» указатели, находящиеся в затрагиваемых страницах. Кроме того, виртуальное адресное пространство динамически переназначается ObjectStore для обеспечения приложению возможности доступа к более крупной части персистентного хранилища. Даже на 32-разрядных клиентских машинах нет ограничения на размер базы данных, кроме ограничений на размер файла, и нет ограничений на одновременное интегрированное использование нескольких баз данных. С появлением 64-разрядного виртуального адресного пространства действительно отсутствуют ограничения на трансляцию адресов.
Заключение
Думаю, что мне удалось показать, что в статье Грина содержится много неточностей и искажений фактов. Цель настоящей статьи состоит в восстановлении более добросовестного обсуждения достоинств и недостатков серверных архитектур, основанных на страницах и объектах. Надеюсь, что эта статья позволит читателям составить более обоснованное мнение о том, какая ООСУБД отвечает их потребностям.
Литература
- OODBMS Architectures: an examination of implementations, R. Greene, 2006
- Advanced C++ API User Guide, Chapter 7 Advanced Schema Evolution, P.201