Стас Выщепан @gandjustas
Умею оптимизировать программы
Information
- Rating
- 270-th
- Location
- Москва, Москва и Московская обл., Россия
- Date of birth
- Registered
- Activity
Specialization
Software Architect, Delivery Manager
Lead
C#
.NET Core
Entity Framework
ASP.Net
Database
High-loaded systems
Designing application architecture
Git
PostgreSQL
Docker
Перечитал, там в 5 пунктах описано что "кандидат умеет решать алгоритмические задачи"
А что ещё проверяет?
У вас решение опирается на уникальный индекс, а сделать его без механизмов обеспечения изоляции в бд не выйдет
А вы представьте что на пайтоне пишите и у вас не ограниченного размера целых чисел.
Поздравляю, вы изобрели READ COMMITED SNAPSHOT уровень изоляции.
Во-первых вам теперь не нужен лог (если надо для бизнеса, то просто пишите отдельным запросом). Вы можете писать сразу в агрегат и сравнивать его версию, то есть делать:
Во-вторых база уже умеет делать READ COMMITED или даже READ COMMITED SNAPSHOT и вы можете просто написать:
А проверку, что Quantity >= 0 повесить в CHECK CONSTRAINT
Пытаясь решить проблемы ES мы получили код без ES.
Да, вы можете сказать, что далеко не для всех операций надо пересчитывать агрегат, на что я вам отвечу, что вы можете использовать кэш в приложении и не на каждый запрос чтения ходить в базу.
Обычно сложность в алгоритме сортировки считается как количество операций сравнения. При поразрядной сортировке это будет O(NM), где M - количество разрядов. Обычно M не зависит от N и может быть принято за константу. Но в этой задаче зависит.
Если вы возьмете
MAX(SequenceId)
, то у вас будет конфликт уникальности при каждой вставке, если возьметеMAX(SequenceId)+1,
то у вас этотSequenceId
будет уникальным и никакой оптимистичной блокировки не будет.Никакой "оптимистичной блокировки" не будет, у вас кажый раз будет новый EventId.
Когда "потом"? Нам сейчас надо проверить, что количество не меньше нуля. Откуда мы это количество возьмем, не собирая агрегат? Из таблицы событий? Это значит вы просто агрегат переместили в таблицу событий и фактически отказались от ES.
Конечно же Аудит не похож на ES, потому что ES требует сохранять все. А в своем аудите мы можем сохранять только то что нужно - можем выбрать поля, можем сохранять метаинформацию, можем сохранять только новое значение или предыдущее тоже.
А можем вообще таблицу иситории завести. Это уже будет зависеть от бизнес-требований.
Так эти детали самые важные. У вас
SequenceId
может получится только после пересчета агрегата, по сути это и есть версия агрегата. Нигде больше выSequenceId
не сможете взять.То есть в вашем между select и insert нужно обновить агрегат, причем пока он обновляется вы или блокируете запись в эвенты (как это в вашем примере) и получаете строгую консистентность, которая тормозит, сильнее чем без ES, или не блокируете если у вас EventStoreDb, как у автора поста и получаете продажу одного товара два раза.
ну это не совсем так. Radix Sort имеет сложность O(NM), где N количество элементов в массиве, а N - количество разрядов. Если в массиве числа от 1 до N, то количество разрядов M = logN.
А что это даст? Вот был у вас агрегат, хранящий остаток товара на складе равный 1. Пользователь №1 сделал чекаут, в эвентстор попало событие, которое должно уменьшить остаток. Пока событие пользователя №1 не обработалось Пользователь №2 выполняет то же самое.
Вы два раза продали товар, который у вас в одном экземпляре. Зато теперь вы сможете автоматически транзакцию откатить. Но все равно пользователь №2 уже ушел с сайта и вам придется сделать много работы, чтобы ему сообщить и он 100% не вернется на ваш сайт.
Аудит возможен и без ES. Более того, без ES даже выгоднее, так как можно логировать только то, что нужно будет потом смотреть, а не все подряд.
Вот это я не понял, можно подробнее?
Хороший лозунг, но на практике просто база данных работает быстрее, чем оптимизированный CQRS, в режиме строгой согласованности.
Выше привел пример почему оптимистичная блокировка не поможет.
Я все еще жду пример, где с ES будет работать лучше чем без него.
Я свой первый комментарий закончил как раз просьбой указать где ES будет работать лучше, чем без него.
Как мы уже выяснили, что взаимодействие с внешним миром и пользователем зачастую лучше без ES, чем с ним. Тогда в чем смысл ES?
Верно
Неверно. Шаридинг БД никто не отменял. В тот же azure он встроен. Вы замучаетесь придумывать задачу, которая плохо шардится на БД.
Все еще хочу увидеть пример задачи, которую с ES можно решить лучше, чем без ES.
Если у вас "вдруг" программа уперлась по быстродействию в блокировки БД и вы хотите пожертвовать частью консистентности ради скорости - просто прикрутите кэш. Обычный, даже можно сказать тупой, ленивый кэш, который хранит данные N секунд\минут\часов и не перезапрашивает из хранилища до устаревания. Добавить такой кэш можно вообще не меняя архитектуру.
Хотя вы прикрутите кэш гораздо раньше, чем реально упретесь в базу, так как отдавать страницы быстрее всегда важно.
ES вырос из DDD. В целом DDD не менее бесполезен, чем ES.
Даже минусанул, потому что больше рекламы, чем пользы.
Последнее решение вообще лишнее, так как не универсальное. Просто автор решил повыделываться своим знанием алгоритмов.
Интересно с чего автор взял что есть решение лучше прямого подсчета? Только вместо hashmap надо применять bitarray(N), который будет требовать N/8 байт. Решение на bitarray может найти не только первое повторяющееся число, но и все повторяющиеся числа.
Если говорить о реальности применения, то решение O(N) по времени и O(N) по памяти выгоднее O(NlogN) по времени и O(1) по памяти.
Простите, а кто будет поллить?
Клиент? Вы реально считаете, что нажав кнопку "добавить в корзину" пользователь будет обновлять страницу пока товар не появится в корзине? (именно такое решение предлагает автор поста, это есть в исходниках)
Или поллить должен сервер? Тогда это просто будет тормозить с точки зрения пользователя и работать в целом хуже, чем с обычной базой данных, так как будут периодически возникать неконсистентности данных. Никаких преимуществ, описанных в статье не будет.
На ES можно писать надежно, но тормозить оно будет СИЛЬНЕЕ чем без ES. Никаких других преимуществ ES тоже не будет.
Поэтому в целом писать в ES архитектуре можно только тогда, когда данные не важны и их можно потерять. Это исчезающе малое количество задач.
Чтобы это работало надо чтобы запись блокировалась запись во время чтения write-модели. Иначе пока один запрос будет считать остатки другой будет их списывать. Блокировка запси во время чтения создает строгую консистентность.
Во-первых это убивает все преимущества ES, которые обозначены в статье.
Во-вторых блокировку поддерживают в основном РСУБД, а имея РСУБД для чтения write-модели не надо делать ES. Вы можете просто читать и записывать состояние. При этом вы вполне можете вместе с изменением состояния писать лог этого изменения, когда логирование нужно для бизнеса.
Лок требует строгой консистентности, которой нет в ES.
Это прекрасный вариант с точки зрения user experience. Пользователь идет оплачивает корзину, переходит на страницу подтверждения, раудется, уходит в вашего сайта. В этот момент фоновый процесс обрабатывает его оплату, понимает что на складе товара уже не осталось и транзакцию нужно откатить, возвращает деньги (хорошо если это произойдёт моментально) и отправляет письмо "извините, несмогла", которое почти 100% попадает в спам. Угадайте сколько еще раз этот пользователь будет покупать в вашем магазине?
И собственно в чем таком особенном заключается преимущество ES архитектуры, что можно терпеть подобные недостатки?
А в чем проблема большого количества продаж в секунду? Интернет-магазин прекрасно масштабируется горизонтально, так как всю базу можно разбить на шарды по товарам (каталог, запасы) и клиентам (корзины и оплаты).
Для этого совсем не нужен ES.
При использовании РСУБД блокировки конечно же навешиваются не на всю таблицу лога, а только на страницы индексов и отдельные записи\диапазоны. Если конечно эскалация не случится.
Но если у вас уже есть РСУБД, то делать ВСЕ изменения через лог крайне неэффективно. Например в случае интернет-магазина удобнее менять непосредственно остаток\резерв когда пользователь оплачивает или добавляет товар в корзину.
А если использовать EventStoreDB, как в статье указано, то никаких блокировок нет и проблемы будут в полный рост.
Я ничего не придумываю, это в статье описано, к которой вы пишите комменты.
Но давайте рассмотрим ваш источник правды. Он предполагает два варианта:
through a scheduled task - это как раз так, как описано в данной статье со всеми преимуществами в скорости и недостатками в согласованности (хорошо что вы перестали спорить с ними)
on demand when handling a request - рассмотрим этот вариант поподробнее.
Предположим вы пишите движения товара в эвентстор, а материализуете в момент создания нового движения.
Во-первых вы сильно теряете в скорости, так как вам каждый раз надо пересчитывать.
Во-вторых вы теряете возможность принимать какие-то решения на базе эвентлога. Ваш пассаж про достоверность результата теряет смысл.
Но самое главное - это не защищает от возможности уйти в минус. Так как во время расчета могут появиться новые записи, которые вы не учтете и покажете положительной остаток для одной операции, когда другая операция уже уменьшила его до нуля.
Чтобы не уйти в минус вам надо блокировать эвентстор от записи на время расчета, а это убьет все оставшиеся плюсы от эвентстора.
Именно блокировка эвентстора на запись в момент расчета "остатков" создает строгую консистентность и сериализуемость транзакций. Поэтому в большинстве систем выгоднее остаток пересчитывать при записи (материализовать), а не при обработке запроса.
Механизм блокирования записи данных во время чтения данных уже встроен в большинство СУБД и вам для этого не надо ничего дополнительно делать.
В каталоге отмечается как "нет на складе".
В некоторых вариантах остаток уменьшается сразу при добавлении в корзину и держится там сутки или около того.
В других вариантах если кто-то успел добавить в корзину, но не успел оплатить, то на этапе чекаута получает ошибку "товар закончился и предложение купить что-то еще".
Даже если повезло и один юзер успел купить товар пока другой делал оплату, то веб-сервис принятия оплаты в магазине упадет с ошибкой и оплата не пройдет.
В любом случае пользователь как можно раньше узнает, что товар закончился и сможет или сам скорректировать заказ или просто не получит возможность добавить товар в корзину.