Эван Джонс, Дэниэль Абади и Сэмуэль Мэдден
Перевод: Сергей Кузнецов
Рис. 1. Архитектура системы
Транзакции оформляются в виде хранимых процедур, состоящих из детерминированного кода, который перемежается операциями над базой данных. Клиент инициирует транзакцию путем посылки в систему сообщения с требованием вызова некоторой хранимой процедуры. Система распределяет работу между разделами. В нашем прототипе отображение работы на разделы делается вручную, но мы работаем над планировщиком запросов, который будет делать это автоматически.
Каждая транзакция разбивается на фрагменты. Фрагмент – это часть работы, которую выполнить в точно одном разделе. В нем может выполняться некоторая смесь пользовательского кода и операций над базой данных. Например, однораздельная транзакция состоит из одного фрагмента, содержащего всю транзакцию. Многораздельная транзакция состоит из нескольких фрагментов, между которыми имеются зависимости по данным.
Для выполнения одноузловых транзакций не требуется никакого управления параллелизмом. В большинстве случаев система выполняет такие транзакции без сохранения информации, требуемой для откатов, что приводит к очень низким накладным расходам. Это возможно благодаря тому, что транзакции сопровождаются аннотациями, в которых указывается, может ли произойти аварийное завершение транзакции по инициативе пользователя. Для транзакций, для которых отсутствует возможность аварийного завершения по инициативе пользователя, при использовании схем управления параллелизмом, гарантирующих отсутствие сихронизационных тупиков (см. ниже), журнал отката не поддерживается. В других случаях система поддерживает в основной памяти буфер отката, который освобождается при фиксации транзакции.
Чтобы обеспечить сериализуемый порядок выполнения многораздельных транзакций без возможности возникновения синхронизационных тупиков, они направляются в систему через центральный координатор, который определяет им глобальный порядок. Достоинством этого подхода является его простота, но понятно, что наличие центрального координатора ограничивает число одновременно выполняемых многораздельных транзакций. Чтобы обеспечить возможность одновременного выполнения большего числа транзакций, необходимо использовать несколько координаторов. В прошлые годы изучались методы глобального упорядочения транзакций с применением нескольких координаторов, например, на основе использования слабо синхронизированных часов [2]. Мы оставляем выбор лучшего варианта на будущие исследования, и в этой работе оцениваем систему с одним координатором.
Центральный координатор разбивает транзакцию на фрагменты и посылает их в разделы. После получения ответов координатор выполняет код приложения, чтобы определить, как следует продолжать выполнение транзакции, для чего может потребоваться посылка дополнительных фрагментов. В каждом разделе фрагменты данной транзакции выполняются последовательно.
Многораздельные транзакции выполняются с использованием буфера отката, а для принятия решения об успешности завершения транзакций применяется двухфазный протокол фиксации (two-phase commit, 2PC). Это позволяет системе сохранять работоспособность при выходе из строя отдельных разделов. Если при выполнении транзакции один из разделов выходит из строя, или если сеть теряет связность, то другие участники транзакции могут откатиться и продолжить выполнение транзакций, не зависящих от отказавшего раздела. При отсутствии информации, требуемой для отката, системе пришлось бы заблокироваться до восстановления работоспособности отказавшего раздела.
Координатор присоединяет сообщение "подготовиться" ("prepare") протокола 2PC к последнему фрагменту транзакции. Когда процесс основного раздела получает заключительный фрагмент, он отсылает все фрагменты транзакции процессам резервных разделов и ожидает их подтверждения до отправки окончательных результатов координатору. Это эквивалентно принуждению участника 2PC к выталкиванию на диск своего решения о фиксации транзакции. Наконец, когда у координатора имеются все решения участников, он завершает транзакию, посылая сообщение "фиксация" ("commit") процессам разделов и возвращая окончательный результат приложению.
При выполнении многораздельных транзакций при ожидании данных от процессов других разделов в процессе основного раздела могут возникнуть сетевые задержки. Этот простой может стать фактором, ограничивающим производительность, даже если многораздельные транзакции составляют лишь малую долю рабочей нагрузки. В нашей экспериментальной системе, описываемой в разд. 5, минимальное время на передачу и подтверждение приема (измеряемое с использованием ping) между двумя машинами в сети, подключенными к одному и тому же коммутатору гигабитного Ethernet, составляет примерно 40 миллисекунд. Среднее процессорное время выполнения транзакции TPC-C в нашей системе – 26 миллисекунд. Таким образом, за время ожидания сетевого подтверждения процесс раздела смог бы выполнить почти две однораздельных транзакции. Что обеспечить системе возможность выполнения полезной работы в то время, которое иначе было бы временем простоя, требуется некоторая разновидность управления параллелизмом. Проблема состоит в том, чтобы это не привело к снижению эффективности простых однораздельных транзакций. В следующем разделе описываются две схемы управления параллелизмом, которые мы разработали для преодоления этой проблемы.