1. Что делать с внешней памятью?
2. Что делать с буферами оперативной памяти?
3. Параллельные серверы баз данных
Заключение

Любой человек, когда-либо занимавшийся проектированием и разработкой систем управления базами данных, безусловно сталкивался с проблемой операционной среды. По этому поводу имеются крайние точки зрения. Одни полагают, что СУБД полностью не должна зависеть от операционной системы, другие, - что ОС обязана предоставить необходимые и достаточные средства. Однако, когда говорят, что имеется единая версия СУБД, работающая и в среде MS NT, и в среде любого варианта Unix, это сильно отдает спекуляцией. Наоборот, практически невозможно изготовить действительно эффективную систему баз данных, не расширяя возможности операционной системы. Известны несколько статей, связанных (хотя бы по названию) с этой тематикой. (Вспомним, например, статьи Джима Грея и Майкла Стоунбрейкера, кстати, отличная литература.) Но они были написаны уже довольно давно, и, видимо, пришло время вернуться к теме, посвященной взаимоотношениям ОС и СУБД, благо, что в последние годы кое-что изменилось.

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

1. Что делать с внешней памятью?

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

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

Далее, конечно, любому разработчику СУБД хотелось бы освободиться хотя бы от части проблем. Может быть, вам это покажется странным, но одной из таких "лишних" проблем является распределение внешней памяти. Поэтому, и только поэтому особенно заманчивым для разработчиков СУБД является использование файловых систем. Основной неприятностью файловых систем являлось хаотическое распределение внешней памяти. Один блок внешней памяти файла мог отстоять на произвольное количество цилиндров, т.е. элементов передвижения головок. Коренной перелом произошел в так называемой "быстрой файловой системе", разработанной Кирком МасКусиком в рамках проекта BSD 4.3. Что же придумал Кирк? Собственно, ничего особенного. Он решил, что лучше головкам магнитного диска двигаться не слишком сильно. Поэтому было введено понятие группы магнитных цилиндров, в пределах которых должен располагаться файл. Вместо произвольного передвижения магнитных головок в масштабе всей поверхности диска для последовательного чтения файла требуется ограниченное смещение головок в выделенной группе цилиндров. Казалось бы, все в порядке. Однако нет...

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

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

Видимо, одной из первых компаний, которые перешли на собственное управление внешней памятью, была компания Informix в своей серии серверов Informix On-Line. Что, собственно, сделала компания? В общем-то, ничего большего, чем своя собственная, встроенная в СУБД файловая система, которая была полностью оптимизирована для нужд СУБД. Для любознательных читателей весьма рекомендую внимательно прочитать руководство компании Informix для системных администраторов. В дальнейшем на аналогичную технологию перешла компания Oracle (прошу прощения, если ошибся в хронологии), а затем и другие ведущие фирмы-производители СУБД.

Все было прекрасно (с точностью до...), пока не появились устройства с магнитными дисками, предъявляющие внешнему миру интерфейс с 24 магнитными головками в то время, когда на самом деле (физически) их было 15. Ну и что же в этом случае мы оптимизируем? Мы как бы управляем движением головок, а что за этим реально стоит? А в общем-то ничего... Аппаратура и встроенное программное обеспечение контроллеров магнитных дисков сами производят собственную оптимизацию, а бедной СУБД кажется, что все уже оптимизировано. Еще хуже дела стали с появлением дисковых массивов (в русский язык уже успешно вошел типично славянский термин RAID), особенно начиная с пятого уровня организации. Тут уж все стало совсем непонятно (и ни один из знакомых мне разработчиков коммерческих СУБД не смог прояснить ситуацию). Система управления базами данных всячески старается повысить надежность хранения данных, сохраняет в журналах изменения базы данных, предоставляет особо мудрые методы их восстановления, а на самом деле RAID уже обеспечивает надежное хранение данных с 90-процентной гарантией и разделенное хранение блока данных на всех дисках, входящих в массив. Что же теперь оптимизируют СУБД по отношению к доступу к базам данных? Who knows? По крайней мере, я не знаю, а также не знаю человека, который знает, а хотелось бы знать...

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

2. Что делать с буферами оперативной памяти?

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

