Python conf in Kharkiv, Nov 16 with Intel, Elastic engineering leaders. Prices go up 21.10

Реальный пример использования Spring Global Lock

Меня зовут Максим Вороной, я — Consultant, Engineering в GlobalLogic (Харьков). С 2000 года я занимаюсь архитектурой ПО, в круг моих интересов входят распределенные и облачные системы, а также построение систем с элементами искусственного интеллекта. Кроме этого, я веду учебный курс для разработчиков по современной архитектуре программных приложений.

Недавно во время GlobalLogic Kharkiv Java Conference 2018 я сделал доклад об использовании Spring Global Lock и написал эту статью на его основе.

Как мы получили проект

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

Специалисты южной страны сказали: «Да! Точно! Мы знаем, в чем причина!». Разработчики добавили парочку временных задержек в коде, установили для пользователя ограничение на длительность сеанса работы, сказали: «Теперь все работает!», — и выпустили второй релиз. Европейская страна проверила, и оказалось, пока в системе два человека, — все прекрасно, а на десяти начинаются уже известные проблемы.

Южная страна выпустила второй, третий релиз, разработчики добавили кое-что «волшебное»: еще больше увеличили задержки, добавили пару лишних инстансов серверов JBoss. Тестировщики опять добросовестно проверили базовые бизнес-сценарии, по которым все работало. А европейская страна сказала: нет, ребята, не работает — пожалуй, на этом мы с вами и расстанемся.

Так проект попал в Украину. В начале мы восторгались «классным» кодом: его прекрасным колоритом, наличием задержек и даже изобретением пузырьковой сортировки. По словам заказчика, в целом в системе все было хорошо — кроме того, что многопользовательский сценарий не работал, и всего лишь надо было подправить код.

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

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

Case Study: книжный магазин

Допустим, существует простой сценарий покупки книги. Пользователю необходимо выбрать 2-3 книги из большого списка, сделать заказ, указав атрибуты платежа. Сервер производит валидацию — например, проверяет, имеется ли в наличии на складе книга, которую пользователь готов оплатить. И после этого совершает транзакцию покупки.

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

Как было

Дизайн таблиц базы данных для задачи тривиальный:

Таблица Store содержит все доступные для покупки книги с первичным ключом isbn.

Таблица Payment содержит результат совершенной покупки, где: payment — первичный ключ, amount — сумма, которую нужно заплатить за приобретаемые книги, transaction — атрибут, отправляемый банком.

Таблица BookOrder, реализует связку 1:N между Store и Payment (1 платеж за N книг).

Южная страна с давними традициями программирования написала следующий код (для простоты приводятся только используемые SQL-запросы в порядке выполнения):

public void buyBooks(List<Book> books, Payment userPayment){
// Validate:
final String checkSql =
"SELECT 1 FROM Store WHERE isbn = ? AND amount > 1";
// Modify business entities
final String insertPaymentSql =
"INSERT INTO Payment (amount, transaction) VALUES ...";
final String insertOrderSql =
"INSERT INTO BookOrder (payment, isbn) VALUES ...";
final String updateStoreSql =
"UPDATE Store SET amount = amount -1 WHERE isbn = ?";
}

Валидация заключается в простом запросе: SELECT 1 FROM Store WHERE isbn = ? AND amount > 1. Таким образом мы проверяем, имеется ли книга на складе. Затем создаем «платежку» в таблице Payment, добавляем новую запись в таблице BookOrder и в финальной строке кода уменьшаем количество экземпляров книги на складе на единицу.

Три подхода к решению

Реализация уровня junior

Стоит подчеркнуть, что эта проверка — несколько искусственная, но она наиболее эффективно показывает проблемы с данным кодом. Наша команда получила его именно в таком виде. Для начала мы поручили исправление проблемы программисту junior-уровня, который предложил решение, добавив в начало следующее описание: @Transactional

