---
title: "Go вимірює, Laravel вирішує: як Sentinel замінив регіональні воркери Horizon на Go-проби"
url: https://laravelukraine.com/blog/go-vimiriuje-laravel-virisuje-iak-sentinel-zaminiv-regionalni-vorkeri-horizon-na-go-probi
date: 2026-10-31
source: https://sentinel.rootstuff.io/blog/laravel-edge-workers-to-go-probes
---

# Go вимірює, Laravel вирішує: як Sentinel замінив регіональні воркери Horizon на Go-проби

Це переказ інженерного розбору [Адама Бале (Adam Balée)](https://sentinel.rootstuff.io/blog/laravel-edge-workers-to-go-probes), автора сервісу моніторингу Sentinel, опублікованого 18 жовтня 2026 року. Ми зберегли структуру й усі технічні деталі, але скоротили відступи.

Sentinel і далі працює на Laravel. Змінилося інше: Laravel більше не запускається в кожному регіоні моніторингу.

Донедавна кожен регіон був, по суті, ще однією копією застосунку: Laravel, Horizon, Redis, nginx, PHP-FPM, supervisord і пул із 50-60 з гаком воркерів на сервері з 16 ГБ, які займали близько 4 ГБ RAM. Тепер той самий регіон - це один Go-процес на 30-50 МБ на машині з 1 ГБ, і він тримає каденцію перевірок стабільніше, ніж PHP-флот будь-коли тримав.

Стара схема працювала, і після довгого тюнінгу працювала непогано. Але автор хотів перевіряти сайти з набагато більшої кількості регіонів, а дублювати весь стек для кожного нового регіону сенсу не мало. На момент переходу регіонів було шість, сьогодні їх п'ятнадцять: дев'ять додали за два дні після того, як вимкнули останній PHP-вузол.

### Стара схема

Завдання регіону моніторингу просте: отримати свої монітори, вчасно виконати перевірки й повідомити результат. Інфраструктура під це завдання простою не була: сервер на 4 vCPU і 16 ГБ, повний Laravel-стек і приблизно 50-60 воркерів Horizon.

Найважче було тримати щільну каденцію. Мережева перевірка більшість часу чекає: на DNS, з'єднання, TLS, саму відповідь або врешті на таймаут. У Horizon перевірка, яка чекає, все одно займає воркер. Автор довго підбирав кількість воркерів, черги, таймаути й розклад, щоб PHP-вузли надійно трималися біля 30-секундного інтервалу. І навіть тоді під навантаженням перевірки час від часу дрейфували до 40 з гаком секунд.

З кількома регіонами з цим можна було жити. Із задумом розгорнути Sentinel у багатьох регіонах - вже ні.

### Чому Go

У списку кандидатів були Go і Rust. Одна з головних причин вибору Go специфічна саме для моніторингу - TLS-відбиток (TLS fingerprint).

Старі PHP-вузли надсилали ClientHello у форматі OpenSSL. Якби їх замінили проби, які для Cloudflare, Akamai та інших WAF раптом виглядають як зовсім інший клієнт, результати перестали б бути еквівалентними. У Go є бібліотека uTLS, яка дозволила зберегти цю поведінку. А це критично, коли мета - змінити реалізацію, не змінивши того, що бачить сайт, який перевіряють.

Модель конкурентності Go теж добре лягає на таке навантаження: сотні перевірок одночасно - це сотні горутин за семафором. Rust із Tokio теж би впорався, але для невеликого обсягу мережевого коду додаткова складність була не потрібна. Стандартна бібліотека Go покрила більшість потреб, крос-компіляція проста, а розгортання - один бінарник. Перший робочий вузол пройшов шлях від порожнього репозиторію до перевірки реальних моніторів за один день.

Rust, можливо, дав би трохи менший бінарник чи заощадив би ще трохи пам'яті, але тут це неважливо: Go-процес і так крихітний і майже не навантажує CPU. Наступне вузьке місце буде не там.

### Go вимірює, Laravel вирішує

Найважливіше архітектурне рішення - скільки відповідальності віддати пробі. Sentinel уміє перевіряти HTTP, SSL, DNS, домени, ping і порти, підтримує редиректи, власні заголовки, автентифікацію, перевірку ключових слів, JSON-перевірки та кілька спеціалізованих типів. Підтримувати другу реалізацію всієї логіки, яка вирішує, чи монітор здоровий, автор не хотів.

Тому проба цього не вирішує. Вона виконує мережеву операцію і відсилає свої спостереження в Laravel. Для HTTP-перевірки це код статусу, час DNS, час з'єднання, час TLS, TTFB і, коли потрібно, тіло відповіді.

Усе інше лишилося в Laravel: допустимі коди статусу, перевірки ключових слів і JSON, термін дії сертифікатів, логіка підтвердження падіння й відновлення, консенсус між регіонами, інциденти та сповіщення.

Розподіл вдався особливо добре тому, що Sentinel уже відокремлював виконання перевірки від запису її результату. Результати проб проходять тим самим шляхом. Ось рекордер, скорочений до двох типів перевірок:

```php
  class ProbeResultRecorder
  {
      public function __construct(
          private readonly RedisResponseTimeService $redis
      ) {}

      public function record(
          Monitor $monitor,
          string $region,
          array $result,
          CarbonInterface $checkedAt,
          ?RegionTimestampBatch $timestampBatch = null
      ): void {
          (new MonitorCheck($monitor, $region))
              ->replay($result, $checkedAt, $this->redis, $timestampBatch);
      }

      public function recordSsl(
          Monitor $monitor,
          string $region,
          array $facts
      ): void {
          (new SSLMonitor($monitor, $region))->replay($facts);
      }
  }
```

Завдяки цьому Go-частина лишилася невеликою, і її легко тестувати: PHP- і Go-реалізації можна запустити проти тих самих сайтів і порівняти, що кожна побачила. PHP при цьому лишається еталоном того, що ці спостереження означають.

### Як проби отримують роботу

Кожна проба автентифікується в control plane і періодично завантажує маніфест моніторів. Скорочений HTTP-монітор у ньому виглядає приблизно так:

```json
  {
    "id": 2481,
    "type": "http",
    "url": "https://api.acme.dev",
    "interval_seconds": 30,
    "timeout": 30,
    "method": "GET",
    "needs_body": false
  }
```

Маніфест будується за тими самими правилами відбору, що й у наявного диспетчера. Монітор на паузі зникає з маніфесту, так само як і тоді, коли акаунт заблоковано чи змінилася конфігурація регіонів. Маніфест віддається з `ETag`, тож більшість синхронізацій закінчуються відповіддю `304`, а не перебудовою й повторною передачею тих самих даних.

Результати повертаються в control plane пакетами. Якщо проба не може до нього достукатися, результати пишуться на диск і надсилаються пізніше з оригінальними мітками часу.

Випуски проб теж незалежні від випусків Laravel-застосунку. Кожен регіон закріплено за певною версією проби, і вузол періодично перевіряє, чи не час оновитися. Тож розгортання основного застосунку більше не означає перезапуску воркерів моніторингу в кожному регіоні.

### Цифри

Обидві архітектури якийсь час працювали паралельно, тож різницю було легко виміряти:

| | PHP-вузол | Go-проба |
|---|---|---|
| Сервер | 4 vCPU / 16 ГБ | 1 vCPU / 1 ГБ |
| Процеси | 50-66 воркерів Horizon плюс решта стеку | 1 |
| Використано RAM | ~3,9-4,9 ГБ | ~30-50 МБ |
| Каденція 30 с | може дрейфувати під навантаженням | зазвичай 30-31 с |

Автор чесно застерігає: цифри пам'яті гарно виглядають у заголовку, але порівнювати 16 ГБ з 1 ГБ не зовсім коректно. PHP-сервери були з запасом і могли б працювати на меншому. Цікавіша тут каденція.

Щоб PHP-воркери трималися так близько до 30 секунд, довелося чимало попрацювати. Go-проби тримають 30-31 секунду на значно менших машинах майже без зусиль, і запас ще великий. Тепер вони обслуговують і 15-секундний тариф, який запустили в день вимкнення останнього PHP-вузла - після доби обкатки в усіх регіонах. CPU й пам'ять навіть близько не стали обмеженням.

І це головне, бо економія RAM не була метою. Мета - додавати регіони й перевірки так, щоб інфраструктура не росла тими самими темпами.

### Новий регіон тепер - це просто

Раніше нова локація моніторингу означала ще один Laravel-стек і ще один флот воркерів. Тепер це один невеликий бінарник на невеликому сервері. Жодної бази даних застосунку на краю мережі, жодного кластера Horizon. Проба отримує маніфест, виконує перевірки й надсилає результати.

Це суттєво змінює економіку розширення: географічне покриття можна додавати, не дублюючи щоразу інфраструктуру застосунку, і є запас, щоб зменшувати інтервали перевірок, не закидаючи проблему новими PHP-воркерами. Останній PHP-вузол вимкнули 19 жовтня, а за два дні мережа виросла з шести регіонів до п'ятнадцяти на шести континентах.

### Що пішло не так

**IPv6.** Одна SSL-перевірка підключалася за іменем хоста, і Go обрав IPv6, тоді як HTTP-перевірка на тому самому вузлі використовувала закріплену IPv4-адресу. Сайт за Cloudflare поводився на цих двох шляхах по-різному, і це дало хибний SSL-інцидент. Тепер усі типи перевірок на вузлі ходять на ту саму закріплену адресу.

**Cloudflare і нові IP.** Бот Sentinel верифікований у Cloudflare і публікує свої IP-адреси для моніторингу, але нову адресу Cloudflare розпізнає не одразу. Коли піднімали новий регіон, проба отримувала `403` від сайтів з managed challenge, доки Cloudflare не наздогнав. Тепер нові IP заздалегідь реєструють, перш ніж регіон стає активним.

**Навантаження на сайти.** Більше регіонів - більше запитів до сайтів, які перевіряють. П'ятнадцять регіонів з інтервалом 15 секунд - це запит щосекунди. В автора є невеликий сайт на WordPress, у якого пул PHP переповнився щойно оновлення плагіна сповільнило рендеринг, - і монітор, який мав повідомити про падіння, сам був частиною причини. Тепер форма монітора показує, скільки запитів на хвилину надсилатиме обрана конфігурація, і попереджає, якщо їх більше тридцяти.

**Control plane.** Коли регіональні вузли стали легкими, вони перестали бути очевидним вузьким місцем. Але Laravel і далі мусить прийняти й обробити кожне спостереження, яке вони надсилають. Автор уже прибрав з цього шляху зайву роботу, а наступного дня після написання статті розділив підтвердження прийому і обробку: пакет від проби підтверджується одразу, щойно його збережено, а обробляє його завдання в черзі. Тож прийом пакета більше не прив'язаний до обробки кожного результату в межах того самого HTTP-запиту.

Наступне обмеження тепер, найімовірніше, буде в control plane, а не в пробах.

### Чим усе закінчилося

Наявні регіони переводили по одному, і тепер усі вони працюють на пробах. Laravel нікуди не дівся: йому й далі належать застосунок, акаунти, конфігурація моніторів, консенсус між регіонами, інциденти, сповіщення і логіка, яка визначає, чи щось справді впало. Go займається тією частиною, яка більшість життя чекає на мережу.

У результаті - значно менший слід у регіонах, щільніший розклад і багато простору для розширення мережі моніторингу.

### Підсумок від нас

Історія Sentinel не про те, що PHP не впорався. Laravel тут лишився мозком системи, а з PHP винесли лише одну річ - гарячий цикл, який майже весь час чекає на мережу. Такий розподіл варто розглядати, коли:

- у вас багато воркерів черги, які переважно чекають на зовнішні HTTP-запити, DNS чи сокети, і саме їхня кількість, а не CPU, визначає розмір серверів;
- одну й ту саму операцію треба виконувати в багатьох місцях одночасно (регіони, edge-вузли), і дублювати там увесь стек дорого;
- вимірювання можна чітко відокремити від рішення: «тупий» агент збирає сирі дані, а вся бізнес-логіка лишається в одному місці;
- важливі низькорівневі деталі мережі (TLS-відбиток, вибір IP), до яких PHP-клієнти дають мало доступу.

Якщо ж черг небагато, затримка в кілька секунд не критична, а основний час іде на роботу з базою й бізнес-логіку, - Laravel з Horizon цілком достатньо, і друга мова програмування в проєкті лише додасть обслуговування. Корисний урок з цієї статті ширший за вибір мови: спершу відокремте виконання операції від запису її результату. Тоді винести виконання кудись іще, якщо колись знадобиться, буде справою кількох днів, а не переписуванням застосунку.
