Все вопросы на одной странице

Общие вопросы

Что такое YDB?

YDB — это распределенная отказоустойчивая Distributed SQL СУБД. YDB обеспечивает высокую доступность и масштабируемость, и, в то же время, строгую консистентность и поддержку ACID-транзакций. Для запросов используется диалект SQL (YQL).

YDB — это полностью управляемая база данных, для создания инстанса базы данных используется сервис управления базами данных YDB.

Какие возможности предоставляет YDB?

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

Какая модель консистентности используется в YDB?

Для чтения данных YDB использует модель строгой консистентности (непротиворечивости) данных.

Как проектировать первичный ключ?

Чтобы правильно спроектировать первичный ключ, стоит придерживаться следующих правил:

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

    Как следствие этого правила, не стоит в качестве первичного ключа таблицы использовать монотонно возрастающую последовательность, например, timestamp.

  • Чем меньше партиций таблиц будет задействовано во время исполнения запроса, тем быстрее он будет исполнен. Чтобы достичь большей производительности стоит руководствоваться правилом: один запрос — одна партиция.

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

Подробнее читайте в разделе Проектирование схемы.

Как равномерно распределить нагрузку по партициям таблицы?

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

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

Подробнее читайте в разделе Проектирование схемы.

Можно ли использовать NULL в ключевой колонке?

В YDB все колонки, включая ключевые, могут содержать значение NULL, однако использовать NULL в качестве значений в ключевых колонках не рекомендуется.

По стандарту языка SQL (ISO/IEC 9075) значение NULL нельзя сравнивать с другими значениями, вследствие чего использование лаконичных конструкций SQL с простыми операторами сравнения может приводить, например, к тому, что строки, содержащие значение NULL, могут быть пропущены при фильтрации.

Существует ли оптимальный размер строки базы данных?

Для достижения высокой производительности не рекомендуется записывать в БД строки размером более 8 МБ и ключевые колонки размером более 2 КБ.

Подробнее существующие ограничения описаны в разделе Ограничения базы данных.

Как работают вторичные индексы в YDB?

Вторичные индексы в YDB — глобальные и могут быть неуникальными.

Подробнее читайте в разделе Вторичные индексы.

Как осуществляется постраничный вывод?

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

Подробнее читайте в разделе Постраничный вывод.

Как удалять устаревшие данные?

Для эффективного удаления устаревших данных рекомендуется использовать TTL.

Как происходит синхронизация данных между дата-центрами в геораспределенных кластерах

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

SDK

Что делать, если SDK экстренно завершает работу при завершении работы приложения?

Не следует оборачивать компоненты SDK в синглтон, так как время их жизни не должно превышать время выполнения функции main(). Так как при уничтожении клиента происходит опустошение пулов сессий, требуется возможность перехода по сети. Но gPRC содержит глобальные статические переменные, которые к этому времени уже могут быть разрушены. Таким образом, работа gRPC невозможна. При необходимости сделать драйвер глобальным объектом, на драйвере нужно вызвать функцию Stop(true) перед выходом из main().

Что делать, если при вызове fork() в дочернем процессе программа работает некорректно?

Вызов fork() в многопоточных приложениях является антипаттерном. Так как и SDK, и gRPC-библиотека являются многопоточными приложениями, стабильность их работы не гарантируется.

Что делать, если я получаю ошибку «Active sessions limit exceeded», хотя текущее количество активных сессий не превышает лимит?

Лимит действует на количество активных сессий. Активная сессия — сессия, переданная клиенту для использования в его коде. Сессия возвращается в пул в деструкторе. При этом, сама сессия является копируемым объектом. Возможно, вы сохранили в коде копию сессии.

Есть ли возможность делать запросы к разным БД из одного приложения?

Да, C++ SDK позволяет переопределять параметры БД и токен в момент создания клиента. Отдельные драйверы создавать не нужно.

Что делать, если отказала одна из ВМ и невозможно сделать запрос?

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

Ошибки

Почему может возникать ошибка «Status: OVERLOADED Error: Pending previous query completion» в C++ SDK?

Q: При запуске двух запросов, пытаюсь получить ответ из фьючера от второго из них. Получаю: Status: OVERLOADED Why: <main>: Error: Pending previous query completion.

A: Сессия в SDK однопоточная. Если нужно параллельно использовать несколько запросов, нужно создавать несколько сессий.

Что делать, если я часто получаю ошибку «Transaction locks invalidated»?

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

Почему возникает ошибка «Exceeded maximum allowed number of active transactions»?

В логике на клиентской стороне надо стараться держать как можно более короткие транзакции.

В рамках сессии разрешено не более 10 активных транзакций. При старте транзакции нужно использовать либо коммит-флаг для автокоммита, либо явный коммит/роллбек.

Что делать, если при запросе я получаю ошибку «Datashard: Reply size limit exceeded»?

Данная ошибка означает, что при выполнении запроса с одного из участвующих в нем даташардов была попытка вернуть более 50 МБ данных, что превышает допустимый лимит.

