# PHP-сервери з тривалим часом роботи

> Налаштування дистрибутиву OpenTelemetry для PHP у Laravel Octane (Swoole, RoadRunner) та інших довготривалих серверних процесів PHP.

---

LLMS index: [llms.txt](/llms.txt)

---

PHP-фреймворки, такі як **Laravel Octane** (з Swoole або RoadRunner), запускають PHP як довготривалий серверний процес замість створення нового процесу на запит. Це змінює поведінку distro в кількох важливих аспектах і вимагає специфічних налаштувань.

## Як працюють традиційні PHP-сервери {#how-traditional-php-servers-work}

З **PHP-FPM** або **Apache mod_php** кожен HTTP-запит відповідає власному життєвому циклу PHP-процесу:

1. PHP-процес стартує → distro bootstrap (OTel SDK ініціалізовано, автоінструментаційні hook-и зареєстровані)
2. Запит обробляється → створюються відрізки для HTTP-транзакції та будь-яких інструментованих викликів (curl, PDO, тощо)
3. Відповідь відправлена → запускаються PHP shutdown-функції → відрізки очищаються та експортуються
4. PHP-процес завершується

**Відрізок транзакції** distro (`OTEL_PHP_TRANSACTION_SPAN_ENABLED`) обгортає саме один HTTP-запит: він стартує, коли запит надходить (web SAPI, `$_SERVER` заповнено) і закінчується, коли процес виходить. Це кореневий відрізок, від якого висять всі дочірні відрізки (curl, DB запити, тощо).

## Чим відрізняються сервери, що працюють безперервно{#how-long-running-servers-differ}

З **Laravel Octane** (Swoole або RoadRunner) один PHP worker-процес обробляє багато HTTP-запитів поспіль без виходу між ними:

1. PHP-процес стартує — worker-процеси стартують з повністю ініціалізованим distro (SDK, hook-и, експортер)
2. Кожен HTTP-запит диспетчеризується до worker-а — hook-и worker-а спрацьовують і створюються відрізки
3. Відповідь відправлена — **PHP shutdown-функції НЕ запускаються** (процес продовжує працювати)
4. Worker-процес виходить лише при зупинці сервера (graceful stop)

Оскільки worker-процес є **CLI-процесом** (запущеним через `php artisan octane:start`), SAPI завжди `cli`, а не `fpm-fcgi` або `apache2handler`. Distro використовує це для розрізнення:

- `OTEL_PHP_TRANSACTION_SPAN_ENABLED` — кореневий відрізок для web SAPI (FPM/Apache). Не актуально тут.
- `OTEL_PHP_TRANSACTION_SPAN_ENABLED_CLI` — кореневий відрізок для CLI-процесів. У сервері, що працює безперервно, це обгортає **весь час життя сервера** (від `octane:start` до `octane:stop`), а не окремий запит.

| Аспект                       | PHP-FPM / Apache                  | Сервер, що працює безперервно (Octane)              |
| ---------------------------- | --------------------------------- | --------------------------------------------------- |
| SAPI                         | `fpm-fcgi` / `apache2handler`     | `cli`                                               |
| Життєвий цикл процесу        | Один процес на запит              | Один worker обробляє багато запитів                 |
| PHP shutdown-функції         | Запускаються після кожного запиту | Запускаються лише при виході worker-а               |
| Distro bootstrap             | Запускається на кожному запиті    | Запускається один раз на worker при старті          |
| Відрізок транзакції (`_CLI`) | Не застосовується                 | Обгортав би увесь час життя сервера — вимкніть його |

## Рекомендована конфігурація {#recommended-configuration}

### Вимкніть CLI відрізок транзакції {#disable-the-cli-transaction-span}

Автоматичний кореневий відрізок (`OTEL_PHP_TRANSACTION_SPAN_ENABLED_CLI`) охоплює весь процес PHP. На сервері, що працює тривалий час, це означає, що один відрізок триватиме аж до вимкнення сервера,
що не є корисною телеметрією.

```sh
export OTEL_PHP_TRANSACTION_SPAN_ENABLED_CLI=false
```

> [!NOTE]
>
> `OTEL_PHP_TRANSACTION_SPAN_ENABLED_CLI` є CLI-специфічним і не має ефекту на web SAPI (FPM/Apache) розгортання.

### Вимкніть виведені відрізки {#disable-inferred-spans}

Виведені відрізки (вибірка трасування стека) призначені для традиційного PHP, що працює на основі запитів. На сервері з тривалим часом роботи вибірка виконується безперервно між запитами, що створює шум і споживає ресурси CPU.

```sh
export OTEL_PHP_INFERRED_SPANS_ENABLED=false
```

Це стандартне значення, тому потрібно лише якщо ви раніше увімкнули виведені відрізки глобально.

### Процесор відрізків та експорт затримки {#span-processor-and-export-latency}

Зазвичай distro використовує `BatchSpanProcessor`, який накопичує відрізки в памʼяті і експортує їх за таймером (стандартно: кожні 5 секунд). PHP є однопотоковим, тому перевірка таймера запускається лише коли новий відрізок завершується через `onEnd()` — немає фонового tick. Завжди використовуйте graceful stop (`php artisan octane:stop`, SIGTERM), щоб worker-и могли завершити поточний запит, запустити PHP shutdown-функції і очистити експортер перед виходом. Жорстка зупинка (SIGKILL) обходить все це.

З graceful stop, відрізки, забуферовані в памʼяті, очищуються перед виходом процесу — за умови, що точка доступу OTLP доступна. Однак `BatchSpanProcessor` вводить **затримку експорту**, пропорційну частоті надходження запитів. В застосунку з низьким трафіком відрізок, створений о 10:00, може не зʼявитися в колекторі до 10:05 (наступний запит нарешті запустить перевірку таймера). Щоб отримати інформацію майже в режимі реального часу, скористайтеся `SimpleSpanProcessor`:

```sh
export OTEL_PHP_TRACES_PROCESSOR=simple
```

Кожен відрізок миттєво потрапляє в чергу експорту при `onEnd()`, незалежно від обсягу трафіку.

Вбудований транспорт C++ цього дистрибутиву (`HttpTransportAsync`) має власну внутрішню чергу та постійне з’єднання з точкою доступу OTLP, тому перехід на `simple` **не** означає один HTTP-запит на кожен відрізок. Рівень PHP синхронно передає дані до черги C++ (швидка операція в середині процесу), а рівень C++ обʼєднує їх у пакети та незалежно надсилає через постійне з’єднання.

## Повний приклад {#complete-example}

Та сама конфігурація застосовується як до Swoole, так і до RoadRunner:

```sh
export OTEL_SERVICE_NAME="my-laravel-octane-app"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

# Налаштування для постійно працюючого сервера
export OTEL_PHP_TRANSACTION_SPAN_ENABLED_CLI=false
export OTEL_PHP_INFERRED_SPANS_ENABLED=false
export OTEL_PHP_TRACES_PROCESSOR=simple

# Swoole
php artisan octane:start --server=swoole

# RoadRunner
php artisan octane:start --server=roadrunner
```

## Як інструментування працює для кожного типу сервера {#how-instrumentation-works-per-server-type}

### Swoole

Swoole створює робочі процеси шляхом розгалуження головного процесу PHP. Дистрибутив завантажується один раз у головному процесі, а робочі процеси успадковують його стан ініціалізації (хуки, SDK, з’єднання з експортером). Кожен HTTP-запит у робочому процесі запускає зареєстровані хуки автоматичної інструментації (Laravel, curl, PDO тощо), а відрізки створюються в рамках TracerProvider робочого процесу.

### RoadRunner

RoadRunner — це сервер застосунків на базі Go, який керує робочими процесами PHP. На відміну від Swoole, він не використовує відгалуження — кожен робочий процес PHP запускається як окремий процес. Як наслідок, кожен робочий процес самостійно завантажує дистрибутив під час запуску. З точки зору інструментування поведінка є ідентичною: SAPI робочого процесу — `cli`, він обробляє багато запитів, не завершуючи роботу між ними, і застосовується та сама конфігурація.

## Затримка у розкладі BatchSpanProcessor {#batchspanprocessor-schedule-delay}

Якщо ви хочете зберегти `BatchSpanProcessor` але зменшити затримку експорту, зробіть меншою затримку у розкладі:

```sh
export OTEL_BSP_SCHEDULE_DELAY=500   # ms, типово 5000
```

Це допомагає лише в застосунках зі стабільним трафіком — таймер спрацьовує на `onEnd()`, так що тихіший застосунок все ще відчуває затримку, пропорційну проміжку між запитами.
