Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 158 · После Manager, Query, курсора и основ объектов

MongoDB: снимок сервера, hello и реальные изменения описания

От источника ответа заказа к настоящему Unknown→Standalone: host, port, роль, RTT, монотонное время и корреляция событий двух клиентов.

От списка заказов к понятному источнику ответа

Страница вернула два оплаченных заказа. Теперь для обращения в поддержку нужно понять: какой сервер дал ответ, что клиент знал о нём раньше, измерялся ли RTT и почему в первой записи журнала стояло Unknown. Разберём это на настоящих запросах и событиях драйвера, сохраняя результат отдельно от технических измерений.

Два связанных пути одного запроса
  1. Query → сервер → документы 101 и 103 → таблица заказов
  2. Драйвер наблюдает сервер → ServerDescription → host, port, тип, hello и время
  3. Описание изменилось → ServerChangedEvent → previousDescription и newDescription
  4. Ограниченный отчёт → данные страницы + понятные наблюдения с единицами

Описание похоже на карточку осмотра: в ней записано, что было известно в определённый момент. Но это не медицинский диагноз и не управляющий пульт: снимок не гарантирует будущую доступность и не меняет сервер. Событие связывает две такие карточки — до и после изменения.

$description = $cursor->getServer()->getServerDescription();
echo $description->getHost(), ':', $description->getPort();
echo $description->getType(); // в нашей лаборатории Standalone

Здесь $cursor — результат уже выполненного Query; -> вызывает метод объекта. Первая строка получает описание сервера этого результата, следующие читают его свойства через методы. Полная подготовка коллекции и Cursor есть в первом архиве. Самостоятельно конструировать выдуманный ServerDescription для отчёта не требуется.

Перед началом: Manager, Query и документы, ограниченная выборка и курсор, объект Server и выбор узла. Для ObjectId и календарного времени пригодится глава BSON-типов.

Различить данные, сервер, снимок и событие

Объект / значениеНа какой вопрос отвечаетЧего из него нельзя заключить
Документ заказаКакие ID, статус и сумма выбраны запросомРоль или доступность серверов
ServerDescriptionЧто было известно о конкретном узле в момент снимкаГарантированный успех следующего запроса
ServerChangedEventЧто поменялось между previous и newЧто обязательно произошла авария
topologyId событияК какой истории клиента относится событиеКакой постоянный ID имеет кластер или заказ
getRoundTripTimeДлительность hello в целых миллисекундах, если известнаВремя фильтрации и чтения страницы
getLastUpdateTimeМонотонная отметка обновления в микросекундахКалендарная дата или возраст заказа

Строковый тип ServerDescription::getType() сравнивается с константами этого класса. Не смешивайте его с целым Server::getType(). При начальном Unknown отсутствие RTT представлено NULL; известный RTT 0 — другое значение. Точные host, port, RTT, отметки и topologyId видны в фактических файлах каждого запуска.

Запуск восьми самостоятельных программ и чтение результатов

Восемь архивов используют PHP 8.4+ CLI x64 и ext-mongodb 2.5+; проверяемая среда — PHP 8.5.11 / mongodb 2.5.5 / MongoDB 8.0.34 standalone. Composer не нужен. В каждом архиве example.php, ss123-helper.php и README.txt с установкой расширения, запуском mongod на loopback и двумя переменными окружения.

$env:PHPMAN_MONGODB_URI='mongodb://127.0.0.1:27075/?directConnection=true&retryWrites=false'
$env:PHPMAN_MONGODB_DATABASE='php_manual_lab'
php example.php

Эти три строки выполняются в PowerShell из каталога распакованного примера. README показывает создание отдельного dbpath и команду mongod; используйте свою учебную базу, а не рабочие данные. Helper разрешает только явный локальный адрес с портом. Каждая программа создаёт одну коллекцию mongodb123_snapshots_… со случайным именем, записывает три заказа, а finally удаляет только собственное точное имя и проверяет отсутствие.

Откройте result.html: сначала увидите полезный результат, затем фактические динамические измерения. result.json содержит повторяемую таблицу; observations.json — реальные снимки с подписями и единицами; metrics.json — проверки, версии и SHA256. Файлы на сайте сохранены после выполнения программы, браузер не подключается к базе.

Новый Manager создаётся с disableClientPersistence=true, подписчик регистрируется до первого I/O. Поэтому начальное обнаружение повторяется без перезапуска mongod. Это изоляция опыта, не рекомендация открывать новый клиент на каждый рабочий запрос. В callback сохраняется не более 20 событий; сетевых запросов из него нет. Подробная регистрация и все callbacks разобраны в соседней главе о топологии.

Связать список оплаченных заказов с описанием ответившего сервера

Сотрудник видит оплаченные заказы, а разработчику нужно понимать, от какого сервера пришла именно эта выборка. Сохраним обычную таблицу заказов и рядом технический снимок источника ответа.

Данные и подготовка

В собственной коллекции три заказа: 101 paid на 12900 копеек, 102 draft на 5000 и 103 paid на 7100. Query оставляет paid, выбирает только ID, статус и сумму, сортирует по ID и ограничивает результат двумя строками. Email покупателя остаётся в базе.

Как получается результат
  1. Query: status=paid → заказы 101 и 103
  2. projection + limit=2 → только поля страницы
  3. Cursor → фактический Server → ServerDescription
  4. Геттеры снимка → адрес, порт, роль, RTT и время в отдельной таблице

Минимальная запись текущего действия

$description = $cursor->getServer()->getServerDescription();
echo $description->getHost(), ':', $description->getPort(), ' ', $description->getType();

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

ЗаказСтатусСумма, коп.Тип ответившего сервера
101paid12900Standalone
103paid7100Standalone

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

Cursor содержит результат выполненного запроса. getServer() даёт объект того сервера, с которым связан этот результат. getServerDescription() получает его описание; затем стрелка -> вызывает метод этого объекта. getHost() возвращает строку адреса, getPort() — целое число порта, getType() — строковый тип, здесь Standalone.

Standalone означает отдельный mongod вне replica set. Это характеристика наблюдаемого узла. Она не относится к статусу заказа paid и не сообщает, какая база или коллекция была прочитана. Эти понятия сохраняем раздельно.

Полный пример проверяет две строки и сумму 20000 копеек. Host и port сравниваются с разрешённым локальным endpoint, а их фактические значения печатаются в HTML. При выборе другого учебного порта результат останется понятным: адрес читается из настоящего описания, а не вставлен в текст программы.

Снимок описания не содержит список заказов. В обычном приложении такой снимок можно приложить к внутреннему диагностическому событию, а пользователю показать только нужные документы.

Границы применения

Один адрес и порт описывают endpoint соединения, но не заменяют TLS-проверку идентичности сервера. Снимок отражает знание драйвера в определённый момент и не гарантирует успех следующего запроса. Методы описания сами по себе не выполняют дополнительный ping.

Полная самостоятельная программа и архив
Связать список оплаченных заказов с описанием ответившего сервераСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$cursor=ss123Orders($manager,$namespace);$orders=$cursor->toArray();
$description=$cursor->getServer()->getServerDescription();
$observations['snapshots'][]=ss123Snapshot($description,'Сервер списка заказов',$event->getTopologyId());
$rows=[];foreach($orders as $order){$rows[]=[$order['_id'],$order['status'],$order['total_kopecks'],$description->getType()];}
$sameEndpoint=$description->getHost()===$settings['host'] && $description->getPort()===$settings['port'];
$checks=['paid_orders_only'=>array_column($orders,'_id')===[101,103],
    'sum_is_20000'=>array_sum(array_column($orders,'total_kopecks'))===20000,
    'origin_endpoint'=>$sameEndpoint,'standalone_role'=>$description->getType()===MongoDB\Driver\ServerDescription::TYPE_STANDALONE,
    'projection_has_no_email'=>!array_key_exists('email',$orders[0]) && !array_key_exists('email',$orders[1])];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-order-origin', ['Заказ', 'Статус', 'Сумма, коп.', 'Тип ответившего сервера'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-order-origin: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-order-origin: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Измените фильтр на draft и ожидаемый ID на 102. Отделите изменение данных страницы от неизменившегося типа сервера: почему статус документа поменялся, а роль Standalone — нет?

