Внутреннее устройство YDB: акторы и таблетки

Когда мы начинали разрабатывать собственную СУБД, перед нами стояли чёткие задачи, продиктованные требованиями Яндекса. И тогда, и сейчас в компании параллельно запускаются десятки внутренних стартапов — и большинство из них быстро вырастает с тысяч пользователей до миллионов.

Одно из основных требований к YDB — бесконечная линейная горизонтальная масштабируемость. Горизонтальная — значит расширение вычислительных ресурсов и объёма хранения достигается за счёт добавления серверов, а не наращивания мощности одного. Линейная — значит прирост производительности пропорционален числу добавленных машин. А «бесконечная» — значит, масштабируемость ограничена только бюджетом и количеством доступного на рынке оборудования. Сегодня крупнейшие инсталляции YDB в Яндексе обрабатывают миллионы запросов в секунду и работают с петабайтами данных.

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

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

Во-вторых, большая часть кода нашей базы выполняется в парадигме акторной модели. Каждый процесс YDB, запущенный на сервере, постоянно создаёт, выполняет и завершает сотни тысяч акторов: программных объектов, имеющих приватное состояние и общающихся друг с другом исключительно через обмен сообщениями.

Получив входящее сообщение, код актора выполняет свою задачу и может как создавать новых акторов, так и отправлять сообщения существующим. Акторы выполняют роль строительных блоков для механизмов масштабирования YDB: код акторов максимально предсказуем, они одинаково работают как локально на одном сервере, так и при отправке сообщений по сети.

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

Таблетки умеют сохранять и считывать из распределённого хранилища не только данные таблиц СУБД, но и своё собственное состояние. Если сервер, на котором выполнялась таблетка, выходит из строя, то YDB достаточно пересоздать эту таблетку на другом сервере: свежесозданная таблетка вычитает из распределённого хранилища своё состояние и продолжит работу с того места, где она была прервана.

Если таблетка, отвечающая за работу с таблицей, не справляется с потоком запросов, то она разделяется на две: каждая начинает обрабатывать в два раза меньше строк таблицы. Чем больше запросов к таблице и чем больше её размер — тем большее количество таблеток на разных серверах будут работать с данными этой таблицы и держать в памяти кеши.

Менеджер смешанной нагрузки — один из механизмов высокого уровня, позволяющих распределять нагрузку так, как нужно пользователям YDB. Если этого не делать, то даже одного OLAP запроса может быть достаточно, чтобы нагрузить любой кластер и заметно снизить его способность к обработке большого потока OLTP-запросов.

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

Как YDB выполняет запрос

Менеджер смешанной нагрузки
Менеджер смешанной нагрузки

В распределённой базе данных выход из строя любого сервера не останавливает её работу. Поэтому все серверы слоя вычисления YDB умеют выполнять любые запросы пользователей. Когда клиентский SDK подключается к YDB, то он устанавливает gRPC-подключение к одному из вычислительных серверов. Этот сервер будет получать от клиента запросы и запускать их выполнение. А если с сервером что-нибудь случится во время работы, то SDK автоматически переподключится к другому.

Получив и распарсив запрос, сервер рассчитывает для него план выполнения. Планы транзакционных запросов обычно несложные, чего не скажешь про аналитические: чем больше в запросе JOIN, тем труднее сделать для такого запроса оптимальный план.

Мы уже писали на Хабре, как работает оптимизатор запросов YDB. А результатом его работы является граф выполнения, который описывает, из каких этапов будет состоять выполнение запроса и в каком порядке эти этапы исполнять. После построения плана YDB запускает акторы, которые исполняют запрос: получают данные от таблеток, выполняют JOIN и другие операции, отдают данные обратно таблеткам на запись, возвращают пользователю результат.

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

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

Акторы, за которыми надо следить

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

Акторам надо держать в памяти промежуточные данные. И чем больше акторов одновременно выполняется — тем больше памяти нужно. Если памяти кластера перестанет хватать, запросы начнут завершаться с ошибками выполнения.

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

Чем больше акторов выполняется на каждом сервере — тем больше становятся паузы между вызовами функций каждого конкретного актора. И тем выше шансы, что клиент не дождётся выполнения запроса и завершит его с тайм-аутом.