Единственным применяемым сегодня подходом, предназначенным для сглаживания разницы в быстроте доступа к основной и внешней памяти, является использование большого буферного пула основной памяти. Общая идея очевидна: чем больше полезной информации из базы данных находится в буферах, тем быстрее можно будет выполнять запросы. Конечно, хотя по некоторым оценкам, только тридцать процентов от общего числа операторов доступа к базе данных составляют операторы обновления базы данных, нельзя допустить, чтобы выполнение такого оператора сопровождалось серией записей во внешнюю память. Это слишком замедлило бы работу сервера баз данных. Поэтому в серверах баз данных используется развитая техника совместного управления буферами основной памяти и журнализацией, основанная на известном протоколе Write Ahead Log ("Пиши сначала в журнал") и гарантирующая возможность восстановления согласованного состояния базы данных во внешней памяти после сбоя любого типа. (В этой статье мы не будем более подробно останавливаться на этой технике.)

Имея в виду предыдущее замечание, сосредоточимся собственно на управлении буферами. Известно, что операционные системы (в частности ОС Unix), вообще говоря, поддерживают два вида буферизации данных в основной памяти. Первый вид буферизации связан с управлением виртуальной памятью. Поскольку суммарный объем виртуальной памяти всех процессов, одновременно выполняемых под управлением ОС, часто существенно превышает доступный для распределения объем основной памяти, операционная система вынуждена перераспределять страницы основной памяти для отображения на них требуемых страниц виртуальной памяти. Хотя в принятой терминологии операционных систем такие действия принято называть замещением страниц оперативной памяти, конечно, на самом деле это самое настоящее управление буферами. Если не вдаваться в детали (их можно почерпнуть в стандартной литературе по операционным системам), существуют две разновидности политики замещения: глобальная политика, при следовании которой кандидатом на замещение является любая страница основной памяти, соответствующая странице какого-либо виртуального пространства, и локальная политика, при следовании которой кандидатом на замещение может являться только некоторая страница основной памяти, соответствующая странице того же самого виртуального пространства, для которого потребовалось замещение. (Прошу прощения за длинное предложение.)

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

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

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

(i) если содержимое буфера не менялось, перейти к выполнению операции (xiii);

(ii) если содержимое буфера менялось, выполнить явное обращение к файловой системе для записи содержимого во внешнюю память;

(iii) ядро ОС выделяет буфер в общесистемном пуле;

(iv) если содержимое этого буфера не менялось, перейти к операции (vi);

(v) если содержимое буфера менялось, то выполнить его выталкивание (например запись во внешнюю память);

(vi) если страница буфера виртуальной памяти присутствует в основной памяти, перейти к операции (xii);

(vii) если страница буфера виртуальной памяти отсутствует в основной памяти, то ядро выделяет страницу, предназначенную для поддержки виртуальной памяти;

(viii) если выделенная страница входит в список свободных страниц, перейти к операции (xii);

(ix) если выделенная страница не изменялась после последнего вталкивания, перейти к операции (xi);

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

(xi) объявить виртуальную страницу, соответствующую выделенной странице основной памяти, отсутствующей в основной памяти;

(xii) фу, наконец-то мы можем вытолкнуть изменявшийся буфер базы данных и производим выталкивание в область внешней памяти, предназначенную для хранения базы данных;

(xiii) если копия требуемого блока внешней памяти базы данных содержится в буфере общесистемного пула, перейти к операции (xviii);

(xiv) если копия требуемого блока внешней памяти отсутствует в буфере общесистемного пула, выделить буфер общесистемного пула;

(xv) если содержимое этого буфера не менялось, перейти к операции (xvii);

(xvi) если содержимое буфера менялось, то выполнить его выталкивание (например запись во внешнюю память);

(xvii) произвести вталкивание в буфер данных из области внешней памяти базы данных;

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

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

Во-первых, явно наблюдаются две лишние операции массовой переписи в основной памяти - из буфера виртуальной памяти сервера в буфер общесистемного пула и из буфера общесистемного пула в буфер виртуальной памяти сервера. Этот дефект классической реализации ОС Unix давно известен и, в частности, был одним из оснований разработки схемы сначала микроядра, а впоследствии и полной операционной системы Mach, где управление виртуальной памятью и общесистемной буферизацией было совмещено. Начиная с System V Release 4.0 подобный подход применяется и в ОС Unix, хотя пока и не в полном объеме. Так что можно сказать, что первый дефект устранен.

