Подход с использованием принципа черного ящика
У меня есть предположение, основанное на личном опыте, почему так часто разработка приложений баз данных заканчивается неудачно. Позвольте уточнить, что к разряду неудавшихся разработок я отношу также проекты, официально не признанные неудавшимися, но потребовавшие на разработку и внедрение намного больше времени, чем планировалось первоначально, поскольку пришлось их существенно "переписывать", "перепроектировать" или "настраивать". Лично я такие не завершенные в срок проекты считаю неудавшимися: очень часто их вполне можно было завершить вовремя (и даже досрочно).
Наиболее типичной причиной неудачи является нехватка практических знаний по используемой СУБД — элементарное непонимание основ работы используемого инструментального средства. Подход по принципу "черного ящика" требует осознанного решения: оградить разработчиков от СУБД. Их заставляют не вникать ни в какие особенности ее функционирования. Причины использования этого подхода связаны с опасениями, незнанием и неуверенностью. Разработчики слышали, что СУБД — это "сложно", язык SQL, транзакции и целостность данных — не менее "сложно". Решение: не заставлять никого делать что-либо "сложное". Будем относиться к СУБД, как к черному ящику, и найдем инструментальное средство, которое сгенерирует необходимый код. Изолируем себя несколькими промежуточными уровнями, чтобы не пришлось сталкиваться непосредственно с этой "сложной" СУБД.
Такой подход к разработке приложений баз данных я не мог понять никогда. Одна из причин, почему мне трудно это понять, состоит в том, что для меня изучение языков Java и C оказалось намного сложнее, чем изучение основ работы СУБД. Я сейчас очень хорошо знаю языки Java и C, но для их освоения мне понадобилось намного больше практического опыта, чем для достижения соответствующего уровня компетентности при использовании СУБД. В случае СУБД необходимо знать, как она работает, но детали знать необязательно. При программировании на языке C или Java, необходимо, например, знать все особенности используемых компонентов; кроме того, это очень большие по объему языки.
Еще одна причина — то, что при создании приложений базы данных самым важным компонентом программного обеспечения является СУБД. Для успешной разработки необходимо учитывать это и доводить до сведения разработчиков, постоянно обращая на это их внимание. Много раз я сталкивался с проектами, где придерживались прямо противоположных воззрений.
Вот типичный сценарий такого рода разработки.
Разработчики были полностью обучены графической среде разработки или соответствующему языку программирования (например, Java), использованных для создания клиентской части приложения. Во многих случаях они обучались несколько недель, если не месяцев.
Команда разработчиков ни одного часа не изучала СУБД Oracle и не имела никакого опыта работы с ней. Многие разработчики вообще впервые сталкивались с СУБД.
В результате разработчики столкнулись с огромными проблемами, связанными с производительностью, обеспечением целостности данных, зависанием приложений и т.д. (но пользовательский интерфейс выглядел отлично).
Не сумев обеспечить нужную производительность, разработчики обращались за помощью ко мне. Особенно показателен один случай. Я не мог вспомнить точный синтаксис новой команды, которую надо было использовать, и попросил руководство SQL Reference. Мне принесли экземпляр из документации по СУБД Oracle версии 6.0, хотя разработка велась на версии 7.3, через пять лет после выхода версии 6.0! Ничего другого для работы у них не было, но это вообще никого не беспокоило. Хотя необходимое им для трассировки и настройки инструментальное средство в то время вообще не существовало. Хотя за пять лет, прошедших после написания имевшейся у них документации, были добавлены такие средства, как триггеры, хранимые процедуры, и многие сотни других. Несложно понять, почему им потребовалась помощь, гораздо труднее было решить их проблемы.
Странная идея о том, что разработчик приложения баз данных должен быть огражден от СУБД, чрезвычайно живуча. Многие почему-то считают, что разработчикам не следует тратить время на изучение СУБД. Неоднократно приходилось слышать: "СУБД Oracle — самая масштабируемая в мире, моим сотрудникам не нужно ее изучать, потому что СУБД со всеми проблемами справится сама". Действительно, СУБД Oracle — самая масштабируемая. Однако написать плохой код, который масштабироваться не будет, в Oracle намного проще, чем написать хороший, масштабируемый код. Можно заменить СУБД Oracle любой другой СУБД — это утверждение останется верным. Это факт: проще писать приложения с низкой производительностью, чем высокопроизводительные приложения. Иногда очень легко создать однопользовательскую систему на базе самой масштабируемой СУБД в мире, если не знать, что делаешь. СУБД — это инструмент, а неправильное применение любого инструмента может привести к катастрофе. Вы будете щипцами колоть орехи так же, как молотком? Можно, конечно, и так, но это неправильное использование инструмента, и результат вас не порадует. Аналогичные результаты будут и при игнорировании особенностей используемой СУБД.
Я недавно работал над проектом, в котором проектировщики придумали очень элегантную архитектуру. Клиент с помощью Web-браузера взаимодействовал по протоколу HTTP с сервером приложений, обеспечивающим поддержку Java Server Pages (JSP). Алгоритмы работы приложения целиком генерировались инструментальными средствами и реализовывались в виде компонентов EJB (с использованием постоянного хранения на базе контейнеров), причем физически они выполнялись другим сервером приложений. В базе данных хранились только таблицы и индексы.
Итак, мы начали с технически сложной архитектуры. Для решения задачи должны взаимодействовать друг с другом четыре компонента. Web-браузер получает страницы JSP от сервера приложений, который обращается к компонентам EJB, а те, в свою очередь, — к СУБД. Для разработки, тестирования, настройки и внедрения этого приложения необходимы были технически компетентные специалисты. После завершения разработки меня попросили оценить производительность приложения. Прежде всего я хотел узнать подход разработчиков к СУБД:
где, по их мнению, у приложения могут быть узкие места, точки потенциальных конфликтов?
каковы, по их мнению, основные препятствия для достижения требуемой производительности?
Они не имели ни малейшего представления об этом. На вопрос о том, кто поможет мне переписать код компонента EJB для настройки сгенерированного запроса, ответ был следующий: "О, этот код нельзя изменять, все надо делать в базе данных". То есть, приложение должно оставаться неизменным. В этот момент я был готов отказаться от работы над проектом — ясно, что заставить это приложение нормально работать невозможно:
приложение было создано без учета масштабирования на уровне базы данных;
приложение нельзя настраивать и вообще изменять;
по моему опыту, от 80 до 90 процентов всей настройки выполняется на уровне приложения, а не на уровне базы данных;
разработчики не представляли себе, что делают их компоненты с базой данных и где искать потенциальные проблемы.
Все это стало понятно уже через час тестирования. Как оказалось, в приложении сначала выполнялся оператор:
select * from t for update;
Это приводило к строго последовательной работе всех клиентов. В базе данных была реализована такая модель, что перед выполнением любых существенных действий приходилось блокировать весьма большой ресурс. Это моментально превращало приложение в очень большую однопользовательскую систему. Разработчики не верили мне (в другой СУБД, использующей разделяемую блокировку чтения, наблюдалась другая ситуация). После десяти минут работы с инструментальным средством TKPROF (о нем подробно написано в главе 10) я смог продемонстрировать, что именно этот оператор SQL выполнялся приложением (они об этом не знали — просто никогда не видели генерируемых операторов SQL). Я не просто показал, какие операторы SQL выполняются приложением, но и с помощью пары сеансов SQL*Plus продемонстрировал, что второй сеанс не начинается до полного завершения работы первым сеансом.
Итак, вместо того, чтобы неделю тестировать производительность приложения, я употребил это время на обучение разработчиков настройке, особенностям блокирования в базах данных, механизмам управления одновременным доступом, сравнение их реализаций в СУБД Oracle, Informix, SQL Server, DB2 и так далее (во всех этих СУБД они различны). Но сначала мне пришлось понять, однако, почему использовался оператор
SELECT FOR UPDATE. Оказалось, что разработчики хотели добиться повторяемости при чтении.
Повторяемость при чтении — это такой режим работы СУБД, когда повторное чтение в транзакции строки, которая уже один раз прочитана в этой транзакции, дает тот же результат.
Зачем им это было нужно? Они слышали, что "это хорошо". Ладно, предположим, повторяемость при чтении действительно необходима. В СУБД Oracle это делается путем установки уровня изолированности транзакции
SERIALIZABLE (что дает не только повторяемость при чтении строки, но и повторяемость при выполнении любого запроса — если два раза выполнить один и тот же запрос в пределах транзакции с таким уровнем изолированности, будут получены одинаковые результаты). Для обеспечения повторяемости при чтении в Oracle не нужно использовать
SELECT FOR UPDATE — это делается только для обеспечения последовательного доступа к данным. К сожалению, использованное разработчиками инструментальное средство это не учитывало — оно было создано для использования с другой СУБД, где именно так повторяемость при чтении и достигалась.
Итак, в данном случае для установки уровня изолированности транзакций
SERIALIZABLE пришлось создать триггер на регистрацию в базе данных, изменяющий параметры сеанса (уровень изолированности транзакций) для данного приложения. Затем мы отключили все установки повторяемости при чтении в использовавшемся инструментальном средстве и повторно запустили приложение. Теперь, без конструкции
FOR UPDATE, в базе данных определенные действия стали выполняться одновременно.
Это была далеко не последняя проблема данного проекта. Нам пришлось разобраться:
как настраивать операторы SQL, не изменяя их кода (это сложно — некоторые методы мы рассмотрим в главе 11);
как измерять производительность;
как находить узкие места;
что и как индексировать, и так далее.
В конце недели разработчики, никогда ранее не работавшие с СУБД, были удивлены тем, что в действительности она дает возможность сделать, как легко получить указанную выше информацию и, что наиболее важно, как существенно все это может сказаться на производительности приложения. Тестированием производительности в течение этой недели мы не занимались (им кое-что пришлось переделывать!), но в конечном итоге проект завершился успешно — просто на пару недель позже запланированного срока.
Это не критика инструментальных средств или современных технологий, таких как компоненты EJB и поддержка постоянного существования на базе контейнеров. Это — критика намеренного игнорирования особенностей СУБД, принципов ее работы и использования. Технологии, выбранные в этом проекте, работали отлично, но лишь после того, как разработчики немного разобрались в самой СУБД.
Подводя итоги: СУБД — это краеугольный камень приложения. Если она не работает как следует, все остальное не имеет значения. Если плохо работает черный ящик, что с ним делать? Его нельзя исправить, нельзя настроить (поскольку непонятно, как он устроен), и такую позицию вы выбрали сами. Но есть и другой подход, который я отстаиваю: разберитесь в используемой СУБД и принципах ее работы, поймите, что она может делать, и используйте весь ее потенциал.
Содержание раздела