@Transactional // (!)
public void buyBooks(List<Book> books, Payment userPayment){

В этом месте читатель, должно быть, улыбнется: понятно, что проблема решена не была.

К счастью, код-ревью у нас поставлен хорошо, и опытный старший программист сказал джуниору: «Парень, это не поможет».

Способы организации транзакций

Тут я напомню о двух из пяти вариантов изоляции транзакций в базах данных:

Read committed — транзакция читает только фиксированные данные (от завершенных транзакций). Этот уровень изоляции используется в большинстве существующих баз данных по умолчанию (Oracle, SQL, MySQL и пр.). Оптимальное соотношение между производительностью и изоляцией, но подвержено т. н. фантомному чтению.

Serializable — гарантия невмешательства в данные. Минус — время ожидания пользователя в очереди увеличивается.

Любые «игры» с описанными уровнями изоляции, к сожалению, наш код не спасали. Даже если мы выберем Serializable, следующая строка останется проблемным местом системы:

UPDATE Store SET amount = amount -1 WHERE isbn = ?

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

Более удачная реализация — уровень middle

Как можно было бы устранить проблему в коде? Мы оставляем сигнатуру транзакции, остальные три запроса остаются как есть, но мы добавляем одно волшебное ключевое слово (в синтаксисе Oracle):

@Transactional
public void buyBooks(List<Book> books, Payment userPayment){
// Validate:
final String checkSql =
"SELECT 1 FROM Store WHERE isbn = ? AND amount >= 1 FOR UPDATE";
// Modify business entities
final String insertPaymentSql =
"INSERT INTO Payment (amount, transaction) VALUES ...";
final String insertOrderSql =
"INSERT INTO BookOrder (payment, isbn) VALUES ...";
final String updateStoreSql =
"UPDATE Store SET amount = amount -1 WHERE isbn = ?";
}

Существует, похожий синтаксис для JPA спецификации:

em.find(isbn, LockMode.PESSIMISTIC_READ )

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

Анализируем дальше.

К сожалению, даже к этому коду есть множество претензий.

Рассмотрим процесс покупки поэтапно:

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

Зададимся вопросом, что в частности произойдет, если получение подтверждения банка займет более 40 секунд (время, установленное для обрыва транзакций в большинстве баз данных по умолчанию)?

Если за 40 секунд транзакция не закончилась, база данных «решает», что система подвисла и прерывает транзакцию. Это время можно увеличить, но смысл?.. Банковский https-запрос, который выполняет проверку платежеспособности данного пользователя, может существенно поломать код.
Следующий вопрос: что мы будем делать, если производительности этого кода недостаточно? Он компактен и прекрасно ложится в уровень архитектуры небольшого приложения. Но если возникнет увеличение нагрузки — на складе, в закупке, в платежных системах? Одним из решений может стать масштабирование системы на облако и использование микросервисов. Следует повторно оговориться, что наш пример искусственный и с микросервисами мы будем вынуждены разбить монолитный код, описанный в нашем примере.

Реализация уровня Senior

Что же может предложить для устранения этой проблемы «сеньор» или архитектор?

Здесь на сцену выходит поставляемая в базовом наборе Spring библиотека Spring Global Lock, позволяющая полностью решить нашу проблему. Как это будет выглядеть?

Autowired
private LockRegistry lockRegistry;
@Transactional
public void buyBooks2(List<Book> books, Payment userPayment){
    for(Book b: books){
        Lock lock = lockRegistry.obtain(b.getIsbn());
        lock.lock(); // lock.tryLock();
        try{
            doInsertAndUpdate(b);
        } catch (InterruptedException e1){
            //give up
            Thread.currentThread().interrupt();
        } finally{
            lock.unlock();
        } //try
    } //for
}

Есть некоторый класс LockRegistry — создадим его Autowired instance.

Блок finally нужно включать в код обязательно и всегда обрабатывать interrupt exception, так как при принудительном завершении текущего потока код остается валидным.

Покупка книги в данной по версии 2 будет выглядеть следующим образом:

  • в виде JSON-файла приходит список книг, которые хочет купить пользователь, а также информация, достаточная для того, чтобы осуществить платеж;
  • из LockRegistry получаем всем известный java.util.concurrent.locks.Lock (стандартный интерфейс библиотеки Java), на котором устанавливается блокировка — можно использовать по желанию методы lock или tryLock;
  • после этого добавляем классический код: try, catch и finally (finally гарантирует разблокировку по окончанию вне зависимости от ошибок);
  • покупка книг происходит в стороннем методе doInsertAndUpdate.

Для любопытных приведу дополнительно пару зависимостей, как они выглядят в Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-core</artifactId>
</dependency>

Как видите, получился простой и компактный код, гарантирующий отсутствие проблем.

Ключ getIsbn в коде позволит точечно заблокировать только необходимую книгу. Что именно вы используете в качестве ключа для Spring значения не имеет: можно указать глобальный идентификатор для блокировки всей системы либо точечный Isbn, блокирующий одну запись. Spring принимает любой Object, который и будет использован в качестве ключа.

Что под капотом

Моя любимая игра с любыми новыми технологиями и библиотеками — разобраться, что внутри и как это работает. При этом всегда открывается множество интересных вещей. Что же из себя представляет LockRegistry?

LockRegistry в базовой поставке библиотеки SpringLock имеет пять основных вариантов реализации:

  1. PassThruLockRegistry.
  2. GemfireLockRegistry.
  3. RedisLockRegistry.
  4. JdbcLockRegistry.
  5. ZookeeperLockRegistry.

PassThruLockRegistry не делает вообще никакой блокировки — это пустая заглушка, применяется для тестирования.

GemfireLockRegistry — как и RedisLockRegistry — используют соответственно Gemfire и Redis базы для реализации глобальных блокировок.

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

Но что произойдет, если в базе сделана запись о блокировке, а код, который запустил транзакцию, «умер» (был намеренно закрыт пользователем либо завершил работу по ошибке)? Естественно, больше никто не сможет уже заблокировать этот ключ. Для разрешения таких ситуаций разработчики предусмотрели поле when. Поле позволяет ограничить нашу транзакцию по времени.

Но даже наличие поля when не спасает нас от другой проблемы. Рассмотрим диаграмму, взятую из статьи Мартина Клепмана.

На изображении выше вы видите сервис блокировки и два клиента. Таблица Lock в данном случае реализует JdbcLockRegistry.

  • Клиент 1 захватывает и удерживает блокировку в течение длительного времени (допустим, у этого клиента в какой-то момент включился Garbage Collector, который затормозил всю Java).
  • Cервис блокировок отслеживает для нас наступление time-out при помощи поля when и отменяет блокировку. Пока все корректно.
  • В игру вступает Клиент 2. Он забирает на себя блокировку, успешно завершает собственную бизнес-транзакцию.
  • Тут Garbage Collector Клиента 1 заканчивает работу, и Клиент 1 точно так же благополучно записывает свои данные в базу.

И это полное фиаско. Все данные транзакции Клиента 2 были перетерты.

Для разрешения подобных ситуаций в JdbcLockRegistry добавляется целочисленное поле version.

Теперь картина выглядит так:

  • На момент, когда происходит захват транзакции, Клиент 1 получает токен (в примере на картинке — 33).
  • Вступает в работу Клиент 2. Он делает захват уже после обрыва блокировки, ему выдается токен 34, Клиент 2 записывает данные.
  • Garbage Collector Клиента 1 просыпается, но когда Клиент 1 пытается произвести какие-то действия с базой, оказывается что устаревший токен 33, конфликтует с токеном 34. В результате Клиент 1 получает ошибку.

Такой код с полем when и version обеспечивает корректное бизнес-поведение в сложных распределенных сценариях.

Вернемся к рассмотренному ранее компактному рабочему коду:

public void buyBooks2(List<Book> books, Payment userPayment){
    for(Book b: books){
        Lock lock = lockRegistry.obtain(b.getIsbn());
        lock.lock(); // lock.tryLock();
        try{
            doInsertAndUpdate(b);
        } catch (InterruptedException e1){
            //give up
            Thread.currentThread().interrupt();
        } finally{
            lock.unlock();
        } //try
    } //for
}

Где здесь обработка экспирации? Каким образом Spring понимает, что отведенное время закончилось? В Spring ответственность за правила экспирации возложена на программиста (предусмотрена даже возможность ввести вечную блокировку). Можно указать явное описание, в течение какого времени данная блокировка должна быть отпущена. Мы вычеркиваем Autowired-код с простым lockRegistry и заменяем его на ExpirableLockRegistry. В этом случае при конфигурации lockRegistry программисту необходимо будет использовать более гибкий подход, создав Scheduler: в нашем примере это fixedDelay, равный 50 секундам, и метод, который очищает все существующие в системе блокировки, превысившие время ожидания — expireUnusedOlderThan (также с указанием 50 000 миллисекунд).

@Autowired
private LockRegistry lockRegistry;
@Autowired
private ExpirableLockRegistry lockRegistry;
@Scheduled(fixedDelay=50000)
public void cleanObsolete(){
    lockRegistry.expireUnusedOlderThan(50000);//that are not currently locked
}

Теперь давайте уделим еще немного внимания реализации ZookeeperLockRegistry .

Apache Zookeeper — иерархическое распределенное хранилище информации. Это не база данных, но может использоваться в качестве базы (хотя это и не совсем удобно). Zookeeper активно используется при реализации крупных распределенных систем, и, например, широкоизвестная Apache Kafka, хранит в нем такие важные для себя настройки как:

  • список разрешенных пользователей;
  • кто выступает лидером;
  • какой уровень избыточности и пр.

Информация в Zookeeper организована в виде дерева, в узлах которого могут храниться какие-либо данные. Zookeeper — практически «неубиваемая» система, которая поднимает несколько экземпляров, для обеспечения требуемого уровень репликации информации. В терминах CAP-теоремы Zookeeper — это типичная СР-система (Consistency — Partition Tolerance): хорошо распределяется в сети и гарантирует, что информация будет надежно сохранена.

Таким образом использование ZookeeperLockRegistry подразумевает, что информация о блокировках будет храниться в распределенном иерархическом хранилище.

Как еще можно использовать Spring Global Lock

Современные распределенные системы постоянно вынуждены решать одну и ту же задачу — «договориться» между собой, кто из них лидер. Это необходимо при проектировании микросервисов, создании fault-tolerant или high-available кластеров и прочих подобных задачах.
Расскажу об алгоритме Algorithm of Leader Election, который работает в нескольких компонентах Spring Cloud (более подробно можно прочитать в Spring Cloud Release Notes).

Реализация алгоритма спрятана в компоненте LockRegistryLeaderInitiator.

Эта компонента:

  • Использует глобальную блокировку с обработкой time-out.
  • Допускает существование только одного лидера.
  • Допускается отсутствие лидера в течение коротких промежутков времени.

Один из примеров применения Algorithm of Leader Election — Zookeeper (написанное на чистой Java без использования Spring). Zookeeper не использует Spring Lock, но с идеей Spring Lock мы можем порассуждать, каким образом реализовывать механизм leader election.

В более простых приложениях роли явные прописываются администратором системы. Например, вы организуете транзакцию в схеме Master-Slave. Данные распространяются от Master к Slave по явно прописанным правилам, кто Master, а кто Slave.

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

В нашей мысленной реализации мы берем Spring Lock и на некоторое время блокируем всю систему, используя в качестве ключа какой-либо абсолютно глобальный идентификатор. Когда система блокируется, один из экземпляров Zookeeper прописывает себя главным. Остальные, не имея возможности захватить блокировку, ждут. Таким образом, реализуется чрезвычайно простой алгоритм выбора лидера.

Почему Zookeeper является именно СР, а не АР-системой? Именно потому, что на момент блокировки мы делаем временно всю систему недоступной. Система глобальной блокировки гарантирует, что если во время выбора какой-либо экземпляр прекратил работу, блокировка со временем будет снята и другой лидер выдвинет себя в качестве главного. Минус такого подхода — на какое-то время (время захвата Global Lock или восстановления по time-out) система может оказаться недоступной.

Подведем итоги

Плюсы

Использование Global Lock — это всегда гарантированное распределенное окружение (например, микросервисное). При этом не стоит забывать о решении уровня Middle — это хорошее, добротное решение. Если вы используете чистую базу данных и есть возможность делать блокировку на уровне таблиц, на уровне записи, оно имеет полное право на жизнь.

Минусы

К сожалению, задержки все же присутствуют и могут быть довольно большими (например, в случае многократного падения системы по тайм-ауту). Global Lock может замедлить систему — особенно при неудачном выборе ключа — и об этом нельзя забывать.

Если вам интересно развитие в Java-направлении, рекомендую также посмотреть материалы GlobalLogic Kharkiv Java Conference 2018.

LinkedIn

Лучшие комментарии пропустить

Не хотелось бы никого расстраивать, но «сеньор», написанное в кавычках — это верно.

Решение на global lock-ах в данном случае — это всего-лишь технически более «продвинутая» версия того, что сделали наши южные коллеги. Задержки и тормоза будут, а с ростом нагрузки уже вы сами окажетесь в роли южных программистов.

Я бы посоветовал Вам обратить внимание на понятие Eventual consistency в распределённых системах (тем более, если Вы учите других людей «современной архитектуре»).

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

Через рік чекаємо статтю від іншого, тепер вже Київського (Львівського, Дніпровського, Одеського) інжинірінг консультанта про те, як до них попала система, конкаренсі в якій вирішили пофіксити глобал локами, як воно почало тупити під ше більшим навантаженням і як вони це все переписували.

96 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

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

Один из лучших источников (главное часто обновляемый) это microservices.io
Задачу с магазином можно решать Saga, Event Sourcing, Transaction log tailing кроме того, как описано у меня применим Distributed Lock и даже Optimistic Locking (при условии что мы поменяем сценарий покупки)

Той хто робив розподілені транзакції на DCOM/COM+/CORBA до кінця життя запам’ятає яке вони зло. І навіть зміна технологічної платформи на JAVA не робить їх кращими.
Звичайно якщо клієнту горить і на фундаментальні зміни він не готовий таке рішення до певного масштабу може жити. Але крутим досягненням я б це не вважав, так ще один костиль в коді.
Думаю що в такій ситуації я б використовува Saga Pattern
blog.couchbase.com/...​using-microservices-part
microservices.io/patterns/data/saga.html
Але він швидше за все вимагатиме суттєвого редизайну системи.

1) В комментариях я уже писал, что Saga также внутри использует блокировку.
2) Для того чтобы делать сознательный выбор, программисты должны знать о разных подходах. Целью моего доклада было рассказать людям имено о блокировках, а не как решать пустяковую задачу. Простой пример, доходчиво объяснил откуда берется поле when, version и как реализовать в спринге разблокирование.

