Thrashing: когда переключение задач топит систему

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

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

Простой и самый очевидный пример — запросы на веб/API-сервер. Запросы выполняются параллельно и, если не уследить за утилизацией, 80% утилизации быстро превращаются в 100% из-за необходимости переключения между большим количеством задач и сервер падает. Есть точка невозврата, после которой при линейном росте запросов утилизация начинает расти экспоненциально за счет оверхеда для переключения между задачами. Поэтому утилизацию рекомендуется поддерживать на уровне 50%.

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

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

Видите параллель с многозадачностью человека?

Многозадачить можно если:

  1. Задачи не требуют длительного формирования контекста.
  2. Между задачами не нужно переключаться слишком часто.
  3. Количество задач ограничено 2-3 максимум.

P.S. Советую почитать книгу Algorithms to Live By, которая проводит параллели между процессами в компьютерных системах и голове человека.

P.P.S. Ситуация с Next.js — классический пример, когда "на локалке все работает". На моем макбуке М1 про с 8 ядрами процесс завершался за 2-3 минуты, а на GitHub Actions через 15 минут наступал timeout.

Выяснилось, что на GHA на машине всего 2 ядра + машинка слабее, что привело к многократному росту времени билда, который перестал укладываться в отведенные ему 15 минут. Чтобы пофиксить пришлось ограничить количество используемых ядер танцами с бубном. Надо отдать должное ChatGPT — предложенное им решение оказалось рабочим. Я спросил как он до такого догадался и он сказал, что прочитал исходники Next.js и увидел там переменную среды, которой можно контролировать используемые ядра.

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