Особенно опасна для кластера аналитическая нагрузка. OLAP-запросы часто выполняются над огромными таблицами и содержат десятки JOIN. Для их выполнения YDB создаёт много акторов-тасок, которые обмениваются сообщениями с акторами-таблетками. И чем больше данных нужно обработать — тем больше таких сообщений. Если никак не управлять нагрузкой на кластер, то запущенный аналитический запрос способен понизить RPS для всей остальной OLTP-нагрузки.

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

Слайд презентации
Слайд презентации

Чтобы пользователи YDB могли управлять работой своих кластеров, мы научили планировщик акторов собирать статистику того, как акторы выполняются. Для каждого актора замеряется время выполнения кода обработки сообщений и занимаемая ими память. Основываясь на заданных пользователем ограничениях, YDB может вызывать код актора чаще или реже. Или вообще перестать создавать новые акторы, если превышена квота по использованию памяти.

Устройство менеджера смешанной нагрузки

Настройки балансировки задаются для пулов ресурсов, и YDB автоматически выбирает один из пулов для каждого запроса. Пул выбирается по пользователю, который сделал запрос. А если пользователь находится в списках разных пулов, YDB выбирает тот из них, ранг которого минимален (чем меньше числовое значение ранга — тем выше его приоритет).

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

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

Каждый узел слоя вычисления большую часть времени выполняет код акторов. У YDB модульная архитектура, поэтому движок выполнения акторов ничего не знает про менеджер смешанной нагрузки и ограничения для запросов на этом узле. Всё, что он делает, — выбирает в цикле очередное сообщение среди всех очередей акторов и выполняет код соответствующего актора для обработки этого сообщения.

Всю коммуникацию с менеджером смешанной нагрузки берут на себя сами акторы. Первое, что делает код актора-таски, — запрашивает у менеджера смешанной нагрузки разрешение на выполнение части запроса. Менеджер смешанной нагрузки по алгоритму Max-min Fairness определяет, может ли актор выполняться. И если не может — то актор отправляет сам себе сообщение, чтобы движок через нужное время снова вызвал обработчик и актор мог повторить попытку.

Так, синхронизируя ключевую информацию между узлами слоя вычисления и ставя на паузу акторы-таски, YDB управляет OLAP и OLTP-нагрузкой кластера.

Сейчас в разработке новая версия YDB, где в менеджере смешанной нагрузки сделано два улучшения. Во-первых, алгоритм Max-min Fairness изменён на Hierarchical Dominant Resource Fairness, который обеспечивает лучшие результаты. А во-вторых, пулы ресурсов могут образовывать иерархии — это позволяет алгоритму гибче распределять нагрузку между пулами.

Дерево балансировки
Дерево балансировки

Будущее менеджера смешанной нагрузки

Мы постоянно получаем обратную связь от разных пользователей YDB. Кто-то использует базу данных в Yandex Cloud, кто-то — коммерческие сборки. Некоторые читатели Хабра используют опенсорс-версию на своих серверах. Общаясь со всеми ними, мы формируем список фич, которые стараемся реализовать в первую очередь:

  • Распределённое квотирование ресурсов, чтобы ограничение можно было поставить на весь кластер в целом, а не на ресурсы каждого сервера по отдельности.

  • Умные классификаторы, которые позволят задавать более сложные правила, нежели «все запросы от такого-то пользователя относятся к такому-то ресурс-пулу».

  • Тесная интеграция планировщика и пулов ресурсов, чтобы акторы-таски распределялись по серверам не только равномерно, но и с учётом их ограничений по нагрузке.

  • Иерархия пулов ресурсов и приоритеты выполнения запросов внутри пулов.

Если вы пользуетесь YDB или другой базой данных, которая умеет управлять смешанной нагрузкой, — поделитесь вашими впечатлениями в комментариях!