В saga гранулярність блокування та scope значно менші.

По-моему, основное сомнительное место здесь:

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

Если транзакция в serializable (для простоты остановимся на этом уровне), если прочли из базы, что книга есть, и успешно выполнился декремент, то основная часть транзакции уже состоялась.
Дальше зависит от движка. Поведение Oracle я тут не знаю совсем. Если клиент A декрементирует количество на складе, а клиент B затем попытается сделать то же самое (оба, для простоты, в serializable транзакции), что будет? Если движок не позволяет попросить немедленного облома (отказа update для клиента B), а приводит к завису в ожидании... значит, он вообще негодный для такой задачи, как интернет-магазин. (Насколько реально его упоминание? Или это только для примера?) Судя по рассказу про таймаут 40 секунд, вы как раз нарвались на что-то подобное? И что, оно не регулируется?

Теперь вы вводите менеджер блокировок... и фактически заменяете им те самые немедленные отказы, которые отсутствуют в БД. Я тут ещё начал писать про странную логику (неадекватную для интернет-магазина), но выдалил, потому что сказано, что интернет-магазин это аналогия для под-NDA деталей. Ок, пусть так. Но что вы будете делать с занятой блокировкой? Дальше вы пишете про сторонний «чистильщик блокировок» с таймаутом 50 секунд. И что, стоило этим заменять тот самый 40-секундный таймаут?

