Comments 37
Типичная сильно упрощенная ситуация.
Для примера 1000 клиентов делают операции оплаты в интернет магазине в течении 1 секунды. Необходимо "проверить" что у весх этих клиентов есть "на балансе" необходимая сумма, "снять" суммы с 1000 клиентских счетов, зачислить все эти 1000 сумм на 1 счет магазина, но перед этим проверить что на "балансе" магазина сумма не превышает определенный "порог". Если превышает - операция не выполняется.
Т.е. успех каждой операции "зависит" от успешного или "неупешного" выполнения предыдущей по времени...
Ну и как тут "мультимастеринг" может помочь?
Магазины существуют разве не для того, чтобы получать как можно больше денег?
А так, технический овердрафт вроде вполне обыденная для банков вещь.
"технический овердрафт" - вынужденная ситуация, и избегать ее техническими средствами необходимо в различных случаях....
Тут даже овердрафта нет, по условиям деньги только приходят. А что это за "сумма не превышает определенный порог", откуда это вообще, почему и зачем? Это бы хотел узнать.
это искуственное условие, высосанное из пальца, только для того чтобы признать негодной банальную shared-nothing схему из N серверов шардированных по клиентам. Которая на самом деле тут работает прекрасно и быстро, и никаких мультимастеров не надо
Надо было инвертировать, тогда и ограничение бы не было таким уж искусственным. 1000 клиентов бронируют (в корзину) один и тот же товар в течение одной секунды, товаров всего скажем 500. Кого отбрасывать? И тут тоже шардированная система не прокатит, и ресурс более-менее обозначен, и кейс относительно реальный (распродажа айфонов онлайн, как пример). Механика та же, только сумма отрицательная (-1 товар).
не для всех типов данных подходит бесконфликтная репликация
А для каких не подходит?
для любых нормальных и не подходит.
То есть конкретики не будет?
ну любой твой инт, бигинт, варчар, флоат, jsonb не будут работать
Почему это?
Реплицируется новое значение на все ноды кластера, что работать в такой схеме не будет.
Что мешает переделать на реплицирование дельты?
json немного сложнее - если json меняется в строковых артибутах, или происходит сложное изменение схемы, то никак. Но если все сводится к дельте числовых, то почему нет?
Да всё там прекрасно работает в CRDT. И даже строки мёржить можно, если хранить их правильно.
Вот, пример как json хранится у нас:
представте несколько параллельных транзакций, которые хотят поменять строковый атрибут: "name": "First" на новое значение:
"name": Jin"
"name": John"
"name": Jess"
Стратегия Last Write Wins - самая простая. Если что, у каждого значения есть таймштамп его внесения.
Самая простая, не значит правильная. В соседней теме жаловались на сплитбрейн. Вот рассинхрон часов на нодах, и привет - каша данных.
Заметьте - я не говорю, что в json невозможно сделать, просто надо дополнительно смотреть, чтобы изменения были не пересекающимися.
Вот рассинхрон часов на нодах, и привет - каша данных.
Нет, с чего бы? Время фиксируется в момент внесения данных, а не в момент слияния. Слияние на всех узлах даёт одинаковый результат.
Отметка времени может быть одинакова - что делать?
Слияние транзакции, которая изменяет несколько значений с отметкой времени после закоммиченной транзакции будет проигнорирована. Мы не можем частично применить, а частично нет. Т.е. нода с отставшими часами при комитите транзакции в кластер - данные игнорирюутся. Или нода с убежавшими часами имеет приоритет. Получается или мы делаем Last Write Wins в nosql, или нормальные транзакции в 2PC.
разговор был за "безконфликтную репликацию", а не за Last Write Wins. Это разные вещи
https://habr.com/ru/articles/418897/ LWW-Register
Не увидел в статье ничего про потенциальную потерю ссылочной целостности (внешние ключи, уникоальность и т.п.) при разрешении конфликтов.
Например - таком сценарии. Допустим есть две таблицы, и на одно из полей первой таблицы наложено ограничение внешнего ключа ссылающееся на первую таблицу (т.е. поле обязано содержать значение первичного ключа имеющейся во второй таблице запис). Допустим что на разных узлах кластера одновременно выполняются две трнзакции: первая меняет значение поля с внешним ключом на первичный ключ некой записи второй таблицы, а вторая транзакция эту запись удаляет (допустим, что других ссылок на момент удаления на нее не было). Транзакции затрагивают совершенно разные таблицы, но если обе они реплицируются успешно, то ссылочная целостность БД будет нарушена.
Поясните, пожалуйста, как имеющиеся средства реализации «Мультимастера» в PostgreSQL решают такой конфликт (и решают ли), а если не разрешают конкретно в вашем продукте - есть ли планы такой конфликт разрешать?
2PC это решает, только одна транзакция закомитится. Производительность записи вот здесь: https://habr.com/ru/companies/postgrespro/articles/793158/comments/#comment_26535635
Во-первых, вопрос был к разработчикам и именно про PostgreSQL и Postgres Pro, в частности,- а они, как я из статьи понял, 2PC не используют.
Во-вторых, про производительность - не понял, что там у вас в итоге написано: она, если используется 2PC, масштабируется, или нет? И если масштабируется, то как: я, к примеру, не вижу других вариантов, кроме завершения транзакции с синхронного с репликацией, а это масштабируется плохо.
если кратко - 2РС может масштабироваться, но как сделано в EDB & Postgres Pro - это может разработчики ответят.
В PostgresPro Multimaster использует 3PC (ещё одна фаза добавляется из-за RAFT-а).
И да: в этом случаи будет распределенный deadlock, поскольку при коммитах транзакций (а они обе дойдут беспрепятственно до коммита) изменения попробуют зареплицироваться между нодами и поймают блокировки. И тогда сработает deadlock detector. Но можно deadlock-а избежать через параметр multimaster.deadlock_prevention
: https://postgrespro.ru/docs/enterprise/13/multimaster#MTM-DEADLOCK-PREVENTION . Это быстрее работает чем ожидание дедлока и его разрешение.
Другими словами в итоге либо будет 1 COMMIT+1ROLLBACK или 2 ROLLBACK-а
Это вы именно про Postgres Pro написали? Тогда мне это странно: согласно статье он RAFT не использует.
И, как я понимаю, репликация должна быть синхронной. И это сразу вызывает вопросы по поводу производительности. 2PC и так пользуется славой слишком медленного (и, видимо, оттого в современнойй стильномодномолодежной микросервисной архитектуре он любовью не пользуется), а тут ещё накладывается задержка от синхронной репликации.
Впрочем, это не единственное сомнительное с точки зрения максимизации производительности решение в Postgres: начать тут можно с его хранилища, поддерживающего хранение несколько версий записей (из-за чего требуется, к примеру, сборка мусора AKA vacuum) - промышленные СУБД (Oracle, MS SQL и пр.) этим AFAIK не страдают.
Верно, в PostgresPro Multimaster используется PAXOS, не RAFT. Я ошибся в своём комментарии выше.
Репликация синхронная, 2PC накладывает свои расходы. Это как раз описано в третьей части данной серии статей: https://habr.com/ru/companies/postgrespro/articles/793158/
По поводу хранения версий - так без этого никак, надо откуда получать данные из прошлого. В Оракле - это UNDO сегмент, в PostgreSQL - это несколько версий строк.
Vacuum бесспорно вещь неприятная. Но его можно научиться готовить, хотя серебряной пули увы нет.
По поводу хранения версий - так без этого никак, надо откуда получать данные из прошлого.
А зачем они вообще нужны - данные из прошлого? Разве что для отката тразакций - но эти данные есть в журнале транзакций, их незачем хранить в основной части БД. И вообще, тот же MS SQL (по крайней мере, в версиях до MS SQL 2000) вполне обходился вообще без хранения данных из прошлого - и был вполне работоспособен.
PS И Postgres тоже обходился: многверсионного хранения в нем изначально не было. По крайней мере, весной 1998 года - не было совершенно точно, у меня есть пруфы.
Хорошо конечно без многоверсионности, но современная армия разработчиков настолько привыкла к режиму изоляции Read Committed, что другого ничего и слышать не хочет (не все, есть и любители Repeatable Read). Вот версионнирование и нужно чтобы делать Read Committed/Repeatable Read.
На самом деле, версионирование для этого не нужно. Уровни изоляции READ COMMITED и REAPETABLE READ (заложенные в реляционные СУБ изначально) вполне поддерживались СУБД 90-х безо всякого версионирования: изоляция транзакций поддерживалась наложением блокировок. Если делать транзакции короткими, то конфликтов за заблокированные записи получается мало. Ну, а в ведущие СУБД внедрение механизма поддержки версий на основе теневого копирования (это - где-то начало 00-х) позволило ещё лучше избегать конфликтов при блокировках без усложнения схемы хранения записей в БД.
О том, что многоверсионность в подсистеме хранения записей не дает решающего преимущества, говорит судьба единственной (AFAIK) коммерческой СУБД того времени, поддерживавшей эту возможность - Interbase: занять серьезное место на рынке СУБД она не смогла.
PS Если обсуждать эту тему дальше, то придется вспоминать много истории, которая, не думаю, чтобы вам (и многим другим) была интересна. К тому же, в контексте импортозамещения, в котором ведущие СУБД enterprise-уровня потребителям недоступны, это ещё и не имеет практического смысла. Поэтому предлагаю осуждение закончить.
Но если интересно - готов продолжить.
Блокировочники проиграли в OLTP субд еще 15 лет назад, когда майкрософт сдалась и внедрила версионность в MSSQL сервер, который был последним серьезным оплотом блокировок.
А так-то блокировать апдейты пока идет чтение - это верх неоптимальности
Ну, раз вы хотите истории - получите.
Многоверсионники с хранением версий внутри основного хранилища проиграли в OLTP ещё лет двадцать назад, когда Borland отчаялась сделать деньги на купленном когда-то ей Interbase и отдала его исходники в общее пользование. А заодно - отказалась от своего ПО промежуточного уровня под названием BDE. BDE работал с SQL-серверами как с файловыми БД, а потому держал курсор открытым на протяжении всей работы с выборкой в GUI (например - во время редактирования записи на форме Delphi). Такая логика работы действительно требовала для хоть сколь-нибудь эффективной многопользовательской работы многоверсионника. Но времена изменились, и уже в Delphi 5 работа с MS SQL была сделана через ADO и по-нормальному, в духе CQS: данные быстро выбирались на клиент, там как надо правились через GUI, а потом изменения отправлялись командами на сервер. И, насколько я понимаю, в то время PostgreSQL в этой битве ещё не участвовал.
А поддержку изоляции на уровне снимков в MS SQL тоже сделали, пусть чуть позже, но уже по уму, малой кровью: не меняя схему хранения - через копирование в другое место старых данных при их перезаписи. Т.е. преимущество в виде отсутствия необходимости в сборке мусора в основном хранилище никуда не делось, а накладные расходы на поддержку нескольких версий тратились только на тех, кому они были реально необходимы.
MySQL вот блокировочник и живёт себе, не особо тужит. Хоть я и «голосую ногами» за переход постгрес.
Мифы и реалии «Мультимастера» в архитектуре СУБД PostgreSQL. Часть. 2