Комментарии (14)


  1. Tenphi
    18.08.2025 10:55

    Спасибо за статью! В чём именно вы видите основную пользу от внедрения HDRF в контексте YDB и насколько ощутимый буст это может дать в общем?


    1. dorooleg Автор
      18.08.2025 10:55

      Спасибо за вопрос. Сейчас используется алгоритм который пересчитывает лимиты в зависимости от того используют конкретный Resource Pool или нет. Соответственно он выступает как некоторый лимитер из-за чего может не достигаться 100% утилизация cpu, если один пул перегружен, а второй недогружен, то перераспределения ресурсов между Resource Pool'ами не произойдет, при этом такой подход обладает минимальными накладными расходами. После перехода на HDRF будет честное распределение ресурсов между пулами в соответствии с весами, что позволит достигать 100% утилизации cpu, также это позволит строить иерархически пулы ресурсов и по предварительным результатам накладные расходы незначительно больше чем у подхода через лимитер.


  1. Darksa
    18.08.2025 10:55

    @dorooleg Ты пишешь, что для обработки каждого запроса от пользователя используется много акторов, которые выполняются на разных серверах.

    А что будет, если один из серверов перезагрузится и те акторы, которые на нем выполнялись, перестанут существовать? YDB их пересоздаст и продолжит выполенние запроса? Или YDB ответит SDK что запрос прерван "по техническим причинам" и SDK перезапустит запрос? Или пользователь получит ошибку "что-то пошло не так, не смогли выполнить запрос"?


    1. dorooleg Автор
      18.08.2025 10:55

      Здесь может быть две глобальных ситуации:

      1. Если перезагрузился сервер на котором жили только специальные акторы, которые называются таблетками (они фактически отвечают за хранение и чтение некоторой части таблицы). В этом случае произойдет переезд таблеток на другие сервера и запрос продолжит свое выполнение (случай "YDB их пересоздаст и продолжит выполение запроса")

      2. Если же перезагрузится сервер на котором были compute акторы, то в этом случае SDK получит ошибку и поретраит запрос (случай YDB ответит SDK что запрос прерван "по техническим причинам" и SDK перезапустит запрос). Но если такое будет происходить часто, то в SDK после некоторого числа срабатываний будет отправлена ошибка пользователю (случай "что-то пошло не так, не смогли выполнить запрос"). Подробнее про error handling можно посмотреть в документации https://ydb.tech/docs/ru/reference/ydb-sdk/error_handling

      Получается что все три варианта возможны


      1. GRbit
        18.08.2025 10:55

        А разве не лучше было бы ретрай делать на уровне базы данных, а не на уровне клентсого SDK? У тебя тысяча акторов 10 минут выполняли аналитический запрос, почти закончили. И тут один из акторов прекратил свое существование, потому что сервер перезагрузился. Разве не лучше пересоздать этот один актор и продолжить выполнение запроса?


        1. dorooleg Автор
          18.08.2025 10:55

          Да, действительно такой подход будет лучше работать, но он сложнее в реализации. Некоторые compute акторы накапливают состояние, например Join, TopSort, Aggregation и если такой актор упадет, то при переподнятии ему нужно будет как-то получить обратно это состояние, в этом случае можно либо прогнать часть вычислений заново или же персистить некоторые шаги. Это важно делать в случае транзакционных баз данных, иначе можно получить не корректные результаты в транзакциях. Но мы будем двигаться в эту сторону.


  1. ValentinDom
    18.08.2025 10:55

    Интересная статья, спасибо!

    Я работаю только со строковыми таблицами, колоночные нужны для быстрой аналитики по тем же данным. Как эти колоночные таблицы правильно создавать и синхронизировать в YDB? База данных сама их может поддерживать?


    1. dorooleg Автор
      18.08.2025 10:55

      Для решения этой задачи существует механизм трансфера. Находимся на финишной прямой к его включению. Ознакомиться с draft документации можно по этой ссылке https://ydb-platform--ydb.viewer.diplodoc.com/ru/concepts/transfer?revision=pr-20816-8de83c1f4a9db380281f6a05a7fefbe904fd738b


  1. zVlad909
    18.08.2025 10:55

    Одно из основных требований к YDB — бесконечная линейная горизонтальная масштабируемость. ..... Линейная — значит прирост производительности пропорционален числу добавленных машин.

    Ну и как удалось достичь линейности? Вы это как то измеряете и видите что линейность имеет место быть?


    1. dorooleg Автор
      18.08.2025 10:55

      На самых крупных кластерах YDB больше 10k хостов на которых проверяется это свойство. Также в статье https://habr.com/ru/companies/ydb/articles/801587/ можно найти результаты экспериментов


      1. zVlad909
        18.08.2025 10:55

        Шрафик "Масштабируемость YDB, tpmC" показывает что на трех машинах на каждую машину пришлось 67606 tpmC, на 9 - 56120, на 18 - 48532, и на 36 - 40201.

        Если бы рост производительности был бы линейным то на 36 машинах должно было быть как минимум 2433816. Т.е. получается что 36 машин обеспечивают рост как минимум на 41% меньше линейной зависимости. Как минимум потому что не известно сколько было бы на одной машине в этом тесте.

        Ну а вообще то график выглядит как бы линейным. Но это лишь "как бы".

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

        Чудес не быват. К счастью.


        1. eivanov
          18.08.2025 10:55

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


  1. zVlad909
    18.08.2025 10:55

    Статья называется:

    Как YDB изолирует OLTP и OLAP

    Вот все что можно было бы отнести к раскрытию этой тему:

    Менеджер смешанной нагрузки — один из механизмов высокого уровня, позволяющих распределять нагрузку так, как нужно пользователям YDB. Если этого не делать, то даже одного OLAP запроса может быть достаточно, чтобы нагрузить любой кластер и заметно снизить его способность к обработке большого потока OLTP-запросов.

    ......

    Особенно опасна для кластера аналитическая нагрузка. OLAP-запросы часто выполняются над огромными таблицами и содержат десятки JOIN. Для их выполнения YDB создаёт много акторов-тасок, которые обмениваются сообщениями с акторами-таблетками. И чем больше данных нужно обработать — тем больше таких сообщений. Если никак не управлять нагрузкой на кластер, то запущенный аналитический запрос способен понизить RPS для всей остальной OLTP-нагрузки.

    ......

    Для каждого актора замеряется время выполнения кода обработки сообщений и занимаемая ими память. Основываясь на заданных пользователем ограничениях, YDB может вызывать код актора чаще или реже. Или вообще перестать создавать новые акторы, если превышена квота по использованию памяти.  

    .....

    Настройки балансировки задаются для пулов ресурсов, и YDB автоматически выбирает один из пулов для каждого запроса. Пул выбирается по пользователю, который сделал запрос. А если пользователь находится в списках разных пулов, YDB выбирает тот из них, ранг которого минимален (чем меньше числовое значение ранга — тем выше его приоритет).

    ....

    Менеджер смешанной нагрузки по алгоритму Max-min Fairness определяет, может ли актор выполняться. И если не может — то актор отправляет сам себе сообщение, чтобы движок через нужное время снова вызвал обработчик и актор мог повторить попытку.

    Так, синхронизируя ключевую информацию между узлами слоя вычисления и ставя на паузу акторы-таски, YDB управляет OLAP и OLTP-нагрузкой кластера.

    Более менее понятно что менеджер смешанной нагрузки в состоянии "сдерживать" тех илил иных акторов используя статискику выполнения и ограничения.

    Это интересно, но складывается впечатление что ответственность за правильную конфигурацию пулов и ограничений несет пользователь. Иначе говоря разделение нагрузкаи OLAP и OLTP это не функция YD, это задача пользователя и от того насколько пользователь удачно смог сконфигурировать YDB будет определяться успех "изоляции" одного от другого.

    Или я не понял Вас?

    Вопрос: В теории БД есть два варианта (architecture) кластеризации БД: 1. Shared-nothing architecture и 2. Shared-disk (everything) architecture. К какову из этих двух вариантов относится распределенная БД YDB?

    Спасибо.


    1. dorooleg Автор
      18.08.2025 10:55

      Спасибо за вопросы!
      1. Workload Manager не про тюнинг настроек, поэтому "удачная конфигурация" не совсем применимо в этом случае. Здесь именно настройка со стороны пользователя, которая позволяет распределять ресурсы между разными задачами. Похожие решения существуют в других базах данных Greenplum, Teradata, Synapse Analytics и другие
      2. YDB относится к shared nothing архитектуре, подробнее про архитектуру YDB можно почитать в документации https://ydb.tech/docs/ru/concepts/