Как-то всё больше вопросов, чем ответов. И обоснования введения новой суперподсистемы не очень видно.

вы как раз нарвались на что-то подобное?

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

Теперь вы вводите менеджер блокировок.

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

Эээ...
Нормализуем все count-ы и amount-ы (как и должно быть), — клиенты перестают писать в Store, — boom shaka laka.

Перед тем как высокомерно писать про «южные страны» в зеркало посмотрелись бы...

Ніфіга не зрозуміло, чим lock запису в базі даних на application — рівні відрізняється від того ж lock-у запису на рівні СУБД, яка вміє робити row-level lock. По-факту, при однаковій тривалості транзакції — нічим. Запис залочений, гуд бай.
Тому народ і пише, що у високонавантажених системах так не роблять.

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

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

У нас в прод. базе зарегистрировано сто тысяч клиентов, в день приходит около 50 тысяч

Предложение решение и статья написана основываясь на принципы CP(Consistency and Partition tolerance) из СAP теоремы, упущен момент что данное решение not scalable and highly available in distributed systems due to Global Look approach that would be a bottleneck under the high load. I highly recommend to take a look into the course about Distributed cloud applications: www.edx.org/...​uted-cloud-applications-3

CP решения как раз хорошо скалируется. Иначе бы мы не писали «Partitioned» Определение скалирования: «...capability of a system, ... to handle a ... work, ... to be enlarged to accommodate that growth» С добавлением новых инстансов мы остаемся CP системой. Но статья не об этом, я скопирую текст из предыдущего комментария:

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

