Реальность сетевого инженера (с лапшой и… солью?)

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

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

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

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

Я всегда задавался вопросом, почему так? Что заставляет программистов так критиковать идею «первопричина — миф»? Словно иммунную систему, которая распознаёт инородного агента. Почему они так реагируют, в то время как девопсы скорее склонны рассмотреть эту идею?

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

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


Основное допущение разработки программного обеспечения: одни и те же входные данные надёжно и детерминированно дают одинаковую выдачу

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

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

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

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

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

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

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

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

Комментарии (32)


  1. amarao
    18.12.2019 11:59

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


    • Rust: любое IO unsafe. Любой вызов в ОС unsafe. В общем случае, любой unsafe code — undefined behavior.
    • Haskell: Монада IO, которая в себя собирает "всё плохое".

    … Но тут есть чуть больше. Время исполнения кода — это side effect. Если мы начинаем закладываться на время, то любая функция перестаёт быть чистой. Острее всего это становится в момент появления параллельного кода, когда относительный порядок выполнения недетерминирован.


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


    На самом деле, любой девопс должен стремиться к этому же. Идемпотентные плейбуки ансибла, воспроизводимые деплои в кубе, reproducible builds при сборке пакетов, декларативный процесс воссоздания любого артефакта (и отсутствие golden artifacts).


    1. PsyHaSTe
      18.12.2019 19:36

      Как только появляются сайд-эффекты (ввод-вывод), поведение системы не детерминированно

      Но ведь это не так. Чисто функциональные ФП языка потому так и называются, что они детерминированны. Естественно, никто не застрахован от событий из разряда пролета высокоэнергетической частицы через память именно в момент когда там производятся вычисления или аппаратного бага, но обычный IO в нормальных условиях — это совсем иная вещь.


      Цитата из хорошей книги на тему:


      Nowhere is it better illustrated than in the Functional Reactive Programming (frp) approach to user interaction. Instead of writing separate handlers for every possible user action, all having access to some shared mutable state, frp treats external events as an infinite list, and applies a series of transformations to it. Conceptually, the list of all our future actions is there, available as the input data to our program. From a program’s perspective there’s no difference between the list of digits of ?, a list of pseudo-random numbers, or a list of mouse positions coming through computer hardware. In each case, if you want to get the nth item, you have to first go through the first n ? 1 items. When applied to temporal events, we call this property causality

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

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


      Если вам нужен порядок вычислений — вот вам ио монада. Если вам нужен стейт — вот вам стейт монада. Если вам нужно аллокации контролировать — вот вам какая-нибудь аллок монада. Нужно проверять что мы не слишком перегрели процессор — температур монада. Хотим писать логи — райтер монада.


      В любом случае у нас не будет сайд эффекта, если нам важно что-то контролировать мы вынесем это в типы.


      1. gecube
        18.12.2019 22:30

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


        1. PsyHaSTe
          18.12.2019 22:56

          Время выполнения — это действительно сайд эффект.

          Зависит от того — важно вам время выполнения или нет. Если нет — то и сайдэффекта нет (по определению).
          Если вам важно учитывать этот эффект — то вы сделаете MeasureTimeMonad и снова будете в дамках.


          И речь вроде шла не про криптографию в любом случае.


          1. gecube
            18.12.2019 23:07
            +1

            Да, спасибо за замечание.


      1. amarao
        19.12.2019 10:16

        Язык программирования без IO смысла не имеет, мы не узнаем результат вычисления. Если же есть IO, то это всегда undefined behavior, потому что никакая модель вычислений не может ответить, что будет, если на pin42 подать +5. Может быть, лампочка загорится. Может быть, байтик в принтер уедет. А, может, это сигнал "выключите меня, пожалуйста". И не существует никакой type safety для защиты от этого.


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


        Алгоритм, допустим, o(1), и, даже, допустим, с одинаковым числом операций. Какое число будет вычислено первым?


        Я хочу заметить, что в этом примере нет "порядка" вычисления как IO, но результат выполнения двух чистых функций является настолько произвольным, что его используют как генератор рандома в современных компьютерах (haveged).


        Т.е. запуск треда — это IO. Ожидание треда — IO. Раздумие про то, сколько времени прошло (в тиках ли, или wallclock time) — это IO.


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


        1. PsyHaSTe
          19.12.2019 11:14

          Язык программирования без IO смысла не имеет, мы не узнаем результат вычисления. Если же есть IO, то это всегда undefined behavior, потому что никакая модель вычислений не может ответить, что будет, если на pin42 подать +5.

          Можно пойти в спеку процессора и посмотреть что происходит когда на пин42 подается +5. Это и будет ответ на вопрос. Если в какой-то момент спека нарушена — ну, это нарушение спеки и аппаратный баг, да. Несите новый камень.


          Алгоритм, допустим, o(1), и, даже, допустим, с одинаковым числом операций. Какое число будет вычислено первым?

          Параллельность — это явный отказ от последовательности. Определение последовательности выполнения параллельного кода — оксюморон.


          Я хочу заметить, что в этом примере нет "порядка" вычисления как IO, но результат выполнения двух чистых функций является настолько произвольным, что его используют как генератор рандома в современных компьютерах (haveged).

          Композицию чистых функций нельзя использовать для рандома просто по определению. Ну или если вас устроит рандом который всегда выдает 4


          image


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

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


          1. amarao
            19.12.2019 12:37

            Я немного не понял вас. Если мы выполнили в двух тредах две функции и джём их результата ( await_any()) — это ещё чистая фукнция или уже нет?


            1. PsyHaSTe
              19.12.2019 12:44

              Так мы ничего не ждем. Мы только создали футуру (или таск, или промиз, как удобно), который нужно выполнить. Создание такой футуры это чистая функция. Интерпретация (выполнение соответствующих запросов) — нет. Сама программа не может дождаться выполнения футуры. Иными словами, вы не можете написать функцию Promise<T> -> T


              x = await new Promise<T>(...) это просто сахар для new Promise<T>(...).then(x => ...)


              1. amarao
                19.12.2019 12:51

                Ну, с тредами почти так же. Код (замыкание) для треда — чистая функция, его запуск и ожидание — IO.


                Я про это и говорю — многопточное программирование закрывает вопрос детерминированности программы полностью.


                1. PsyHaSTe
                  19.12.2019 13:02

                  Вопрос в том что считать под детерминированностью. Возьмем например вывод текста параллельно на экран.


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


                  Свести к Д можно всегда (не считая аппаратного сбоя), вопрос что мы под этим понимаем.


                  1. amarao
                    19.12.2019 13:07

                    Если мы делаем математику/логику между параллельными тредами, то мы можем получить чистый jitter, достойный haveged. Например, если мы из первого полученного числа вычитаем второе и выводим результат на экран. Цифра — одно из множества возможных значений, а не фиксированная величина.


                    На самом деле вывод на экран — это уже IO, которое совершенно не определено и является UB.


                    1. PsyHaSTe
                      19.12.2019 13:24

                      Как по вашему в ФП языках живут если они и на экран выводят, и в многопоток умеют? У них "Нечестная" чистота? Или нечисто, но все старательно делают вид, что всё нормально?


                      1. amarao
                        19.12.2019 13:27

                        На экран выводят в монадках или их заменителях. Делают UB, который чаще всего то, что им обещали, но обещали не создатели языка, а посторонние люди (у которых в доках 100500 нюансов, а финальная документация на Си с вставками на ассемблере, ссылающимся на architectural guide, в котором есть инструкции процессора, которые делают UB тоже).


                        1. PsyHaSTe
                          19.12.2019 13:40

                          делают UB, который чаще всего то, что им обещали

                          Либо вы не понимаете, что такое UB, либо нарочно вводите в заблуждение.


  1. Sdima1357
    18.12.2019 12:12

    Недетерминированных программистов детерминировано увольняют и берут детерминированных


    1. ne_kotin
      18.12.2019 16:05

      В общем случае детерминированных программистов не существует…


      1. Sdima1357
        18.12.2019 16:17

        В общем случае меня нет, но я есть и меня детерминировано не будет и я программист.


  1. BOM
    18.12.2019 12:46
    +1

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


    1. rzerda
      18.12.2019 15:05

      Как-то раз я месяц фулл-тайм ловил (как оказалось) баг в ядре Linux (про фриз при нескольких очередях в veth), вызванный изменением, вызванным другим багом в ядре Linux (про iptables и MSS adjust), вызванным другим изменением, вызванным желанием сэкономить PPS за полтора года до начала проявления бага с фризом. PPS, что характерно, в итоге так и не экономился.


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


      1. edo1h
        18.12.2019 22:01

        неужели не было потом приятно "я же понял как эта чёртова система работает"?


        1. gecube
          18.12.2019 22:32

          Лучше бы, чтобы некоторого опыта не было ) с одной стороны — да, круто, разобрались, повысили квалификацию, а, с другой стороны — сколько полезного (на самом деле нет) мы могли сделать за время расследования причин проблем, которые сами же и придумали.
          Вот коллеги тоже, например, постреляли себе по ногам с обеих рук: https://m.habr.com/ru/company/timeweb/blog/428954/


  1. toivo61
    18.12.2019 13:13

    Реальный мир, он, вообще-то аналоговый и с помехами, а не цифровой…
    И причиной может быть интерференция событий, а не суперпозиция…


  1. t13s
    18.12.2019 16:34

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

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


  1. edo1h
    18.12.2019 19:09

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

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


  1. Naves
    18.12.2019 19:36

    Не сказано про стоимость поиска первопричины. Если сервис падает раз в полгода и это не имеет особого влияния. То можно и не искать причины.
    А если каждый второй отчет в бухгалтерии падает с непонятной ошибкой, в 1с ничего не понятно, в БД ничего не понятно, RAID-контроллер, как пионер, говорит готов. То придется искать первопричину, которая будет в одном битом секторе на одном жестком диске в массиве.


    1. edo1h
      18.12.2019 22:00

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


  1. AliluyaFak
    18.12.2019 20:48

    Как-то много философии в комментах)


    1. amarao
      19.12.2019 10:21

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


  1. erthad
    19.12.2019 13:43

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


    1. erthad
      19.12.2019 13:48

      Если перевести на язык программистов, к примеру: у тебя в форме входа нет валидации на поле е-мейл, нет защиты от SQL-инъекций, пароли хранятся в базе в открытом тексте, при проверке логина вычитывается из базы запись о пользователе целиком несмотря на то, что нам нужен только логин, плюс наверняка есть еще несколько багов про которые мы не знаем.
      У тебя угнали пароли пользователей через форму входа, в чем корневая и единственная причина этого инцидента?


  1. igor-sheludko
    20.12.2019 13:20

    Случилась проблема, нужно пофиксить, что делать?
    Ответ программиста — постараться локализовать причину, а в идеале найти причину и пофиксить. Бывает, что проблема воспроизводится с трудом, случается нечасто, тогда, может быть и не стоит дальше копать. Но какие-то решения пользователям нужно предложить.
    Что-то я не понимаю, какие тут могут быть альтернативы от инженеров или кто-там не такой как программист?