MAATRIX / Блог / Предзаказ открыт: нагрузка, которая приходит точно по расписанию

Предзаказ открыт: нагрузка, которая приходит точно по расписанию

MAATRIX

На баннере уже неделю висит дата и время: «Предзаказ откроется 15 сентября в 10:00 МСК». Тысячи людей ставят будильник на этот момент и держат палец над кнопкой «Обновить» — потому что тираж ограничен, а купившие в первую минуту точно получат товар, а купившие на пятой — уже не факт. Это не вирусный пик, сваливающийся без предупреждения, и не рядовая распродажа, где неважно, зашёл покупатель в первую секунду или в двадцатую. Это нагрузка с точным адресом на часовой шкале — и вопрос в том, совпадёт ли момент, когда сервер реально откроет продажи, с той секундой, которую вы сами публично объявили.

Чем предзаказ отличается от распродажи и вирусного пика

У всплесков трафика разная природа предсказуемости. Вирусный пик — пост разлетелся по соцсетям, статья попала в топ агрегатора — приходит без даты и часа: рост может начаться сегодня или завтра, но момент с точностью до минуты неизвестен. Сезонная распродажа — чёрная пятница, новогодняя акция — обычно объявлена заранее по дате, но открывается «с утра» или «в течение дня»: пользователь может зайти в 9:00 или в 14:00, разница для него не критична, товара хватит примерно на всех.

Предзаказ на лимитированный товар — третий, отдельный случай. Здесь есть три признака одновременно, и именно их сочетание задаёт всю специфику:

ПризнакВирусный пикОбычная распродажаПредзаказ с лимитом
Время начала известно заранееНетПримерно, до дняДа, до секунды
Количество товара ограничено физическиНе при чёмОбычно нетДа, часто жёстко
Значение имеет момент входаНетСлабоКритично — секунды решают
Аудитория действует синхронноНет, размазано по времениЧастичноДа, массово в одну секунду

Ограниченный тираж — ключевое отличие от распродажи, стартующей в полночь: там нагрузка после пика постепенно спадает, но купить можно и через час, если товар остался. На предзаказе ограниченной серии кроссовок, новой консоли или билетов на концерт позиция в очереди буквально определяет, уйдёт человек с покупкой или без неё. Операционный чек-лист первых минут во многом пересекается с чек-листом обычной распродажи, но здесь добавляется требование, которого там не было: открыть продажи не «примерно вовремя», а ровно в ту секунду, которая была объявлена публично.

Секунда старта: почему здесь точность важнее, чем на распродаже

На обычной распродаже расхождение открытия на 5-10 секунд почти никто не заметит: товара хватает всем, кто зайдёт в течение дня, и опоздавший просто купит чуть позже — не критично. На предзаказе с жёстким лимитом та же разница в 5-10 секунд означает другой исход: часть покупателей, успевших раньше, получит товар, часть — увидит «распродано» уже через минуту. Это конкретный источник жалоб в поддержку, негативных отзывов и — если предзаказ заметный — репутационного удара с формулировкой «у них специально открывалось для своих раньше».

Отсюда два следствия, которые стоит держать в фокусе весь текст ниже.

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

Отклонение от объявленного времени воспринимается как нарушение правил, а не техническая деталь. Пользователь, который специально освободил утро под предзаказ и не успел, не будет разбираться, что случилось на бэкенде — виноват сервис, который открылся не в 10:00:00, а в 10:00:07. Права на «мы почти успели» здесь нет.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Синхронизация часов: сервер и объявленное время должны совпадать до секунды

Первый и самый дешёвый шаг — убедиться, что системные часы сервера (или всех серверов, если их несколько) синхронизированы с реальным временем, а не просто «настроены при установке ОС». Проверяется одной командой:

timedatectl status

В выводе важна строка System clock synchronized: yes и NTP service: active. Если используется chrony (стандарт на большинстве современных дистрибутивов), детальную картину даёт:

chronyc tracking
chronyc sources -v

