Привет, Хабр! Это моя первая проба пера. Статья будет на тему «как разработчик из кровавого Enterprise статейник писал». Может быть, интересно либо таким же как я, либо наоборот, ребятам которые занимаются сайтами‑статейниками и подумывают в сторону работы в больших корпорациях. Словом, для всех, кто хочет посмотреть, как оно там, у других. В ходе написания статьи я буду использовать привычную терминологию и пояснять ее. Статья идет от простого к сложному, поэтому если вы из «кровавого Enterprise» — можно пролистать статью до момента деплоя (часть 2).

Когда речь заходит о разработке сайтов, можно поделить людей на 2 категории. Назовем их так (долго думал над названиями, получилось как получилось):

  • Вебмастер — человек, который умеет подключать и настраивать CMS (и как правило, больше чем одну), знает что‑то про SEO, про инструменты продвижения, про рекламу, и имеет какое‑то представление про Frontend/Backend. Эдакий «универсальный солдат»

  • Enterprise‑инженер (Frontend/Backend) — человек, который глубоко погружен в специфику своей сферы. Как правило работает над проектами, для которых нужна глубокая кастомизация, нужно писать много кода.

Для кого статья: 

  • Вебмастер, которому интересен ход мыслей инженера из крупных компаний (я достаточно долго работал в Яндексе и нескольких зарубежных компаниях на 1000+ разработчиков). Для вебмастера больше подойдет часть 1 текущей статьи.

  • Enterprise‑инженер, делающий отдельно Frontend/Backend в крупной компании, которому интересно, с чем сталкивается такой же человек, пробующий запустить что‑то в продакшене вне привычной экосистемы и деплоя по кнопке. Для Enterprise‑инженера больше подойдет часть 2 текущей статьи. Здесь будут более продвинутые вещи, например, как загрузка картинки через админку приводила к downtime сайта, и как я исследовал проблему. А так же проблемы, что же делать после деплоя, и как с этим жить

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

Часть 1. Сбор требований и выбор инструментов

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

// Нефункциональные требования (aka non‑functional requirements) — требования, не относящиеся непосредственно к бизнес‑логике. Надежность, отказоустойчивост��, легкость внесения правок, покрытие метриками, скорость загрузки сайта и так далее

Часто сталкиваясь с выбором тех или иных вещей я подумал, что мне нравится идея сайта, который будет помогать с выбором. Например, как выбрать кофе, чай, кроссовки для бега… Так я и определился с функциональными требованиями.

// Функциональные требования (aka functional requirements) — требования к бизнес логике приложения. Какие сценарии должны решаться, какие роли должны быть в системе, что они должны уметь, и как

Функциональные требования:

  • Сайт должен представлять из себя набор статей на разные темы, содержать текст и картинки в статьях

  • Должна быть возможность быстро и удобно добавлять новые статьи

  • Статьи должен уметь добавлять администратор, пользователи могут только смотреть статьи

Нефункциональные требования:

  • Эффективность с точки зрения ресурсов (слабее железо = меньше расходов = больше прибыль)

  • Портируемость (иметь возможность перенести на более мощное железо, либо отделить компоненты отдельно, например, СУБД) // это, кстати, пригодилось, но об этом позже

  • Масштабируемость (не умереть если сайтом будет пользоваться много людей)

  • Скорость (пользователь не должен думать «как же туго сайт работает»)

  • Легкость добавления статей (нужно чтобы я мог добавлять статьи быстро, фокусируясь только на контенте, а не на том, как бы это закодить)

Я не стал идти в сторону долгостроев с React/Angular, написания собственного API… Посоветовавшись с ChatGPT и сделав несколько запросов в поисковик я остановился на Wordpress. Большое комьюнити + популярность = залог стабильности. При этом что меня смущало в Wordpress — я понимал, что сайт будет работать медленно, так как скорость — не главный конёк для Wordpress. 

При этом можно оставить Wordpress, но сам сайт сделать быстро при помощи дополнительных инструментов. Один из таких инструментов — Nginx.

