Предзаказ открыт: нагрузка, которая приходит точно по расписанию
На баннере уже неделю висит дата и время: «Предзаказ откроется 15 сентября в 10:00 МСК». Тысячи людей ставят будильник на этот момент и держат палец над кнопкой «Обновить» — потому что тираж ограничен, а купившие в первую минуту точно получат товар, а купившие на пятой — уже не факт. Это не вирусный пик, сваливающийся без предупреждения, и не рядовая распродажа, где неважно, зашёл покупатель в первую секунду или в двадцатую. Это нагрузка с точным адресом на часовой шкале — и вопрос в том, совпадёт ли момент, когда сервер реально откроет продажи, с той секундой, которую вы сами публично объявили.
Содержание
- Чем предзаказ отличается от распродажи и вирусного пика
- Секунда старта: почему здесь точность важнее, чем на распродаже
- Синхронизация часов: сервер и объявленное время должны совпадать до секунды
- Как технически открыть продажи ровно в назначенную секунду
- Виртуальный зал ожидания вместо честной гонки за F5
- Гонка за лимитированным товаром: защита от гонок и двойных заказов
- Репетиция открытия: единственный пик, который можно прогнать один в один
Чем предзаказ отличается от распродажи и вирусного пика
У всплесков трафика разная природа предсказуемости. Вирусный пик — пост разлетелся по соцсетям, статья попала в топ агрегатора — приходит без даты и часа: рост может начаться сегодня или завтра, но момент с точностью до минуты неизвестен. Сезонная распродажа — чёрная пятница, новогодняя акция — обычно объявлена заранее по дате, но открывается «с утра» или «в течение дня»: пользователь может зайти в 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 ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →