Ночь, когда мой компьютер перезагрузился сам

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

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

Сбой без сообщения

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

Обычный баг оставляет тело. Этот оставил пустую комнату. И ошибка без сообщения — это особого рода улика: обычно она означает, что сбой произошёл где-то под вашей программой, в слое, который не останавливается, чтобы объясниться, прежде чем утянуть за собой всё остальное.

Я заложил в бюджет не ту память

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

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

Куда ушла память хоста

Отправка огромного пакета работы на GPU не бесплатна на стороне хоста. Прежде чем GPU сможет что-либо запустить, работу нужно подготовить, настроить и передать — и эта подготовка живёт в системной RAM. Каждая часть этой настройки сама по себе была скромной. Помноженные на пакет, достаточно большой, чтобы насытить GPU, скромные части сложились в нечто огромное.

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

Почему я этого не предвидел

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

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

Исправление: ждать стену и отскакивать от неё

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

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

Предположения о ресурсах молчат, пока не перестанут

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

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

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

— Никаких сигналов, никаких доходностей, не инвестиционная рекомендация.