chronyc tracking покажет текущее смещение (System time) и стабильность хода часов; chronyc sources -v — список источников и то, насколько они согласуются между собой. Офсет в пределах единиц-десятков миллисекунд для задачи «открыть предзаказ ровно в объявленную секунду» более чем достаточен. Если офсет скачет на сотни миллисекунд или секунды — проблему нужно чинить заранее, а не в день Х. Подробно про саму настройку — в материале про настройку NTP и синхронизацию времени сервера.

Нюанс именно для сценария с точным временем открытия: chrony по умолчанию не «дёргает» часы скачком при небольшом расхождении, а плавно подстраивает скорость их хода (slew) — механика описана в статье про то, как NTP подводит часы сервера, ни разу их не переводя. Практический вывод: если офсет обнаружился за пять минут до открытия, рассчитывать, что chrony успеет плавно скорректировать секунду разницы к моменту старта, — не самая надёжная тактика. Синхронизацию стоит проверить за день-два до события, когда есть время и на диагностику, и на перезапуск службы времени, а не в последние минуты перед стартом.

Второй момент — работайте с временем открытия в UTC внутри системы, а объявленное покупателям время («10:00 МСК») переводите в UTC один раз, явно, и используйте именно этот момент во всех конфигах и скриптах. Путаница часовых поясов между тем, что видит маркетинг в баннере, и тем, что вписано в таймер на сервере, — частая причина, когда продажи открылись на час раньше или позже анонсированного.

Как технически открыть продажи ровно в назначенную секунду

Cron для этой задачи не подходит напрямую — его минимальная гранулярность одна минута, а нужна точность до секунды. Практичный вариант на системах с systemd — таймер с явно заданной точностью:

# /etc/systemd/system/predzakaz-open.timer
[Unit]
Description=Открытие предзаказа точно по расписанию

[Timer]
OnCalendar=2026-09-15 07:00:00 UTC
AccuracySec=1s
Persistent=false

[Install]
WantedBy=timers.target
# /etc/systemd/system/predzakaz-open.service
[Unit]
Description=Снять флаг блокировки предзаказа

[Service]
Type=oneshot
ExecStart=/usr/local/bin/open-predzakaz.sh

Важная деталь: у systemd-таймеров по умолчанию AccuracySec равен одной минуте — сделано специально, чтобы группировать срабатывания и экономить ресурсы. Для точности до секунды параметр нужно понижать явно, как в примере выше; без этого таймер может сработать в любой момент внутри минутного окна вокруг заданного времени — и смысл точной подготовки теряется.

Сам скрипт open-predzakaz.sh не должен заниматься деплоем кода или запуском новых сервисов в момент открытия — это лишний риск ровно тогда, когда цена ошибки максимальна. Правильный паттерн — заранее выкатить и протестировать всю логику в выключенном состоянии (за флагом в конфиге или Redis), а в момент X скрипт лишь снимает этот флаг:

#!/usr/bin/env bash
# open-predzakaz.sh: снимает флаг блокировки предзаказа
redis-cli SET predzakaz:open "1"
logger -t predzakaz "opened at $(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')"

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

Виртуальный зал ожидания вместо честной гонки за F5

Если тысячи или десятки тысяч пользователей одновременно, буквально в одну секунду, отправляют запрос на сервер — нагрузка приходит не размазанной волной, а единым импульсом. Обычная схема «пользователь видит страницу товара и жмёт кнопку заказа» здесь плохо работает: вал запросов на установку соединения способен переполнить очередь принятых, но ещё не обработанных подключений — механика разобрана в статье про backlog соединений и отказ в соединении на живом сервере. Симптом узнаваем: сервер жив, процесс отвечает на мониторинг, но часть пользователей в первую секунду получает обрыв соединения или таймаут ещё до того, как запрос дошёл до кода приложения.

