У меня есть PHP-скрипт, который берет N документов из MongoDB, разветвляет процесс на K дочерних процессов PHP, каждый процесс делает что-то с каждым документом и пытается обновить информацию о документе (см. код ниже).
В моем локальном окружении (Docker) все круто, но на сервере (там нет Docker) иногда во время цикла происходят странные вещи...
Случайным образом все разветвленные процессы не могут подключиться к MongoDB. Команда updateOne возвращает ошибку:
"Failed to send "update" command with database "databasename": Invalid reply from server. in /vendor/mongodb/mongodb/src/Operation/Update.php on line 158".
Это происходит со всеми процессами одновременно только для одной (или нескольких) случайных итераций цикла. Когда каждый процесс переходит на другую итерацию (берёт следующий документ) -- всё снова в порядке. Я делаю 5 попыток записи в MongoDB.
Каждая попытка связана с задержка +1 сек. предыдущей, поэтому первая попытка выполняется немедленно, если возникнет какое-либо исключение -- подождите секунду и повторите попытку, следующая попытка будет через 2 секунды и так далее. Но это не помогает, все эти 5 попыток ломаются.
Это не проблема mongoDB, его журнал пуст, и он даже ничего не получает от PHP, когда происходит ошибка.
Также я признал, что чем больше одновременных процессов я запускаю, тем чаще возникают ошибки.
Также это не проблема ресурсов сервера, при возникновении ошибки половина оперативной памяти (4 гига) свободна, а центральный процессор работает на половину своей мощности.
Может быть, у PHP есть какая-то конфигурация для этого? Какие-то ограничения памяти или что-то в этом роде...
Я использую PHP версии 7.1.30 МонгоДБ v 3.2.16 Пакет PHP mongodb/mongodb v 1.1.2
<?php
$processesAmount = 5;
$documents = $this->mongoResource->getDocuments();
for ($processNumber = 0; $processNumber < $processesAmount; $processNumber++) {
// create child process
$pid = pcntl_fork();
// do not create new processes in child processes
if ($pid === 0) {
break;
}
if ($pid === -1) {
// some errors catching staff here...
}
else if ($pid === 0) {
// create new MongoDB connection
}
else {
// Protect against Zombie children
// main process waits before all child processes end
for ($i = 0; $i < $processesAmount; $i++) {
pcntl_wait($status);
}
return null;
}
// spread documents to each process without duplicates
for ($i = $processNumber; $i < count($documents); $i += $processesAmount) {
$newDocumentData = $this->doSomeStaffWithDocument($documents[$i]);
$this->mongoResource->updateDocument($documents[$i], $newDocumentData);
}
}






Здесь может быть много проблем, одна из которых заключается в том, что все процессы совместно используют 1 соединение с БД, и первый, кто подключается, затем отключается и уничтожает соединение для них всех. Проверьте второй пример в документации здесь: https://www.php.net/manual/en/ref.pcntl.php
Если это не поможет, то, как я читаю ваш код, часть «распространения» происходит в каждом процессе, когда это должно происходить один раз. Разве вы не должны помещать «работу» в дочерний раздел, как показано ниже?
$processesAmount = 5;
$documents = $this->mongoResource->getDocuments();
$numDocs = count($documents);
$i = 0;
$children = [];
for ($processNumber = 0; $processNumber < $processesAmount; $processNumber++) {
// create child
$pid = pcntl_fork();
if ($pid === -1) {
// some errors catching staff here...
} else if ($pid) {
//parent
$children[] = $pid;
} else {
//child
while (!empty($documents) && $i <= $numDocs) {
$i += $processNumber;
$doc = $documents[$i] ?? null;
unset($documents[$i]);
$newDocumentData = $this->doSomeStaffWithDocument($doc);
$this->mongoResource->updateDocument($doc, $newDocumentData);
}
}
}
//protect against zombies and wait for parent
//children is always empty unless in parent
while (!empty($children)) {
foreach ($children as $key => $pid) {
$status = null;
$res = pcntl_waitpid($pid, $status, WNOHANG);
if ($res == -1 || $res > 0) { //if the process has already exited
unset($children[$key]);
}
}
}
}
Спасибо за ваш ответ! Проблема была с подключением. Глупый я! :) У меня есть синглтон, который удерживает соединение, и сервисы, которые сохраняют состояние соединения. Я создавал службу, а затем создавал дочерние процессы и обновлял соединение в синглтоне, но не в службе. ха-ха