[Перевод] Как НЕ надо строить надежные системы

im4rb_xwvgpaimvrb29jggmyc4q.png

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

▍ Что такое моделирование?


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

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

Все модели ошибочны, но некоторые из них полезны.

— Джордж Бокс


В целом все модели можно поделить на две группы:

  1. Бесполезные (в определённом контексте);
  2. Полезные (также в определённом контексте).


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

▍ Что такое надёжность?


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

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

▍ Как моделирование влияет на надёжность систем?


d9wajgh4qwgwawtfkvbegulqzrg.png
Процесс решения задач

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

Позвольте объяснить:

Обычно мы стремимся всё для себя упростить, снизив материальные, умственные или иные затраты.

  1. Мы начинаем воспринимать имеющиеся обстоятельства как проблему, которую нужно решить.
  2. Для её решения необходимо собрать данные.
  3. Затем сопоставить реальность с её упрощённой моделью, чтобы она уместилась в нашем сознании.
  4. Определить стоящую перед нами задачу и начать экспериментировать с моделью.
  5. Надеяться, что через несколько попыток задачу удастся решить.
  6. Для достижения изначальной цели необходимо найти способ реализовать это решение в реальности.


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

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

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

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


▍ Игнорирование бизнеса


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

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


Здесь на помощь приходит предметно-ориентированное проектирование (DDD). Под этим видом проектирования я подразумеваю стратегические паттерны и идеи, а не тактические вроде сущности и репозитория. Это учит нас находить существующие бизнес-модели вместо того, чтобы пытаться создать очередную с возможными нестыковками. Таким образом мы уменьшаем общую сложность и берём в расчёт всесторонние социотехнические факторы.

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

▍ Излишнее упрощение системы


Делай всё настолько простым, насколько возможно, но не проще.

— Альберт Эйнштейн


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

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

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

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

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

В реальном мире не существует состояния гонки, если только ты не упускаешь принцип.


▍ Излишнее абстрагирование


Дилберт: О, нет! Излишний уровень абстракции сделал нас невесомыми!

(фраза из м/с «Дилберт», ссылка может не работать, — прим. пер.)


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

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

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

Именно поэтому использование RPC при проектировании надёжных распределённых систем практически всегда будет плохой идеей.

▍ Игнорирование физики


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

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

Непонимание отличия распределённых/конкурентных систем от наиболее привычных для разработчиков является причиной многих бизнес-провалов и мучений для связанных с этим людей.

Мой первый закон проектирования распределённых объектов: не распределять объекты.

— Мартин Фаулер


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

  • время: как я уже говорил, время и все его производные (скорость и т.д) исчисляются относительно наблюдателей, то есть в распределённой системе время сравнивать нельзя, и общий порядок действий оказывается не определён;
  • упорядочивание: порядок необходимо моделировать явно, поскольку неявного не бывает. Наиболее распространённый подход предполагает использование идентификаторов взаимосвязанности, которые создадут частичную упорядоченность и смогут решить все упомянутые выше проблемы за счёт замены времени тем, что мы подразумеваем при его использовании, то есть причинно-следственной зависимостью;
  • синхронность: в распределённой системе всё асинхронно. Иначе быть не может, поскольку такова её природа. Для лучшего понимания представьте, что даже свет, электрический ток и ядерные силы — всё это управляется событиями. А поскольку распространяются эти явления путём передачи крохотных частиц (фотонов, глюонов, W- и Z-бозонов) в качестве сообщений, то после выхода за пределы квантовой сферы их синхронное поведение просто невозможно.

    Заметьте, что под асинхронностью я имею в виду не использование брокеров сообщений, а асинхронную коммуникацию. Её можно реализовать с помощью HTTP, к примеру, используя технологию pull вместо push-and-wait, как при использовании Atom feeds вместо RPC;.

  • доставка: следствием вышеприведённых свойств является невозможность обеспечить ни доставку строго один раз, ни упорядоченность сообщений, как это шутливо описывает следующая цитата:
    В распределённых системах есть всего две существенных проблемы:

    2. Доставка строго один раз
    1. Гарантированный порядок сообщений
    2. Доставка строго один раз

    — Матиас Верраес

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


Прочие факторы


▍ Игнорирование структуры организации


Моделирование реальных приложений — это сложная задача, и каждой системе значительного размера требуется значительный объём командной работы, которая далеко выходит за возможности отдельного человека. Трудится в команде нелегко. Этот навык необходимо нарабатывать, как и любой другой, но для многих данный процесс не является естественным, поэтому обычно здесь требуется наставничество и хорошее руководство. Это сложная задача, поскольку в ней необходимы не только навыки руководителя, но и подходящие условия для их использования. А для большинства отдельных людей и даже команд эти условия обычно находятся вне зоны контроля.

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

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

Как гласит закон Конвея:

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

— Мелвин Е. Конвей


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

▍ Игнорирование людей и команд


И хотя это может прозвучать тривиально или излишне очевидно, не каждая команда или отдельный специалист обладают актуальными навыками, необходимыми для разработки или проектирования всевозможных систем. Разные люди обладают не только разным опытом и умениями, но и мотивацией. В то же время большинство проектов по созданию ПО зачастую выглядят как простые CRUD-приложения (по крайней мере — внешне), с которыми знакомо большинство разработчиков. Однако б«ольшая часть критически важных систем, для которых надёжность является наиболее важным критерием, к таковым не относятся.

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

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

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

▍ Игнорирование целей


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

Мы можем решить эту задачу, если внедрим Kafka там, MongoDB вон там, а Django здесь, и развернём всё это на высокодоступном кластере k8s, который на случай апокалипсиса будет размещён на трёх континентах.

— Подход проектирования ради резюме, ведущий к созданию сайта электронной коммерции с десятью посетителями в месяц.


Всё это происходит в первую очередь из-за синдрома блестящего объекта или концепции разработки ради резюме, у которой даже есть свой манифест!

Разработка ради резюме

Мы ценим:

  • конкретные технологии превыше рабочих решений;
  • найм на основе присутствия в резюме заумных слов, а не подтверждённых трудовых достижений;
  • креативные названия должностей превыше технического опыта;
  • реагирование на тренды, а не более прагматичные решения.


Такой подход даже близко не приведёт к хорошим результатам.

▍ Игнорирование обслуживания и развития ПО


dw4jbz6kdibkadqx3x9asc7eoro.png
Часть моста Майлс-Глейшер с клуджем (временным решением) после землетрясения

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

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

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

Работоспособная сложная система неизбежно является следствием развития простой работоспособной системы. Сложная система, спроектированная с нуля, никогда не заработает, и её не получится подлатать, чтобы этого добиться. Начинать необходимо с простой системы.

— закон Галла


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

Telegram-канал с полезностями и уютный чат

sz7jpfj8i1pa6ocj-eia09dev4q.png

© Habrahabr.ru