Внезапно, одной лишь системы сборки мусора недостаточно

Автор оригинала: Simon Arneaud, The Art of Machinery blog
  • Перевод
Вот вам небольшая история о таинственных сбоях сервера, которые мне пришлось отлаживать год назад (статья от 05 декабря 2018, прим.пер). Сервера некоторое время работали нормально, а затем в какой-то момент начинали аварийно завершаться. После этого попытки запустить практически любую программу, что были на серверах, терпели неудачу с ошибками «На устройстве нет места», хотя файловая система сообщала только о нескольких занятых гигабайтах на ~20 ГБ дисках.

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

Файловые системы в *nix разделяют имена файлов и данные в файлах. Данные на диске могут иметь несколько имён файлов, указывающих на них (т.е. жёсткие ссылки), и данные удаляются только тогда, когда удалена последняя ссылка. Открытый дескриптор файла считается ссылкой, поэтому если файл удаляется во время чтения программой, то имя файла исчезает из каталога, но данные файла остаются живы до тех пор, пока программа не закроет его. Вот что и происходило с логгером. Команда du («disk usage») ищет файлы, используя листинг каталогов, поэтому она не видела гигабайты файловых данных для тысяч лог-файлов, которые еще были открыты. Эти файлы были обнаружены только после запуска lsof («list open files»).

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

Когда-то я писал большую часть своего кода на Си, а затем на Си++. В те времена я думал, что ручного управления ресурсами достаточно. Насколько сложным это было? Каждому malloc() нужна функция free(), а каждому open() нужна close(). Просто. За исключением того, что не все программы просты, поэтому ручное управление ресурсами со временем стало смирительной рубашкой. Потом однажды я открыл для себя подсчет ссылок и сборку мусора. Я подумал, что это решает все мои проблемы, и полностью перестал заботиться об управлении ресурсами. Опять же, для простых программ это было нормально, но не все программы просты.

Рассчитывать на сбор мусора не получается, потому что это решает только проблему управления памятью, а сложным программам приходится иметь дело с гораздо большим, чем просто с памятью. Есть популярный мем, который отвечает на это тем, что память — это 95% проблем с ресурсами. Можно даже сказать, что все ресурсы — это 0% ваших проблем — до тех пор, пока у вас не закончится один из них. Тогда этот ресурс становится 100% ваших проблем.

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

Планирование владения и срока жизни ресурсов является неизбежной частью проектирования сложного программного обеспечения. Это проще, если вы используете некоторые общие паттерны. Один из паттернов — это взаимозаменяемые ресурсы. Примером является иммутабельная строка «foo», которая семантически такая же, как и любая другая иммутабельная «foo». Этот вид ресурсов не нуждается в заранее заданном сроке жизни или владении. На самом деле, для того, чтобы система была как можно проще, лучше не иметь заранее установленного срока жизни или права владения (привет Rust, прим.пер). Другой паттерн — это ресурсы, которые не являются взаимозаменяемыми, но имеют детерминированную продолжительность жизни. Это включает в себя сетевые подключения, а также более абстрактные понятия, такие как право управления частью данных. Самым разумным является явное обеспечение продолжительности жизни таких вещей при кодировании.

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

Подробнее
Реклама

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

    0
    «На устройстве нет места», хотя файловая система сообщала только о нескольких занятых гигабайтах на ~20 ГБ дисках.

    Просто нужно использовать нормальную, современную ФС с динамическим выделением I-node, а не это гавно мамонта ext4 (у которого из под закрытого кувалдой капота торчат грабли и костыли ext2). И тогда таких странностей не будет.

    … правда будет уже 100% заполнение томов логами, но это легче детектируется стандартными средствами мониторинга.
      +1

      Вот не понятно, чем XFS, или любая другая posix совместимая файловая система спасла бы от незакрытых файловых дескрипторов на 20ГБ логов.


      Вы просто хотели поделиться своей болью от EXT4? Так напишите пост, вместо того, чтобы комментарии не по теме оставлять.

        0
        Так я же уточнил — от кривой софтины никакая FS не спасет, но постоянно растущий объем занятого пространства — легко мониторится в отличии от мониторинга числа доступных I-node.

        А в плане закрытия всяких ресурсов — я прямо таки влюбился в Go-шный defer. Просто не понимаю почему такую «киллер-фичу» не тащат в другие языки (ну или я просто не в курсе).
          0
          RAII есть почти везде. В виде например using в C# или scope(exit) в D, try-with-resources в Java, и конечно классические деструкторы C++
            0

            Чем этот defer отличается от scope guards в плюсах?


            Я уж не говорю о мощности вещей вроде хаскелевского rank-2 polymorphism или ResourceT.

          0
          Какую стильно-модно-молодёжную ФС предлагаете использовать вместо ext4?
            0
            «стильно-модно-молодёжную ФС» — это не ко мне, я про нормальную, современную говорил.
            И если про нормальную, то Btrfs или XFS. Да, я знаю, сейчас скажите, что XFS старая и файлы теряет, а от btrfs красный шляпы отказались. Так что пользуйтесь лучше дальше ext4… модная — вполне, стильно… ну если костыли под капотом не замечать, то можно и стильной назвать…
              0
              Т. е. Ваш ответ — никакую. ext4 неправильная, xfs ненадёжная, btrfs неподдерживаемая.
                0
                Это зависит от того — распознали ли вы мой сарказм или приняли его за чистую монету.
          +1
          Поражает способность западных айтишников превращать свой фейл в челендж, если бы у меня такая ситуация возникла, гигабайты есть, а места нет, тут же проверил бы inode (а еще раньше чем закончились дескрипторы, об этом бы сообщил мониторинг), перезапустил сервак и отписался разработчикам, делов на 15 минут, а у них сразу появляются таинственные сбои, отладка, и статья, как вишенка на торте.
            +1
            люди учатся, общаются, а вы молодец, конечно
            +1

            В наши дни на C++ достаточно средств для полу-автоматического управления ресурсами. Умные указатели где и подсчёт ссылок, и автоматическое освобождение ресурса, и идиома RAII. Если подходить с умом, проблем не будет

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

            Самое читаемое