Следующий дефект расположения буферов сервера в виртуальном пространстве пользовательского процесса ОС Unix состоял в том, что сервер обязательно должен был однопроцессным, поскольку в классическом варианте ОС Unix виртуальная память и приписанная к ней основная память не могли разделяться между несколькими процессами. Конечно, это существенное ограничение, если учитывать стремление к обеспечению реально многопользовательского доступа к базе данных, что требует хотя бы квази-распараллеливания работы сервера. O"key, и это ограничение было осознано, и в Unix System V Release 3.2 появились механизм разделяемой памяти и соответствующие средства синхронизации доступа. Если не вдаваться в детали, можно сказать, что механизм разделяемой памяти представляет возможность иметь в нескольких связанных отношением подчиненности или полностью независимых процессах общие сегменты виртуальной памяти, т.е. непрерывные отрезки виртуальной памяти (возможно, с разными адресами в разных процессах), такие, что ядро ОС обеспечивает для них единую приписку к основной памяти.

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

Теперь основная проблема состоит в том, что у сервера БД и операционной системы, вообще говоря, разные политики замещения. ОС, в принципе, не имеет информации о будущем поведении процесса, для которого поддерживается виртуальная память. В лучшем случае производится "предсказание" будущего на основе ближайшего прошлого (все это базируется на "принципе Деннинга", который утверждает, что при выборе достаточно малого интервала времени t поведение программы по отношению к памяти во время [T-t] с большой вероятностью совпадает с ее поведением во время [T+t]). С другой стороны, СУБД обладает гораздо большей информацией. Например, буферы, содержащие копию корневых страниц индексов, построенных на основе B-деревьев, целесообразно по возможности удерживать в основной памяти, поскольку любой поиск по такому индексу начинается с корневой страницы. Если сервер производит последовательный просмотр таблицы, то разумно удерживать в основной памяти страницу, в которой находится очередная строка, а при приближении к концу этого буфера разумно инициировать чтение с внешней памяти следующей страницы, содержащей строки той же таблицы. Другими словами, СУБД знает больше, чем операционная система.

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

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

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

Ошибки в программе, фиксирующей память, могут привести к краху операционной системы. Конечно, СУБД - это системная программа, которая не должна содержать серьезных ошибок. Но серверы баз данных работают в пользовательском режиме. С другой стороны, если появляется возможность фиксировать память, то наверняка находятся программы, отличные от СУБД, для которых это тоже полезно. Чем больше таких программ, тем слабее защита и тем вероятнее крах ОС. Фактически, возможность фиксации памяти - это лазейка для пользовательских программ в область деятельности, за которую должны отвечать ядро ОС и/или администратор системы, или суперпользователь в терминах Unix. Кстати, то же самое можно сказать и по поводу обсуждавшейся в предыдущем разделе возможности прямой работы (минуя файловую систему) с внешней памятью. В принципе напрямую работать с дисками можно было в любой версии ОС Unix. Но эта возможность всегда была доступна только в режиме суперпользователя. Как только она становится доступной для сервера баз данных, тут же возрастает риск разрушения файловой системы по причине ошибки в пользовательской программе. В результате резко возрастает ответственность администратора системы, которому и так очень нелегко жить.

3. Параллельные серверы баз данных

Поддерживаемые в современных операционных системах (в частности в ОС Unix) понятия нити (thread), потока управления или легковесного процесса на самом деле появились и получили реализацию около 30 лет тому назад. Наиболее известной операционной системой, ориентированной на поддержку множественных процессов, которые работают в общем адресном пространстве и с общими прочими ресурсами, была легендарная ОС Multics. Эта операционная система заслуживает длительного отдельного обсуждения, но, естественно, не в данной статье. Мы рассмотрим (в общих чертах) особенности легковесных процессов в современных вариантах операционной системы Unix и то, как легковесные процессы разного рода используются в серверах баз данных. По всей видимости, все или почти все содержимое этого раздела можно легко отнести к любой операционной системе, поддерживающей легковесные процессы.