p.s. И я заскриню ваш комент для следующих выступлений как раз про CAP теорему. (ну конечно без упоминания имен)

Можно и с упоминанием имени и приглашением на выступление, дайте знать только где и когда.
И я попробую вас переубедить, что в ваше предложенное решение не идеально, оно не масштабируемое и вы игнорируете базовые принципы построения дистрибутивных систем.
По сути, в существующую легаси систему вы прикрутили глобал координатор/локер который синхронизирует состояние дистрибутивной транзакции(Banking processing + persist database state), да может вы и улучшили original solution, но это и осталось узким горлышком и при большом количестве запросов, не масштабируемое.
Если ваше решение работает и удовлетворяет потребности бизнеса, вы достигли определенного баланса при N count requests and users, but not scalable and etc

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

Ну, врешті-решт, автор зробив проект, заробив бабло, а далі хоч потоп — вони ж не будуть цю систему саппортити. Ринок автора нагородив, тому має право вважати себе правим.

Вы все время путаете понятие масштабируемости и доступности. Масштабируемость — это возможность улучшить параметры (давайте для ясности сузим понятие до производительности) системы путем добавления новых инстансов. Когда мы проектируем CP систему мы сознательно принимаем решение о том что мы гарантируем консистентность © данных и возможность роста за счет (P), но конечно страдает доступность (A). Но облачный автоскейлинг продолжает работать добавляя/уменьшая инстансы и мы готовы встречать любую нагрузку. В реальной бизнес задаче никого не волнует что клиент получит ответ через 10 сек или 2 минуты, значительно важнее защита полученых данных.

Как и обещал, вот здесь я буду рассказывать о вашем коментарии:
GlobalLogic Kharkiv
Java Conference 2019
June 9
Доклад: 16:40 — 17:30 «Ignorance of CAP Is Not an Excuse»

Просто очепятка:

Read committed — транзакция читает только фиксированные данные (от завершенных транзакций). Этот уровень изоляции используется в большинстве существующих баз данных по умолчанию (Oracle, SQL, MySQL и пр.).

не знаю як в oracle, але в mysql дефолтний — repeatable read. В mssql, postgresql — read commited.

Спасибо за внимательное прочтение статьи. Я не думаю что это стоит исправлять особенно в разрезе того что и Repeatable reads и Read Committed увы допускают phantom reads — что будет приводить к ошибкам во время валидации.

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

Лютый лайк этому комментарию. Первый конструктив за день!

то есть заказчик пошел к одним, там накидали кое-как (а что, потом пофиксим), потом другие решили фиксить, тоже прикрутили что-то (а что, потом улучшим)? Ок

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

Так они же переделывают, одно быстрое решение уже пытались использовать (предыдущая команда).

Предыдущая команда вообще не айс 😁

Нет ничего постоянней чем временное

Так я об этом и написал. Если система работает нормально и в эти локи не упирается то можно так и продолжать работать.

но не стоит рассказывать что это хорошее синьйор решение. Это костыль, подпорка в неправильно спроектированной системе

Я как-то был на собеседовании в местной финтех компании и мне там рассказали, как круто они разрулили concurrency используя Redis Distributed Locks. Хз какие у них там нагрузки. Так что разные люди приходят к подобным решениям и считают их норм вариантом.

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

Есть какие-то собственные публикации? Я б почитал (раз докладами занимаешься на эту тему)

Есть только запись доклада, думаю оформить в виде статьи когда будет время, уже после НГ. И это первое выступление еще много над чем работать нужно, но тем не менее. Я рассказывал о том, как мы решали проблему atomic writes состояния и события с MongoDB www.youtube.com/...​9SGQdBS8wKrKyMYP_XxUXszq1.

(в синтаксисе MS SQL):

@Transactional
public void buyBooks(List books, Payment userPayment){
// Validate:
final String checkSql =
„SELECT 1 FROM Store WHERE isbn = ? AND amount > 1 FOR UPDATE”;

Тут явно одно из двух или Oracle или убрать FOR UPDATE.

Вы совершенно правы, сейчас исправлю

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

А взагалі читаєш отаке і сум бере за нашу аутсорс долю яка складається от с таких от переписувань після наших коричневих братів.

До речі брати теж бувають різні. Просто країна велика, зрозуміло що вайтишників там багато (бо їх там всіх багато)

А в продуктових хіба інакше? Бізнес завжди шукатиме баланс між часом(грошима) до виходу на ринок і можливими ризиками. І в більшістю випадків менший час(гроші) виграють.
Якийсь рік тому проводив оцінку коду купленого стартапу. В них в 2017-му році використовувалися 2-гі рельси без комерційної підтримки, патчів і тому подібного. Це приблизно як би досі сидіти на Visual Basic 6.0.
Але нічого. Оригінальні розробники заробили на свою піна коладу і садочок біля нової хати. Клієнт успішний продукт з плітіжоспроможними клієнтами. А ми проект по переносу цього всього на сучасні і підтримувані версії з мінімальним давнтаймом.
Якщо ви читаєте інженерні блоги twitter, airbnb, uber, facebook, google то побачите, що вони приблизно кожних 3-5-10 років(чим зріліша компанія тим рідше) повністю редизайнять свої системи, бо старі костилі не задовільняють їхні нові потреби.

Суть в тому що в продуктових ми самі то легасі написали і далі самі переписуємо.

А в аутсорсі написали коричневі брати і сам замовник додатково дає купу обмежень.

Через рік чекаємо статтю від іншого, тепер вже Київського (Львівського, Дніпровського, Одеського) інжинірінг консультанта про те, як до них попала система, конкаренсі в якій вирішили пофіксити глобал локами, як воно почало тупити під ше більшим навантаженням і як вони це все переписували.

А ще через рік якусь англомовну статтю де розпишуть чому не треба віддавати продукти в аутсорс

Где вы видели в статье что мы используем глобальные блокировки? У нас используется распределенная блокировка, а название продукта действительно Spring Global Lock, что никак не связано. В свою очередь следует отметить, что распределенные блокировки используются в большинстве распределенных систем, посмотрите ниже большой thread где обсуждается, например, зачем нужна распределенная блокировка в Mongo

Там вообще забавный тред вышел. Один участник говорил о распределенных транзакциях, а второй о распределенных блокировках. Кстати, сам термин distributed lock в доках монги найти не получится :).

