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

CGI и первые динамические сайты

В начале 1993 года, когда веб перестал быть набором статических страничек, команда NCSA опубликовала в рассылке www-talk спецификацию для вызова исполняемых файлов из командной строки. Другие разработчики тепло приняли её, и с тех пор CGI (Common Gateway Interface, не путать с computer-generated imagery) стала стандартом для веб-серверов. 

Эта спецификация интерфейса позволяла веб-серверу запускать скрипт при каждом HTTP-запросе и передавать ему входные данные (например, информацию из формы), а затем возвращать пользователю ответ. Грубо говоря, это был очень простой способ оживления веб-страниц, потому что CGI-скрипты можно было писать на Perl, C, Python и даже Bash. Пример скрипта оставил ниже: 

#!/usr/bin/perl

print "Content-Type: text/html\n\n";

print "<html><body><h1>Hello from CGI!</h1></body></html>";

В свою очередь, веб-сервер передавал параметры запроса через переменные окружения, контент запроса — через STDIN, а скрипт выводил результат для сервера через STDOUT. Старожилы ещё, наверное, помнят папку cgi-bin на серверах в то время. В ней лежали заветные скрипты — гостевые книги на Perl, счётчики посещений и формы обратной связи.

В целом, CGI быстро стал стандартом динамического веба, так как его поддерживали все популярные веб-серверы (Apache, IIS и другие). Казалось бы, всё идеально… пока трафик не начал расти.

Курс на CGI-катастрофу

Изначально CGI-сайты были небольшими, и модель «один процесс на запрос» не вызывала никаких проблем. Но стоило нагрузке немного подрасти, проявились серьёзные недостатки. 

Во-первых, каждый новый HTTP-запрос порождал внешний скриптовый процесс (из-за той самой изоляции), а по завершении он уничтожался. Если пользователи одновременно отправляли десятки запросов (а тем более сотни или тысячи), сервер прождал множество процессов. 

Во-вторых, оверхед на создание процесса был огромен. Системе приходилось каждый раз загружать интерпретатор, инициализировать окружение, открывать файлы, и всё это было ради одного короткого ответа. В результате большая часть ресурсов CPU тратилась впустую на постоянный запуск/закрытие внешних программ. Неудивительно, что при высокой посещаемости CGI начинал «задыхаться».

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

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

В конечном счёте, к концу 90-х CGI уже не тянул высоконагруженный веб. Если сайт становился популярным, прирост трафика вызывал лавину процессов, а они, в свою очередь, забивали систему и тратили и так ограниченные ресурсы хостинга тех времён. Вот он, рецепт интернет-катастрофы — нужен был новый подход. 

Первые обходные пути

Из-за того, что проблема касалась всех, несколько решений появились почти одновременно. Разработчики веб-серверов решили сделать так, чтобы код выполнялся внутри самого сервера, минуя запуск внешних процессов. Так появились проприетарные серверные API, например, NSAPI от Netscape и ISAPI от Microsoft. 

Apache изначально был мультипроцессным, но CGI всё равно требовал запуска отдельной внешней программы на каждый запрос. Чтобы убрать этот оверхед, Apache тоже обзавёлся своим API для модулей — вместо того, чтобы каждый раз запускать новую программу, он позволял загружать код расширений в память сервера и вызывать его напрямую при запросах. Позднее на основе таких API появились модули вроде mod_perl и mod_php, которые фактически встраивали интерпретатор Perl или PHP прямо в процессы веб-сервера. За счёт этого скрипты выполнялись заметно быстрее.

Эта архитектура набрала популярность в начале нулевых. Достаточно было установить модуль, и можно крутить динамический сайт без внешних CGI-программ. Однако у так называемого (мной) модульного подхода были свои минусы:

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

  • зачастую модули были привязаны к языку (писались на C/C++), и код приложений должен быть потокобезопасным, 

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

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

Нужен был компромисс, который сохранил бы плюсы CGI (простоту и изоляцию), но убрал главный его минус — запуск процесса на каждый запрос. И такое решение появилось.

Спасение веба

В 1996 году компания Open Market предложила FastCGI. Идея разработчиков состояла в том, чтобы внешняя программа запускалась один раз и продолжала работать, обслуживая множество запросов подряд. То есть вместо того, чтобы «убивать» процесс после каждой страницы, его держали живым и переиспользовали.

Интересно, что изначально FastCGI разрабатывался как открытое расширение CGI, устраняющее узкое место с производительностью без отказа от самой модели взаимодействия. Но после стал отдельным протоколом. 

В отличие от CGI, нужный FastCGI-процесс запускается при старте веб-сервера или при первом запросе. Веб-сервер вместо прямого запуска программы устанавливает с ней соединение (через Unix-сокет или TCP) и передаёт данные запроса в уже запущенный процесс. FastCGI-процесс получает запрос, обрабатывает его и затем возвращается в режим ожидания следующего запроса. 

Один и тот же процесс может последовательно обработать сотни и тысячи запросов, порождая новые процессы лишь для параллельности или резервирования. Так устраняется главная проблема CGI — все инициализации (загрузка интерпретатора, подключение к базе, чтение конфигов) происходят один раз при старте FastCGI-пула.

FastCGI в буквальном смысле сохранил архитектуру «процесс вне сервера», но сделал её долгоживущей. Это дало сразу несколько преимуществ: 

  • произошёл скачок в производительности — CGI-скрипт упирался в 5–10 запросов в секунду, а FastCGI на момент создания уже выдавал десятки и сотни,

  • сохранилась независимость языка — FastCGI-приложение может быть написано на любом языке, поддерживающем сокеты (таких подавляющее большинство),

  • осталась изоляция процессов — отдельный процесс, падение или утечка памяти в нём не приведут к падению веб-сервера и не затронут другие приложения,

  • появилась гибкость в развёртывании и горизонтальное масштабирование — FastCGI-процессы могут работать не только на той же машине, что и веб-сервер, но и на внешних серверах через TCP/IP. 

Стоит отметить, что безопасность и изоляция с FastCGI относительно mod_php et al тоже улучшились. Поскольку FastCGI-демоны запускаются вне веб-сервера, их можно запускать от имени разных системных пользователей, ограничивать chroot-джейлами и прочими методами. Например, хостинг-провайдеры получили возможность запускать PHP-пулы для каждого пользователя отдельно, изолируя сайты друг от друга. При этом дикая просадка, которая была бы при CGI, тут не появляется. 

Конечно, за всё приходится платить. Долгоживущие FastCGI-процессы означают, что в памяти в ожидании запросов постоянно висит рабочий пул, увеличивая базовое потребление памяти. Например, пул из десяти FastCGI-процессов может потреблять сотни мегабайт RAM, даже когда трафика почти нет. К слову, CGI же ничего не ел в простое, ведь процессы просто отсутствовали.

Ещё разработчикам нужно было переписывать или адаптировать свой код для таких процессов. Приходилось исправлять утечки памяти и учитывать глобальные состояния, которые при CGI-сценарии сбрасывались после каждого запуска. Но для проектов с большой нагрузкой выбор был очевиден — лучше выделить лишние 200 мегабайт памяти, чем бесполезно тратить половину мощности процессора на постоянные запуски и завершения процессов.

Спустя годы технология FastCGI незаметно вошла во многие архитектуры. Особо заметно её влияние на примере PHP-хостинга. Сначала PHP работал как модуль Apache (mod_php) или через CGI. Когда нагрузка становилась большой, mod_php на Apache начинал буксовать, поэтому в 2008–2010 годах многие крупные проекты в Рунете перешли на связку Nginx + PHP-FPM (это, по сути, и есть FastCGI-пул для PHP). На Хабре до сих пор можно найти истории тех лет о том, как переход с Apache на FastCGI давал заметный прирост производительности.

Аналогичные решения появились практически во всех экосистемах. Даже приложения на Python работают через WSGI-совместимые серверы (gunicorn, uWSGI) — архитектурно это очень похоже на FastCGI, только протокол другой. 

Куда делся CGI, и при чём тут FastCGI сегодня

CGI проиграл битву за высоконагруженный веб. Как только появилась возможность избежать создания процесса на каждый запрос, практически все крупные проекты начали уходить — кто на FastCGI, кто на встроенные интерпретаторы вроде mod_php.

Сегодня трудно встретить серьёзный веб-сайт, который бы генерировался настоящими CGI-скриптами, запускаемыми при каждом обращении. Если только это не какой-то legacy-скрипт или очень небольшой внутренний инструмент, где производительность не критична.

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

Да и в Unix-мире сама модель «для каждого запроса отдельный процесс» никуда не делась и не денется. Интересно, что в эпоху FaaS главная идея CGI возродилась в новом виде — в облаке изолированный контейнер для обработки события поднимается, выполняется и утилизируется. Только происходит это теперь в масштабах ЦОД и куда эффективнее, чем CGI на одном сервере. 

В обычной же веб-разработке CGI сегодня — редкость. Большинство языков либо работают через постоянные процессы, либо вообще сами являются постоянными сервисами. Например, приложению на Node.js или Java не нужен отдельный веб-сервер — оно само слушает порт и работает постоянно. То есть подход, противоположный CGI, окончательно победил. 

В свою очередь, FastCGI сегодня практически повсюду — он работает под капотом nginx, обслуживает PHP-пулы и крутит бэкенды — просто его мало кто замечает. А ведь без него и решений, которые пошли тем же путём, современные веб-сервисы появились бы значительно позже. Или не появились бы вовсе, потому что при первом же всплеске трафика веб бы просто «захлебнулся» пятисотыми ошибками.

Если у вас в продакшене где-то до сих пор крутится CGI-скрипт на Perl, не стесняйтесь, расскажите в комментариях. Тут не осуждают…

© 2026 ООО «МТ ФИНАНС»