Несмотря на различия в терминологии, в разных реализациях легковесных процессов выделяются три класса. Базовым классом являются ядерные нити. В мире Unix это не новость. Когда в пользовательском процессе происходит системный вызов или прерывание, выполняется ядерная составляющая пользовательского процесса в своем собственном контексте, включающем набор ядерных стеков и регистровое окружение. Естественно, все ядерные составляющие пользовательских процессов работают в общем адресном пространстве с общим набором ресурсов ядра. Поэтому их вполне можно назвать ядерными легковесными процессами. Наличие ядерных нитей, в частности, облегчает обработку прерываний в режиме ядра. Как и в случае прерывания обычного пользовательского процесса, обработка прерывания ядерной нити производится в ее контексте, и после возврата из прерывания продолжается выполнение прерванной ядерной нити. Кроме того, каждая ядерная нить, вообще говоря, обладает собственным приоритетом по отношению к праву выполняться на процессоре (конечно, этот приоритет связан с приоритетом соответствующего пользовательского процесса). Это позволяет использовать гибкую политику планирования процессорных ресурсов для ядерных составляющих. Итак, ядерные нити должны существовать независимо от того, поддерживаются ли легковесные процессы в режиме пользователя. Наверное, трудно найти сегодня какую-либо многопользовательскую операционную систему, в ядре которой в каком-то виде не поддерживались бы нити.

Видимо, следующим по важности классом легковесных процессов являются пользовательские LWP (LightWeight Processes). Механизмы этого рода позволяют организовать несколько потоков управления в одном адресном пространстве. Все LWP одного пользовательского процесса совместно используют все ресурсы процесса. При поступлении сигнала процессу на него реагируют все LWP в соответствии со своими собственными установками. С другой стороны, каждый LWP обладает своим собственным контекстом, включающим, как и в случае ядерных нитей, стек и регистровое окружение (в частности содержимое индивидуального счетчика команд). Любому LWP пользовательского процесса соответствует отдельная ядерная нить. Это означает, что каждый LWP может отдельно планироваться (и поэтому LWP одного пользовательского процесса могут параллельно выполняться на разных процессорах симметричного мультипроцессорного компьютера), и системные вызовы и прерывания LWP могут обрабатываться независимо. Основным преимуществом использования LWP является возможность достижения реального распараллеливания программы при ее выполнении на симметричном мультипроцессоре (на недостатках мы остановимся ниже).

Наконец, к третьему классу легковесных процессов относятся пользовательские нити. Они называются пользовательскими, поскольку реализуются не ядром ОС, а с помощью специальной библиотеки функций (поэтому, например, в ОС Mach их называют C-Threads). Это тоже очень старая идея, к которой неоднократно прибегали все опытные программисты (здесь уже даже не важно, в среде какой операционной системы выполняется программа). Суть идеи состоит в том, что вся программа пользователя строится в виде набора сопрограмм (coroutine), которые выполняются под управлением общего монитора. Естественно, что в мониторе поддерживаются контексты всех сопрограмм, но и монитор, и сопрограммы являются составляющими одного пользовательского процесса, которому соответствует одна ядерная нить. Конечно, применением пользовательских нитей невозможно достичь реального распараллеливания программы. Единственный реальный эффект, которого можно добиться, состоит в возможности распараллеливания обменов при использовании асинхронного режима системных вызовов. Как считают некоторые специалисты (к числу которых не относится автор этой статьи), основное достоинство применения пользовательских нитей состоит в лучшей структуризации программы.

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

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

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

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

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

Заключение

Мне хотелось бы сделать некоторые выводы. Как видно из этой статьи, если бы нужно было разработать сервер баз данных, работающий в одиночку на выделенном компьютере, то лучшим решением было бы создавать его в расчете на "голую" машину (без операционной системы). Фактически, это означало бы создание некоторого гибрида ОС и СУБД, в котором отсутствовало бы все лишнее, а нужные средства были бы реализованы оптимально. Но обычным требованием к серверам баз данных является то, что должна существовать возможность их запуска в смеси с другими пользовательскими программами, которые, естественно, не могут выполняться без поддержки операционной системы. Именно из-за этого в ОС появляются лазейки, позволяющие некоторым пользовательским программам (в том смысле, что они работают в режиме пользователя) обойти мешающие им ограничения операционной системы. Пожалуй, это и все, что дают современные операционные системы разработчикам серверов баз данных.


Кузнецов Сергей Дмитриевич, тел.: 932-92-12