Сила простых решений
Table of Contents
Мы, инженеры в ИТ (админы, программисты и тысячи других профессий) работаем со сложными системами. Наши системы росли десятилетия. Умнейшие инженеры планеты наслаивали абстракции, упрощали себе и своим коллегам жизнь, искали различные способы улучшить производительность и надежность. Описание одного только ядра одной операционной системы Linux - это кирпич в 500 страниц убийственно сложного текста, который невозможно читать иначе как по паре страниц за заход. Это я про Linux Kernel Developement сейчас говорю, но вообще он там не один такой - таких книг целая полка и остальные не проще. Описание сетевого стека UNIX еще хуже, там почти тысяча страниц. Если кому-то кажется, что юниксоидов обижают и у пользователей Microsoft жизнь много проще – вас ждет Windows Internals от Соломона и Руссиновича, она тоже порядка 600 страниц, да и внутренний мир windows описывает ограничено, ибо коммерческая тайна. Традиционно ИТ – это много вдумчивого и внимательного чтения гигантских объемов текста. Системы, с которыми мы работаем по сложности напоминают космический корабль – миллион различных ручек, и каждая нужна для чего-то. А неправильная ручка гарантированно приведет к катастрофе. Кажется, что сложность – неотъемлемая часть нашей работы. Как не может быть света без тени, так и не может быть ИТ без сложных, непонятных, опасных в неумелых руках систем. Я жил с этой идеей 20 лет своей карьеры.
Я был неправ.
Первый раз я задумался о том, насколько я неправ, прочитав чудесную статью про разработчика с мозгом груга. Программисты борются со сложностью давно, потому что их она кусает первой и быстрее всех. На тему борьбы со сложностью написаны тонны программистской литературы, классический труд по рефакторингу от Мартина Фаулера и не менее известный чистый код от Роберта Мартина известны, наверное, каждому уважающему себя программисту. Не все их читали, не все следуют им слепо и в полной мере, но знают о них, наверное, почти все. Сложность в программировании идет рука об руку с шансом ошибки, а количество ошибок – легко измеримая метрика. Понять, какой код лучше можно относительно просто, посчитав шанс ошибки в пересчете на количество строк. У программистов есть набор техник для борьбы со сложностью – архитектурные процессы, изоляция участков кода, абстракции, тестирование, SAST/DAST и прочие анализаторы. Это позволяет удержать сложность в узде, выбивая ее по кусочку за раз. Нам, инженерам по эксплуатации, повезло меньше – мы вынуждены жить с системами, которые кто-то когда-то уже написал, в меру своего разумения запустил, а теперь этот велосипед надо как-то заставить ехать. Операционные системы сложны, их писали десятки лет для выполнения сложнейших задач (простые ОС есть, но нужна серьезная причина, чтобы ими пользоваться). Сетевая инфраструктура постоянно пытается обмануть фундаментальные законы физики, а физическое оборудование пытается быть надежным и безопасным, но при этом быстрым и в первую очередь – дешевым и простым. Любая ИТ-инфраструктура – это пирамида из абстракций, плотно покрытая с одной стороны набором правил безопасности (чтобы никто не отстрелил себе ногу), а с другой стороны – обмазанная разными хитрыми трюками для ускорения работы. В абстракциях порой случаются дырки и тогда они “протекают” – появляются странные, подчас безумные свойства. В умелых руках такие свойства превращаются в атаки, которые позволяют “подслушать” чужую память даже в 100% изолированном процессе (атака Heartbleed), сломать память соседу (Rowhammer) или влезть на сервер пользователем, у которого никогда не было, нет и вообще не может быть пароля (sshhole). Даже если убрать злонамеренные действия за рамки – протекающие абстракции могут неприятно удивить. Производительность диска упадет в 200 раз на простейшей операции, на пустой файловой системе кончится место, а электронная почта будет ходить ровно на 50 миль и не метром дальше. Сложность делает проблемы намного опаснее, потому что сложную систему невозможно понять, она не помещается в голову целиком. Сложная система непредсказуема, а потому опасна. И мы, как инженеры, должны взять на себя ответственность в борьбе с ней.
#
Откуда берется сложность
Сложное решение редко задумывается таким с самого начала. Никто не хочет портить себе жизнь сознательно, но сложность как-то появляется. По моему личному опыту (и немного из литературы) я составил вот такой список причин, которые усложняют проекты:
##
Непонимание проблемы (или предметной области)
Мое личное первое место. Если не знать, какую проблему мы решаем – мы будем решать максимально абстрактную проблему. Чем более абстрактна проблема – тем шире нужно решение. В конечном итоге решение превращается в универсальный решатель для всего. Цена универсальности – сложность, непонятно, что и как решатель должен решать. Безопасность универсального решения становится всеобъемлющей проблемой, потому что нужно угадать, в какую сторону ваш продукт будет двигаться. Без понимания цели вам придется строить систему, которая готова делать абсолютно все. На выходе вы получите или систему, которая умеет все понемногу и ничего нормально, или систему, которая строится бесконечно долго по времени и стоит бесконечно много денег. Предметная область одновременно помогает и мешает: с одной стороны, она создает ограничения, с которыми вашей системе предстоит жить. С другой стороны, ограничения, с которыми вы живете могут быть фундаментом дизайна вашей системы. Подход к проектированию веб-поиска, учета денег в банке и системы космической связи принципиально разный. Задача инженера с одной стороны – понять эти ограничения, а с другой стороны – понять и исключить ограничения, которых на самом деле нет.
##
Непонимание инструмента
Любимое второе место. Интернет полон различных статей, книг и руководств (этот сайт не исключение). Можно найти совет на любую тему и решение для любой проблемы. Вот только обычно авторы очень мало сил уделяют основам – статьи чисто прикладные, иначе никто не будет их читать. Основы и теория – это время, которого при решении обычно нет, читателю нужен простой ответ на четкий специфичный вопрос. Кроме того авторы пишут свои рекомендации в меру своего ограниченного разумения (мы все ограничены, даже Танненбаум, Торвальдс и Кнут). Автор пытается выразить свои идеи (не всегда полностью правильные) в форме текста (не всегда точно передавая свою идею). Потом этот текст прочитают (а иногда еще и перескажут) и попытаются в меру своего ограниченного понимания понять и применить. Это уже 4 слоя трансляции и каждый добавляет искажений. Люди ищут решение проблемы, находят какой-то мануал-заплатку, прилепляют и радуются – вроде все работает. Копать проблему вглубь, выясняя первопричину – сложно и затратно, а бизнес требует решения сейчас, ведь процесс должен двигаться и приносить деньги. С появлением LLM проблема стала еще хуже, потому что им доверяют слепо и часто LLM выдает решения, очень похожие на правду. Не 100% всегда, но часто. Проблема в том, что решение проблемы не всегда может быть рабочим, и может сделать хуже чем было. Или починить одну проблему и создать другую, ее найдут потом. LLM пересказывает некий усредненный вариант (собрав все возможные ошибки, перечисленные выше), и выдает готовый текст без ссылок на источники. В процессе ИИ может слегка приврать и что-то сказать неправильно или вообще выдумать кусок из головы. В результате решение будет сложным, громоздким, не оптимальным и часто – небезопасным. А может и вовсе частично нерабочим.
##
Решение воображаемых проблем
В первую очередь достается масштабированию и безопасности. Вы строите инфраструктуру. У вас маленький бизнес, ему нужен маленький набор сервисов. Руки чешутся сделать “как в Google” – кросс-региональная репликация базы, точки присутствия по всей планете, in-memory cache для горячих данных, RAFT или Paxos для согласования данных, статика в S3, батчинг, очереди сообщений, 33 микросервиса, service mesh – в общем гуляем на все деньги. Такая инфраструктура переживет трафик Твиттера или Youtube, если придется. Вот только это не нужно, если вы не Google. Конечно, нельзя совсем уж игнорировать производительность, но большинство современных проектов комфортно живет на Raspberry Pi или ноутбуке десятилетней давности. Этой проблеме много лет, еще классик программирования Дональд Кнут писал о том, что чрезмерная оптимизация – корень немалого количества зол. Проблемы производительности и надежности нужно решать не раньше, чем они наступают. Когда они наступают? Надо считать – метрики подскажут, где мы сейчас и как далеко до финиша. Большинство проектов просто не доживает до стадии, когда нужен гео-распределенный кластер. Вместо Hadoop вам может хватить простого Excel. Ну хорошо, Excel может не хватит – возьмем Apache Parquet и Numpy, данные просто влезут в память, на таких масштабах стоить она будет копейки. Простую систему значительно проще поддерживать. Сложные, комплексные системы стоят дорого, не только финансово, но и с точки зрения ресурсов и труда.
##
Желание быть в клубе
Частично связано с решением воображаемых проблем и заметно - с непониманием инструмента. Нужно иметь немалую внутреннюю смелость, чтобы не согласится со мнением большинства. В нашем мире есть стандарты и мало кто задумывается, откуда они берутся и почему они выглядят именно так. Значительно проще плыть по течению. Все делают Kubernetes – и мы сделаем Kubernetes. У всех есть Kafka – конечно, и у нас должна быть Kafka. У всех Agile и SCRUM – и у нас будет Agile и SCRUM, и не важно что мы строим завод под ключ. Все уезжают на AWS – обязательно сделаем так же! И AI SRE подключим, пусть проблемы решает сам. Не важно, что половины метрик просто нет, а вторая половина метрик просто врет, и только живой человек с опытом и контекстом в курсе, что реально означает out of paper on disk C:. Ведь так делает большинство, а большинство не может ошибаться. Проблема клуба в том, что двигают его гранды, которые решают свои проблемы. Kubernetes был придуман в Google и решал он проблемы Google. Kafka решала проблемы аналитики для Linkedin. Им пришлось писать Kafka просто потому, что никакой другой инструмент их проблемы не решал. Попыта перенимать лучшие практики без понимания причин и ограничения ставит инженера в неловкое финансово-техническое положение. Да, использовать стандартные инструменты проще – все их более-менее знают, легко найти себе коллег, ошибки стандартные, документации тьма. Но нужно уметь задать себе вопрос, а мы сейчас решаем свою проблему или подгоняем решение под инструмент? Инструмент всегда, в 100% должен решать задачу. Если задачу приходится пинками утрамбовывать в процесс – значит инструмент к задаче не походит. То есть инструмент выбран плохо.
#
Что с этим делать
Мы инженеры. Задача инженера – надежно решать сложные проблемы техническим путем. Сложность мешает нам решать проблему надежно. Сложность заводится сама и расползается как плесень, заражая все вокруг. Она делает надежное ненадежным, дешевое дорогим, а решение в конечном итоге – не нужным. Сразу скажу – я оставлю за рамками обсуждения вайбкодеров, которым сойдет “лишь бы заработало”, а также тех, кто считает, что важнее всего получить деньги за сиюминутное решение проблемы, даже если проблема не решена и решение не работает. Быстрый выигрыш ценой глобального проигрыша это тема отдельного разговора, а может и книги. Пусть таких людей судят боги, заказчики и время. Я считаю себя инженером и пишу для тех, кто думает так же.
Итак, первое – нужно признать, что сложность – это не свойство, а ограничение. Со сложностью нужно бороться, она не является объективным явлением или свойством, как гравитация. Помним про то, откуда сложность берется и пробуем действовать от противного. Самое главное – нам надо понять, какую задачу мы решаем. Порой это на удивление непросто. Возможно – придется подумать. Возможно – посидеть с блокнотом. Поговорить с заказчиками. Поговорить с клиентами. У формулировки задачи может быть много словесных кружев, которые скрывают проблему. Задача инженера – найти эту проблему. Тут же можно поискать ограничения. К ограничениям стоит относится скептически: какие-то ограничения могут оказаться несущественными, какие-то не будут влиять на решения, а какие-то вообще воображаемыми (так бывает намного чаще, чем может показаться). С проблемой на руках можно попробовать прикинуть решение. Главное свойство решения – оно должно быть максимально простым. Любой сервис, код, настройки, процесс – это ваше обязательство. Его надо поддерживать в актуальном состоянии, тестировать, защищать, документировать, обучать людей. Чем меньше движущихся частей в вашем решении – тем лучше. В идеальном случае проблема решается сама, без внешних инструментов. Если нет инструмента – он не сломается, не зависнет, его не взломают. Так, конечно, не всегда получается, но к идеалу стоит стремиться. Решение любой задачи стоит упрощать до предела, пока не останется ничего лишнего. И только на этом этапе решение можно автоматизировать. Если решение изначально сломано – вы получите сломанную автоматизацию. Автоматизация позволяет масштабироваться почти бесконечно, что “вверх” (по объему обрабатываемых данных), что “в стороны” (по количеству решений). Если ваше решение имеет проблемы, автоматизация позволит вам размножить проблемы и получить очень много проблем вместо одной. Автоматизация не решает проблем сама по себе, она просто усиливает существующее решение. Мир полон ужасных решений – неоптимальных, дорогих и ненадежных. Инженер должен делать мир лучше, пусть и понемногу, шаг за шагом.