Привет! Меня зовут Полина Кудрявцева, я инженер DBA в Авито. Мы с командой развиваем и поддерживаем системы управления базами данных (СУБД) PostgreSQL и CockroachDB на платформе DBaaS. Если с PostgreSQL всё более-менее понятно, то к смузи-технологии CockroachDB может возникнуть немало вопросов. Сегодня расскажу, как этот «таракан» заполз и обжился в Авито.
Текст основан на моём выступлении НЕмитап Database#1 — Cockroach DB на платформе DBaaS.
Мир баз данных в Авито до CockroachDB
Долгое время в Авито жили только классические системы управления базами данных (СУБД). Среди них:
- документоориентированная СУБД MongoDB для хранения километровых JSON;
- key-value СУБД Redis — первая помощь для работы с кэшами;
- колоночная аналитическая СУБД ClickHouse для OLAP-нагрузки;
- поисковый движок Elasticsearch;
- объектное хранилище Ceph, используемое, в частности, для бэкапов;
- брокеры сообщений Kafka и RedPanda, незаменимые для реализации очередей;
- широко известный PostgreSQL.
Некоторые из этих СУБД поддерживаются на платформе в полном объёме, другие живут почти на ручном приводе и автоматизированы минимально. Все они поддерживаются разными командами и у каждой технологии есть свои нюансы сетапа, поддержки, жизненного цикла и условий дебага. Эти СУБД решают разные задачи и далеки друг от друга, как звёзды в космосе. Однако они долго существовали совместно и делали жизнь продуктовых разработчиков лучше и веселее. Чего же не хватало?
Как мы пришли к CockroachDB
Для того, чтобы сервис мог выдерживать 99.99% надежности, необходимо, чтобы СУБД могла переживать пиковые нагрузки до x10 и быстро масштабироваться на новые дата-центры.
Соответственно, нужны специальные инструменты — в тот момент на платформе их не было. Критерии выбора были такими: наличие возможности автоматического масштабирования, отказоустойчивость, репликация между регионами
Что еще важно для этого инструмента:
- поддержка согласованности и целостности данных, обеспечение параллельного доступа, атомарности, возможность восстановления в случае ошибок и свойства ACID;
- PostgreSQL-совместимый протокол, поскольку у многих разработчиков в Авито уже есть опыт использования PostgreSQL для написания сервисов;
- наличие Go-клиента, ведь основной язык написания сервисов в Авито — это Go.
Все эти критерии сошлись на CockroachDB — это распределённая СУБД с открытым исходным кодом, написанная на Go и поддерживающая SQL. Ниже — её главные преимущества.
Уровень изоляции Serializable. Он гарантирует, что результат будет одинаковым, как бы ни выполнялись транзакции — параллельно или последовательно. Это обеспечивает корректность данных и предотвращает все «аномалии», допускаемые более слабыми уровнями изоляции.
Подходит для сервисов с высокими требованиями к отказоустойчивости. CockroachDB продолжает работать при сбоях нод или дата-центров благодаря особенностям репликации данных. Обновление базы данных также происходит без простоя.
Горизонтальное масштабирование «из коробки». Достаточно добавить новые ноды в кластер и система сама перераспределит не только нагрузку, но и данные по новой топологии. Сторонние решения не нужны. Кроме того, CockroachDB легко интегрируется с оркестраторами контейнеров и шедулерами, например Kubernetes (k8s).
Поддержка MVCC. CockroachDB предоставляет возможность параллельного доступа к данным, не нарушая их согласованности, за счет хранения нескольких версий строк. Версии строк, не используемые ни одной транзакцией, впоследствии удаляются сборщиком мусора. При этом занимаемое ими место на диске освобождается физически.
Как распределяются данные в CockroachDB
При выборе СУБД для конкретной задачи важно понимать, как распределяются данные. Допустим, есть монолитная таблица с первичным ключом и одной колонкой. На низком уровне CockroachDB делит таблицу на примерно равные части — их называют ренджами (ranges).
Как CockroachDB делит данные на ренджи:
Каждый range копируется столько раз, сколько указано в факторе репликации в настройках СУБД. Фактор репликации не может быть ниже трёх, то есть каждый рендж представлен как минимум в трёх экземплярах (один мастер и две реплики).
Каждый рендж копируется столько раз, сколько указано в настройках:
В зависимости от сетапа копии ренджа распределяются равномерно по нодам кластера в соответствии с настройками локальности.
У каждого range есть мастер или лидер, который обрабатывает запросы на чтение и запись. Нода, содержащая лидера конкретного ренджа, называется leaseholder-нодой этого ренджа.
Leaseholder-нодой голубого ренджа будет первая нода, так как там находится лидер, розового — вторая нода, фиолетового — третья, а жёлтого — вторая:
Все изменения данных в конкретном рендже распространяются от leaseholder-ноды на ее реплики. Чтение и запись по умолчанию выполняются только на leaseholder-ноде.
Чтобы выполнять запросы, нужно хранить знание о том, где находятся лидеры ренджей. Распределение ренджей по нодам хранится в глобальных пространствах ключей meta1 и meta2.
Ключ meta1 можно рассматривать как верхнеуровневый поисковик по ренджам (range of ranges — рендж ренджей), а meta2 содержит указатели, на какой ноде находится искомый ключ. В рендже meta1 есть указатель на нужный meta2, а meta2 хранит указатель на leaseholder-ноду ренджа, содержащего искомый ключ. Таким образом, meta1 и meta2 можно рассматривать как двухуровневый индекс, используемый для определения местоположения мастера искомого range.
Например, пользователь запросил данные для ключа key3. В худшем случае сначала система обратится к meta1, чтобы узнать, где находится нужный meta2, хранящий местоположение мастера range. В дальнейшем система будет использовать этот указатель, чтобы узнать, на какой ноде находится leaseholder ключа key3. Наконец, система обратится к leaseholder-ноде, чтобы получить данные для искомого range.
Двухуровневый индекс для хранения данных в CockroachDB:
Как выполняется запрос в CockroachDB
Когда клиент базы данных — разработчик или сервис — отправляет запрос в СУБД, он может выполниться на любой ноде кластера. В примере ниже запрос отправился на вторую ноду. Сначала читается рендж meta1, где указано, что meta2 для нужного ключа находится на первой ноде. Запрос перенаправляется на первую ноду, и следом читается meta2. Из meta2 известно, что холдер ренджа (leaseholder) с нужным ключом находится на третьей ноде. Запрос перенаправляется туда. Там же можно работать с данными.
На изображении выше база данных состоит из трёх узлов: Node 1, Node 2, Node 3. Каждая из них содержит данные (ключи и значения). Метаданные разделены на две части (meta1 и meta2), они содержат информацию о распределении ключей по узлам. Но все точки, через которые должен пройти запрос, находятся на разных нодах. В реальности CockroachDB обрабатывает запросы быстрее, минимизируя задержки и избегая ненужных сетевых операций
CockroachDB использует в качестве механизма распределенного консенсуса протокол Raft. В CockroachDB каждый рендж представляет собой отдельную группу Raft — консенсус для каждого range определяется независимо от других ренджей. Когда клиент отправляет запрос на любую ноду кластера CockroachDB, он перенаправляется на leaseholder ренджа, затрагиваемого этим запросом, а оттуда — на лидера Raft. Лидер Raft фиксирует изменения в Raft-log, который затем распространяется на все реплики ренжа. Когда большинство реплик подтвердит изменения, Raft-leader отправит результат leaseholder, который вернет его ноде, выполняющей запрос. После этого результат отдается клиенту.
Leaseholder и Raft-лидер имеют схожие функции:
Leaseholder отвечает за контроль доступа к ренджу, чтобы обеспечить транзакционную целостность и изоляцию. Raft-лидер контролирует репликацию и сохранность данных, записывая изменения в Raft-log и распространяя их на соответствующие реплики ренджа. Такой алгоритм требует, чтобы большинство узлов подтвердили каждую запись данных. К сожалению, это часто приводит к высокой latency на запись.
Как CockroachDB минимизирует latency
CockroachDB из соображений оптимизации старается сделать так, чтобы leaseholder ренджа совпадал с Raft-лидером этого range — так получается уменьшить количество походов по сети между нодами и минимизировать latency. Вообще leaseholder часто совпадает с лидером Raft именно потому, что CockroachDB из соображений оптимизации старается их держать на одной ноде для конкретного range.
Выполнение запроса в CockroachDB начинается с проверки кэша, а не с поиска нужного leaseholder в системных ренжах. CockroachDB кэширует все возможные данные, чтобы свести походы по сети к минимуму:
- Сначала CockroachDB кэширует последние прочитанные данные из meta2. Это позволяет системе сразу знать, где находится leaseholder конкретного ключа, и направить запрос напрямую к нему, минуя промежуточные шаги.
- Если ключ не закэширован, CockroachDB пытается обратиться к кэшу местоположения meta2. Система пропускает meta1 и сразу обращается к meta2, чтобы найти необходимые данные и определить местоположение leaseholder.
- Если местоположение meta2 не закэшировано, любая нода CockroachDB всегда знает, где находится leaseholder ренджа meta1. Эта информация хранится в рендже meta0. Он доступен всем нодам и всегда содержит актуальные данные о местоположении meta1.
Кэширование помогает CockroachDB избегать лишних походов по сети, поэтому задержки при выполнении запросов — исключение, а не правило. Система не проходит через все точки на разных нодах, а сразу направляет запрос к нужному leaseholder, используя закэшированные данные.
Как выглядит CockroachDB на платформе DBaaS в Авито
Топология кластера
По умолчанию кластер CockroachDB на платформе DBaaS состоит из трёх нод — по одной в каждом дата-центре (DC). Такой кластер переживает недоступность одной ноды и одного дата-центра.
Три ноды с равномерно распределёнными ренджами:
Но какой смысл заносить на платформу распределённую СУБД без возможности масштабироваться? Посмотрим, что будет, если увеличить количество нод до шести, сохраняя настройки по умолчанию:
Кластер из шести нод с настройками по умолчанию также безболезненно переживает недоступность только одной ноды. При выходе из строя двух нод кластера есть риск, что павшие ноды будут содержать большинство реплик какого-нибудь ренджа. Например, выход из строя node1 и node2 приведёт к недоступности розового ренджа, падение node4 и node5 — к недоступности голубого ренджа и так далее.
Здесь видно, что из-за падения датацентра становятся недоступными данные фиолетового ренджа, поскольку две из трех реплик этого ренджа выходят из строя:
Такая конфигурация уже не позволяет пережить падение целого дата-центра. Каждый DC содержит большинство реплик хотя бы одного ренджа: DC1 содержит большинство реплик фиолетового ренджа, DC2 — большинство реплик голубого ренджа, DC3 — большинство реплик жёлтого ренджа, поэтому при выходе DC из строя появятся недоступные ренджи.
Так жить — не спортивно. Один из вариантов, как исправить это досадное поведение — увеличить фактор репликации, например, до пяти:
Кластер из шести нод с фактором репликации пять позволяет пережить недоступность любых двух нод и целого DC. Однако увеличение фактора репликации повышает надёжность хранения ценой увеличения общего объёма данных и пишущей нагрузки на ноды.
При запуске кластера можно указать локальность — опцию, которая определяет, как CockroachDB должен оптимизировать распределение данных по нодам
Можно указать --locality region=msk, zone=<datacenter>, и тогда CockroachDB разложит данные по нодам так, чтобы в каждом из дата-центров не было дисбаланса по репликам ренджа. В таком случае выход из строя целого дата-центра не приведёт к недоступности кластера, но недоступность двух нод в разных дата-центрах всё ещё будет означать потерю консистентности части данных.
CockroachDB с такой настройкой распределит ренжи равномерно по дата-центрам:
Именно на таком варианте мы остановились: и при конфигурации из трёх нод, и при конфигурации из шести кластер CockroachDB на платформе DBaaS переживает падение дата-центра.
Создание БД
Рождение базы данных на платформе DBaaS в Авито выглядит следующим образом: в каждом дата-центре находятся автономные и независимые k8s-кластеры, в которых работает контроллер, регулярно проверяющий DBaaS на предмет появления метаданных нового хранилищ.
В каждом DC находится k8s-кластер с запущенным контроллером:
Если метаданные нового хранилища появились, то контроллер создаёт нужные манифесты, и CockroachDB применяет их в соответствующем kubernetes-кластере.
Так на платформе появляется кластер CockroachDB, ноды которого распределены по дата-центрам:
Базу нужно не только создать, но и обслуживать. Чтобы делать это без помощи ручного труда разработчиков, в Авито используют агент — компонент, разработанный для обслуживания и поддержки кластера CockroachDB. Он запускается как сайдкар-контейнер рядом с каждой нодой базы.
Агент CockroachDB имеет широкую функциональность:
У агента есть несколько функций:
- создание ролевой модели и управление ей — создание сервисных и персональных учетных записей с определенными правами в базе данных в соответствии с их уровнями доступа;
- создание базы данных с платформенной конфигурацией;
- регулярные запуски бэкапов. Агент отвечает за регулярное автоматическое создание резервных копий базы данных;
- проверка готовности нод. Агент регулярно отправляет сигналы heartbeat для мониторинга состояния каждой ноды CockroachDB.
Ролевая модель
Ролевая модель на платформе DBaaS — это роли и группы, которые определяют права доступа пользователей в базе данных.
Роль — это пользователь с атрибутом Login, который может зайти в базу данных. Роли предоставляются конечным пользователям: администраторам, разработчикам или сервисам.
Группа — пользователь с атрибутом NOLOGIN, с помощью которого можно настроить доступы для роли к объектам через наследование прав в CockroachDB.
Вот какие группы существуют на платформе DBaaS:
Full Access (FA). Пользователь может выполнять операции DML (Data Manipulation Language) и DDL (Data Definition Language) — создавать объекты и изменять данные.
Read Write (RW). Пользователь может выполнять только DML-операции и использовать последовательности.
Read Only (RO). Пользователь может только читать данные таблиц и текущие значения последовательностей.
Для сервисных учётных записей есть два вида пользователя: Deploy-пользователь и Production-пользователь.
Deploy-пользователь:
- принадлежит группе FA (Full Access);
- выполняет DML- и DDL-операции;
- используется для создания схемы данных в базе CockroachDB.
Production-пользователь:
- принадлежит группе RW (Read Write);
- выполняет только DML-операции;
- используется для штатной работы сервиса.
Каждому пользователю выдаётся та группа, которую он запросил и которую одобрили.
Группа FA включает двух пользователей: deploy_01 и deploy_02, они нужны для ротации. Если сервис использует учётную запись deploy_01, а там нужно сменить пароль, то сервис переключается на deploy_02, пока пароль для deploy_01 обновляется. Группа RW также включает двух пользователей для ротации, как и группа FA.
Как мы планируем дальше развивать CockroachDB в Авито
Команда DBA Авито уже разрабатывает и поддерживает библиотеку crdb для работы с базой данных со стороны сервиса, механизм миграций и сэмплирование. Регулярно выполняются бэкапы, настроены алерты и метрики для круглосуточной бесперебойной работы кластера.
Ключевые метрики, которые мы отслеживаем:
- Unavailable ranges, или недоступные диапазоны. Такое может быть, если больше половины реплик ренджей недоступны. Следует немедленно вмешаться, чтобы предотвратить потерю данных;
- Under-replicated ranges, или недостаточно реплицированные диапазоны. Это означает, что количество реплик какого-либо ренджа не соответствует фактору репликации, то есть CockroachDB не может дореплицировать данные;
- Node Availability, или доступность ноды. Нода может выйти из строя по разным причинам, включая аппаратные сбои, проблемы с сетью или расхождение времени;
- синхронизация времени. Расхождение времени между нодами может привести к сбоям в распределенной системе. Есть вероятность, что время на кубовых нодах не будет совпадать, и в том случае, если время рассинхронизации превышает пороговое значение, указанное в конфигурации, CRDB не способен определить, какая транзакция была раньше, а какая позже. Поэтому кластер становится недоступным. Так происходит, когда системные часы не согласованы, есть сетевые задержки и аппаратные проблемы.
В дальнейших планах развития CockroachDB на платформе DBaaS — разработка механизма для отправки потока данных в DWH (Data Warehouse). Этот инструмент необходим, чтобы анализировать данные из разных источников, хранить их историчность и предоставлять данные в удобном для бизнеса виде.
Для разработки этого инструмента будем использовать встроенную функциональность changefeeds CockroachDB, которая позволяет отслеживать DML-события. DDL-операции будем определять с помощью постоянного сравнения схемы данных с последним зафиксированным состоянием. Все полученные события будут конвертироваться в формат CDC (Change Data Capture) и отправляться в Kafka.
Важно помнить, что CockroachDB не заменяет другие системы управления базами данных. Каждая технология предназначена для решения конкретных задач – поэтому перед проектированием сервиса нужно определить, какая система лучше подходит для ваших требований
Спасибо вам за уделенное статье время!