Практический вопрос разработчика: localhost и ::1: подключение идёт не по ожидаемому адресу. В вопросе Node.js служба MongoDB работает, но ошибка содержит другой адрес подключения. Берём потребность явно видеть endpoint; настройки и ответы чужого драйвера не переносим как PHP-контракт. Это постановка проблемы, а не источник контракта PHP API; объяснение методов сверено с первичными источниками. Наш успешный локальный опыт не воспроизводит все причины чужого сбоя.

Отделить предел BSON-документа от размера сообщения и числа записей

Перед импортом полезно понимать, что ограничивает размер одного документа, что — сетевого сообщения и что — число записей в пакете. Эти три величины нельзя подменять одной проверкой количества товаров.

Данные и подготовка

Берём реальный оплаченный заказ из ограниченного Query и кодируем его в BSON. Длину двоичного BSON сравниваем с maxBsonObjectSize из последнего ответа hello в описании. Фактические пределы и размер документа показаны в observations.json и HTML.

Как получается результат
  1. Заказ 101 из базы → BSON-документ → число байтов
  2. ServerDescription → getHelloResponse → известные драйверу пределы
  3. Байты документа сравниваем с байтами документа; число записей — с числом операций
  4. Сохраняем отдельные выводы; размер сетевого пакета не угадываем

Минимальная запись текущего действия

$hello = $description->getHelloResponse();
$documentBytes = strlen((string) MongoDB\BSON\Document::fromPHP($order));
$fits = $documentBytes <= $hello['maxBsonObjectSize'];

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

Что проверяемЕдиницаВывод
Один документ заказабайтыпомещается
Очередь из двух записейоперациив пределах числа
Один сетевой пакетотдельный предел байтовне обещаем по числу операций

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

hello — служебная команда, которой драйвер узнаёт возможности и роль сервера. getHelloResponse() читает массив из уже полученного ответа, относящегося к моменту создания снимка. Этот вызов не отправляет новый hello. Документ ответа зависит от роли и версии узла.

Document::fromPHP создаёт BSON-контейнер из выбранных полей заказа. Приведение контейнера к строке даёт двоичный BSON; strlen считает байты. Поэтому здесь не используется длина JSON: у JSON другая запись чисел, имён и типов.

maxBsonObjectSize относится к одному BSON-документу, maxMessageSizeBytes — к сообщению протокола, maxWriteBatchSize — к количеству операций. Пример получает все три значения как положительные целые и показывает их отдельно. Две операции могут уложиться по числу, но это ещё не проверка размера их тел и обёртки команды.

Разделение большого пакета по ограничениям выполняет драйвер. Ручное чтение hello удобно для диагностики и понятного сообщения об очень большом документе; оно не заменяет проверку результата реальной записи. Пределы здесь наблюдаются, настройки сервера не меняются.

Границы применения

В LoadBalancer описание не содержит ответа мониторинга: getHelloResponse() вернёт пустой массив. Не обращайтесь к полю без проверки существования и типа. Учебная программа требует standalone MongoDB 8.0 и проверяет ожидаемые поля; это явно более узкая среда, чем весь контракт метода.

Полная самостоятельная программа и архив
Отделить предел BSON-документа от размера сообщения и числа записейСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$cursor=ss123Orders($manager,$namespace);$orders=$cursor->toArray();
$description=$cursor->getServer()->getServerDescription();$hello=$description->getHelloResponse();
foreach(['maxBsonObjectSize','maxMessageSizeBytes','maxWriteBatchSize'] as $field){
    if(!isset($hello[$field]) || !is_int($hello[$field]) || $hello[$field]<1){throw new RuntimeException('Expected positive hello capacity: '.$field);}
}
$bytes=strlen((string)MongoDB\BSON\Document::fromPHP($orders[0]));
$observations['snapshots'][]=ss123Snapshot($description,'Пределы hello для этого клиента',$event->getTopologyId());
$observations['measurements']=['order_bson_bytes'=>$bytes,'proposed_write_operations'=>count($orders),
    'max_bson_bytes'=>$hello['maxBsonObjectSize'],'max_message_bytes'=>$hello['maxMessageSizeBytes'],
    'max_write_operations'=>$hello['maxWriteBatchSize']];
$rows=[['Один документ заказа','байты',$bytes<=$hello['maxBsonObjectSize']?'помещается':'не помещается'],
    ['Очередь из двух записей','операции',count($orders)<=$hello['maxWriteBatchSize']?'в пределах числа':'превышает число'],
    ['Один сетевой пакет','отдельный предел байтов','не обещаем по числу операций']];
$checks=['actual_bson_document'=>$bytes>0 && $bytes<=$hello['maxBsonObjectSize'],
    'two_write_candidates'=>count($orders)===2 && count($orders)<=$hello['maxWriteBatchSize'],
    'distinct_message_limit'=>$hello['maxMessageSizeBytes']>=$hello['maxBsonObjectSize'],
    'hello_is_snapshot_array'=>is_array($hello) && $hello!==[]];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-hello-capabilities', ['Что проверяем', 'Единица', 'Вывод'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-hello-capabilities: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-hello-capabilities: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Добавьте в копию заказа текстовое поле и заново измерьте BSON-байты. Объясните, почему рост строки меняет maxBsonObjectSize-проверку, но не количество операций 2. Сам предел сервера при этом не увеличивается.

Записать RTT hello отдельно от времени получения заказов

Страница может отвечать медленно, хотя мониторинг соединения показывает небольшой RTT. Чтобы искать причину осмысленно, запишем отдельно время служебного hello и интервал получения нужных заказов.

Данные и подготовка

Менеджер уже обнаружил сервер и подготовил учебную коллекцию. Один hrtime ставится непосредственно перед executeQuery, другой — после получения двух строк. Тот же Cursor позволяет получить описание и известный ему RTT.

Как получается результат
  1. hrtime: начало → executeQuery → получение ограниченного результата → hrtime: конец
  2. Разность hrtime в наносекундах / 1000 → интервал запроса и чтения в микросекундах
  3. getRoundTripTime → отдельное измерение hello в миллисекундах
  4. Два числа записаны рядом с названиями операций и единицами

Минимальная запись текущего действия

$start = hrtime(true);
$cursor = $manager->executeQuery($namespace, $query);
$orders = $cursor->toArray(); // query уже ограничен двумя нужными заказами
$queryAndFetchUs = intdiv(hrtime(true) - $start, 1000);
$helloMs = $cursor->getServer()->getServerDescription()->getRoundTripTime();

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

НаблюдениеЕдиница / ответСмысл
hello RTTмиллисекундычисло, в том числе 0
Запрос и получение двух заказовмикросекундыотдельное измерение hrtime
Можно ли объявить запрос быстрым по одному RTTнетэто разные операции

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

Round trip — путь запроса до сервера и ответа обратно. Здесь getRoundTripTime() относится к hello, а не к вашему фильтру заказов. Значение nullable: если измерения нет, возвращается NULL. После успешного обнаружения в нашей лаборатории проверяется целое неотрицательное значение.

Нулевой RTT допустим при представлении в целых миллисекундах. Нельзя заменять NULL на 0 и затем говорить, что неизвестное соединение очень быстрое. Программа сохраняет эту разницу и в JSON, и в таблице.

hrtime(true) даёт монотонные наносекунды для измерения собственного участка программы. intdiv(..., 1000) переводит разность в микросекунды. Здесь участок включает отправку Query, ожидание ответа и материализацию небольшой выборки; это наблюдение клиентского кода, а не чистое время CPU сервера.

Один запуск не является сравнительным тестом производительности. Поэтому проверки подтверждают типы, неотрицательность интервала и нужные документы, но не требуют, например, запроса быстрее 1 мс. Фактические числа остаются открытыми для просмотра.

Границы применения

