Как стать автором
Поиск
Написать публикацию
Обновить
26
0
Евгений Иванов @eivanov

Разработчик YDB

Отправить сообщение

Рад, что статья понравилась :) Мне кажется, что либо пытаться ориентироваться на TPC-C, либо, что лучше и правильнее, использовать свой бенчмарк, соответствующий профилю нагрузки.

Вася сначала прочитал старые данные, которые были закомичены когда-то давно, с шарда, куда еще не дошёл коммит новых. Потом с другого шарда, где уже случился коммит Алисы, т.е. тоже данные закомичены. Поэтому в примере "Read Committed". Интересно, что в случае "Read Uncommitted" конкретно в данном примере получился бы консистентный результат.

Да, это наш официальный репозиторий. Если будут вопросы, то у нас есть группа в tg, где мы помогаем пользователям.

К сожалению (или к счастью), абсолютного победителя пока нет. Но мы в YDB над этим усиленно работаем :)

Да, я с Вами согласен: всё зависит от требований приложения. Иногда можно показать неверные сходящиеся данные, а иногда это может быть проблемой.

ну прийдет ему попап на телефон, на минуту позже. Никакой разницы Васе, что он узнал о транзакции позже (если он не собирается ее обновлять).

Кажется я понял, в чём недопонимание. Проблема не в том, что Вася узнаёт что-то с задержкой, а в том, что он видит неправильный результат. Eventually consistency означает не задержку, с которой результат транзакции становится видим всем, а то, что какое-то время виден неверный результат.

Закоммиченных кем? Алисой? в вашем примере они оба комитят.

Алисой

Откуда Вася будет знать, что данные старые? Ему сказали об этом?

В этом и проблема, что Вася этого знать не будет. И сказать ему некому. Он начнёт звонить Алисе и в банк :)

Дедлок, только если делать SELECT FOR UPDATE на разные ноды. Понятно, что не надо так делать.

В этом и заключался первоначальный вопрос. И вопрос правильный, так как эта конструкция часто используется, чтобы достичь serializable изоляции. Кроме того, отсутствие многошардового SELECT FOR UPDATE является еще одним хорошим примером того, как шардированный PostgreSQL отличается от монолитного.

Не знаю как в Citus, но в простейшем случае - 2РС должен сломаться (rollback) на одной из транзакций, которая попытается commit после commit другой.

Извините, но я не понял, что Вы имеете в виду.

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

SELECT FOR UPDATE в citus ограничен одним шардом. Представьте две транзакции, которые берут локи на одни и те же строки на каждом из двух шардов. Будет дедлок.

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

Транзакция Васи только читает, а пишет транзакция Алисы. Когда транзакция Алисы пишет в несколько шардов, то транзакция Васи может прочитать часть старых данных и часть новых, но уже закомиченных.

И вывод - отсутствие консистенции в данных, но эту задержку в консистентности 'Вася' узнает, только если ему сказать, что данные поменялись в независимым способом, но в 'своей' ноде он этого пока не видит.

Проблема в том, что Вася видит лишь часть изменений, что и создаёт неконсистентность. Если бы он видел только старые данные или только новые, то всё было бы хорошо.

Спасибо за интересное дополнение. Наверное, мы не очень чётко отразили это в тексте, но весь посыл статьи следующий:

  1. При простом шардировании теряются гарантии изоляции.

  2. Распределенные СУБД (сюда я бы отнёс любой форк постгреса, у которого serializable многошардовые транзакции) не имеют такой проблемы, но у них есть накладные расходы на реализацию распределенных транзакций и распределенного снепшота.

  3. Накладные расходы не так велики, когда датацентры находятся близко друг к другу.

Мы выбрали Citus в силу его популярности. Конечно, есть и другие решения. Но в первую очередь нам интересны решения из OSS и то, что реализовано, а не предложено, но застряло по каким-то причинам. Из мира распределенных СУБД взяли YDB по понятным причинам. Но выбор конкретной распределенной СУБД в данном случае никак не влияет на основную идею.

