%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/Job/ |
| Current File : /www/varak.net/wiki.varak.net/extensions/CirrusSearch/includes/Job/ElasticaWrite.php |
<?php
namespace CirrusSearch\Job;
use CirrusSearch\Connection;
use CirrusSearch\DataSender;
use CirrusSearch\SearchConfig;
use JobQueueGroup;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use Status;
use Title;
/**
* Performs writes to elasticsearch indexes with requeuing and an
* exponential backoff when the indexes being written to are frozen.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
class ElasticaWrite extends Job {
const MAX_ERROR_RETRY = 4;
/**
* @param Title $title A mediawiki title related to the job
* @param array $params
*/
public function __construct( $title, $params ) {
parent::__construct( $title, $params + [
'createdAt' => time(),
'errorCount' => 0,
'retryCount' => 0,
'cluster' => null,
] );
}
/**
* This job handles all its own retries internally. These jobs are so
* numerous that if they were to start failing they would possibly
* overflow the job queue and bring down redis in production.
*
* Basically we just can't let these jobs hang out in the abandoned
* queue for a week like retries typically do. If these jobs get
* failed they will log to CirrusSearchChangeFailed which is a signal
* that some point in time around the failure needs to be reindexed
* manually. See https://wikitech.wikimedia.org/wiki/Search for more
* details.
*/
public function allowRetries() {
return false;
}
protected function doJob() {
$connections = $this->decideClusters();
$clusterNames = implode( ', ', array_keys( $connections ) );
LoggerFactory::getInstance( 'CirrusSearch' )->debug(
"Running {method} on cluster $clusterNames {diff}s after insertion",
[
'method' => $this->params['method'],
'arguments' => $this->params['arguments'],
'diff' => time() - $this->params['createdAt'],
'clusters' => array_keys( $connections ),
]
);
$retry = [];
$error = [];
foreach ( $connections as $clusterName => $conn ) {
$sender = new DataSender( $conn, $this->searchConfig );
try {
$status = call_user_func_array(
[ $sender, $this->params['method'] ],
$this->params['arguments']
);
} catch ( \Exception $e ) {
LoggerFactory::getInstance( 'CirrusSearch' )->warning(
"Exception thrown while running DataSender::{method} in cluster {cluster}: {errorMessage}",
[
'method' => $this->params['method'],
'cluster' => $clusterName,
'errorMessage' => $e->getMessage(),
'exception' => $e,
]
);
$status = Status::newFatal( 'cirrussearch-send-failure' );
}
if ( $status->hasMessage( 'cirrussearch-indexes-frozen' ) ) {
$retry[] = $conn;
} elseif ( !$status->isOK() ) {
$error[] = $conn;
}
}
foreach ( $retry as $conn ) {
$this->requeueRetry( $conn );
}
foreach ( $error as $conn ) {
$this->requeueError( $conn );
}
if ( !empty( $error ) ) {
$this->setLastError( "ElasticaWrite job reported " . count( $error ) . " failure(s) and " . count( $retry ) . " frozen." );
return false;
}
return true;
}
/**
* Re-queue job that is frozen, or drop the job if it has
* been frozen for too long.
*
* @param Connection $conn
*/
private function requeueRetry( Connection $conn ) {
$diff = time() - $this->params['createdAt'];
$dropTimeout = $conn->getSettings()->getDropDelayedJobsAfter();
if ( $diff > $dropTimeout ) {
LoggerFactory::getInstance( 'CirrusSearchChangeFailed' )->warning(
"Dropping delayed ElasticaWrite job for DataSender::{method} in cluster {cluster} after waiting {diff}s",
[
'method' => $this->params['method'],
'cluster' => $conn->getClusterName(),
'diff' => $diff,
]
);
} else {
$delay = self::backoffDelay( $this->params['retryCount'] );
$job = clone $this;
$job->params['retryCount']++;
$job->params['cluster'] = $conn->getClusterName();
$job->setDelay( $delay );
LoggerFactory::getInstance( 'CirrusSearch' )->debug(
"ElasticaWrite job reported frozen on cluster {cluster}. Requeueing job with delay of {delay}s",
[
'cluster' => $conn->getClusterName(),
'delay' => $delay
]
);
JobQueueGroup::singleton()->push( $job );
}
}
/**
* Re-queue job that failed, or drop the job if it has failed
* too many times
*
* @param Connection $conn
*/
private function requeueError( Connection $conn ) {
if ( $this->params['errorCount'] >= self::MAX_ERROR_RETRY ) {
LoggerFactory::getInstance( 'CirrusSearchChangeFailed' )->warning(
"Dropping failing ElasticaWrite job for DataSender::{method} in cluster {cluster} after repeated failure",
[
'method' => $this->params['method'],
'cluster' => $conn->getClusterName(),
]
);
} else {
$delay = self::backoffDelay( $this->params['errorCount'] );
$job = clone $this;
$job->params['errorCount']++;
$job->params['cluster'] = $conn->getClusterName();
$job->setDelay( $delay );
// Individual failures should have already logged specific errors,
LoggerFactory::getInstance( 'CirrusSearch' )->info(
"ElasticaWrite job reported failure on cluster {cluster}. Requeueing job with delay of {delay}.",
[
'cluster' => $conn->getClusterName(),
'delay' => $delay
]
);
JobQueueGroup::singleton()->push( $job );
}
}
}