Добавить в корзинуПозвонить
Найти в Дзене
Цифровая Переплавка

Версионирование против координации в СУБД: почему «версионный» подход побеждает

Современные распределённые системы — особенно базы данных — требуют сочетания высокой доступности, масштабируемости и согласованности. В своей статье «Versioning versus Coordination» Марк Брукер (Marc Brooker) показывает, как версионирование (multi-versioning) позволяет избавляться от дорогостоящей координации при чтении данных и достигать большей производительности. Ниже я поделюсь своим взглядом на ключевые идеи, упомянутые в этом материале, а также добавлю немного технических подробностей. Чтобы достичь высокой готовности и пропускной способности, мы обычно делим данные на шардированные сегменты и храним по нескольку реплик (для надёжности). Однако при параллельных операциях чтения и записи мы рискуем столкнуться с «гонками» данных и несогласованными результатами. Например, две транзакции: Чтобы соблюсти логическую последовательность (например, сериализуемость), возникает классический вопрос: нужны ли блокировки и центральный диспетчер (lock manager), который скажет, что «T2» должна
Оглавление

Современные распределённые системы — особенно базы данных — требуют сочетания высокой доступности, масштабируемости и согласованности. В своей статье «Versioning versus Coordination» Марк Брукер (Marc Brooker) показывает, как версионирование (multi-versioning) позволяет избавляться от дорогостоящей координации при чтении данных и достигать большей производительности. Ниже я поделюсь своим взглядом на ключевые идеи, упомянутые в этом материале, а также добавлю немного технических подробностей.

🏗️ Задача: распределённая СУБД с высокой параллельностью

Чтобы достичь высокой готовности и пропускной способности, мы обычно делим данные на шардированные сегменты и храним по нескольку реплик (для надёжности). Однако при параллельных операциях чтения и записи мы рискуем столкнуться с «гонками» данных и несогласованными результатами.

Например, две транзакции:

  • 🍀 T1: читает строки id=1, id=2, id=3 и хочет видеть значения, существовавшие до любого апдейта.
  • 🚀 T2: параллельно обновляет несколько строк, повышая значение value на 2.

Чтобы соблюсти логическую последовательность (например, сериализуемость), возникает классический вопрос: нужны ли блокировки и центральный диспетчер (lock manager), который скажет, что «T2» должна ждать окончания «T1»?

⏳ Подход с координацией (locking) против версионирования

Раньше классическое решение — блокировать строки для считывания, чтобы писатели (writers) ждали читателей (readers) и наоборот. Но есть недостатки:

  • 🙅 Меньше параллелизма. Писатели блокируются при активных чтениях.
  • 🌍 Где хранить глобальную информацию о блокировке при нескольких репликах? Приходится выбирать «центральный узел» или синхронно блокировать на всех репликах.

Версионирование (Multi-Version Concurrency Control — MVCC) предлагает иной путь:

  • Новая транзакция (T2) не переписывает значения «на месте», а создаёт новую версию строк.
  • Транзакция (T1), начавшаяся раньше, продолжает видеть старую версию, пока не завершится.
  • Таким образом, T2 вообще не блокируется о «T1»; она просто пишет данные в новую «версию», видимую более поздним транзакциям.

Как следствие, не требуется глобальная координация лишь для чтения: читатели видят свою «снимок-версию» (snapshot), писатели не ждут читателей, и наоборот. Это даёт параллелизм, высокую пропускную способность и более стабильное время отклика.

📅 Физическое время и «момент выбора» версии

Чтобы транзакции знали, какую версию данных им читать, системам часто нужен «маркер времени». Самый простой подход — назначать специальный логический счётчик или использовать глобальные часы. Но глобальные счётчики могут стать «бутылочным горлышком» в распределённой среде.

  • ⚙️ В Aurora DSQL (пример Марка) используют физические часы (в облачной среде типа AWS есть сервисы синхронизации с точностью до микросекунд). Это позволяет каждой транзакции брать «timestamp» без дополнительной координации.
  • 🏷️ Чтение «as-of timestamp»: транзакция запрашивает строки, у которых version <= my_start_time. И сервер знает, какие версии нужны.

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

🤔 Задача хранения версий: что с «мусором»?

Основная сложность MVCC в том, что система копит версии. Нужно как минимум хранить последнюю версию (для устойчивости и просмотра «последних» данных), а также версии, которые всё ещё могут быть нужны активным транзакциям:

  • 💡 Если транзакция (T1) всё ещё не завершена, нужно хранить её «старое состояние» в базе. Иначе потеряем согласованность при чтении.
  • 🧹 Когда транзакция завершается, соответствующие старые версии становятся неактуальными и могут быть удалены (через механизм сборки мусора «garbage collection»).

В некоторых системах делают глобальную координацию: «какой самый ранний несвободный timestamp у транзакций?». Но, как отмечает Марк, в Aurora DSQL этого избегают, жёстко ограничивая время жизни транзакции (например, 5 минут). За счёт этого не нужно вести постоянный учёт «всех таймстемпов», а лишь ждать, пока не истечёт лимит времени для «старых» транзакций.

🏆 Преимущества версионирования

MVCC позволяет «распараллелить» чтения и записи практически без конфликтов:

  • 🌐 Масштабируемость: читатели могут читать с копий, не блокируясь о других транзакциях.
  • 🚀 Высокая пропускная способность: «писатели» не ждут окончания «читающих».
  • ⏱️ Устойчивое время отклика: минимизация пауз из-за блокировок.

По сути, мы «зашиваем» информацию о согласованном состоянии в версии данных, а не выводим её через дорогостоящую распределённую блокировку.

🏁 Выводы и ссылки

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

Ссылки:

Таким образом, выбор в пользу версионирования позволяет эффективно обходиться без централизации и глобальной сериализации при чтении. Это особенно актуально для облачных систем, где синхронизация часов стала проще, а проекты наподобие Aurora наглядно демонстрируют, как практический «timestamp-based» подход решает ключевые проблемы распределённой согласованности.