Практическое решение — виртуальный зал ожидания (waiting room): пользователи, зашедшие до объявленного времени, попадают на лёгкую статическую страницу с обратным отсчётом, отданную через CDN без обращения к бэкенду. В момент открытия зал ожидания начинает выдавать токены допуска не всем одновременно, а контролируемым потоком, даже если формально «дверь открылась» для всех в одну секунду. Технически это отдельный лёгкий сервис (токен-бакет на Redis, счётчик с лимитом выдачи в секунду) перед основным приложением, а не логика внутри самого приложения, которое и так занято обработкой заказов.

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

Гонка за лимитированным товаром: защита от гонок и двойных заказов

Когда тысячи клиентов одновременно пытаются купить последние единицы ограниченного тиража, возникает классическая проблема гонки (race condition): два параллельных запроса читают остаток «1 штука в наличии» и оба успевают его списать, прежде чем кто-то из них запишет новое значение — товар продан дважды. Решение — атомарная операция уменьшения остатка на уровне хранилища, а не последовательность «прочитать — проверить — записать» в коде приложения:

# атомарное уменьшение счётчика в Redis с проверкой, что остаток не ушёл в минус
redis-cli EVAL "
  local stock = redis.call('GET', KEYS[1])
  if tonumber(stock) > 0 then
    return redis.call('DECR', KEYS[1])
  else
    return -1
  end
" 1 predzakaz:stock:sku123

Если заказы и остатки живут в реляционной базе, аналог — транзакция с SELECT ... FOR UPDATE на строку остатка или атомарный UPDATE ... SET stock = stock - 1 WHERE stock > 0, где сам факт обновлённой строки говорит, удалось списать единицу товара или нет, без промежуточного чтения и отдельной проверки в коде.

Второй источник дублей — не гонка на сервере, а поведение клиента: пользователь на медленном мобильном интернете жмёт «Оформить заказ», не видит ответа за пару секунд и жмёт повторно, или клиентский JS сам ретраит запрос при таймауте. Без защиты это превращается в два-три реальных заказа на одного человека. Стандартное решение — идемпотентный ключ, который клиент генерирует один раз при первом нажатии и передаёт с каждой попыткой отправки одного и того же заказа; сервер по этому ключу распознаёт повтор и возвращает результат первой обработки, а не создаёт новую запись:

POST /api/preorder
Idempotency-Key: 6f1a9e2c-...-b7d4

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

Репетиция открытия: единственный пик, который можно прогнать один в один

Главное практическое преимущество предзаказа перед вирусным пиком — точное время известно заранее, а значит, всю процедуру открытия можно один раз полностью отрепетировать, а не только протестировать компоненты по отдельности. Смысл репетиции — прогнать в тестовом окружении (или на проде с тестовым SKU и минимальным тиражом) ровно тот же путь: тот же systemd-таймер, тот же скрипт снятия флага, тот же зал ожидания, та же логика списания остатка и идемпотентности — и зафиксировать, насколько фактическое время открытия совпало с заданным.

Практический план репетиции за несколько дней до реальной даты:

  • назначить тестовое открытие на удобное время (ночью или в будний день), используя тот же код и конфигурацию, что пойдут в бой;
  • замерить фактическое отклонение времени открытия от заданного по логам (logger из скрипта выше даёт точную метку с миллисекундами);
  • сгенерировать синтетическую нагрузку в форме резкого всплеска параллельных запросов в первую секунду, а не плавного роста;
  • проверить, что счётчик остатка не уходит в минус и не блокируется намертво при параллельных попытках списания, а повторные нажатия кнопки заказа не создают дублей;
  • зафиксировать результат в том же документе, где описан регламент открытия, — чтобы вторая репетиция начиналась не с нуля.

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Что если объявленное время указано в другом часовом поясе, чем настроен на сервере?

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

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

Сверьте chronyc tracking на каждом узле в одно время и сравните значения System time — если все узлы синхронизированы с одним пулом источников и офсет у каждого в пределах единиц-десятков миллисекунд, разброс между узлами не будет заметен на секундной точности.

Что делать, если несмотря на подготовку сервер всё равно «просел» в первую секунду?

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

Нужен ли отдельный зал ожидания, если покупателей ожидается всего несколько сотен?

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

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →