MAATRIX / Блог / Запись на событие открылась: как пережить первую минуту

Запись на событие открылась: как пережить первую минуту

MAATRIX

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

Чем открытие бесплатной записи отличается от продажи билетов

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

Разница не в том, будет ли пик — он будет в обоих случаях, — а в том, где именно рвётся система. При продаже билетов узкое место чаще всего платёжный шлюз и корзина: пока человек вводит номер карты и ждёт 3-D Secure, он физически не может отправить второй запрос. Это естественный тормоз, который размазывает пик по времени хотя бы на несколько секунд. У бесплатной записи такого тормоза нет: форма — это имя, email, кнопка «Записаться», и весь путь от клика по ссылке до отправки формы занимает три-пять секунд. Меньше трения — больше синхронности, и именно поэтому пик регистрации может быть уже, чем пик продаж с сопоставимым числом участников.

Почему бесплатное иногда бьёт по серверу сильнее платного

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

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

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

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

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

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

Форма регистрации как узкое место

При продаже билетов узким местом обычно становится база данных и платёжный шлюз. При бесплатной записи чаще рвётся сама форма — конкретно три её части.

Валидация email. Если проверка формата email происходит только на клиенте (JavaScript в браузере), любой бот, который шлёт запрос напрямую на API, минуя фронтенд, обходит её без усилий. Проверку формата, длины и (по возможности) существования домена нужно дублировать на сервере — иначе база быстро заполняется мусором вида test@test, а строка с ограничением на количество мест уменьшается на несуществующих людей.

Двойная отправка формы. Классическая ошибка: пользователь кликает «Записаться», ничего не происходит за секунду-две (сервер под нагрузкой отвечает медленнее обычного), пользователь кликает ещё раз, а то и три раза. Если кнопка не блокируется после первого клика и на сервере нет проверки на дубль по email в рамках одного события, вы получаете несколько записей одного и того же человека — и они честно съедают лимит мест, которого и так не хватает на всех.

-- уникальный индекс не даст записать одного человека дважды
-- на одно и то же событие, даже при гонке параллельных запросов
CREATE UNIQUE INDEX idx_registration_unique
ON registrations (event_id, lower(email));

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

Гонка за последнее место: overselling без денег

В продаже билетов все знают про overselling — когда десять человек одновременно покупают последний билет и система должна честно продать его только одному. У бесплатной записи та же проблема существует один в один, просто про неё реже думают заранее: если счётчик свободных мест читается и уменьшается двумя раздельными операциями («прочитать текущее значение», «записать значение минус один»), при параллельных запросах несколько человек могут одновременно прочитать «осталось 1 место» и все получить подтверждение.

Решение то же самое, что и для платных мест — атомарная операция вместо чтения-и-записи:

-- атомарное уменьшение счётчика с проверкой лимита в одном запросе
UPDATE events
SET seats_taken = seats_taken + 1
WHERE id = $1 AND seats_taken < seats_limit
RETURNING seats_taken;
-- если RETURNING не вернул строку — места закончились,
-- регистрацию не создаём и явно сообщаем об этом пользователю

Или тот же принцип на Redis, если счётчик мест вынесен в кэш ради скорости:

# INCR атомарен сам по себе; проверка лимита — сразу после
redis-cli INCR event:123:seats_taken
# если результат больше лимита — откатываем инкремент
# и не создаём запись
redis-cli DECR event:123:seats_taken

Без этой атомарности при резком одновременном наплыве вы почти гарантированно получите событие, на которое записалось на 5-15% больше людей, чем есть мест — обычно это выясняется не сразу, а когда начинают писать «я записался, а мне пришёл отказ» уже после того, как форма закрылась.

Антиспам и защита формы от массовых заявок

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

Практический минимум для формы записи на событие:

  • Honeypot-поле. Скрытое от человека CSS-ом поле формы, которое боты заполняют, а люди — нет. Заявка с непустым honeypot-полем отбрасывается без лишних проверок и без нагрузки на базу.
  • Rate limiting по IP. Ограничение вроде «не больше пяти отправок формы с одного IP за минуту» отсекает наивные скрипты, хотя и не спасает от ботов с ротацией прокси.
  • Отложенная (invisible) CAPTCHA вместо классической с картинками — не мешает обычным посетителям, но добавляет трение автоматическим запросам. Ставить видимую CAPTCHA на форму записи стоит только если honeypot и rate limiting уже не справляются — лишний шаг перед формой снижает конверсию и у живых людей тоже.
  • Double opt-in по email для событий, где цена ошибки высока (мероприятие с ограниченным доступом на территорию, где реально нужен точный список гостей). Письмо с подтверждением отсекает и часть ботов, и часть случайных дублей, но добавляет шаг в воронку — используйте его выборочно, а не по умолчанию.

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

Что подготовить на сервере до открытия записи

Технически подготовка похожа на подготовку к любому известному пику трафика, но с поправкой на специфику формы, а не корзины.

  1. Разнесите статику и форму по разным путям. Лендинг с описанием события — статическая страница, которую спокойно отдаёт CDN или кэш даже при десятках тысяч одновременных посетителей. Сама отправка формы — динамический запрос к API, и именно его нужно нагрузочно тестировать отдельно, не полагаясь на то, что раз страница открывается быстро, то и форма выдержит.
  2. Заранее проверьте пул соединений к базе. Резкий залп INSERT-ов в таблицу регистраций упирается в тот же лимит подключений, что и любая другая нагрузка — если пул рассчитан на десять одновременных соединений, а форму пытаются заполнить пятьсот человек за секунду, очередь на соединение вырастет мгновенно.
  3. Готовьте страницу "мест не осталось" заранее. Как только атомарный счётчик показывает, что лимит исчерпан, форма должна честно и быстро сообщать об этом, а не продолжать принимать заявки, которые потом придётся отклонять вручную. Лучше явный отказ сразу, чем список ожидания, который никто не обрабатывает.
  4. Свой сервер под форму, а не только облачный конструктор форм. Готовые сервисы форм удобны для разовых мероприятий, но упираются в лимит ответов в самый неподходящий момент — именно об этом сценарии подробно рассказано в статье про форму регистрации гостей на своём сервере вместо чужого конструктора: те же грабли с лимитами и передачей персональных данных актуальны и для формы записи на вебинар или мастер-класс, а не только для списка гостей на свадьбу.
  5. Мониторинг на первые пять минут. Держите открытым дашборд с числом активных соединений к базе, долей ошибок 4xx/5xx и временем ответа формы — именно в первые секунды после публикации ссылки на запись видно, где начинает копиться очередь, и есть время среагировать, пока не начались жалобы.

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

Что делать, если лимит мест уже превышен

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

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

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

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

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

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

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

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

Нужна ли CAPTCHA, если событие маленькое и внутреннее, например корпоративное мероприятие на полсотни человек?

Обычно нет — при закрытой аудитории риск бот-трафика и скальпинга низкий, и лишний шаг в форме только раздражает сотрудников. Honeypot-поле как минимальную защиту всё равно стоит оставить — оно бесплатно с точки зрения UX.

Почему нельзя просто увеличить мощность сервера на время открытия записи и не заниматься атомарностью счётчика?

Мощность сервера решает проблему пропускной способности, но не проблему гонки при параллельном чтении-и-записи одного и того же счётчика. Даже мощный сервер с сотней ядер выдаст overselling мест при неатомарном обновлении — это логическая ошибка, а не ограничение производительности.

Что делать, если форму регистрации оформляли через стороннюю платформу и лимит ответов внезапно уперся во время самого пика?

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

Стоит ли открывать запись сразу для всех или лучше сделать поэтапное открытие по волнам (например, сначала подписчикам рассылки, через час всем остальным)?

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

Как понять заранее, сколько трафика ожидать, если раньше таких пиков не было?

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

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

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

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