---
title: 'Проектування систем збору подій під високим навантаженням | DevSense'
description: 'Як обробляти тисячі вхідних HTTP-подій на секунду: валідація на межі, буферні шари, пакетний запис у сховище та запобігання нестачі з’єднань з базою даних.'
faq:
    - { question: 'Чому не можна робити синхронний запис у базу даних на ендпоінті прийому подій?', answer: "Реляційні бази даних спроектовані для транзакційної цілісності (ACID) і погано справляються з частими паралельними записами одиночних рядків. Синхронний запис створює блокування таблиць, вичерпує пул з'єднань і перевантажує диск операціями I/O. Перенесення запису в швидку чергу або лог (наприклад, Kafka або Redis Streams) дозволяє відв'язати швидкість прийому від лімітів диска СУБД." }
    - { question: 'У чому користь використання API-шлюзу (API Gateway) з обмеженням частоти запитів (rate limiting) на вході?', answer: 'API-шлюз блокує невалідні або шкідливі запити (DDoS, некоректний JSON, помилки авторизації) до того, как вони досягнуть серверів застосунку. Це захищає внутрішні сервіси та брокери повідомлень від нестачі ресурсів та зберігає доступність системи при піках навантаження.' }
    - { question: 'Як пакетна обробка на стороні клієнта (client-side batching) допомагає розвантажити систему?', answer: "Пакетна обробка групує безліч дрібних подій в один HTTP-запит. Це радикально скорочує накладні витрати на мережеві рукостискання, серіалізацію HTTP та число відкритих з'єднань на серверах прийому, дозволяючи обробляти мільйони подій при набагато меншому числі сирих мережевих запитів." }
    - { question: 'Що таке зворотний тиск (backpressure) і чому він важливий?', answer: "Зворотний тиск — це механізм, при якому компоненти обробки даних (воркери, бази даних) повідомляють вищестоящому сервісу прийому подій про необхідність уповільнити потік даних, якщо вони не справляються з поточним обсягом. Без цього механізму внутрішні сервіси переповнюються даними, витрачають всю пам'ять і падають." }
published: '2026-05-31'
---
# Проектування систем збору подій під високим навантаженням: буфери, батчинг та ліміти

Написати ендпоінт, що приймає вебхук або подію трекера, просто. Написати систему, яка залишається швидкою, дешевою та доступною, коли десять тисяч мобільних застосунків надсилають логи **в одну і ту ж секунду** — зовсім інше завдання. Під навантаженням простий шлях «прийняти запит, провалідувати, записати в SQL» миттєво падає: закінчуються з'єднання з БД, диск забивається операціями запису, а воркери PHP-FPM зависають в очікуванні, поки балансувальник не почне скидати трафік за таймаутом. Надійний збір подій будується на трьох принципах: **швидко прийняти байти**, **відразу покласти їх у буфер** та **записати пачкою (батчем)** з тією швидкістю, з якою здатне працювати сховище.

**Пов’язані матеріали:** [Порівняння черг повідомлень](message-queues-compared) · [Бази даних під навантаженням](database-performance-and-scaling) · [Спостережуваність і моніторинг](observability-monitoring-laravel)

## Зміст