Mongo

))))))))

Дурная затея лочить таблицы на время ожидания ответа от сторонних сервисов. Куда интереснее сделать все мультизапросы на изменение данных транзакционными и атомарными, добавить в таблицу дополнительный столбец reserved который больше amount на % погрешности нерасторопности хомячков (делаем reserved минус 1 если товар положили в корзину), если купили — делаем amount минус 1 при условии что оно больше нуля, если нет — ставим в очередь и периодически чЕкаем когда появится, не появилось за какой-то разумный промежуток времени — вертаем гроши и извиняемся. Положили в корзину и не купили в течении разумного времени — помечаем в корзине что товар закончился и +1 в reserved. Разумное время ожидания можно корректировать в учётом оставшегося количества товара. Ну и логично знать что уже в корзине есть, когда и в каком количестве туда покладено. Так продашь всё без остатка, но пару клиентов могут остаться недовольны в «чёрную пятницу» (положил в корзину, долго чухался-оплачивал и товар закончился), зато 100500 счастливых хомячков будет, на крайняк пару возвратов денежных средств таки придётся автоматически вернуть. Но ты продал всё, без нервов, без локов таблиц в БД и получил довольных хомячков )))

пару клиентов могут остаться недовольны в «чёрную пятницу» (положил в корзину, долго чухался-оплачивал и товар закончился)

с авиабилетами такое часто и густо, пока дошёл до оплаты, по старой цене больше нет, есть за +50

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

Укрзалізниця продаёт билеты за месяц, но после выбора мест у тебя есть только 15 минут, чтобы почухаться и оплатить, иначе корзина заказов превратится в тыкву.

Где вы видели лок таблиц?

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

Ну не знаю ... я б в реальний проект жоден би спосіб не використав.
Ну, хіба, може, початкове, бажне, рішення від жителів південної країни.
(проскрипіти зубами і визнати, що наврядчи 100 користувачів одночасно будуть купувати одну книгу... а якщо і будуть, то на склад мали б підвезти їх побільше)

Junior та middle варіанти тримають відкриту транзакцію в БД, можливо, більше 40 секунд. Це вже привід відмовитись від таких рішень.
Senior рішення робить глобальний лок на цю книгу, і інші користувачі не зможуть перейти до її оплати (привіт, 504 gateway timeout), поки попередні не закінчать оплату, навіть якщо таких книг валом (що, в певному сенсі, навіть гірше, ніж сценарій від індусів).

Рішення, яке б запропонував я .... це, банально, резервувати товари перед оплатою. Реалізацій можна запропонувати кілька.
Наприклад, замінити перший select на щось схоже на
UPDATE Store SET amount=amount-1, amountReserved=amountReserved+1 WHERE isbn = ? AND amount>0
і якщо affected rows > 0, можна продовжити оплату.
(звісно, решту логіки доведеться відповідно поправити).

Щодо GlobalLock в цілому — пізнавально вийшло :)

После чего вся остальная бизнес-логика может работать с пустым складом, поскольку книга уже может быть куплена другим пользователем.

Непонятно, почему уровень изоляции serializable не поможет. Если вся логика (проверка и уменьшение amount) будет в одной транзакции и все транзакции к базе с уровнем Serializable, то никаких гонок данных не будет, на сколько я понимаю. Другое дело, что serializable убьет производительность, но это другая проблема.

SELECT 1 FROM Store WHERE isbn = ? AND amount >

небольшая опечатка, наверное amount >=1

Вы правы, сейчас буду исправлять

Если правильно понял, то вы заставляете пользователя ждать завершения предыдущей транзакции не удостоверившись сначала, что, возможно, результат предыдущей транзакции никак не повлияет на следующую. Проще говоря, если на складе 10 книг и пользователь 1 хочет купить 1, то пользователь 2, который тоже хочет купить 1, должен зачем-то ждать завершения предыдущей операции. Или может я упустил что-то..?

Все правильно понял. Автор написал и сам не понял о чем. Залокать товар по айди, пока проходит платеж, это, простите, пипец. Я тут представил что амазон, например, так продавал бы книги (и не только книги)...

В очередь, сукины дети)

В амазоне блокировки все таки используются (но реже) так как там применен паттерн Event Sourcing, который дает существенное ускорение т.к. вместо UPDATE есть только INSERT. Для избавления от задержек на чтение иногда приходится делать т.н. snapshot (иногда называют baselining) и вот тогда все равно нужно включать блокировку.
Если наберется достаточно голосов «за» могу рассказать об использовании этого паттерна в разных продакшен системах включая Suro, Kafka.

ты вообще представляешь что такое глобальный лок на десятках тысяч инстансов?

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

Я о том что запрос на лок этой записи может прийти из тысяч инстансов. Что локается запись тут все поняли.

Вот скажите, где по тексту хоть небольшой намек на существование в нашем коде задержки со стороны лока? Описанное поведение говорит, что система бросает exception когда не может приобрести блокировку.
Далее все базы данных (!) используют блокировки (посмотрите thread где объясняется как Mongo вынуждена их использовать) и при этом никакие инстансы не лочаться, тем более тысячи.

Блин, да вот пример. Магазин по продаже книг. Достаточно крупный, работающий, например, на трех дата центрах (чтоб быть поближе к клиенту) с одним складом. Выходит новая книга какого-нить Гарри Поттера или еще какая-то хрень популярная. Как только книга появляется в продаже — сразу куча народа бегут ее покупать. Кладут в корзину, делают оплату. У кого-то оплата не проходит с первого раза, у кого-то банк подозревает фрод и проводит проверку, так что оплата занимает секунды. Ты вообще понимаешь какой хаос начнется когда тысячи человек попытаются купить эту книгу одновременно?
Отлично будет, пока один сидит и тупит с пейментом, тысяча человек получает отказ в возможности положить книгу в корзину. А учитывая что все это ранится на нескольких датацентрах, то жопа будет еще глубже, т.к. будет еще и кросс-региональное получение лока.
Мне не надо смотреть что ты там пишешь про монгу. Лок при репликации это совершенно не то же самое. Когда идет запись в мастер и потом слейвы, мастер и слейвы не ходят на пеймент процессор (да и вообще на внешние сервера не ходят). Репликация происходит в 99.99% случаев в одном датацентре. Мало того, даже такое решение далеко не для всех проектов работает и тогда жертвуют C (из CAP)

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

У меня сильное подозрение, что с магазином тут была просто неудачная аналогия. Вот этот комментарий как-то совсем не соответствует шаблону нагрузки обычного магазина. Тут какой-то SaaS сервис с заметным разделением БД на слабопересекающиеся предметные «домены», но относительно высокой плотностью доступа к информации в каждом из них. Например, это может быть какая-нибудь «бухгалтерия онлайн» для фирмочек размера «два продавана, кассир и директор». Для них такое решение без «корзины» и прочих мер — вполне может пойти, за один объект одновременно более 3-5 конкурировать не будут, и могут между собой легко разобраться.

Возможно. Это многое объяснит (кроме самоуверенности автора и причины выбора такого плохого примера)

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

результат предыдущей транзакции никак не повлияет на следующую

может быть рассмотрен как вариант оптимизации, но увы он требует лишнего обращения в базу. Допустим у нас 11 пользователей и один уже проверил что книг на складе 10, значит можно не блокировать, а второй человек (и остальные 9) тоже решили что все хорошо и ринулись покупать — в результате 1 останется без книги, но с опустошенным банковским счетом.

Не вижу проблемы лишнего обращения в базу. Точнее, она ничто, по сравнению с возможным ожиданием в 50 сек... В комментах тут предлагались вполне простые и резонные решения. В частности, резервирование тех книжек, за которые сейчас происходит оплата. В случае с 10 книжками и 11 покупателями только 1 будет ждать, а не 10.

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

Вы описываете сценарий с точки зрения покупателя и такое поведение оправдано. Блокировки все равно будут в любой реализации когда вы начнете делать покупку (перекладывать из резерва).
Но давайте я проясню структуру статьи т.к. большинство комментаторов вкладываются в попытку улучшить именно этот алгоритм:
— Есть задача (не раскрою тайну NDA — она в десятки раз содержит больше сущностей, чем в примере 1 книги и эти сущности связаны между собой и валидация связей действительно требует блокировки)
— Есть возможность рассказать людям о GlobalLock
— Ваш выбор рассказать на 40 мин докладе о десятке сущностей (всем плевать) или об 1 книге и показать как GlobalLock можно применить?

Есть такое понятие, как Saga. И вот норм видео на эту тему www.youtube.com/watch?v=7dy5WPSv2DQ. В больших системах глобальные блокировки убивают производительность и их стараются избегать всеми возможными способами. Об это большинство комментариев тут.

www.cs.cornell.edu/...​/2002fa/reading/sagas.pdf почему-то не открывается. Вот из кеша webcache.googleusercontent.com/...​&cd=5&hl=en&ct=clnk&gl=ua.

Вы не поняли. Статья не про покупку книг. Вы как эксперт оценили какой-то пример и вынесли суждение (я даже не буду вступать в спор о том применимы GlobalLock / Saga или есть лучше microservices.io/i/data/saga.jpg — там видно про локальные транзакции, которые все равно блокируют), но чтобы стать экспертом вы должны были где-то узнать, какие в принципе есть решения. Статья дает аудитории знания про существование механизма и несколько раз акцентирует: пример с книгами служит лишь для того чтобы рассказать о GlobalLock и его недостатках.
Я бы понял ваши усилия если бы они были направлены на то, чтобы предложить аудитории более удачный пример как просто объяснить механизм блокировок, зачем им нужна версия и дата экспирации. Но нет, вы тратите время на то чтобы показать как бы вы решали действительно пустяковую задачу используя весь арсенал современных технологий.

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

