Как стать автором
Обновить

Об ошибках, возникающих ниоткуда и в которых некого винить: Феномен Размазывания Ответственности

Время на прочтение 5 мин
Количество просмотров 8.3K
Всего голосов 16: ↑13 и ↓3 +10
Комментарии 48

Комментарии 48

В данном случае проблема не в разделении ответственности, а в database integration antipattern — доступ двух разных приложений к одной таблице в БД. Почему это проблема? Потому, что, в отличие от сетевого RPC, интерфейс взаимодействия между системами через таблицу в БД сложен и избыточен, поэтому его трудно специфицировать и отладить.


Разделение ответственности — это хорошо, но самый важный принцип в микросервисной системе — изоляция мутабельного состояния (базы данных и кеши) и максимально жесткая спецификация интерфейсов между микросервисами.

Я употребил термин «размазывание» ответственности как альтернатива разделению ответственности. И мне кажется, это более общее и частое явление, чем database integration antipattern. Так что я с Вами и согласен и не согласен одновременно.
Жуть, а по сути это разумно, Hibernate для одной таблицы?
Ну разумеется нет. Таблиц было много.
НЛО прилетело и опубликовало эту надпись здесь
Странно, что это за база и удивлен про hybernate.
+1024
Вдвойне странно, что столько людей не видят в этом большой проблемы. Не говоря уже о том, что правильное использование сиквенсов будет нагружать БД ничуть не больше, чем такой «оптимизированный» Hibernate.

Эммм, всё немного сложнее. Базы бывают разные и с разным набором фич, при массовой вставке дополнительный roundtrip для инкремента сиквенса может значительно увеличить время вставки. Особенно если пинг до базы ненулевой.


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

Базы бывают разные и с разным набором фич
Я сразу скажу: я в ОРМах не разбираюсь, никогда ими не пользовался, так что знаю только то, что Мойша напел. Типа как в этой статье. А поёт Мойша гнусаво, и в ноты не попадает. И знает только три блатных аккорда.
Вот тут главное, что мне не нравится в ОРМах. Есть некоторое количество разных СУБД. С разными фичами. И тут мы такие берем ОРМ, который работает со всеми универсально, и в итоге получается, что доступны только те фичи, которые есть у всех. То есть самая крутая и навороченная СУБД за сотни нефти низводится до самой дешманской, потому что «у нас же лапки ОРМ».
В том же оракле, например, есть кеширование сиквенсов. Я сам лично сталкивался с проблемой, когда (слава богу, никаких ОРМов не бло) поднятие кэша с дефолтных 20 до 1000 прекрасно убирало все проблемы с доступом к сиквенсу. А тут получается так: сначала платим 100500 денег за СУБД, потом берем ОРМ, а потом оказывается, что чукча обманул таксиста.

И да, хибернейт по дефолту не рассчитан на параллельную модификацию базы чужими приложениями. Это необходимое условие для работы его знаменитых кешей первого и второго уровня.
Ваши слова меня огорчают. 40 лет развития многопользовательского доступа в СУБД закончились тем, что «а давайте работать с БД в однопользовательском режиме».

Я всё еще надеюсь, что это просто я что-то не понимаю, и мне кто-нибудь объяснит.
Все не так плохо. Большинство проектов работают с Hibernate в многопользовательском режиме. Никто Вас назад в 20-й век не зовет.
А самая главное — статья не о этом. Она — о Феномене Размазывания Ответственности. Постарайтесь в ней увидеть именно этот посыл.
ORM — дешевый способ накидать большую часть логики работы с базой, будто это какое-то хранилище привычных объектов. Зачастую всякой бд-специфичной крутизны в них нет, но вот тот же EF позволяет сделать raw sql запрос к бд и спроецировать его результаты куда следует. Запрос можно даже параметризовать! Это вполне себе способ воспользоваться какой-либо фичей СУБД, которая не реализована в ее драйвере для этой ORM.
Ну а дальше вопрос архитектуры — где держим эту логику использующую какие-то уникальные фичи БД? В слое бизнесовой логики? В data access layer? Запихиваем еще глубже в storage procedure в бд?
Не претендую на истину, все на своем опыте работы с постгресом, ef.core и неумением последнего в jsonb.
НЛО прилетело и опубликовало эту надпись здесь
Поверить в это действительно трудно, пока не прочитаешь внимательно документацию (или указанную в ссылке в спойлере дискуссию на StackOverflow). Но у разработчиков Hibernate были на то свои разумные соображения.
Сами того не ведая, они поработали на Феномен Размывания Ответственности.
А где можно почитать эти разумные соображения? Очень интересно какие могут быть причины, ибо сам я не могу вообразить ни одного аргумента за, по-моему опыту разумностью в конкретном случае вообще не пахнет.

Стали копать глубже. Детальный анализ и сопоставление лог-файлов показали

ИМХО, при ошибках на проде анализ логов это первое, с чего стоит начинать.
НЛО прилетело и опубликовало эту надпись здесь
Понятно, спасибо. Они хоть тестировали где-нибудь свою микрооптимизацию? Выигрыша никакого не будет, т.к. запросить последнее используемое значение счетчика в базе ничего не стоит и в некоторых субд (а может и во всех) его можно сразу получить тем-же запросом на вставку записи.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
С новой веткой Вы правы. Извините. А соображения разработчиков описаны в комментарии/ответе koluka вверху.
НЛО прилетело и опубликовало эту надпись здесь
Я не хочу выступать в роли адвоката Hibernate, да и не это фокус моей статьи. Но могу себе представить, что в нулевые годы, когда Hibernate создавался, были распространены десктоповые системы и распределенные системы с одним сервером и многими клиентами. Тогда было логично предположить, что класс Hibernate является единственным «властителем» таблицы в системе и может позволить себе такого рода микрооптимизацию.
Но это мои предположения.
Угу, ну а счетчики в БД, если не ошибаюсь, на несколько десятков лет старше нулевых.
Как-раз в нулевых моим основным занятием были базы данных, всякие Ораклы и энтерпрайз юзающий их.
И тогда народ не отдавал БД на откуп фреймворкам, а отдельно ее проектировали всякие архитекторы и работали с базой напрямую, без каких-либо ORM, а за подоход этого хибернейта: угадывание праймари кея, могли забить канделябром по голове до смерти :)
Хороший тон было всегда рассчитывать на то, что параллельно с тобой с базой работает куча клиентов через что угодно и если две операции делают что-то, что должно быть атомарным, надо обеспечить атомарность средствами БД, рассчитывать на то, что между операциями пройдут микросекунды, никто другой влезть не успеет могли только новички.

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


Прагматичное? Серьёзно?
Писать напрямую в БД, да ещё зная что есть другие части системы, которые пишут в эту же БД. Очевидно же, что проблема возникнет рано или поздно

Писать напрямую в БД, да ещё зная что есть другие части системы, которые пишут в эту же БД. Очевидно же, что проблема возникнет рано или поздно

Я не отстаиваю подход с прямой записью а банк данных из разных компонент, но это объективная реальность многих Enterprise систем. И если Вы углубитесь в документацию Oracle, то увидите, какие они предпринимают усилия, чтобы именно так их ДБ и использовали.

Сама по себе параллельная запись в БД из нескольких мест — это ещё не проблема. Но при этом совершенно очевидно, что любое ПО, с такой БД работающее, обязано учитывать тот факт, что БД может быть изменена чем-то помимо него. Так что это не проблема "размазывания ответственности", это проблема плохого проектирования. И ответственные за проблему вполне себе четко определяются в зависимости от конкретной ситуации, на мой взгляд. В вашем случае:


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

  1. Если человек, который принял такое решение, ЗНАЯ, что есть другие пишущие в БД компоненты, не согласовал свое решение с ответственными за работу тех компонент — ответственнен он.
  2. Если он не знал и не мог знать, что есть другие пишущие в БД компоненты — ответственнен тот, кто организует рабочий процесс и не дал ему нужную информацию.
  3. Если он свое решение согласовал с нужными людьми, а те прощелкали возможные последствия — ответственны они.

А если два или все три пункта — это одни и те же люди, то вообще не о чем говорить. :)

В жизни всё сложнее.
Например — текучка кадров, ответственных в данный момент просто нет или новые ответственные еще не вработались.
Или: компонента не меняется, ответственные переводятся на другие фронты.
Или: таблиц так много, что ответственный проглядел коварно заданное значение.
Задним умом мы все сильны. (Шутка).

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

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

Собственно вопрос, почему в микросервисном подходе сама таблица не реализована как микросервис? Да и «угадайка» с primary-key в распределенной системе это конечно сильно.
Моим ответом на Ваш вопрос можно считать мой комментарий к комментарию maxzh83 внизу.
Дело не в размазывании ответственности, а в неправильном разбиении на микросервисы. Кроме этого, у каждого микросервиса должна быть своя БД и в БД других микросервисов он лезть не должен. Если у вас так не получается, то у вас что-то не так с проектированием/разбиением. На самом деле, правильно разбить монолит на сервисы очень сложно и влечет за собой избыточные взаимодействия и т.д., но, увы, такова плата за микросервисы.
Сейчас все меньше систем строится с нуля, на зелёной лужайке, когда всё можно заранее спокойно продумать. Чаще приходится в существующей системе что-то исправлять или что-то добавлять. Функциональность на микросервисы уже разбита. Этого не переделать. А вот при внесении дополнений можно наделать ошибок и «размазать» ответственность.
Сейчас все меньше систем строится с нуля, на зелёной лужайке, когда всё можно заранее спокойно продумать.

Как раз монолит проще разбить, чем с нуля продумывать, т.к. видна вся картина целиком. Часто советуют пока не понятно как делить, писать монолит.
А вот при внесении дополнений можно наделать ошибок и «размазать» ответственность.