В посте была ссылка на описание наших распределенных транзакций. Там же рядом описание MVCC. Вот выжимка, которая отвечает на Ваш вопрос:

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

...

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

Рад, что пост понравился! :) Мне кажется, что это часто вызвано тем, что мы, разработчики, хотим разрабатывать что-то интересное. А пошардировать базу куда интереснее, чем делать задачи от команды продаж. Надо почаще вспоминать, что мы в первую очередь инженеры и должны решать задачи пользователей наиболее эффективным способом.

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

Вот здесь в коммите есть полное объяснение: цель была отпускать физический поток под vthread (семафор позволяет это делать), а не блокировать в synchronized-блоке внутри c3p0 (от которого, правда, мы потом вообще отказались). Проблема была не при работе с самой СУБД, а в том, как реализован пул сессий к БД внутри c3p0.

Мне кажется, что это полностью аналогично обсуждению Citus: в итоге, получается СУБД на базе Postgres с другими характеристиками и гарантиями. Т.е. нельзя взять произвольное приложение, работающее с Postgres, и использовать его с этой фичей.

И мультимастер, имхо, очень плох, если могут возникать конфликты. Citus в этом плане гораздо привлекательнее (если уж никак не уйти с постгреса).

Полностью с вами согласен, что serializable по умолчанию - безопаснее. Аппелировал к сильному утверждению, что нет доказательств, что производительность "serializable" ощутимо хуже. С оговоркой "в большинстве случаев" было бы нормально. Ещё лучше было бы сделать оговорку, что при невысокой concurrency. Т.к. при высокой конкуренции, даже без блокировок, множественные ретраи могут эту производительность убить. Другие уровни изоляции именно для решения данной проблемы и были оставлены.

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

А может и будет. В примере выше нередко бывает так, что сериализация это дедлок и отстрел транзакции-жертвы секунд через 30. На три порядка выше целевого по p99, упс.

Хорошее замечание. В Postgres дефолтный таймаут для дедловок 1 секунда и это минимальное рекомендуемое значение. Я привык к тому, что в YDB оптимистичные блокировки и дедлоков нет: кто первый, тот и закомитился - остальные сразу получили ошибку "transaction locks invalidated". Поэтому в большинстве случаев останется достаточно времени на ретрай.

Если у вас сериализация на клиенте, то зачем вам транзакции в базе?

Я имел в виду, что с точки зрения производительности в целом не имеет значения, где делать сериализацию. Поэтому лучше в базе.

От архитектуры конкретной СУБД зависит. Первый контрпример, который приходит в голову, это классические реляционные блокировочники, например, SQL Server. В них serializable может приводить к фактически последовательному выполнению транзакций.

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

  1. Если надо учесть constraint, включающий в себя подобный широкий диапазон строк, то обычно не так важно, где произойдет сериализация: в базе или на стороне клиента (особенно когда ping небольшой).

  2. Как часто транзакции, которые требуют сериализацию, пересекаются по диапазону ключей, на который берется лок. Если у приложения требование, скажем, 50 ms на p99, а транзакции выполняются за миллисекунду + сеть, то сериализация небольшого числа таких транзакций не будет проблемой.

Весь посыл поста в том, что при использование "serializable" снижается вероятность багов, но при этом в большинстве случаев не страдает производительность. И мы ни в коем случае не призываем не использовать слабые уровни, если это необходимо. Просто мы считаем, что по умолчанию должен быть уровень "serializable", а более слабые уровни должны указываться разработчиками приложений явно. Такие СУБД, как YDB и CockroachDB, вполне доказали, что это хорошо работает.

Рад, что статья оказалась полезной для Вас.

Когда проекту 10+ лет, то скорее всего уже всё отлажено. Конечно, баги могут всплывать, но вероятность к этому времени уже гораздо ниже.

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

Информация

В рейтинге
137-й
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Работает в
Дата рождения
Зарегистрирован
Активность

Специализация

Backend Developer, Database Developer
Senior
Git
C++
Multiple thread
Database design
Algorithms and data structures
Code Optimization
System Programming
Python
Bash
English