Skip to content
Troy Köhler

Как сделана CockroachDB?

программирование4 min read

Недавно наткнулся на упоминание CockroachDB и не понял юмора, связанного с неймингом. При чем тут тараканы? Неужели у тех, кто называл это произведение искусства, отсутствуют болезненные триггеры родом из студенческих общежитий? Я лично до сих пор помню, как проснулся от того, что один из этих рыжих усатых ребят полз по моему лицу.

🐜 🐝 🐜 🐝

Основные термины, которые помогут нам понять как устроена CockroachDB

Кластер

С этим понятием вы, скорее всего, знакомы, если вас интересуют распределенные системы. В данном случае кластер — это ваша CockroachDB. Кластер состоит из нод — машин-участниц.

Рейндж

CockroachDB сохраняет все данные (пользовательские и системные) в огромной отсортированной мапе (с парами ключ-значение). Ключи поделены на рейнджи так, что каждый ключ может быть найден в каком-то одном конкретном рейндже.

Как только рейндж достигает 512 MiB, он делится на два рейнджа.

Реплика

CockroachDB копирует каждый рейндж (3 раза, по умолчанию) и сохраняет каждую копию на отдельной ноде.

Арендодатель

Для каждой рейнджа есть реплика, которая выступает арендодателем рейнджа. Эта реплика координирует все реквесты на чтение и модификацию этого рейнджа.

Клиент: Можно мне пожалуйста x?
Арендодатель: гуляй лесом, сударь, у нас перерыв на обед.

Лидер

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

Лог

Рафт лог. Он сохраняется на диске вместе с каждой репликой и является single source of truth для рейнджа. Лог является ключевым компонентом Рафта и нужен для того, чтобы все машины в кластере имели одинаковый state (состояние).

Слои CockroachDB

SQL

Этот слой несет ответственность за SQL API для разработчиков. С помощью него вы можете пользоваться привычным синтаксисом. Задача этого слоя трансформировать входящие SQL statements в набор key-value пар, которые содержат информацию о необходимых операциях. Этими key-value парами пользуются все остальные слои базы данных.

После превращения SQL statements в набор key-value пар, база данных отправляет запросы в свой transactional слой.

Transactional

Этот слой ответственен за то, чтобы все транзакции были ACID (Atomicity, Consistency, Isolation, Durability) посредством координации всех операций.

Чтобы осуществлять транзакции в распределенной системе CockroachDB использует коммит протокол, который называется *Параллельные коммиты (Parallel commits)*.

Этой слой получает KV пары со слоя выше и контролирует процесс отправления этих KV пар в Distribution слой.

Distribution

Для того, чтобы сделать данные доступными в любой ноде кластера, CockroachDB сохраняет данные в огромной сортированной мапе с KV парами. Ключ описывает данные в кластере и их местонахождение. Все ключи поделены на рейнджи.

Для коммуникации с другими слоями используется протокол gRPC. Его имплементация хранится в этом слое. Distribution слой — это первый слой, который общается с другими нодами.

Replication

Replication слой ответственен за консистентность данных в кластере. Тут происходит репликация данных, и тут находится имплементация Рафта — алгоритма консенсуса, который использует эта база данных.

CockroachDB требуется 3 ноды для того, чтобы обеспечить high availability. Это связано с тем, что 3 — это минимальное количество нод, в которых возможно организовать форум.

Storage

Каждая нода в кластере CockroachDB содержит по крайней мере одно хранилище, где ведется учет, когда нода начала свою работу. Оттуда процесс cocroach читает данные в случае необходимости и так же в случае необходимости записывает их на диск. Данные хранятся в виде key-value пар. По дефолту для этих целей используется Pebble.

Как происходит транзакция?

Начнем с того, что наше приложение хочет добавить запись. Подключение к кластеру происходит с помощью одной единственной connection string и для этого используется стандартный драйвер постгреса.

На мой взгляд, это очень удобно! CockroachDB практически полностью воссоздает опыт взаимодействия с самой популярной реляционной базой данных.

Реквест на модификацию данных падает на ваш load balancer , который выбирает на какую ноду его перенаправить. Так как все ноды CockroachDB имеют симметрический доступ к данным, это означает, что load balancer может подключить клиент (ваше приложение, которое хочет сделать запись в базе данных) к любой ноде и дать доступ к любым данным, гарантируя при этом strong consistency.

Как только load balancer определил самую подходящую ноду для подключения, соединение успешно установлено и нода, к которой вы подключились, выступает в роли gateway (врат) для кластера.

Приключения gateway ноды

Первым делом при подключении вас и получении вашего запроса на запись, gateway нода проверит все ли в порядке с синтаксисом, который вы прислали. Если все норм, она генерирует то, что называется logical SQL plan.

Logical SQL plan — это план базы данных про то, как выполнять кверю, которые вы ей отправили 🙂 Он состоит из нескольких последовательных шагов. Сначала происходят промежуточные калькуляции и оптимизации, потом прикидывается алгоритм реализации того, что вы хотите сделать. Мы ведь помним, скорее всего, что SQL — это декларативный язык, и это значит, что вы просто говорите, что вам надо, а база данных сама пытается найти самые эффективные пути реализации ваших желаний. Это и есть составление logical SQL plan.

Так же этот план разбивает все операции на пары ключ-значение, и так же конвертирует SQL методы в методы, которыми она работает с парами ключ-значение.

Например, INSERT превращается в put()

Затем gateway нода отправляет это все ноде, которая указана как leaseholder рейнджа.

Leaseholder нода

Leaseholder отвечает на реквест gateway ноды одним из таких статусов.

  • No Longer Leaseholder — если нода больше не leaseholder для этого рейнджа, но все еще хранит реплику рейнджа, она отказывает в процессинге реквеста и присылает адрес последнего известного ей leaseholder этого рейнджа.-
  • No Longer Has/Never Had Range — в этом случае нода просто отказывается процессить реквест без каких-либо изменений. Тогда gateway нода пытается найти leaseholder рейнджа в метаданных кластера.
  • Success — все нашлись и нода leaseholder приступает к обработке запроса.

Leaseholder проверяет присланные данные но корректность, убеждается, что действительно может выполнить реквест, генерирует timestamp — их база данных использует для того, чтобы обеспечить serializability. Так вы всегда можете доверять тому, что данные, которые вы получите во время чтения, самые свежие.

Если нода распознала что операции содержат write intent т.е. вы хотите сделать запись каких-то данных, она пытается разрешить (resolve) его. Если же это просто чтение данных, leaseholder ищет и возвращает данные из необходимого рейнджа.

Write intent исполняется вовлекая ноду-лидер, согласно Raft протоколу.

Лидер

Leaseholder рейнджа направляет запрос к лидеру кластера с предложением внести модификации в данные. Сразу как команда получает одобрение от форума, она заносится в Raft лог лидера и пишется в storage.

Как только операция занесена в лог, она считается commited.

Обратный путь

Лог разносится по всем нодам, включая leaseholder . Как только она заканчивает с модификацией своего лога, она отправляет gateway ноде "добро". Все это время gateway нода смиренно ждала, пока все остальные ребята порешают свои вопросы и сообщат ей о том, как у них там дела.

После получения ответа, она возвращает ответ клиенту и готова служить человечеству дальше.

Graceful shutdown

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

Посмотрите какой милый ascii art! Это книжки.

Автора не нашел, но, думаю, hjw это его подпись.

       .--.                   .---.
   .---|__|           .-.     |~~~|
.--|===|--|_          |_|     |~~~|--.
|  |===|  |'\     .---!~|  .--|   |--|
|%%|   |  |.'\    |===| |--|%%|   |  |
|%%|   |  |\.'\   |   | |__|  |   |  |
|  |   |  | \  \  |===| |==|  |   |  |
|  |   |__|  \.'\ |   |_|__|  |~~~|__|
|  |===|--|   \.'\|===|~|--|%%|~~~|--|
^--^---'--^    `-'`---^-^--^--^---'--' hjw

Понравился пост? ⇒ Подписывайся на мою емэйл рассылку 🕊️

Нашли ошибку? ⇒ Откройте issue в репозитории этого блога ⚠️

Хотите дополнить? ⇒ Откройте pull request ⚙️

У меня есть email рассылка.

Не могу сказать, что она о чем-то конкретном, поэтому подписывайтесь на свой страх и риск!

Высылаю 1 письмо в месяц.