// Nginx — популярный веб‑сервер, часто использующийся как reverse‑proxy. Типичное применение Nginx — пользователь отправляет запросы в Nginx, тот делает запросы к другим серверам (upstream), и отдает пользователю результат. Как правило, используется стратегия round robin (Сначала Server A, потом Server B, потом Server C, потом Server A…)

Частый сценарий использования Nginx
Частый сценарий использования Nginx

Nginx зарекомендовал себя как очень эффективный инструмент. С чем точно может помочь Nginx для статейника:

  1. https (достаточно легко настраивается работа с сертификатами)

  2. Cжатие. Gzip помогает существенно снизить объем передаваемых данных по сети, а значит и скорость загрузки (Если коротко и на пальцах — gzip превращает последовательность символов в код, то есть грубо говоря, перекодировать '<div class' → 1, '<a href=' → 2). Сам по себе HTML содержит большое количество повторяемых символов, следовательно, хорошо «гзипуется»

  3. Кеширование. Nginx может запоминать ответы на какое‑то время, и вместо того, чтобы каждый раз Wordpress доставал что‑то из базы Nginx может просто отдавать закешированную страницу, что должно существенно ускорить время отклика. Здесь я видел самую главную пользу — для 10–50 статей (даже если 200 и больше) нет смысла каждый раз исполнять php код и доставать данные из mysql. Nginx может кешировать статьи неделями

Отсюда появилась базовая архитектура статейника:

Базовая архитектура статейника
Базовая архитектура статейника

В архитектуре меня все устраивало. На первом этапе я планировал развернуть все компоненты (Nginx + WordPress + MySQL) на одном сервере, но в то же время архитектура была достаточно гибкой для того, чтобы можно было масштабироваться. То есть в каком‑то отдаленном будущем система могла находиться на разных серверах и выглядеть как‑то так:

Потенциальное масштабирование
Потенциальное масштабирование

Осталось разобраться с переносимостью. Мне не хотелось бы каждый раз устанавливать заново Nginx, WordPress, MySQL на сервера, на которые мне бы пришлось «переезжать». Поэтому нужно было что‑то, что поможет мне с установкой и разворачиванием моего стека. Здесь на помощь приходит Docker.

// Если вы никогда не использовали Docker, то я бы попробовал его объяснить как архивы, которые можно запускать, и удобно скачивать. Если упростить: «возьми мне Mysql версии 8, и запусти». Docker скачает нужный образ, и запустит. Удобность докера в том, что он работает везде одинаково. Если вы отладили Dockerfile (файл с настройками), то развернуть приложение в продакшене будет можно той же командой, что и локально (вам не нужно думать о том, не забыли ли вы чего‑нибудь установить).

Итого я собрал почти все, что мне нужно. Остались метрики. Я подключил метрики на сайте от Яндекс и Google, но мне хотелось бы видеть еще потребляемые ресу��сы в моменте, некую картинку типа этой (взял со своей админки). Для сбора и отображения использовал Prometheus (база данных для метрик) и Grafana (инструмент для визуализации метрик. Умеет работать в том числе с используемым Prometheus).

Grafana UI выглядит следующим образом (можно настроить разные промежутки времени, все достаточно удобно и интуитивно). Конкретно на этом графике виден всплеск потребления CPU, на который, возможно, стоит обратить внимание:

Совсем полная схема включая инструменты для админа (меня) выглядит так:

Финальная архитектура
Финальная архитектура

Пользователи читают статьи. Я загружаю статьи через админку, и смотрю на метрики. Метрики читаю через Grafana, Grafana берет метрики из Prometheus. В свою очередь Prometheus берет метрики из Nginx, Wordpress, MySQL.

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

  • Wordpress

  • MySQL

  • Nginx

  • Prometheus

  • Grafana

  • Docker

  • Git (+Github) для ведения истории разработки и релизов

Часть 2. Запуск и проблемы

Спустя некоторое время танцев с бубном я убедился локально, что:

  1. Сайт на Wordpress запускается через Docker

  2. Nginx запускается, проксирует запросы в Wordpress

  3. Кеш на Nginx работает для всего, кроме админки (/wp‑admin), для того, чтобы можно было спокойно загружать контент

