Pull to refresh

Comments 29

Очень интересный пост, спасибо, Женя! Наблюдал в одном проекте решение задачи "а теперь давайте распилим этот шард" – это очень интересная задача оказывается. Которую решают лучшие люди, вместо того, чтобы делать задачки от команды продаж, например.

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

Citus - не лучший пример в смысле распределенных транзакций. Стоит посмотреть в форки - оно конечно стоит денег, а что будет бесплатно?

Тема консистентной видимости в распределенном Postgres решалась уже давно (https://www.postgresql.org/message-id/21BC916B-80A1-43BF-8650-3363CCDAE09C%40postgrespro.ru). Дело остановилось по причине патентных ограничений, что однако не является стоппером для чисто российских энтерпрайзов.

Так что решения есть, их просто нет в open-source сейчас.

С другой стороны, никак не описано, а как это решают другие. Например, YDB. Может у него тоже пока нет готового ответа? Отдельная задача - распределенный коммит. 2PC даже не паллиатив, поскольку уходить в recovery после неудачного коммита (один инстанс завершил COMMIT, а другой ушёл в ROLLBACK) - не выглядит хорошим решением. Или нет?

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

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

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

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

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

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

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

...

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

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

  1. У нее разделение storage и compute. То есть процесс(ы), которые выполняют запрос, и так читают и пишут данные по сети. При такой архитектуре нет разницы, на одном сервере находятся такие данные или на сотне.

  2. У нее акторная модель. Слой вычисления выполняет легковестные акторы с надежно сохраняемым стейтом и дает им возможность обмениваться сообщениями. Тоже по сети. Для акторов их бессмертие и возможность безнаказанно отправлять друг другу сообщения — тот мир, в котором они существуют. Ну и в рамках такого мира уже несложно организовать оркестрацию любого количества изменений в рамках транзакции 😊

Это не укладывается у меня в голове. Вроде бы вопрос видимости и транзакционной целостности зависит от устройства менеджера транзакций - и тут либо глобальный менеджер транзакций, либо CSN, либо какие-то eventually-consistent варианты. При чем здесь разделение на compute & storage?

Разделение на compute & storage позволяет проще организовать распределение по нодам с сохранением отказоустойчивости. И делает акторы бессмертными 😊

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

При попытке изменения в действие вступает Global Transaction Coordinator, который не дает изменить устаревшие данные - Strong consistency на запись.

Такой вот концепт вроде работает - обращайтесь. Бенчмарк сделаю - отдам в опенсорс.

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

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

Вы имеете в виду "видит" свои commited изменения? нет, не видит - strong consistency on write. Не даем commit на старые версии.

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

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

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

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

  • ну так сразу и скажите ему, что новые данные пришли, и они - "вот такие" (т.е. используется данные из кеша)

  • Вася собирается не только читать, но и писать (как в примере)? см. выше - consistency on write.

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

Но! если не дожидаясь, тоже захочет обновить - два варианта, в зависимости от кода бакенда - или ошибка пробросится на телефон и Васе явно скажут, что надо эту (table row / запись / счет) обновить перед изменением; или бакенд ждет сам, т.е. база накатывает комит Васи, после комита Алисы.

В любом случае, комит Васи (на этой row) идет после комита Алисы.

Еще одно наблюдение: если транзакция Васи началась, до комита транзакции Алисы, то 2РС просто не даст комиту Васи пройти.

Кстати, 2РС не обязательно должно быть на все ноды/шарды кластера, но обязательно на шардах, которые меняют одинаковые row.

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

Алисой

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

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

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

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

Или еще пример:

Вася в оффисе, Алиса дома заказывает на Озоне /Амазоне, Алиса нажимает последнее подтверждение. Вася спамит кнопку обновить аккаунте (select в шард) и видит изменения через 5 минут после покупки, а не сразу - это проблема? (если он не собирается ничего покупать (нет update)).

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

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

 какое-то время виден неверный результат.

Это и есть задержка, которая не проблема. Но в то же время проблема?

Еще раз - данные в шарде у Васи были верные, потом стали не_верные, потом опять стали верные. Вот этот момент, пока они были не_верные: (1) ни на что у Васи не влияет, если нет изменений со стороны Васи. Ему никто и не сказал, что данные, которые он получал некоторое время были устаревшими, (2) реалтайм не является целью 2РС этого кластера.

Eventually consistency on read, strong on write.

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

Вася в момент когда данные были "неверные" может, например, запросить через банк-клиент выписку по счёту и получить ее. Куда и зачем Вася предоставит выписку с ЭЦП банка - не суть. Но последствия могут быть.

Я не знаком с citus, но какая роль координатора в схеме обращений Алисы и Васи? Как я понимаю Вася обращается к субд так же через координатор, разве у координатора нет информации, что транзакция только частично закоммичена? Если я сделаю SELECT FOR UPDATE в транзакции Алисы, то будет ли Вася ожидать коммита на обоих шардах?

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

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

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

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

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

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

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

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

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

Меня теперь другой вопрос интересует - а какая СУБД справляется лучше?

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

Просто пользуясь случаем, вчера после вашего поста начал смотреть YDB (думаю попробовать для одного нового проекта) И наткнулся на репозиторий https://github.com/ydb-platform/ydb-dotnet-sdk Так как я разрабатываю на C#, было приятно увидеть что там есть поддержка Entity Framework, но как я понимаю его добавили только месяц назад, это будет официальный провайдер?

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

Разве в примере распределённой транзакции "Read Committed"? Это же "Read Uncommitted"

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

А в случае rollback получилась бы катастрофа 😂

Sign up to leave a comment.