Рекомендации:

  • Общая рекомендация — уменьшить объем данных, обрабатываемых в транзакции.
  • Если выполняется операция Join, стоит убедиться, что она выполняется способом Index lookup Join.
  • Если осуществляется простая выборка, стоит убедиться, что она делается по ключам, или добавить LIMIT в запрос.

Что делать, если при запросе я получаю ошибку «Datashard program size limit exceeded»?

Данная ошибка означает, что размер программы (в сумме со значениями параметров) для одного из даташардов превысил лимит в 50 МБ. Чаще всего это означает попытку записать в таблицы базы более 50 МБ данных в одной транзакции. В качестве записей учитываются все модифицирующие операции в транзакции, такие как UPSERT, REPLACE, INSERT, UPDATE.

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

YQL

Общие вопросы

Как выбрать из таблицы строчки по заданному списку ключей?

Выборка строчек таблицы по заданному списку значений первичного ключа (или префикса ключа) таблицы выполняется с помощью оператора IN:

DECLARE $keys AS List<UInt64>;

SELECT * FROM some_table
WHERE Key1 IN $keys;

В случае если выборка делается по составному ключу, параметр запроса должен иметь тип списка таплов:

DECLARE $keys AS List<Tuple<UInt64, String>>;

SELECT * FROM some_table
WHERE (Key1, Key2) IN $keys;

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

Осуществляется ли поиск по индексу для условий, содержащих оператор LIKE?

Оператор LIKE может быть использован для поиска по индексу таблицы только в случае, если он задает префикс строки:

SELECT * FROM string_key_table
WHERE Key LIKE "some_prefix%";

Почему в результате запроса выводится только 1000 строк?

1000 строк — ограничение на размер одного результата для YQL запроса. В случае если результат запроса был обрезан, он будет помечен флагом Truncated. Чтобы получить большее количество строк из таблицы, можно воспользоваться постраничным выводом или операцией ReadTable.

Как обновить только те значения, ключей которых нет в таблице?

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

DECLARE $values AS List<Struct<Key: UInt64, Value: String>>;

UPSERT INTO kv_table
SELECT v.Key AS Key, v.Value AS Value
FROM AS_TABLE($values) AS v
LEFT JOIN kv_table AS t
ON v.Key = t.Key
WHERE t.Key IS NULL;

Операции Join

Есть ли особенности в работе операции Join?

Операция Join в YDB выполняется одним из двух способов:

  • Common Join;
  • Index Lookup Join.

Common Join

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

Index lookup Join

Для строчек из левой части операции Join осуществляется поиск (lookup) соответствующих значений в правой части. Данный способ применяется, когда правая часть является таблицей и ключ операции Join является префиксом ее первичного ключа или ключа вторичного индекса. При использовании данного способа из правой таблицы делаются ограниченные выборки, вместо полного чтения, что позволяет использовать его при работе с большими таблицами.

Примечание

Для большинства OLTP запросов рекомендуется использовать Index Lookup Join с небольшим размером левой части. Такие операции читают мало данных и могут быть выполнены эффективно.

Как сделать Join с данными из параметров запроса?

Данные из параметров запроса можно использовать как константную таблицу. Для этого нужно использовать модификатор AS_TABLE с параметром, имеющим тип списка структур:

DECLARE $data AS List<Struct<Key1: UInt64, Key2: String>>;

SELECT * FROM AS_TABLE($data) AS d
INNER JOIN some_table AS t
ON t.Key1 = d.Key1 AND t.Key2 = d.Key2;

Явного ограничения на количество записей в константной таблице нет, но нужно иметь в виду стандартное ограничение на общий размер параметров запроса (50 МБ).

Как лучше реализовать запрос вида (key1, key2) IN ((v1, v2), (v3, v4), ...)?

Это лучше записывать через JOIN с константной таблицей:

$keys = AsList(
    AsStruct(1 AS Key1, "One" AS Key2),
    AsStruct(2 AS Key1, "Three" AS Key2),
    AsStruct(4 AS Key1, "One" AS Key2)
);

SELECT t.* FROM AS_TABLE($keys) AS k
INNER JOIN table1 AS t
ON t.Key1 = k.Key1 AND t.Key2 = k.Key2;

Транзакции

Насколько эффективно выполнение нескольких запросов в транзакции?

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

Является ли отдельный запрос атомарным?

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

Serverless

Как вторичные индексы влияют на стоимость запроса?

Операции с индексами оцениваются по тем же правилам, что и операции с таблицами. Они отражаются в статистике исполнения запроса и включаются в суммарные показатели, на основании которых делается расчет стоимости в RequestUnits (RU). Подробнее читайте в правилах тарификации бессерверного режима для YDB API.

При чтении данных из таблицы с использованием индекса в статистике исполнения запроса будет видно количество считанных из индекса записей и их объем.

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

При изменении строки таблицы в статистику попадет операция удаления старой записи из индекса и добавления новой для всех индексов, в которые включены изменяемые поля.

При удалении строки таблицы в статистику попадет удаление записей из всех индексов на этой таблице.