---
title: 'Проектиране на системи за събиране на събития при високо натоварване | DevSense'
description: 'Как да управлявате хиляди входящи HTTP събития в секунда: валидация на границата на мрежата, буферни слоеве, групово записване в хранилището и предотвратяване на недостиг на връзки към БД.'
faq:
    - { question: 'Защо трябва да избягвате синхронни записи в базата данни на ендпоинта за събиране на данни?', answer: 'Релационните бази данни са проектирани за трансакционна съвместимост (ACID) и работят лошо при често паралелно записване на единични редове. Синхронните записи създават блокировки, изчерпват пула от връзки и претоварват диска. Пренасянето на записите в бърза опашка или лог (като Kafka или Redis Streams) разделя процеса на приемане от ограниченията на СУБД.' }
    - { question: 'Каква е ползата от използването на API шлюз (API Gateway) с ограничение на честотата (rate limiting) на входа?', answer: 'API шлюзът блокира невалидни или злонамерени заявки (DDoS, грешен JSON, проблеми с оторизацията) преди да достигнат до сървърите за приложения. Това предпазва вътрешните услуги и брокери от недостиг на ресурси и запазва достъпността на системата.' }
    - { question: 'Как груповата обработка от страна на клиента (client-side batching) помага на системите за събиране на данни?', answer: 'Груповата обработка обединява множество малки събития в една HTTP заявка. Това драстично намалява оверхеда от мрежови ръкостискания, сериализация и броя отворени връзки на сървърите за приемане, позволявайки събиране на милиони събития с много по-малко заявки.' }
    - { question: 'Какво представлява обратният натиск (backpressure) и защо е важен?', answer: 'Обратният натиск е механизъм, при който обработващите звена (воркери, бази данни) сигнализират на по-горните нива да забавят потока от данни, ако не успяват да се справят с обема. Без този механизъм вътрешните услуги се претоварват, изразходват паметта си и се сриват.' }
published: '2026-05-31'
---
# Проектиране на системи за събиране на събития при високо натоварване: буфери, батчинг и лимити

Създаването на ендпоинт, който приема уебхук или събитие от тракер, е лесна задача. Но проектирането на такъв, който остава бърз, евтин и достъпен, когато десет хиляди мобилни приложения изпратят данни **в една и съща секунда**, е съвсем различен проблем. При високо натоварване простият подход „приемане, валидация, запис в SQL“ бързо се срива: връзките към БД се изчерпват, дисковият I/O скача, а процесите на PHP-FPM увисват в изчакване, докато балансьорът не започне да прекъсва връзките. Надеждното събиране на събития се основава на: **бързо приемане на байтовете**, **незабавното им записване в буфер** и **записване на пачки (батчове)** със скорост, която хранилището понася добре.

**Свързани ръководства:** [Сравнение на опашки от съобщения](message-queues-compared) · [Бази данни под натоварване](database-performance-and-scaling) · [Наблюдаемост и мониторинг](observability-monitoring-laravel)

## Съдържание

* [Анатомия на тясното място](#bottleneck)
* [Архитектура: разделяне на приемането от записа](#architecture)
* [Ендпоинт за приемане: лек и без състояние (stateless)](#endpoint)
* [Обработка на границата на мрежата и API Gateway валидация](#edge)
* [Буферно ниво: Redis Streams, Kafka или дискови логове](#buffering)
* [Групова обработка и воркери](#batching)
* [Справяне с пикове: обратен натиск и сброс на трафик](#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)** приема заявката, проверява набързо структурата на данните, записва събитието в буфера и веднага връща отговор `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)** — воркерите съобщават на шлюза за претоварване, принуждавайки го да ограничи приема на нови данни или да увеличи времето за изчакване на клиентите.
* **Сброс на натоварването (Load Shedding)** — шлюзът започва да връща `429 Too Many Requests` за нископриоритетни заявки, за да предпази ядрото на системата от срив.
* **Предпазители (Circuit Breakers)** — ако базата данни или буферът спрат да отговарят, шлюзът веднага започва да връща бързи грешки на клиентите, без да запълва процесите на изчакване.

---

<a id="common-mistakes"></a>
## Чести грешки

1. **Синхронно записване в СУБД**: Опит за записване на лог или събитие директно в основната база данни в рамките на HTTP заявката на клиента.
2. **Липса на лимити на входа**: Възможност за едно неправилно работещо мобилно приложение да претовари шлюза с повтарящи се заявки.
3. **Тежка оторизация на събития**: Обращане към базата данни за проверка на токена на всяка входяща малка заявка. Използвайте кеширани в паметта токени.
4. **Игнориране на метриките на буфера**: Мониторинг на здравето на шлюза без проследяване на опашката (lag) в буферния слой.

---

<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.
- В) Груповият запис предпазва воркерите от изтичане на памет.

<details>
<summary>Кликнете, за да видите отговора</summary>

**Отговор: Б**
Колоносните бази данни са оптимизирани за последователен запис на големи блокове. Записът на единични редове кара машината постоянно да презаписва и обединява файлове на диска, което води до рязко натоварване на дисковата подсистема.
</details>