Мільйони подій без колапсу основної бази
У спокійний день все здається простим: подія прийшла — зберегли рядок. А потім злітає кампанія, гравці масово натискають кнопки, партнери просять сирі логи показів, і ваш PostgreSQL, де вже сидять гаманці й сесії, раптом отримує ще один потік записів — без окремого дихання для диска й індексів. Найкоротший шлях «все в одну базу» часто перший, який доводиться розбирати ніччю. Нижче — робочі прийоми без обіцянок «вічної масштабованості».
Пов’язані матеріали: Черги й брокери: Redis, RabbitMQ, Kafka · API gateway і обмін повідомленнями · Sail: черги та RabbitMQ
Зміст
- Чому змішувати телеметрію з грошима небезпечно
- Як приймати події, не блокуючи клієнта
- Redis: списки й стріми як амортизатор
- Черга завдань проти журналу подій: RabbitMQ і Kafka
- Де має жити аналітика
- Що уточнити до вибору схеми
Чому змішувати телеметрію з грошима небезпечно
OLTP заточений під короткі узгоджені операції: зняти кошти, змінити статус, перевірити унікальність. Індекси й обслуговування таблиць розраховані саме на цей ритм.
Потік подій інший:
- записів багато, вони часто не зводяться до одного оновлення рядка — важливий сам факт «сталося о ось такій миті»;
- навантаження нерівномірне, піки різко піднімають конкуренцію за буфери та WAL;
- для аналітики потрібні скани за часом, воронки, з’єднання з довідниками — це вже не ті запити, під які проєктували ядро.
Якщо все в одній купі, зазвичай ростуть хвости латентності на критичних транзакціях, роздуваються індекси, відстає реплікація. Зовні це виглядає як «повільні звіти», а всередині страждає те, де користувач платить або ставить.
Як приймати події, не блокуючи клієнта
Логіка проста: HTTP-відповідь не повинна чекати, поки подія ляже в остаточне сховище для BI. Спочатку — надійний проміжний шар, далі — воркери.
Що зазвичай роблять:
- На краї — перевірка формату, контекст (користувач, кампанія, пристрій), за потреби ключ ідемпотентності, щоб повтори мережі не подвоїли наслідки.
- Буфер — не нескінченна RAM у процесі, а Redis з політикою персистентності, брокер або інший append-friendly шар, ризик втрати якого ви назвали вголос.
- Пакетний запис у ціль — менше поїздок і менше тиску на диск, ніж «рядок за рядком».
- Back-pressure — якщо споживач відстав, обмежити чергу, сповільнити продьюсера або відсіяти в dead letter з алертом, а не ловити OOM.
У Laravel природно виглядає черга після легкої валідації. Якщо RPS величезний, часто виокремлюють сервіс прийому. Важливо не плутати: запис у Redis-list без реплікації й AOF — це не те саме, що аудиторський слід, який переживе падіння вузла.
Redis: списки й стріми як амортизатор
Список (типу LPUSH / BRPOP) — мінімальний канал: поклали, забрали. Зручно, якщо Redis уже є під кеш або rate limit. Але кілька воркерів без дублікатів доведеться проєктувати самим — у брокера це вже «з коробки» для черги.
Streams (XADD, XREADGROUP, XACK) додають групи споживачів, ідентифікатори повідомлень і pending для завислих. Це ближче до короткого журналу: порядок у межах ключа, кілька незалежних читачів, без обов’язкового стрибка в Kafka «завтра».
Обмеження ті самі: пам’ять, eviction. Якщо політика витіснення зачепить ключі з подіями — частина історії зникне. Для критичних шляхів — ліміти, моніторинг довжини стріму, або перехід на дисковий брокер.
Черга завдань проти журналу подій: RabbitMQ і Kafka
RabbitMQ — це маршрутизація й черги: routing keys, TTL, dead-letter, знайома модель для Laravel Horizon. Добре лягає на задачі (лист, вебхук, перерахунок) і середні обсяги; при екстремальному throughput доведеться дробити черги і тюнити кластер.
Kafka — це партиційний лог з retention і offset. Зручно, коли багато сервісів читають одну й ту саму стрічку, потрібна історія й повторне програвання. Ціна — інша експлуатація й мислення потоком, а не «одна джоба виконана».
Вибір — не про моду. Це обсяг, кількість читачів, скільки часу зберігати стрічку і хто вміє це підтримувати. Часто довго живе тандем: Rabbit для імперативних задач, Kafka (або керований аналог) для сирих подій.
Де має жити аналітика
OLAP тут — узагальнення: будь-яке сховище під важкі читання великих діапазонів — колонковий DWH, озеро, або окрема Postgres з іншими індексами, без джойнів звітів до гарячих таблиць гаманця.
Типовий ланцюжок: OLTP лишається джерелом правди для грошей → події йдуть у потік/чергу → воркер або ELT наповнює вітрини → дашборди читають лише цей контур. «Свіженькі» цифри — через матеріалізовані уявлення, розклад або стрімінгову агрегацію.
Одна таблиця events у продовій базі економить тижні на старті й може коштувати місяців рефакторингу пізніше. Компроміс — окрема база чи схема з лімітами ресурсів і без важких запитів з BI в ядро.
Що уточнити до вибору схеми
- Чи можна втратити хоч один івент? Якщо ні — буфер має бути персистентним, реплікованим, з моніторингом відставання.
- Наскільки жорсткий порядок — по користувачу, по сесії, чи достатньо часу з похибкою?
- Скільки різних споживачів прочитають той самий потік? Чим більше — тим частіше виграє лог із збереженням.
- Де малюються графіки? Якщо там же, де баланс — потрібна ізоляція: репліка read-only, таймаути, окремий інстанс.
- Чи є ідемпотентність на вході? Без неї мережеві повтори псують метрики й облік.
Далі в розділі архітектури розберемо стійкі інтеграції, гонки при списанні і практичні патерни в Laravel. Головна думка: злити потік подій і грошовий контур без буфера — це рішення з відомою ціною, а не технічна необхідність.