Разработка Real-Time голосований и опросов на сайте

Представьте: конференция на 10 000 участников, организаторы запускают голосование, а результаты обновляются раз в 30 секунд через обычный AJAX-polling. Пользователи жалуются на задержки, сервер падает от 10 000 запросов в секунду. Знакомая ситуация? Выбирая real-time голосование, вы решаете две зада

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Разработка Real-Time голосований и опросов на сайте
Средний
~3-5 дней

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1422
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    984
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    986
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    1000

Представьте: конференция на 10 000 участников, организаторы запускают голосование, а результаты обновляются раз в 30 секунд через обычный AJAX-polling. Пользователи жалуются на задержки, сервер падает от 10 000 запросов в секунду. Знакомая ситуация? Выбирая real-time голосование, вы решаете две задачи: мгновенная доставка результатов и снижение нагрузки на сервер. Мы реализовали такие системы для 30+ проектов — от корпоративных опросов до масштабных прямых эфиров.

Почему SSE, а не polling?

Polling — это N запросов в секунду, где N — количество пользователей. При 500 пользователях — 500 req/s впустую. SSE или WebSocket дают одно соединение на пользователя, через которое сервер отправляет данные только при изменении. Для голосований подходят два механизма: SSE (Server-Sent Events) и WebSocket. Третий вариант — polling — не рассматриваем: 1 запрос в секунду на 500 одновременных пользователей — это 500 req/s нагрузки только ради проверки «ничего не изменилось». SSE — однонаправленный поток от сервера к клиенту, описанный в спецификации EventSource. Для опросов этого достаточно: голос отправляется обычным POST, результат прилетает через SSE. WebSocket оправдан, если нужна немедленная обратная связь (анимация «ваш голос принят») или дополнительные интерактивные элементы.

Параметр SSE WebSocket
Направление Однонаправленный (сервер → клиент) Двунаправленный
Сложность реализации Низкая (нативный EventSource) Средняя (требуется WebSocket-сервер)
Инфраструктура Не требует sticky sessions Требует sticky session или отдельный сервер
Производительность До 10 000 соединений на один PHP-воркер До 100 000 соединений на Node.js
Поддержка браузеров Все современные, кроме IE Все современные

Как предотвратить дублирование голосов?

Для авторизованных пользователей — unique constraint (option_id, user_id) и проверка в контроллере. Для анонимных голосований — защита через IP + fingerprint. Fingerprint генерируется на фронте (библиотека fingerprintjs) и передаётся в заголовке. Это не абсолютная защита, но достаточна для большинства случаев.

$fingerprint = $request->header('X-Client-Fingerprint'); $alreadyVoted = PollVote::where('poll_id', $poll->id) ->where(function ($q) use ($request, $fingerprint) { $q->where('ip', $request->ip()) ->orWhere('fingerprint', $fingerprint); })->exists(); 

Дополнительно можно использовать Redis-блокировки: Cache::lock('vote:'.$poll->id.':'.$userId, 10)->get() — это предотвратит одновременную отправку с одного аккаунта.

Как масштабировать голосование на тысячи участников?

PHP-приложение с SSE держит соединение открытым. 1 000 одновременных пользователей = 1 000 PHP-воркеров. Это дорого. Решение: вынести broadcast через Pusher или Laravel Echo Server (socket.io). Тогда SSE-контроллер больше не нужен — клиент подписывается на канал, сервер публикует событие poll.updated в Redis, Laravel Echo транслирует всем подписчикам.

// После записи голоса broadcast(new PollUpdated($poll->id, $counts))->toOthers(); 
Echo.channel(`poll.${pollId}`) .listen('PollUpdated', ({ counts }) => updateBars(counts)); 

Такая архитектура держит сотни тысяч подключений на одном процессе Node.js. Для мониторинга используйте Laravel Horizon: он показывает количество активных SSE-воркеров и время отклика. Это экономит до 70% затрат на серверную инфраструктуру по сравнению с прямыми SSE-воркерами.

Схема данных и оптимизация

CREATE TABLE polls ( id BIGSERIAL PRIMARY KEY, title VARCHAR(500) NOT NULL, is_multiple BOOLEAN NOT NULL DEFAULT false, is_active BOOLEAN NOT NULL DEFAULT true, ends_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE TABLE poll_options ( id BIGSERIAL PRIMARY KEY, poll_id BIGINT NOT NULL REFERENCES polls(id) ON DELETE CASCADE, label VARCHAR(255) NOT NULL, position SMALLINT NOT NULL DEFAULT 0 ); CREATE TABLE poll_votes ( id BIGSERIAL PRIMARY KEY, option_id BIGINT NOT NULL REFERENCES poll_options(id), user_id BIGINT REFERENCES users(id), ip INET, voted_at TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE(option_id, user_id) ); 

Агрегация считается через materialized view или прямым COUNT. При пиковой нагрузке (прямой эфир, 5 000+ участников) лучше хранить счётчики отдельно и инкрементировать через Redis: HINCRBY poll:42:counts 1 1.

Клиентская часть и отправка голоса

const pollId = 42; const source = new EventSource(`/api/polls/${pollId}/stream`); source.onmessage = (event) => { const { counts } = JSON.parse(event.data); updateBars(counts); }; source.onerror = () => { console.warn('SSE reconnecting...'); }; function updateBars(counts) { const total = Object.values(counts).reduce((a, b) => a + Number(b), 0); document.querySelectorAll('[data-option-id]').forEach(el => { const id = el.dataset.optionId; const pct = total > 0 ? Math.round((counts[id] || 0) / total * 100) : 0; el.querySelector('.bar').style.width = pct + '%'; el.querySelector('.label').textContent = pct + '%'; }); } async function vote(optionId) { const resp = await fetch(`/api/polls/${pollId}/vote`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ option_id: optionId }), }); if (resp.status === 409) { showMessage('Вы уже голосовали'); } } 

Как реализовать SSE-эндпоинт на Laravel

Route::get('/api/polls/{poll}/stream', function (Poll $poll) { return response()->stream(function () use ($poll) { while (true) { if (connection_aborted()) break; $counts = PollVote::selectRaw('option_id, COUNT(*) as votes') ->whereIn('option_id', $poll->options->pluck('id')) ->groupBy('option_id') ->pluck('votes', 'option_id'); $data = json_encode(['counts' => $counts, 'ts' => now()->timestamp]); echo "data: {$data}\n\n"; ob_flush(); flush(); sleep(2); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'X-Accel-Buffering' => 'no', ]); }); 

X-Accel-Buffering: no — обязательный заголовок при использовании Nginx как proxy, иначе данные будут накапливаться в буфере.

Как тестировать real-time голосование?

Используйте инструменты: Postman для отправки голосов, k6 для нагрузочного тестирования, встроенную консоль браузера для проверки SSE-соединений. Основные сценарии: 1) проверка получения обновлений после голосования; 2) симуляция одновременных голосов от тысячи пользователей; 3) проверка устойчивости при обрыве соединения (SSE автоматически переподключается).

Типичные ошибки и их решение

  • N+1 запросы: при получении списка голосов с подгрузкой опций через lazy loading. Используйте with('options') в Eloquent.
  • Отсутствие X-Accel-Buffering: без этого заголовка Nginx буферизует SSE-поток, и пользователи видят данные пачками.
  • Игнорирование connection_aborted(): без этого PHP-воркеры продолжают висеть, потребляя память.
  • Неиспользование Redis для счётчиков: прямой COUNT из БД при каждом обновлении создаёт нагрузку. Redis инкременты в разы быстрее.

Сроки разработки

Этап Время
Базовое голосование (SSE, авторизованные) 2–3 дня
Анонимное голосование + anti-duplicate +1 день
Многовариантные опросы + история +1 день
Масштабирование через Pusher/Echo +2 дня
Административный интерфейс 2–3 дня

Мы гарантируем отсутствие багов в течение 30 дней после сдачи, предоставляем документацию и обучаем вашу команду. Для оценки вашего проекта свяжитесь с нами — получите детальный план и точные сроки. Закажите консультацию, чтобы убедиться, что ваше голосование будет работать без сбоев при любой нагрузке.