* [Анатомія вузького місця](#bottleneck)
* [Архитектура: розділяємо прийом та запис](#architecture)
* [Ендпоінт прийому: легковажний та без стану (stateless)](#endpoint)
* [Обробка на межі мережі та валідація на API Gateway](#edge)
* [Буферний шар: Redis Streams, Kafka або дискові логи](#buffering)
* [Пакетна обробка та фонові воркери](#batching)
* [Боротьба з піками навантаження: backpressure та скидання трафіку](#spikes)
* [Часті помилки](#common-mistakes)
* [Чеклист](#checklist)
* [Квіз для самоперевірки](#self-test-quiz)

---

<a id="bottleneck"></a>
## Анатомія вузького місця

Коли великий потік подій йде на стандартний стек застосунку:

* **Накладні витрати HTTP** — узгодження TLS, парсинг заголовків та ініціалізація фреймворку на кожен запит споживають багато CPU.
* **Синхронні виклики до сховища** — якщо ендпоінт чекає завершення `INSERT INTO ...`, з'єднання з клієнтом залишається відкритим. Це забиває пам'ять процесу та пул PHP-FPM воркерів.
* **Навантаження на диск та блокування** — кожен одиночний запис змушує базу даних виконувати скидання логу (WAL) на диск. Сто паралельних вставок генерують сто дискових операцій; запис однією пачкою з тисячі рядків генерує одну операцію.

---

<a id="architecture"></a>
## Архитектура: розділяємо прийом та запис

Ключовий принцип проектування — **асинхронність**:

```
[ Клієнт ] ──(HTTP POST)──> [ Шлюз прийому подій ] 
                                   │
                           (Запис у буфер)
                                   ▼
                            [ Буферний шар ] (Redis/Kafka)
                                   ▲
                             (Читання пачкою)
                                   │
                            [ Пул воркерів ]
                                   │
                             (Пакетний запис)
                                   ▼
                           [ Сховище даних ] (ClickHouse/БД)
```

1. **Шлюз прийому (Ingestion Gateway)** приймає HTTP-запит, швидко перевіряє структуру даних, пише подію в буфер і відразу повертає клієнту відповідь `202 Accepted`.
2. **Буферний шар (Buffer Tier)** (черга в пам'яті або журнал подій) зберігає сирі дані.
3. **Пул воркерів (Worker Pool)** забирає події з буфера пачками та виконує груповий запис у СУБД.

---

<a id="endpoint"></a>
## Ендпоінт прийому: легковажний та без стану (stateless)

Код, що приймає вхідні дані, повинен робити абсолютний мінімум роботи:

```php
// app/Http/Controllers/IngestController.php
public function __invoke(IngestRequest $request)
{
    // 1. Легковажна валідація (тільки відповідність схемі)
    $payload = $request->validated();

    // 2. Відправка в буфер (наприклад, Redis Stream або Kafka)
    $this->buffer->push('events', [
        'event_id' => Str::uuid()->toString(),
        'received_at' => now()->getTimestamp(),
        'data' => json_encode($payload),
    ]);

    // 3. Швидка відповідь клієнту про прийом запиту
    return response()->json(['status' => 'accepted'], 202);
}
```

Не робіть на цьому шляху важких перевірок авторизації за базою даних, звернень до зовнішніх API або обробки зображень.

---

<a id="edge"></a>
## Обробка на межі мережі та валідація на API Gateway

Відсікайте некоректний трафік до того, як він навантажить ваші сервери застосунків:
* **API Gateway (Nginx, Kong, AWS API Gateway)** — перевіряє API-ключі, накладає ліміти частоти запитів (rate limiting) та блокує невалідний JSON.
* **Валідація схем** — за можливості переносьте базову перевірку структури JSON на рівень шлюзу, розвантажуючи код застосунку.
* **Використання CDN** — для збору статистики через GET-запити з пікселями відстеження віддавайте порожній піксель відразу з серверів CDN, скидаючи логи відвідувань у сховище асинхронно.

---

<a id="buffering"></a>
## Буферний шар: Redis Streams, Kafka або дискові логи

Вибирайте технологію буферизації виходячи з обсягів даних та вимог до надійності:

| Буфер | Пропускна здатність | Складність підтримки | Примітка |
|--------|----------------|------------------------|------|
| **Redis Streams** | Дуже висока | Низька | Відмінне рішення в оперативній пам'яті. Стежте за лімітами RAM. |
| **Apache Kafka** | Екстремальна | Висока | Індустріальний стандарт для потоків подій. Надійно зберігає дані на диск. |
| **AWS Kinesis / GCP PubSub** | Висока | Низька (Managed) | Хмарні рішення з оплатою за використання. |

> [!NOTE]
> **Утилізація пам'яті**
> При використанні буферів в оперативній пам'яті (Redis) ретельно моніторте обсяг зайнятого простору. Якщо воркери запису почнуть відставати, буфер швидко переповнить RAM і сервер упаде.

---

<a id="batching"></a>
## Пакетна обробка та фонові воркери

Запис подій по одній — головний вбивця продуктивності баз даних. Воркери повинні читати буфер пачками:

```php
// app/Console/Commands/ProcessBufferBatch.php
public function handle()
{
    // Читання пачки до 1000 подій зі стріму
    $events = $this->buffer->readBatch('events', 1000);

    if (empty($events)) {
        return;
    }

    // Перетворення та запис одним запитом (Bulk Insert)
    $this->storage->bulkInsert(
        $this->transform($events)
    );

    // Підтвердження успішної обробки зміщень у буфері
    $this->buffer->acknowledge('events', collect($events)->pluck('id'));
}
```

Для великих аналітичних систем використовуйте колоночні бази даних (наприклад, **ClickHouse**), оптимізовані для швидкої вставки мільйонів рядків на секунду.

---

<a id="spikes"></a>
## Боротьба з піками навантаження: backpressure та скидання трафіку

Коли вхідний трафік перевищує можливості системи:
* **Зворотний тиск (Backpressure)** — воркери повідомляють шлюзу про перевантаження, змушуючи його тимчасово обмежити прийом нових даних або збільшити затримки для клієнтів.
* **Скидання навантаження (Load Shedding)** — шлюз починає повертати код `429 Too Many Requests` для низькопріоритетних запитів, щоб врятувати ядро системи від падіння.
* **Запобіжники (Circuit Breakers)** — якщо база даних або буфер перестали відповідати, шлюз моментально починає віддавати швидкі помилки клієнтам, не забиваючи потоки очікування.

---

<a id="common-mistakes"></a>
## Часті помилки

1. **Синхронний запис у СУБД**: Спроба зберегти лог або подію безпосередньо в основну базу даних у межах HTTP-запиту клієнта.
2. **Відсутність лімітів на прийомі**: Можливість для одного мобільного застосунку, що некоректно працює, забити запитами весь шлюз.
3. **Важка авторизація подій**: Звернення в базу даних для перевірки токена на кожен вхідний дрібний запит. Використовуйте кешовані в пам'яті токени.
4. **Ігнорування метрик буфера**: Моніторинг працездатності шлюзу без відстеження лагу (черги) в буферному шарі.

---

<a id="checklist"></a>
## Чеклист

1. **Stateless-ендпоінт:** Код прийому запиту повертає відповідь, не здійснюючи блокуючих викликів до диска.
2. **Буферизація:** Між прийомом подій та їх фінальним збереженням є проміжний буфер.
3. **Валідація на межі:** Невалідні схеми та неавторизовані запити блокуються на шлюзі.
4. **Пакетний запис:** Воркери групують записи перед відправкою до бази даних.
5. **Готовність до перевантажень:** Налаштована політика скидання трафіку при переповненні буфера.

---

## Підсумок

Масштабований збір подій вимагає розділення **процесу прийому** та **процесу збереження**. Тримайте «вхідні двері» максимально простими та швидкими, використовуючи буфер для згладжування піків трафіку та передачі даних до БД у комфортному для неї режимі.

---

<a id="self-test-quiz"></a>
## Квіз для самоперевірки

### Запитання 1: У чому основна перевага повернення статус-коду `202 Accepted` при зборі подій?
- А) Він автоматично стискає тіло відповіді в JSON.
- Б) Він повідомляє клієнту, що дані прийняті в обробку та поставлені в чергу, дозволяючи закрити HTTP-з'єднання без очікування запису на диск.
- В) Він гарантує відсутність синтаксичних помилок у відправленому JSON.

<details>
<summary>Показати правильну відповідь</summary>

**Правильна відповідь: Б**
Код `202 Accepted` вказує на те, що запит успішно прийнятий, але його обробка буде виконана асинхронно. Це дозволяє миттєво завершити мережеве з'єднання, заощаджуючи ресурси веб-сервера.
</details>

### Запитання 2: Чому аналітичні СУБД (наприклад, ClickHouse) вимагають запису даних великими пачками (батчами), а не по одному рядку?
- А) Одиночні вставки відключають механізми безпеки СУБД.
- Б) Колоночні рушії пишуть дані стиснутими блоками на диск; часта запис одиночних рядків породжує мільйони дрібних файлів, паралізуючи дисковий I/O.
- В) Пакетний запис рятує PHP-воркери від витоків пам'яті.

<details>
<summary>Показати правильну відповідь</summary>

**Правильна відповідь: Б**
Колоночні бази даних оптимізовані для послідовного запису великих блоків. Запис по одному рядку змушує рушій постійно перезаписувати та об'єднувати файли на диску, призводячи до лавиноподібного зростання навантаження на диск.
</details>