Следующими шагами было купить домен и https сертификат. На этих шагах останавливаться не буду, получилось без каких‑либо проблем. Во время выбора домена пришлось сменить первоначально задуманное название «easychoice» на «tvoisovet», так как «easychoice» был занят, и пришлось выдумать что‑то другое, но в то же время звучное. Так мой статейник наконец оказался в продакшене.

Я выбрал тему более‑менее приятную по внешнему виду, чтобы она была не слишком плохой, и чтобы текст можно было удобно читать.

Статьи я генерировал через chatgpt, используя заготовленный промпт: Ты пишешь статью для сайта с рекомендациями от имени экспертов. Нужно написать статью на тему «Как выбрать кофе»

Полученный текст я копировал, вставлял в сайт, и подгружал пару картинок из интернета. Местами дописывал/переписывал руками. Какие‑то куски текста убирал, какие‑то просил расширить. Но в общем и целом костяк страницы был сгенерирован, и дополнен картинками вручную. На каждую статью уходило в среднем 25–50 минут.

Долгожданная первая страница увидела свет.

Часть 2.1 downtime на загрузку картинки из админки

Достаточно быстро обнаружилось, что если добавить 2–3 картинки в статью из админки, то сайт начинает сначала заметно медленно отвечать, потом перестает работать вообще. Спустя некоторое время он начинал работать после перезапуска процессов.

«Наверное, оперативной памяти не хватает», подумал я, уверенно введя top в терминале, и обнаружил, что оперативная память в норме, и процессор не выглядит нагруженным. Здесь стоит отметить, что на этом этапе у меня еще не были реализованы метрики через Prometheus + Grafana. Я хотел запустить сайт в продакшене пораньше, и обзавестись метриками после запуска.

Простая нагрузка была непосильной для моей конфигурации, и было непонятно почему. Нужно было как‑то это исследовать. Здесь мне помогла подготовка к одному из собеседований в бигтех (который я, к слову, благополучно завалил) с уклоном в SRE(Site Reliability Engineer).

Одна из очень классных стратегий исследования системы — the USE (Utilisation, Saturation, Errors) method (https://www.brendangregg.com/usemethod.html). Ее идея достаточно прозрачна. Берем каждый из ресурсов (CPU, RAM, Network, i/o), проверяем utilization (сколько% занято), saturation (как правило, задержки, ожидание, блокировки), errors (ошибки). Есть много разных инструментов, пользуясь случаем рекомендую прекрасную статью — SRE in 60 seconds (https://netflixtechblog.com/linux‑performance‑analysis‑in-60-000-milliseconds‑accc10403c55). Она дает хорошее понимание большинства инструментов, которые вы можете использовать для отладки.

Я запустил vmstat в терминале и начал смотреть на чиселки в момент загрузки картинок. Вывод vmstat был следующим:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st          
 1  3  81900  72016   2260 120104    1   43  7320    89    0 1409  2  3  0 95  0
 0  2  81900  65464   2264 126412    0    0  6300     0    0  958  3  2  0 95  0
 0  2  81900  63196   2396 128436    4    0  2172     0    0  822  1  1  0 98  0
 0  2  81900  57148   2468 134488    4    0  6108    60    0  986  1  0  0 99  0
 0  2  81900  73036   1112 119704    0    0  3592    92    0  928  1  3  0 96  0
 0  4  81900  72032   1112 120404    0    0   700     0    0  832  0  0  0 100  0
 0  3  81900  69512   2068 122124    0    0  2696     0    0  918  1  2  0 97  0
 0  3  81900  66488   3252 123576    0    0  2604     0    0  953  1  2  0 97  0
 0  3  81900  61196   4448 127012    0    0  4600     0    0 1143  0  2  0 98  0
 0  4  81900  57444   5108 128244    4    0  1872    24    0 1188  2  4  0 94  0
 0  9  81900  67376   5088 118360    0    0  5608     4    0 1295  2  1  0 97  0
 0  6  81900  61328   5384 123728    0    0  5636     0    0 2042  1  2  0 97  0
 1  3  81900  62500   4300 121628    0    0  9408     0    0 1584  6  3  0 91  0
 1  3  81900  58244   4680 125136    0    0  3348    12    0 1182  2  3  0 95  0
 0  3  81900  71564   2160 114448    0    0  4268    20    0  924  1  3  0 96  0

Сразу стало расти procs→b и cpu→wa. Это значило, что ряд процессов был чем‑то заблокирован, и не мог продолжить работу. Это пролило хоть какой‑то свет на происходящее. Пройдя по ресурсам я начал смотреть на утилизацию i/o и сети. К своему большому удивлению, я обнаружил утилизацию i/o на 100%. 

В целом картина сходилась. i/o не справляется с нагрузкой в то время когда мы загружаем картинку, и не может вычитывать html/css из файловой системы. Это приводит к долгому ответу (ждем пока i/o освободится, потом считываем файлы со статикой).

Я открыл хостинг, и увидел, что для хранения был выбран HDD. Из‑за малого iops (input‑output per second) он не справлялся с нагрузкой. 

Переезд на SSD был достаточно безболезненным (слепок диска, новый инстанс из слепка, переезд домена на ip нового инстанса). После переезда на SSD проблем с работоспособностью больше не наблюдалось.

Для проверки «пострелял» сайт через Apache Benchmark. Проверка получилась достаточно грубой, но значительно лучше, чем никакая. В примере ниже запускаем 500 запросов на <URL> посредством 10 параллельных запросов:

ab -n 500 -c 10 <URL>

Вывод выглядел примерно так:

Concurrency Level:      10
Time taken for tests:   24.250 seconds
Complete requests:      500
Failed requests:        0
Total transferred:      34708000 bytes
HTML transferred:       34495000 bytes
Requests per second:    20.62 [#/sec] (mean)
Time per request:       484.998 [ms] (mean)
Time per request:       48.500 [ms] (mean, across all concurrent requests)
Transfer rate:          1397.72 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      223  253  17.0    251     329
Processing:   213  222   6.0    221     285
Waiting:      141  147   3.9    146     169
Total:        439  475  18.4    472     551

Я был готов к наплыву пользователей

Часть 2.2 Первые пользователи

Итак, сайт был в продакшене. Но по метрикам пользователей было грустновато:

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

Рекламная кампания «Большие надежды»

Я настроил через Яндекс.Директ рекламу на свою первую страницу — «как выбрать кофе». На удивление это было достаточно легко сделать. Сервис сам предложил сгенерированные через ИИ слоганы и картинки. А также показал, какие из них кликаются больше, а какие — меньше. В ходе рекламной кампании можно было их редактировать, словом, очень удобно. Бюджет рекламной кампании был 2500 рублей (около $30 на тот момент). Забегая вперед — бюджет второй рекламной кампании был таким же.

За неделю мою страницу увидели почти 15 тыс. людей. Я увидел, что «глубина просмотра» небольшая, но средне время на сайте заметное — больше минуты. «Значит, люди читают одну страницу и уходят» решил я. Дописав больше статей и добавив перекресных ссылок я запустил вторую кампанию: «как правильно бегать».

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

Рекламная кампания «Надежда умирает последней»

Результат был прежним: пока я готов платить — люди готовы смотреть. Спустя пару месяцев трафик на сайте вернулся к нулевому уровню. Я добавил Турбо страницы и AMP, посмотрел как можно прийти к органическому трафику, и основные рекомендации были примерно «нормально делай — нормально будет». 

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

Главные выводы, которые я сделал для себя: 

  1. Продавать сложнее, чем реализовать (по крайней мере, для меня)

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

  3. 1 час в неделю достаточно для того, чтобы довести что‑то до продакшена

  4. Я точно хочу выпустить еще какой‑нибудь проект, но уже, видимо, не статейник.

Кстати, буду рад любым советам по продвижению. Я уже не очень верю, что в него можно вдохнуть жизнь, но мало ли.

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


  1. Yevvv
    08.11.2025 14:53

    Турбо страницы и AMP - этож ровно обратное органическому трафику) тупо трафик остается в поисковике.


  1. spions
    08.11.2025 14:53

    Как вы решили/собираетесь решить проблему с разными wp-content при масштабировании?


  1. siarheiblr
    08.11.2025 14:53

    Вот интересно, а классический дедовский «статический хтмл с картинками» тут бы не подошел? Вроде как по описанию подходит.

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