Блокировки все равно будут в любой реализации когда вы начнете делать покупку (перекладывать из резерва).

не надо в примере с магазином и книгами никаких блокировок

показать как GlobalLock можно применить?

показать как глобал лок НЕ надо применять

Боюсь вы банально путаете понятия — продукт от Spring GlobalLock и паттерн Distributed Lock, и для того чтобы показать как работает паттерн я использовал пример с книгами. Можете рассказать лучше — публикуйтесь

я — Consultant Engineering

Может Consulting Engineer?

Не понятно что там у вас за задача была — возможно GlobalLock оправдан (другими словами распределенная транзакция).

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

Если вы выбираете CP from CAP то некоторые ваши запросы будут failed (system not available), что часто неприемлемо для конечного пользователя.

В микросервисах GlobalLock (или Paxos, Raft, etc.) не используют или используют очень редко для весьма специфичных задач типа выбора мастера в кластере с кучей нод. В остальных случаях избегают распределенных транзакций — есть куча способов.

В статье так и написано — в продакшен коде мы остались на SELECT FOR UPDATE.
Paxos — это всего лишь протокол достижения консенсуса, микросервис может его использовать а может (поскольку не может гарантировать, что другой микросервис написанный в другой фирме о нем вообще знает) использовать блокировку. Не проанализировав требования и хотя бы выслушать почему коллектив(!) принял такое решение нельзя категорично говорить: «неприемлемо», «весьма специфичные задачи» ...
И кстати — лидер распределенных баз данных Spanner от Google прекрасно поддерживает распределенные транзакции. MongoDB долго держались и вынуждены были недавно добавить поддержку распределенных транзакций — т.к. очень много задач действительно решаются проще.

Распределенные транзакции в MongoDB? Та ладно. Они добавили ACID транзакции между документами. И очень не рекомендуют их использовать по понятным причинам 🙂.

Когда происходит репликация Master-Slave внутри одной транзакции на Mongo разработчики вынуждены подключать механизмы распределенных транзакций (пусть вы их и не видите как пользователь)

При чем тут репликация?

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

Говорит о том, что распределенные транзакции доступны клиету монги. Что есть неправда.

Когда база сконфигурена на Master-Slave поддержку (читай репликация) и программист говорит: я хочу запись WriteConcern="majority" Монга вынуждена быть уверена что запись (результатов транзакции) произведена в том числе и на Slave инстансы. Значит разработчики вынуждены задействовать алгоритмы распределенных транзакций.
docs.mongodb.com/...​n-w-majority.bakedsvg.svg

Это совсем в другую сторону и не про распределенные транзацкии. В монге нет поддрежки распределенных транзакций.

Хотя, тут может быть такое, что мы говорим о разных вещах. В моем понимании распределенная транзакция (Two Phase Commit) это когда я, как разработчик, хочу в одной транзакции записать данные в могну, sql server и rabbitmq. Так вот такой поддержки нет ни в могне ни в ребите.

А majority достигается достаточно просто — отслеживанием на какие ноды операция реплицировалась по timestamp операции. Каждая нода может легко предоставить timestamp последней реплицированной операции. Это не есть распределенные транзакции.

смею вас уверить сам механизм репликации внутри реализован как механизм распределенной транзакции. Представим 1-master-1-slave конфигурацию, что должно произойти на мастере если slave сломался? Мастер (не достигший требования о majority) вынужден начать rollback на своей стороне и вернуть программисту ошибку.

Давайте определимся мы про внутренности репликации монги или о поддержке распределенных транзакций для клиентов монги? Или разница от вас ускользает?

Ну конечно «внутренности». Я это сразу и написал в моем комментарии выше:

разработчики вынуждены подключать механизмы распределенных транзакций (пусть вы их и не видите как пользователь)

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

Мда. Возвращаясь к изначальному комментарию,

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

Поддержка {w:"majority"} у них появилась задолго до релиза 4.0, в котором появились транзакции меду документами, которые вы перепутали с разпределенными и начали мне рассказывать про роллбеки при отваливании secondary ноды. Хз зачем все это было. Но, так и быть, убедили. Ушел пить пиво.

Хорошего вечера. Мои коментарии с ролбеками были к тому, что если вы не видите локов, это не означает что их нет. Их от вас спрятали под капот. На сегодняшний день известно лишь ограниченное число алгоритмов в которых реально избежать блокировок.
В любом случае спасибо за дискуссию — адекватная техническая беседа увы редкость.

Разве я говорил, что не вижу блокировок? До WireTiger там вообще вся БД локалась при записи, если я не ошибаюсь. Мы же про распределенные транзакции говорили. Но, да, спасибо.

Не хотелось бы никого расстраивать, но «сеньор», написанное в кавычках — это верно.

Решение на global lock-ах в данном случае — это всего-лишь технически более «продвинутая» версия того, что сделали наши южные коллеги. Задержки и тормоза будут, а с ростом нагрузки уже вы сами окажетесь в роли южных программистов.

Я бы посоветовал Вам обратить внимание на понятие Eventual consistency в распределённых системах (тем более, если Вы учите других людей «современной архитектуре»).

Дякую, було цікаво. Особливо сподобалось про відому південну країну :)

Класна стаття і по ділу. Не то що всяка водичка яка тут переважно буває.

Подписаться на комментарии