RTT не заменяет explain, индексы, анализ объёма ответа или профилирование PHP. Интервал Query нельзя вычитать из getLastUpdateTime: это другой контракт часов. Для большого экспорта измеряйте нужный участок и обрабатывайте строки последовательно; не переносите toArray на неограниченную коллекцию.

Полная самостоятельная программа и архив
Записать RTT hello отдельно от времени получения заказовСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$started=hrtime(true);$cursor=ss123Orders($manager,$namespace);$orders=$cursor->toArray();
$queryMicros=intdiv(hrtime(true)-$started,1000);
$description=$cursor->getServer()->getServerDescription();$rtt=$description->getRoundTripTime();
$observations['snapshots'][]=ss123Snapshot($description,'RTT мониторинга',$event->getTopologyId());
$observations['measurements']=['query_and_fetch_us'=>$queryMicros,'hello_round_trip_ms'=>$rtt,'returned_orders'=>count($orders)];
$rows=[['hello RTT','миллисекунды',is_int($rtt)?'число, в том числе 0':'не измерен'],
    ['Запрос и получение двух заказов','микросекунды','отдельное измерение hrtime'],
    ['Можно ли объявить запрос быстрым по одному RTT','нет','это разные операции']];
$checks=['known_rtt_is_nonnegative'=>is_int($rtt) && $rtt>=0,'query_clock_nonnegative'=>$queryMicros>=0,
    'measured_real_query'=>array_column($orders,'_id')===[101,103],
    'rtt_not_replaced_by_query'=>$observations['measurements']['hello_round_trip_ms']===$description->getRoundTripTime()];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-rtt-and-query', ['Наблюдение', 'Единица / ответ', 'Смысл'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-rtt-and-query: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-rtt-and-query: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Добавьте отдельную обработку уже полученных двух строк и измерьте её самостоятельным hrtime-интервалом. Почему это не изменит RTT сохранённого ServerDescription?

Увидеть настоящий переход от неизвестного сервера к Standalone

В журнале встретился Unknown. Это ещё не означает, что MongoDB прислал «неправильный тип». Посмотрим, как новый клиент сначала ничего не знает о работающем узле, а затем получает информацию и меняет описание.

Данные и подготовка

Manager создаёт новый непереиспользуемый клиент. Ss123Observer регистрируется до первого I/O. Когда buildInfo запускает обнаружение, драйвер сам вызывает serverChanged. Мы сохраняем настоящий event, затем находим в нём переход нужного host:port от Unknown к Standalone.

Как получается результат
  1. Свежий клиент → описание Unknown, RTT неизвестен
  2. Первый I/O → обнаружение узла через служебный обмен
  3. Драйвер → serverChanged(event) → сохраняем event
  4. getPreviousDescription / getNewDescription → две разные временные точки

Минимальная запись текущего действия

public function serverChanged(MongoDB\Driver\Monitoring\ServerChangedEvent $event): void {
    $this->changes[] = $event; // сохранить событие, не делать запрос из callback
}

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

МоментТип / результатRTT / действие
Предыдущее описаниеUnknownNULL
Новое описаниеStandaloneизмерен
Заказы после обнаружения101,103обычный Query

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

SDAM означает обнаружение и наблюдение серверов. Subscriber — объект, которому драйвер сообщает события. Интерфейс SDAMSubscriber требует девять методов: helper содержит каждый с нужным типом события и void. Здесь только serverChanged сохраняет данные; пустые остальные методы означают выбранный интерес наблюдателя.

Callback — метод, который вызывает драйвер при событии. Мы не создаём событие вручную и не вызываем свой serverChanged для изображения результата. Внутри callback лишь добавляем объект в ограниченный список; запросы к MongoDB выполняются снаружи, чтобы не создавать повторный вход в мониторинг.

getPreviousDescription возвращает снимок до изменения, getNewDescription — после него. В первом RTT равен NULL, во втором уже измерен. Host и port события совпадают с адресом нового описания. После этого обычный Query получает заказы 101 и 103.

disableClientPersistence=true нужен для воспроизводимого начального обнаружения: иначе другой Manager может разделять уже известный клиент. Создавать новый клиент на каждый рабочий HTTP-запрос ради событий обычно не нужно. В конце подписка снимается, общий mongod не останавливается.

Границы применения

Unknown может быть и начальным состоянием, и результатом недоступности/ошибки обнаружения. Один этот тип не устанавливает причину. Начальное событие не равно сбою сервера. Здесь проверяется нормальное обнаружение standalone; выборы primary и аварийное переключение не инсценируются.

Полная самостоятельная программа и архив
Увидеть настоящий переход от неизвестного сервера к StandaloneСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$previous=$event->getPreviousDescription();$current=$event->getNewDescription();
$cursor=ss123Orders($manager,$namespace);$orders=$cursor->toArray();
$observations['snapshots']=[ss123Snapshot($previous,'До обнаружения',$event->getTopologyId()),
    ss123Snapshot($current,'После обнаружения',$event->getTopologyId())];
$rows=[['Предыдущее описание',$previous->getType(),$previous->getRoundTripTime()===null?'NULL':'измерен'],
    ['Новое описание',$current->getType(),is_int($current->getRoundTripTime())?'измерен':'NULL'],
    ['Заказы после обнаружения',implode(',',array_column($orders,'_id')),'обычный Query']];
$checks=['real_transition'=>$previous->getType()==='Unknown' && $current->getType()==='Standalone',
    'unknown_rtt_not_zero'=>$previous->getRoundTripTime()===null,'new_rtt_exists'=>is_int($current->getRoundTripTime()),
    'event_endpoint'=>$event->getHost()===$current->getHost() && $event->getPort()===$current->getPort(),
    'read_after_discovery'=>array_column($orders,'_id')===[101,103]];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-first-discovery', ['Момент', 'Тип / результат', 'RTT / действие'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-first-discovery: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-first-discovery: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Выведите тип предыдущего описания перед чтением заказов и после. Почему чтение новых документов не превращает уже сохранённое прошлое Unknown в Standalone?

Практический вопрос разработчика: Почему в описании работающего кластера один узел остаётся Unknown. Автор связывает Unknown с неправильным ответом primary и отмечает различие поведения процессов. Наш опыт объясняет знание конкретного клиента и начальное обнаружение; он не устанавливает причину обсуждаемого сбоя и не воспроизводит fork/Atlas. Это постановка проблемы, а не источник контракта PHP API; объяснение методов сверено с первичными источниками. Наш успешный локальный опыт не воспроизводит все причины чужого сбоя.

Различить события двух клиентов, обращающихся к одному серверу

Два компонента приложения — каталог и отчёт — могут обращаться к одному endpoint. При объединении журналов нужно отличить наблюдения разных клиентов и не посчитать их двумя разными серверами.

Данные и подготовка

Первый Manager создаёт учебную коллекцию. Второй использует тот же разрешённый URI и читает ту же ограниченную выборку. Оба получают свой настоящий переход Unknown→Standalone; ни один не перезапускает сервер.

Как получается результат
  1. Клиент каталога → свои события и topologyId A
  2. Клиент отчёта → свои события и topologyId B
  3. Host и port совпали → один наблюдаемый адрес
  4. A и B различаются → это две отдельные истории клиентов

Минимальная запись текущего действия

$key = (string) $event->getTopologyId();
$endpoint = [$event->getHost(), $event->getPort()];
// key связывает события клиента; endpoint показывает адрес сервера

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

Потребитель / выводЗаказы / числоАдресИдентификатор наблюдения
Каталог101,103один endpointсвой topologyId
Отчёт101,103один endpointдругой topologyId
Различных адресов в наблюдениях1host и port совпалиtopologyId не ID сервера

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

getTopologyId() возвращает BSON ObjectId — идентификатор топологии драйвера, связанной с событием. Для журнала его удобно преобразовать в строку. Это технический ключ корреляции: по нему связывают записи одной истории наблюдения.

ObjectId события не является _id заказа, connectionId из hello или постоянным ID кластера. В этом опыте два клиента получили разные topologyId и одинаковые строки заказов с одного host:port. Сами фактические идентификаторы показаны ниже результата.

Проверка сравнивает host и port двумя полями, не пытаясь разобрать строку URI из журнала. На localhost может работать несколько служб с разными портами; совпадения одного имени машины недостаточно.

Программа сохраняет подписи «Каталог» и «Отчёт» как контекст приложения. Это полезнее двух неразличимых строк «подключено»: становится видно, какой компонент заново обнаруживал узел и какой запрос связан с ним.

Границы применения

Разные topologyId не доказывают разные кластеры. Совпадение host:port не является вечной или криптографической идентичностью сервера: процесс мог быть заменён, адрес может вести через прокси. Здесь вывод ограничен двумя клиентами и одним endpoint одного запуска.

Полная самостоятельная программа и архив
Различить события двух клиентов, обращающихся к одному серверуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$cursor=ss123Orders($manager,$namespace);$firstOrders=$cursor->toArray();
$secondObserver=new Ss123Observer;$secondManager=ss123Manager($settings,$secondObserver);
$secondCursor=ss123Orders($secondManager,$namespace);$secondOrders=$secondCursor->toArray();
$other=ss123Discovery($secondObserver,$settings);
$firstId=(string)$event->getTopologyId();$secondId=(string)$other->getTopologyId();
$same=$event->getHost()===$other->getHost() && $event->getPort()===$other->getPort();
$observations['snapshots']=[ss123Snapshot($event->getNewDescription(),'Клиент каталога',$event->getTopologyId()),
    ss123Snapshot($other->getNewDescription(),'Клиент отчёта',$other->getTopologyId())];
$rows=[['Каталог','101,103','один endpoint','свой topologyId'],
    ['Отчёт','101,103','один endpoint','другой topologyId'],
    ['Различных адресов в наблюдениях',1,'host и port совпали','topologyId не ID сервера']];
$checks=['same_endpoint'=>$same,'separate_topology_ids'=>$firstId!==$secondId,
    'topology_id_objects'=>$event->getTopologyId() instanceof MongoDB\BSON\ObjectId && $other->getTopologyId() instanceof MongoDB\BSON\ObjectId,
    'same_order_results'=>$firstOrders===$secondOrders && array_column($firstOrders,'_id')===[101,103]];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-event-correlation', ['Потребитель / вывод', 'Заказы / число', 'Адрес', 'Идентификатор наблюдения'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-event-correlation: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-event-correlation: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Добавьте к наблюдению имя задания импорта, не меняя topologyId. Какие поля позволят найти все события клиента, а какие — только операции задания?

Поставить два описания в порядок без превращения времени в дату

Чтобы поставить обновления описания в порядок, нужен интервал между наблюдениями. Но попытка превратить getLastUpdateTime в календарную дату создаёт бессмысленные даты: метод использует другие часы.

Данные и подготовка

Настоящее событие содержит прежнее и новое описание одного endpoint. Из каждого читаем getLastUpdateTime. Разность нового и прежнего сохраняем как description_update_delta_us; значения не подставляются из заранее подготовленного эталона.

Как получается результат
  1. Прежнее описание → t1 из getLastUpdateTime
  2. Новое описание → t2 из того же API
  3. t2 - t1 → микросекунды между обновлениями
  4. Данные заказа и его бизнес-даты остаются отдельной темой

Минимальная запись текущего действия

$deltaUs = $event->getNewDescription()->getLastUpdateTime()
         - $event->getPreviousDescription()->getLastUpdateTime();
// Не date(), не time() и не возраст бизнес-документа

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

ПоказательЗначение / выводКак читать
Первый снимокUnknownмонотонные микросекунды
Следующий снимокStandaloneмонотонные микросекунды
Разность нового и прежнегонеотрицательнаинтервал между обновлениями, не возраст заказа
Преобразование в календарную датуне выполняетсяэто не Unix timestamp

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

Монотонные часы идут от произвольной точки и предназначены для сравнения интервалов. Официальный контракт разрешает сравнивать это значение с другими возвратами getLastUpdateTime. Оно не является количеством микросекунд с 1970 года.

В примере оба снимка относятся к одному наблюдаемому клиенту и одному событию. Проверяем целые значения и t2 ≥ t1. Нулевую разность нельзя объявить ошибкой только потому, что ожидали положительное число: нужное свойство здесь — отсутствие движения назад.

Этот интервал показывает время между обновлениями описания, но не длительность заказа, транзакции или только сетевого hello. Для сетевого измерения есть RTT, для участка PHP — отдельный hrtime, для времени бизнес-события — BSON UTCDateTime.

Фактические t1, t2 и разность видны в HTML. Во встроенной ожидаемой таблице приведён устойчивый вывод «неотрицательна», чтобы повторный запуск с другими часовыми значениями оставался тем же опытом.

Границы применения

Не вычитайте эти значения из time(), microtime() или hrtime(). Не сравнивайте сырые отметки разных машин как универсальное время UTC. Для межсервисного журнала храните отдельную календарную отметку с оговоркой о синхронизации часов, а API-отметку — с её единицами и источником.

Полная самостоятельная программа и архив
Поставить два описания в порядок без превращения времени в датуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$before=$event->getPreviousDescription();$after=$event->getNewDescription();
$beforeUs=$before->getLastUpdateTime();$afterUs=$after->getLastUpdateTime();
$delta=$afterUs-$beforeUs;
$cursor=ss123Orders($manager,$namespace);$orders=$cursor->toArray();
$observations['snapshots']=[ss123Snapshot($before,'Ранее в этом клиенте',$event->getTopologyId()),
    ss123Snapshot($after,'Позднее в этом клиенте',$event->getTopologyId())];
$observations['measurements']=['description_update_delta_us'=>$delta,'order_count'=>count($orders),
    'clock_basis'=>'arbitrary monotonic; compare getLastUpdateTime values only'];
$rows=[['Первый снимок',$before->getType(),'монотонные микросекунды'],
    ['Следующий снимок',$after->getType(),'монотонные микросекунды'],
    ['Разность нового и прежнего',$delta>=0?'неотрицательна':'отрицательна','интервал между обновлениями, не возраст заказа'],
    ['Преобразование в календарную дату','не выполняется','это не Unix timestamp']];
$checks=['integer_monotonic_values'=>is_int($beforeUs) && is_int($afterUs),'ordered_updates'=>$delta>=0,
    'same_event_endpoint'=>$before->getHost()===$after->getHost() && $before->getPort()===$after->getPort(),
    'business_read_still_separate'=>array_column($orders,'_id')===[101,103]];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-update-clock', ['Показатель', 'Значение / вывод', 'Как читать'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-update-clock: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-update-clock: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Подпишите в своём отчёте три поля: календарная дата заказа, монотонное обновление описания и RTT. Для каждого укажите отдельную единицу и вопрос, на который оно отвечает.

Сохранить прошлое состояние после успешного чтения новых данных

При расследовании нужно сохранить, что клиент знал раньше. Проверим, что последующие успешные запросы не переписывают прежнее описание внутри уже сохранённого события.

Данные и подготовка

Сохраняем обе стороны ServerChangedEvent. Затем читаем два оплаченных заказа и выполняем обычный ping. После этих операций повторно читаем тот же event и сравниваем все разрешённые поля снимков с сохранёнными значениями.

Как получается результат
  1. Событие обнаружения → сохранить previous и new
  2. Query и ping → текущая работа продолжается
  3. Тот же event → прежние снимки остаются прежними
  4. Readonly properties 2.3+ → другая форма доступа к данным события

Минимальная запись текущего действия

$old = $event->getPreviousDescription();
// После следующих запросов этот old всё ещё описывает прошлое событие.
echo $old->getType(); // Unknown
echo $event->newDescription->getType(); // Standalone, readonly property

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

НаблюдениеРезультатПричина
Прежний снимок после Query и pingUnknownне обновился задним числом
Новый снимок этого событияStandaloneостался снимком события
Readonly properties и gettersсогласованысвойства появились в 2.3.0
Результат отдельного чтения101,103данные не внутри описания

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

ServerDescription — объект значения: он представляет описание на определённый момент. Получение следующего ответа не превращает старое previousDescription в новый объект состояния. Именно поэтому журнал способен показать переход, а не только последнее известное слово.

Проверка сравнивает не только тип Unknown/Standalone, но и сохранённые host, port, время обновления, RTT, topologyId и выбранные поля hello. Фактические измерения не фиксированы между запусками, зато внутри одного события его сохранённое содержимое не должно меняться.

С mongodb 2.3.0 событие также имеет публичные readonly properties: host, port, topologyId, previousDescription, newDescription. Например, $event->newDescription читает объект, а $event->getNewDescription() вызывает метод. Здесь обе формы дают согласованное представление.

Readonly означает, что событие не является редактируемым конфигом: присваивать новый host или менять previousDescription нельзя. Чтобы собрать свой отчёт с другими названиями полей, создавайте отдельный PHP-массив; не пытайтесь править объект драйвера.

Границы применения

Сохранённое событие годится для истории, но не как вечная проверка текущей доступности. Для следующего действия нужен актуальный запрос/выбор сервера с обработкой ошибки. Получение property не делает дополнительного сетевого вызова.

Полная самостоятельная программа и архив
Сохранить прошлое состояние после успешного чтения новых данныхСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$old=$event->getPreviousDescription();$new=$event->getNewDescription();
$oldSaved=ss123Snapshot($old,'Сохранённое прошлое',$event->getTopologyId());
$newSaved=ss123Snapshot($new,'Сохранённое новое',$event->getTopologyId());
$cursor=ss123Orders($manager,$namespace);$orders=$cursor->toArray();
ss123Command($manager,$database,['ping'=>1]);
$oldAgain=ss123Snapshot($event->getPreviousDescription(),'Сохранённое прошлое',$event->getTopologyId());
$newAgain=ss123Snapshot($event->getNewDescription(),'Сохранённое новое',$event->getTopologyId());
$propertiesSame=$event->host===$event->getHost() && $event->port===$event->getPort()
    && (string)$event->topologyId===(string)$event->getTopologyId()
    && $event->previousDescription->getType()===$old->getType()
    && $event->newDescription->getType()===$new->getType();
$observations['snapshots']=[$oldAgain,$newAgain];
$rows=[['Прежний снимок после Query и ping',$event->getPreviousDescription()->getType(),'не обновился задним числом'],
    ['Новый снимок этого события',$event->getNewDescription()->getType(),'остался снимком события'],
    ['Readonly properties и getters',$propertiesSame?'согласованы':'различаются','свойства появились в 2.3.0'],
    ['Результат отдельного чтения',implode(',',array_column($orders,'_id')),'данные не внутри описания']];
$checks=['old_snapshot_retained'=>$oldSaved===$oldAgain && $oldAgain['type']==='Unknown',
    'new_snapshot_retained'=>$newSaved===$newAgain && $newAgain['type']==='Standalone',
    'readonly_property_view'=>$propertiesSame,'new_read_completed'=>array_column($orders,'_id')===[101,103]];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-retained-event', ['Наблюдение', 'Результат', 'Причина'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-retained-event: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-retained-event: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Скопируйте тип и RTT в свой массив с понятными русскими подписями. Почему можно изменить этот массив для отображения, но нельзя присвоить новое значение $event->host?

Собрать полезный отчёт поддержки без URI и персональных полей

Внутренней поддержке нужен короткий ответ: какую страницу получали, какие ID вернулись, какой endpoint наблюдался и какой переход произошёл. Полный dump заказа или URI для этого не требуется.

Данные и подготовка

Повторяем ограниченный запрос paid с двумя документами. К ID и сумме страницы добавляем выбранные сведения из настоящего события и описания сервера курсора. В исходных заказах есть email, который projection не выдаёт.

Как получается результат
  1. Запрос страницы → ID 101, 103 и сумма 20000 копеек
  2. Реальное событие → Unknown→Standalone и topologyId
  3. Описание источника → whitelist полей с единицами
  4. result.html + observations.json → понятный диагностический отчёт

Минимальная запись текущего действия

$diagnostic = ['host'=>$description->getHost(), 'port'=>$description->getPort(),
    'type'=>$description->getType(), 'rtt_ms'=>$description->getRoundTripTime()];
// Выбираем необходимые поля явно: не dump всего URI, hello или заказа.

Фрагмент выделяет изучаемый вызов. Все подготовленные переменные, наблюдатель и безопасная очистка включены в полную программу ниже.

Повторяемый результат

Поле отчётаРезультат
Страница оплаченных заказов101,103
Сумма страницы, коп.20000
Наблюдавшийся переходUnknown -> Standalone
URI и email в диагностическом JSONотсутствуют
Реальные host / port / RTT / время / topologyIdпоказаны ниже, не подменены константами

Под этой таблицей фактический HTML дополнительно показывает реальные host, port, тип, RTT в мс, монотонную отметку в мкс и topologyId. Эти значения не подменены приведённым здесь эталоном.

Разбор действий

Белый список означает, что мы перечисляем нужные поля явно. Так случайно добавленное в исходный документ поле не попадёт в отчёт автоматически. Для hello helper выбирает только числовые/логические сведения о возможностях, а не сериализует весь служебный ответ.

Request label paid-orders-page — наша подпись операции, а topologyId — ключ событий драйвера. Они отвечают на разные вопросы. Первый помогает найти пользовательское действие, второй — историю клиента, через который оно прошло.

Сумма 20000 копеек относится только к двум возвращённым заказам. Это не общий оборот коллекции. Такой явный смысл важен и в диагностике: число строк страницы нельзя выдавать за count всех записей.

Программа проверяет отсутствие email и URI в диагностическом JSON. После формирования результата снимает подписку и удаляет только свою коллекцию; фактические статические файлы остаются для просмотра. Выдача этой информации посетителю сайта не требуется: это пример внутреннего отчёта.

Границы применения

Отчёт показывает один успешный сценарий в локальной среде, а не причину любого production-сбоя. В рабочей системе дополнительно учитывают разрешения на логи, срок хранения, код/класс исключения и связи запросов. Сам снимок не заменяет проверку сети, TLS, прав доступа и индекса запроса.

Полная самостоятельная программа и архив
Собрать полезный отчёт поддержки без URI и персональных полейСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ss123-helper.php';
$settings=ss123Settings();$database=$settings['database'];
$observer=new Ss123Observer;$manager=ss123Manager($settings,$observer);
$secondManager=null;$secondObserver=null;$owned=[];
$observations=['snapshots'=>[],'measurements'=>[]];
try{
$version=ss123Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$event=ss123Discovery($observer,$settings);
$namespace=ss123Create($manager,$database,$owned);
$cursor=ss123Orders($manager,$namespace);$orders=$cursor->toArray();
$description=$cursor->getServer()->getServerDescription();
$observations['snapshots']=[ss123Snapshot($event->getPreviousDescription(),'До первого ответа',$event->getTopologyId()),
    ss123Snapshot($description,'Сервер фактической страницы',$event->getTopologyId())];
$observations['measurements']=['request_label'=>'paid-orders-page','order_ids'=>array_column($orders,'_id'),
    'total_kopecks'=>array_sum(array_column($orders,'total_kopecks')),'returned_count'=>count($orders),
    'transition'=>$event->getPreviousDescription()->getType().' -> '.$event->getNewDescription()->getType()];
$encoded=json_encode($observations,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE);
$safe=strpos($encoded,'private@example.invalid')===false && strpos($encoded,'mongodb://')===false
    && strpos($encoded,'"email"')===false;
$rows=[['Страница оплаченных заказов',implode(',',array_column($orders,'_id'))],
    ['Сумма страницы, коп.',array_sum(array_column($orders,'total_kopecks'))],
    ['Наблюдавшийся переход',$observations['measurements']['transition']],
    ['URI и email в диагностическом JSON',$safe?'отсутствуют':'найдены'],
    ['Реальные host / port / RTT / время / topologyId','показаны ниже, не подменены константами']];
$checks=['bounded_paid_page'=>count($orders)===2 && array_column($orders,'_id')===[101,103],
    'support_total'=>array_sum(array_column($orders,'total_kopecks'))===20000,
    'allowlisted_diagnostics'=>$safe,'reported_transition'=>$observations['measurements']['transition']==='Unknown -> Standalone',
    'event_matches_query'=>$event->getHost()===$description->getHost() && $event->getPort()===$description->getPort()];
    $checks['snapshot_types_and_endpoint']=ss123SnapshotChecks($observations['snapshots'],$settings);
}finally{
    unset($cursor,$secondCursor);
    if($secondManager!==null && $secondObserver!==null){$secondManager->removeSubscriber($secondObserver);}
    $manager->removeSubscriber($observer);
    foreach(array_reverse($owned) as $name){ss123Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(ss123Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ss123Report('mongodb123-snapshots-support-report', ['Поле отчёта', 'Результат'], $rows, $checks, $runtime, $observations);
echo 'mongodb123-snapshots-support-report: saved result.html and result.json
';
Ожидаемый результат
mongodb123-snapshots-support-report: saved result.html and result.json

Реальные Query и ServerChangedEvent в MongoDB 8.0.34. result.html/result.json показывают результат; observations.json сохраняет измерения. Подготовка, helper, README и проверка очистки включены.

Открыть фактическую таблицу и измерения · JSON результата · JSON реальных наблюдений

Попробуйте изменить. Добавьте идентификатор внутреннего обращения поддержки и проверьте, что email по-прежнему отсутствует. Какие сведения полезны сотруднику поддержки, но не должны отображаться покупателю?

Практический вопрос разработчика: PHP: socket timeout calling hello при обращении к MongoDB. Вопрос показывает, что ошибка служебного hello требует контекста подключения и последовательности запросов. Наш оригинальный отчёт разделяет endpoint, наблюдаемое изменение и данные запроса; не выдаёт RTT за диагноз аутентификации или сети. Это постановка проблемы, а не источник контракта PHP API; объяснение методов сверено с первичными источниками. Наш успешный локальный опыт не воспроизводит все причины чужого сбоя.

Методы события и его readonly properties

Readonly property с mongodb 2.3.0ТипМетод для того же поля
hoststringgetHost()
portintgetPort()
topologyIdMongoDB\BSON\ObjectIdgetTopologyId()
previousDescriptionMongoDB\Driver\ServerDescriptiongetPreviousDescription()
newDescriptionMongoDB\Driver\ServerDescriptiongetNewDescription()

Событие сообщает о состоявшемся изменении. Его свойства доступны для чтения, но не служат настройками подключения. Для собственного формата отчёта скопируйте нужные поля в PHP-массив. Методы доступны с 1.13.0; readonly properties появились позже, поэтому версию расширения проверяют отдельно от версии сервера.

Справочник шести методов ServerDescription и пяти методов ServerChangedEvent

Каждая карточка сохраняет сигнатуру, отсутствие параметров, тип результата, ошибки, версии и ограничения. Два применения ведут к самостоятельным программам этой главы; один и тот же архив не размножается ради количества примеров.

МетодДля чего нужен
MongoDB\Driver\ServerDescription::getHelloResponseПрочитать служебный ответ hello, на основе которого было создано описание.
MongoDB\Driver\ServerDescription::getHostПолучить имя хоста из снимка конкретного сервера.
MongoDB\Driver\ServerDescription::getPortПолучить порт endpoint, описанного этим снимком.
MongoDB\Driver\ServerDescription::getLastUpdateTimeУзнать монотонную отметку обновления описания в микросекундах.
MongoDB\Driver\ServerDescription::getRoundTripTimeПрочитать время служебного hello в миллисекундах или отсутствие измерения.
MongoDB\Driver\ServerDescription::getTypeПолучить строковый тип сервера, который знает этот снимок драйвера.
MongoDB\Driver\Monitoring\ServerChangedEvent::getHostПрочитать hostname того сервера, чьё описание изменилось.
MongoDB\Driver\Monitoring\ServerChangedEvent::getPortПрочитать порт сервера из события изменения описания.
MongoDB\Driver\Monitoring\ServerChangedEvent::getPreviousDescriptionПолучить описание сервера до наблюдаемого изменения.
MongoDB\Driver\Monitoring\ServerChangedEvent::getNewDescriptionПолучить описание, которым завершилось конкретное изменение состояния.
MongoDB\Driver\Monitoring\ServerChangedEvent::getTopologyIdПолучить идентификатор топологии драйвера, связанной с этим событием.

Превратить наблюдение в полезный диагностический вопрос

Для диагностического сообщения сохраните, какое действие выполнялось, какие данные требовались и что фактически наблюдал драйвер. Разделяйте адрес, ID клиента, строковый тип, RTT и монотонные отметки. Тогда журнал помогает задать следующий вопрос о сбое, а не маскирует его одним словом Unknown.

Продолжение: реальные события топологии и управление подпиской; выбор сервера; ограниченное чтение страниц; индексы под нужную выборку; согласованность чтения и записи.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

MongoDB\Driver\ServerDescription::getHelloResponseПрочитать служебный ответ hello, на основе которого было создано описание.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\ServerDescription::getHelloResponse(): array

Прочитать служебный ответ hello, на основе которого было создано описание.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Ни параметры, ни возвращаемый тип не зависят от выбранных полей пользовательского Query.

Сигнатура и параметры

final public function MongoDB\Driver\ServerDescription::getHelloResponse(): array
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта описания.

Возврат, ошибки и ограничения

Возвращает PHP-массив сведений о сервере из последнего на момент создания ServerDescription ответа hello, полученного мониторингом. Состав полей зависит от узла. При подключении к LoadBalancer возвращает пустой массив: балансировщики не наблюдаются этим мониторингом. Это отличается от Server::getInfo(), который может содержать ответ начального handshake с сервером за балансировщиком.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Это сохранённая информация, а не команда «спросить сейчас». Проверяйте наличие/тип нужного поля. Размер документа, размер сетевого сообщения и количество операций имеют разные единицы. Не выдавайте весь hello посетителю или в общий лог автоматически.

Проверить размер одного товара перед импортом

В примере с возможностями кодируем реальный заказ в BSON и сравниваем байты с maxBsonObjectSize. Фактическое число байтов и предел сохранены; это не сравнение длины JSON.

$hello = $description->getHelloResponse();
$bytes = strlen((string) MongoDB\BSON\Document::fromPHP($orders[0]));
$fits = isset($hello['maxBsonObjectSize']) && $bytes <= $hello['maxBsonObjectSize'];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Подготовить небольшой диагностический отчёт

В отчёт поддержки передаётся только белый список числовых и логических полей ответа. Ss123Snapshot вызывает getHelloResponse и ss123Hello, оставляя прочие сведения за пределами файла.

$safeHello = ss123Hello($description->getHelloResponse());
// Например: maxBsonObjectSize, maxMessageSizeBytes, maxWriteBatchSize.

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\ServerDescription::getHostПолучить имя хоста из снимка конкретного сервера.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\ServerDescription::getHost(): string

Получить имя хоста из снимка конкретного сервера.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Ни параметры, ни возвращаемый тип не зависят от выбранных полей пользовательского Query.

Сигнатура и параметры

final public function MongoDB\Driver\ServerDescription::getHost(): string
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта описания.

Возврат, ошибки и ограничения

Возвращает строку hostname этого сервера. В локальной лаборатории это 127.0.0.1; в другом окружении строка может быть именем DNS. Метод не возвращает целый URI, имя базы или имя коллекции.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Для endpoint учитывайте также getPort(). Сам hostname не является доказательством TLS-идентичности и не означает, что два клиента разделяют один жизненный цикл.

Указать источник страницы заказов

После ограниченного Query получаем описание от сервера его курсора. В реальной таблице под заказами показан именно hostname описания, не скопированный адрес из строки настроек.

$description = $cursor->getServer()->getServerDescription();
$host = $description->getHost();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Сопоставить два независимых клиента

В снимках каталога и отчёта сравниваются реальные hostname и port. Совпадающий адрес при разных topologyId показывает, почему нельзя считать ID клиента идентификатором сервера.

$a = $event->getNewDescription(); $b = $other->getNewDescription();
$same = $a->getHost() === $b->getHost() && $a->getPort() === $b->getPort();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\ServerDescription::getPortПолучить порт endpoint, описанного этим снимком.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\ServerDescription::getPort(): int

Получить порт endpoint, описанного этим снимком.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Ни параметры, ни возвращаемый тип не зависят от выбранных полей пользовательского Query.

Сигнатура и параметры

final public function MongoDB\Driver\ServerDescription::getPort(): int
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта описания.

Возврат, ошибки и ограничения

Возвращает целое число — порт, на котором слушает этот сервер. В примере настроен 27075, но код использует фактическое значение объекта и допускает согласованно выбранный другой учебный порт.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Порт не версия протокола, не номер соединения и не ID запроса. Две службы на одном host могут работать на разных портах. Не сравнивайте одну только строку host.

Проверить нужный локальный экземпляр

Для страницы оплаченных заказов описание сравнивается с разрешёнными settings host/port. Это помогает обнаружить перепутанный учебный порт, даже если адрес машины одинаков.

$correct = $description->getHost() === $settings['host']
        && $description->getPort() === $settings['port'];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Не потерять порт при передаче обращения в поддержку

В observations.json порт хранится отдельным числовым полем, рядом с host и типом. Так разбор диагностического документа не зависит от ручного склеивания адреса с двоеточием.

$endpoint = ['host'=>$description->getHost(), 'port'=>$description->getPort()];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\ServerDescription::getLastUpdateTimeУзнать монотонную отметку обновления описания в микросекундах.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\ServerDescription::getLastUpdateTime(): int

Узнать монотонную отметку обновления описания в микросекундах.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Ни параметры, ни возвращаемый тип не зависят от выбранных полей пользовательского Query.

Сигнатура и параметры

final public function MongoDB\Driver\ServerDescription::getLastUpdateTime(): int
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта описания.

Возврат, ошибки и ограничения

Возвращает int с временем последнего обновления этого описания в микросекундах. Отсчёт монотонный и начинается от произвольной точки. По контракту значение предназначено только для сравнения с другими результатами getLastUpdateTime().

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Не передавайте его в date(), не считайте Unix timestamp и не вычитайте time()/hrtime(). Это не дата записи заказа и не время пользовательского Query. Сохранённое описание остаётся историческим снимком.

Упорядочить изменения состояния

Реальный event содержит обе отметки. В одном клиенте вычисляем разность нового и прежнего описаний и показываем её отдельно от календарных дат.

$beforeUs = $event->getPreviousDescription()->getLastUpdateTime();
$afterUs = $event->getNewDescription()->getLastUpdateTime();
$deltaUs = $afterUs - $beforeUs;

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Проверить сохранённую историю обращения

После Query и ping повторно читаем прежний event. Его отметки вместе с остальными полями не переписаны поздними запросами; это проверяется сравнением двух реальных снимков.

$saved = $old->getLastUpdateTime();
// Выполнить следующий Query и ping, как в полном архиве.
$unchanged = $saved === $event->getPreviousDescription()->getLastUpdateTime();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\ServerDescription::getRoundTripTimeПрочитать время служебного hello в миллисекундах или отсутствие измерения.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\ServerDescription::getRoundTripTime(): ?int

Прочитать время служебного hello в миллисекундах или отсутствие измерения.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Ни параметры, ни возвращаемый тип не зависят от выбранных полей пользовательского Query.

Сигнатура и параметры

final public function MongoDB\Driver\ServerDescription::getRoundTripTime(): ?int
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта описания.

Возврат, ошибки и ограничения

Возвращаемый тип ?int допускает NULL. При известном измерении это целое число миллисекунд: клиентская длительность команды hello. В начальном Unknown нашего события RTT неизвестен; после обнаружения число уже есть.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Не время выполнения запроса к каталогу и не сумма ожидания всей HTTP-страницы. 0 — допустимое известное число в целых миллисекундах, NULL — отсутствие измерения. Server::getLatency() связан с этим показателем, но не превращает его в профиль произвольного запроса.

Разделить задержку мониторинга и загрузку страницы

В опыте Query измеряется собственным hrtime вокруг executeQuery и получения двух строк. RTT hello записан в отдельное поле с другой единицей. Не вводится порог «успешно только быстрее X».

$helloMs = $description->getRoundTripTime();
$measurement = ['hello_round_trip_ms'=>$helloMs, 'query_and_fetch_us'=>$queryMicros];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Не показывать неизвестный сервер как мгновенный

Сравнение previous/new сохраняет NULL прежнего описания. Преобразование NULL в ноль скрывает смысл первого наблюдения, поэтому таблица явно отображает отсутствие измерения.

$before = $event->getPreviousDescription()->getRoundTripTime();
$after = $event->getNewDescription()->getRoundTripTime();
echo $before === null ? 'RTT пока неизвестен' : $before . ' мс';

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\ServerDescription::getTypeПолучить строковый тип сервера, который знает этот снимок драйвера.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\ServerDescription::getType(): string

Получить строковый тип сервера, который знает этот снимок драйвера.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Ни параметры, ни возвращаемый тип не зависят от выбранных полей пользовательского Query.

Сигнатура и параметры

final public function MongoDB\Driver\ServerDescription::getType(): string
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта описания.

Возврат, ошибки и ограничения

Возвращает string, соответствующую одной из констант ServerDescription::TYPE_*. Например, TYPE_STANDALONE имеет значение Standalone. Это другой контракт, чем Server::getType(), который возвращает целое число.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Unknown не является самостоятельным диагнозом и не означает автоматически остановленный сервер. Строка описывает знание драйвера, а не поле, присланное вашим бизнес-документом. Для сравнения используйте константы именно ServerDescription.

Константа ServerDescriptionСтрока getType()Значение
TYPE_UNKNOWNUnknownДрайвер пока не определил тип или не может считать узел известным. Само слово не устанавливает причину.
TYPE_STANDALONEStandaloneОтдельный mongod вне replica set.
TYPE_MONGOSMongosМаршрутизатор шардированного кластера.
TYPE_POSSIBLE_PRIMARYPossiblePrimaryУзел ещё не проверен, но другой член replica set сообщил о нём как о primary.
TYPE_RS_PRIMARYRSPrimaryPrimary в replica set.
TYPE_RS_SECONDARYRSSecondarySecondary в replica set.
TYPE_RS_ARBITERRSArbiterАрбитр replica set, не сервер пользовательских данных.
TYPE_RS_OTHERRSOtherДругой член replica set: например, hidden, starting или recovering; не выбирается для запросов, но может дать сведения об узлах.
TYPE_RS_GHOSTRSGhostУзел в состоянии запуска/неинициализированного или исключённого члена; не выбирается для запросов и не используется для открытия других членов, но может дальше наблюдаться.
TYPE_LOAD_BALANCERLoadBalancerСоединение через балансировщик, для которого серверный мониторинг описания не выполняется.

Подписать тип источника реальной страницы

Таблица заказов содержит Standalone из описания реального сервера курсора. Суммы и статусы по-прежнему читаются из документов, а не из описания.

$type = $description->getType();
$isStandalone = $type === MongoDB\Driver\ServerDescription::TYPE_STANDALONE;

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Отличить нормальное обнаружение от неизменного неизвестного состояния

Обработанное событие проверяет Unknown→Standalone. Это обычное первоначальное получение информации; оно не используется как искусственная имитация отказа.

$transition = $event->getPreviousDescription()->getType()
            . ' -> ' . $event->getNewDescription()->getType();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\Monitoring\ServerChangedEvent::getHostПрочитать hostname того сервера, чьё описание изменилось.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getHost(): string

Прочитать hostname того сервера, чьё описание изменилось.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Публичные readonly properties события появились в 2.3.0; метод остаётся отдельным способом чтения.

Сигнатура и параметры

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getHost(): string
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта события.

Возврат, ошибки и ограничения

Возвращает строковое имя хоста сервера данного события. Это адрес объекта наблюдения, независимо от того, было его прежнее описание Unknown или уже известным. Readonly property host представляет то же поле с mongodb 2.3.0.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Событие не обязательно относится к последнему пользовательскому запросу. Фильтруйте нужный host вместе с port и связывайте историю через topologyId. Не выполняйте запрос к базе внутри callback ради уточнения адреса.

Выбрать нужное событие обнаружения

Наблюдатель сохраняет события; ss123Discovery снаружи callback выбирает реальный переход разрешённого endpoint. Host проверяется вместе с портом и типами обеих сторон.

$sameHost = $event->getHost() === $settings['host'];
$samePort = $event->getPort() === $settings['port'];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Связать событие с источником результата поддержки

В отчёте сравниваем hostname события с hostname описания сервера конкретного Cursor. Совпадение адреса и порта проверено, а не подразумевается по общему URI.

$matches = $event->getHost() === $description->getHost()
        && $event->getPort() === $description->getPort();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\Monitoring\ServerChangedEvent::getPortПрочитать порт сервера из события изменения описания.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getPort(): int

Прочитать порт сервера из события изменения описания.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Публичные readonly properties события появились в 2.3.0; метод остаётся отдельным способом чтения.

Сигнатура и параметры

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getPort(): int
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта события.

Возврат, ошибки и ограничения

Возвращает int с портом сервера, к которому относится событие. Readonly property port доступна с mongodb 2.3.0. Это поле адреса, не номер события и не идентификатор топологии.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Без host порт не идентифицирует endpoint. При нескольких локальных экземплярах или прокси не считайте все сообщения одного host одной и той же службой.

Не спутать событие другого учебного экземпляра

Выбор перехода Unknown→Standalone включает проверку фактического порта. Если ожидаемый endpoint не наблюдался, программа сообщает ошибку и не рисует правильный переход из другого источника.

$expectedEndpoint = $event->getHost() === $settings['host']
                 && $event->getPort() === $settings['port'];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Объединить адреса двух клиентов, сохранив их разные истории

У каталога и отчёта получены два настоящих события. Их порты совпадают, как и hostname, хотя topologyId различаются. Все поля видны в фактических снимках.

$sameEndpoint = $event->getHost() === $other->getHost()
             && $event->getPort() === $other->getPort();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\Monitoring\ServerChangedEvent::getPreviousDescriptionПолучить описание сервера до наблюдаемого изменения.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getPreviousDescription(): MongoDB\Driver\ServerDescription

Получить описание сервера до наблюдаемого изменения.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Публичные readonly properties события появились в 2.3.0; метод остаётся отдельным способом чтения.

Сигнатура и параметры

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getPreviousDescription(): MongoDB\Driver\ServerDescription
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта события.

Возврат, ошибки и ограничения

Возвращает объект MongoDB\Driver\ServerDescription — прежнюю сторону этого события. Это не копия пользовательского документа и не текущее состояние по запросу. В первоначальном обнаружении нашего клиента прежний тип Unknown, а RTT отсутствует. С mongodb 2.3.0 то же направление доступно через readonly previousDescription.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Не заменяйте отсутствие сведений нулевыми метриками. Прошлый снимок нужен для истории; он не должен использоваться как единственная проверка доступности будущей операции.

Объяснить первую запись Unknown в журнале

В начале драйвер ещё не знает роль узла. Из настоящего previousDescription читаем Unknown и NULL RTT, затем рядом показываем новое Standalone. Это объясняет происхождение записи без объявления ложной аварии.

$previous = $event->getPreviousDescription();
$before = [$previous->getType(), $previous->getRoundTripTime()];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Сохранить исходную точку расследования после новых запросов

Следующие Query и ping не переписывают previousDescription сохранённого события. Программа сравнивает его поля до и после работы и оставляет прошлое состояние доступным для разбора.

$old = $event->getPreviousDescription();
// Последующие запросы не меняют old в новое описание.
$stillUnknown = $old->getType() === MongoDB\Driver\ServerDescription::TYPE_UNKNOWN;

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\Monitoring\ServerChangedEvent::getNewDescriptionПолучить описание, которым завершилось конкретное изменение состояния.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getNewDescription(): MongoDB\Driver\ServerDescription

Получить описание, которым завершилось конкретное изменение состояния.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Публичные readonly properties события появились в 2.3.0; метод остаётся отдельным способом чтения.

Сигнатура и параметры

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getNewDescription(): MongoDB\Driver\ServerDescription
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта события.

Возврат, ошибки и ограничения

Возвращает MongoDB\Driver\ServerDescription — новую сторону данного события. Getter читает сохранённый объект значения и не запускает новую проверку сервера. С mongodb 2.3.0 доступно readonly property newDescription.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

«Новое» здесь означает новое относительно предыдущей стороны того же события, а не самое позднее за всё время приложения. Для следующего запроса состояние может уже измениться; ошибки операции должны обрабатываться независимо.

Увидеть, что драйвер узнал о работающем узле

После обнаружения из нового снимка получены роль Standalone и измеренный RTT. Затем отдельный Query реально читает оплаченные заказы, показывая связь диагностического и прикладного шагов.

$new = $event->getNewDescription();
$after = [$new->getType(), $new->getRoundTripTime()];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Поставить обновление описания в правильный порядок

Для интервала берём отметку новой стороны и вычитаем отметку прежней стороны того же события. В результате получаем разность монотонных микросекунд, не дату заказа.

$laterUs = $event->getNewDescription()->getLastUpdateTime();
$deltaUs = $laterUs - $event->getPreviousDescription()->getLastUpdateTime();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

MongoDB\Driver\Monitoring\ServerChangedEvent::getTopologyIdПолучить идентификатор топологии драйвера, связанной с этим событием.

mongodb 1.13.0 · mongodb

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getTopologyId(): MongoDB\BSON\ObjectId

Получить идентификатор топологии драйвера, связанной с этим событием.

Версии

Доступен с ext-mongodb 1.13.0. В этих программах используется 2.5.5. Публичные readonly properties события появились в 2.3.0; метод остаётся отдельным способом чтения.

Сигнатура и параметры

final public function MongoDB\Driver\Monitoring\ServerChangedEvent::getTopologyId(): MongoDB\BSON\ObjectId
ПараметрСмысл
НетМетод читается без аргументов у существующего объекта события.

Возврат, ошибки и ограничения

Возвращает BSON ObjectId. Для текстового журнала используйте (string), сохраняя смысл технического идентификатора. С mongodb 2.3.0 доступно readonly property topologyId. Этот ID связывает событие с топологией клиента, а не заменяет host/port сервера.

Официальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Правильный вызов не принимает аргументов; сетевые ошибки возникают при получении ответа/события, а не превращаются этим getter в новый успешный ответ.

Не _id заказа, не постоянный ID кластера, не hello.connectionId. Разные непереиспользуемые клиенты могут наблюдать один endpoint с разными topologyId. Не извлекайте из ObjectId бизнес-время заказа.

Разделить журнал каталога и отчёта

Два независимых клиента читают одинаковые документы одного endpoint. Их фактические topologyId различны и показаны рядом: это две истории обнаружения, а не доказательство двух серверов.

$catalogTopology = (string) $event->getTopologyId();
$reportTopology = (string) $other->getTopologyId();

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Приложить историю подключения к обращению поддержки

В диагностическом файле topologyId хранится вместе с request_label paid-orders-page. По request_label находят прикладное действие, по topologyId связывают события клиента; эти поля не смешиваются.

$supportKey = ['request_label'=>'paid-orders-page',
    'topology_id'=>(string) $event->getTopologyId()];

Объекты этого короткого фрагмента подготовлены в полном самостоятельном примере. Там же показаны входные данные, результат и фактические измерения.

Контекст: различие сервера, снимка и события; подписка и события топологии; выбор узла и объект Server.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.