Эти ошибки это и есть ошибки проектирования, архитектуры и т.д. Если не можете развести микросервисы так чтобы они не лезли в чужие БД (а это иногда и правда непросто сделать), так может лучше оставаться на монолите и не играть в модные тренды?
Речь не идёт о способах разбиения монолита на части. Распределённые системы объективно давно существуют и по разным причинам деградируют.
И хорошо бы попробовать в этом спокойно разобраться.
Свести все к «раздолбайству» ничего не даст. Вряд ли все участники процитированной дискуссии на StackOverflow и других похожих дискуссий были «раздолбаями».
Не все спроектированные самолеты взлетают, и многие из них долго доводят до кондиции уже после начала эксплуатации. У сложных технических систем свои законы. В том числе и у ИТ-систем.
Речь не идёт о способах разбиения монолита на части

Я лишь о том, что ваша проблема «размазывания ответственности» на самом деле проблема неправильного разбиения, которая не нова и давно известна.
Свести все к «раздолбайству» ничего не даст.

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

Доводят, но не приматыванием деталей скотчем. Ваш вариант с чтением двумя микросервисами из одной БД это именно костыль, примотанный скотчем. Можно было перепроектировать сервисы и, например, выделить еще один, который будет один работать с проблемной таблицей. И тогда даже странный кейс с предсказыванием id на hibernate будет работать.
Про «раздолбайство» написали действительно не Вы, прошу прощения.
Но и про скотч Вы зря. Во-первых, как проблема была решена, я не писал вообще. И не об этом я хотел рассказать, а о феномене, который можно замечать, а можно не замечать и все сводить к конкретным проблемам и решениям.
Я хотел, чтобы читатели рассмотрели описанную проблему и схожие проблемы на другом уровне абстракции.

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

Разумеется, в проблеме виноват человек или орган, координирующий разработку. В ситуации с Enterprise системами, разработку которых могут вести разные фирмы на разных континентах, проверять мелкие детали очень сложно. Лучше пытаться следовать общим принципам и опасаться нарваться на анти-паттерн. Я попытался в статье показать пример опасного феномена. Он не сводится к параллельной записи в ДБ и может быть вызван совсем другими причинами.
К сожалению, по некоторым комментариям можно предположить, что этот посыл либо не всеми понят, либо с ним не все согласны.
как проблема была решена, я не писал вообще.

Так я, собственно, не о решении, а о самой проблеме, о том почему она появилась.
а о феномене, который можно замечать, а можно не замечать

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

Сурово, это как на полном ходу поезда дёрнуть стоп-кран, если мне резко понадобилось выйти именно тут.
Уж если делать аналогии с физическим миром, то больше подходит такая: велосипедист обгонял стоящую на обочине машину, а водитель в это время открыл дверь…
Впрочем, любые аналогии имеют свои границы.
Не согласен. Есть компонент, который корректно работает с БД, вдруг появляется другой компонент, которому позарез нужно что-то впихнуть в эту базу. И вот вместо того, чтобы делегировать эту задачу первому компоненту, пусть и в ущерб скорости (в моём варианте аналогии — дождаться станции и сойти) этот самостоятельный кусок кода совершает несогласованный и незащищённый акт насилия над БД, игнорируя установленные правила (дёргает стоп-кран).
Можно назвать это как угодно, но на мой взгляд, самое культурное определение этого решения — костыль, но никак не «прагматическое решение».
Человек честно рассказал о том, с чем столкнулся, его надо поощрить, а не взыскание накладывать. Мне кажется, что с похожими до изоморфизма ситуациями не сталкивался только тот, кто ничего не делал.
Про изоморфизм вы хорошо заметили. И схожесть ситуаций заключается в том, что речь идёт о «размывании ответственности» — процессе, когда компоненты обрастают несвойственными им функциями и данными, которые становится всё труднее контролировать и которые приводят к ошибкам в совместной работе.
A Hibernate работает по умолчанию при записи следующим образом. Чтобы оптимизировать процесс записи, она запрашивает в индексе значение следующего primary key один раз, и после этого записывает несколько раз просто увеличивая значение key (по умолчанию — 10 раз).


О — оптимизация. :)

Поразительная корреляция с вчерашней статьёй про ORM.

Чтобы так не было, используйте, пожалуйста, следующие простые правила:

  1. Если работаете с Oracle, то повесьте на таблицу триггер, который будет генерировать значение синтетического первичного ключа, используя выделенную последовательность (sequence)

  2. Если работаете с MS SQL, используйте свойство IDENTITY на столбце с синтетическим первичным ключом прямо во время создания таблицы.

  3. Всегда, когда нету очень веских причин против, используйте в каждой таблице синтетический первичный ключ целочисленного типа.

А если использовать конструкции вроде newId = SELECT MAX(ID) FROM MyTable + 1, рано или поздно проблемы возникнут. Потому что генерация первичного ключа - слишком важная часть логики, чтобы отдавать её любому постороннему коду. Этим должна заниматься сама СУБД.

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

За советы спасибо.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации