Ejemplo n.º 1
0
 /**
  * Set the master wait position and wait for a "generic" slave to catch up to it
  *
  * This can be used a faster proxy for waitForAll()
  *
  * @param DBMasterPos $pos
  * @param int $timeout Max seconds to wait; default is mWaitTimeout
  * @return bool Success (able to connect and no timeouts reached)
  * @since 1.26
  */
 public function waitForOne($pos, $timeout = null)
 {
     $this->mWaitForPos = $pos;
     $i = $this->mReadIndex;
     if ($i <= 0) {
         // Pick a generic slave if there isn't one yet
         $readLoads = $this->mLoads;
         unset($readLoads[$this->getWriterIndex()]);
         // slaves only
         $readLoads = array_filter($readLoads);
         // with non-zero load
         $i = ArrayUtils::pickRandom($readLoads);
     }
     if ($i > 0) {
         $ok = $this->doWait($i, true, $timeout);
     } else {
         $ok = true;
         // no applicable loads
     }
     return $ok;
 }
Ejemplo n.º 2
0
	protected function doPop() {
		$key = $this->getCacheKey( 'empty' );

		$isEmpty = $this->cache->get( $key );
		if ( $isEmpty === 'true' ) {
			return false;
		}

		$partitionsTry = $this->partitionMap; // (partition => weight)

		while ( count( $partitionsTry ) ) {
			$partition = ArrayUtils::pickRandom( $partitionsTry );
			if ( $partition === false ) {
				break; // all partitions at 0 weight
			}
			$queue = $this->partitionQueues[$partition];
			try {
				$job = $queue->pop();
			} catch ( JobQueueError $e ) {
				$job = false;
				MWExceptionHandler::logException( $e );
			}
			if ( $job ) {
				$job->metadata['QueuePartition'] = $partition;
				return $job;
			} else {
				unset( $partitionsTry[$partition] ); // blacklist partition
			}
		}

		$this->cache->set( $key, 'true', JobQueueDB::CACHE_TTL_LONG );
		return false;
	}
Ejemplo n.º 3
0
 protected function doPop()
 {
     $partitionsTry = $this->partitionRing->getLiveLocationWeights();
     // (partition => weight)
     $failed = 0;
     while (count($partitionsTry)) {
         $partition = ArrayUtils::pickRandom($partitionsTry);
         if ($partition === false) {
             break;
             // all partitions at 0 weight
         }
         /** @var JobQueue $queue */
         $queue = $this->partitionQueues[$partition];
         try {
             $job = $queue->pop();
         } catch (JobQueueError $e) {
             ++$failed;
             $this->logException($e);
             $job = false;
         }
         if ($job) {
             $job->metadata['QueuePartition'] = $partition;
             return $job;
         } else {
             unset($partitionsTry[$partition]);
             // blacklist partition
         }
     }
     $this->throwErrorIfAllPartitionsDown($failed);
     return false;
 }
Ejemplo n.º 4
0
 /**
  * Given an array of non-normalised probabilities, this function will select
  * an element and return the appropriate key
  *
  * @deprecated since 1.21, use ArrayUtils::pickRandom()
  *
  * @param $weights array
  *
  * @return bool|int|string
  */
 function pickRandom($weights)
 {
     return ArrayUtils::pickRandom($weights);
 }
Ejemplo n.º 5
0
	/**
	 * Get the index of the reader connection, which may be a slave
	 * This takes into account load ratios and lag times. It should
	 * always return a consistent index during a given invocation
	 *
	 * Side effect: opens connections to databases
	 * @param $group bool
	 * @param $wiki bool
	 * @throws MWException
	 * @return bool|int|string
	 */
	function getReaderIndex( $group = false, $wiki = false ) {
		global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;

		# @todo FIXME: For now, only go through all this for mysql databases
		if ( $wgDBtype != 'mysql' ) {
			return $this->getWriterIndex();
		}

		if ( count( $this->mServers ) == 1 ) {
			# Skip the load balancing if there's only one server
			return 0;
		} elseif ( $group === false and $this->mReadIndex >= 0 ) {
			# Shortcut if generic reader exists already
			return $this->mReadIndex;
		}

		wfProfileIn( __METHOD__ );

		$totalElapsed = 0;

		# convert from seconds to microseconds
		$timeout = $wgDBClusterTimeout * 1e6;

		# Find the relevant load array
		if ( $group !== false ) {
			if ( isset( $this->mGroupLoads[$group] ) ) {
				$nonErrorLoads = $this->mGroupLoads[$group];
			} else {
				# No loads for this group, return false and the caller can use some other group
				wfDebug( __METHOD__ . ": no loads for group $group\n" );
				wfProfileOut( __METHOD__ );
				return false;
			}
		} else {
			$nonErrorLoads = $this->mLoads;
		}

		if ( !$nonErrorLoads ) {
			wfProfileOut( __METHOD__ );
			throw new MWException( "Empty server array given to LoadBalancer" );
		}

		# Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
		$this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $wiki );

		$laggedSlaveMode = false;

		# First try quickly looking through the available servers for a server that
		# meets our criteria
		do {
			$totalThreadsConnected = 0;
			$overloadedServers = 0;
			$currentLoads = $nonErrorLoads;
			while ( count( $currentLoads ) ) {
				if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
					$i = ArrayUtils::pickRandom( $currentLoads );
				} else {
					$i = $this->getRandomNonLagged( $currentLoads, $wiki );
					if ( $i === false && count( $currentLoads ) != 0 ) {
						# All slaves lagged. Switch to read-only mode
						wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode\n" );
						$wgReadOnly = 'The database has been automatically locked ' .
							'while the slave database servers catch up to the master';
						$i = ArrayUtils::pickRandom( $currentLoads );
						$laggedSlaveMode = true;
					}
				}

				if ( $i === false ) {
					# pickRandom() returned false
					# This is permanent and means the configuration or the load monitor
					# wants us to return false.
					wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false\n" );
					wfProfileOut( __METHOD__ );
					return false;
				}

				wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: {$this->mServers[$i]['host']}...\n" );
				$conn = $this->openConnection( $i, $wiki );

				if ( !$conn ) {
					wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki\n" );
					unset( $nonErrorLoads[$i] );
					unset( $currentLoads[$i] );
					continue;
				}

				// Perform post-connection backoff
				$threshold = isset( $this->mServers[$i]['max threads'] )
					? $this->mServers[$i]['max threads'] : false;
				$backoff = $this->getLoadMonitor()->postConnectionBackoff( $conn, $threshold );

				// Decrement reference counter, we are finished with this connection.
				// It will be incremented for the caller later.
				if ( $wiki !== false ) {
					$this->reuseConnection( $conn );
				}

				if ( $backoff ) {
					# Post-connection overload, don't use this server for now
					$totalThreadsConnected += $backoff;
					$overloadedServers++;
					unset( $currentLoads[$i] );
				} else {
					# Return this server
					break 2;
				}
			}

			# No server found yet
			$i = false;

			# If all servers were down, quit now
			if ( !count( $nonErrorLoads ) ) {
				wfDebugLog( 'connect', "All servers down\n" );
				break;
			}

			# Some servers must have been overloaded
			if ( $overloadedServers == 0 ) {
				throw new MWException( __METHOD__ . ": unexpectedly found no overloaded servers" );
			}
			# Back off for a while
			# Scale the sleep time by the number of connected threads, to produce a
			# roughly constant global poll rate
			$avgThreads = $totalThreadsConnected / $overloadedServers;
			$totalElapsed += $this->sleep( $wgDBAvgStatusPoll * $avgThreads );
		} while ( $totalElapsed < $timeout );

		if ( $totalElapsed >= $timeout ) {
			wfDebugLog( 'connect', "All servers busy\n" );
			$this->mErrorConnection = false;
			$this->mLastError = 'All servers busy';
		}

		if ( $i !== false ) {
			# Slave connection successful
			# Wait for the session master pos for a short time
			if ( $this->mWaitForPos && $i > 0 ) {
				if ( !$this->doWait( $i ) ) {
					$this->mServers[$i]['slave pos'] = $conn->getSlavePos();
				}
			}
			if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $i !== false ) {
				$this->mReadIndex = $i;
			}
		}
		wfProfileOut( __METHOD__ );
		return $i;
	}
Ejemplo n.º 6
0
 /**
  * Get the index of the reader connection, which may be a slave
  * This takes into account load ratios and lag times. It should
  * always return a consistent index during a given invocation
  *
  * Side effect: opens connections to databases
  * @param bool|string $group
  * @param bool|string $wiki
  * @throws MWException
  * @return bool|int|string
  */
 function getReaderIndex($group = false, $wiki = false)
 {
     global $wgReadOnly, $wgDBtype;
     # @todo FIXME: For now, only go through all this for mysql databases
     if ($wgDBtype != 'mysql') {
         return $this->getWriterIndex();
     }
     if (count($this->mServers) == 1) {
         # Skip the load balancing if there's only one server
         return 0;
     } elseif ($group === false && $this->mReadIndex >= 0) {
         # Shortcut if generic reader exists already
         return $this->mReadIndex;
     }
     $section = new ProfileSection(__METHOD__);
     # Find the relevant load array
     if ($group !== false) {
         if (isset($this->mGroupLoads[$group])) {
             $nonErrorLoads = $this->mGroupLoads[$group];
         } else {
             # No loads for this group, return false and the caller can use some other group
             wfDebug(__METHOD__ . ": no loads for group {$group}\n");
             return false;
         }
     } else {
         $nonErrorLoads = $this->mLoads;
     }
     if (!count($nonErrorLoads)) {
         throw new MWException("Empty server array given to LoadBalancer");
     }
     # Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
     $this->getLoadMonitor()->scaleLoads($nonErrorLoads, $group, $wiki);
     $laggedSlaveMode = false;
     # No server found yet
     $i = false;
     # First try quickly looking through the available servers for a server that
     # meets our criteria
     $currentLoads = $nonErrorLoads;
     while (count($currentLoads)) {
         if ($wgReadOnly || $this->mAllowLagged || $laggedSlaveMode) {
             $i = ArrayUtils::pickRandom($currentLoads);
         } else {
             $i = $this->getRandomNonLagged($currentLoads, $wiki);
             if ($i === false && count($currentLoads) != 0) {
                 # All slaves lagged. Switch to read-only mode
                 wfDebugLog('replication', "All slaves lagged. Switch to read-only mode");
                 $wgReadOnly = 'The database has been automatically locked ' . 'while the slave database servers catch up to the master';
                 $i = ArrayUtils::pickRandom($currentLoads);
                 $laggedSlaveMode = true;
             }
         }
         if ($i === false) {
             # pickRandom() returned false
             # This is permanent and means the configuration or the load monitor
             # wants us to return false.
             wfDebugLog('connect', __METHOD__ . ": pickRandom() returned false");
             return false;
         }
         wfDebugLog('connect', __METHOD__ . ": Using reader #{$i}: {$this->mServers[$i]['host']}...");
         $conn = $this->openConnection($i, $wiki);
         if (!$conn) {
             wfDebugLog('connect', __METHOD__ . ": Failed connecting to {$i}/{$wiki}");
             unset($nonErrorLoads[$i]);
             unset($currentLoads[$i]);
             $i = false;
             continue;
         }
         // Decrement reference counter, we are finished with this connection.
         // It will be incremented for the caller later.
         if ($wiki !== false) {
             $this->reuseConnection($conn);
         }
         # Return this server
         break;
     }
     # If all servers were down, quit now
     if (!count($nonErrorLoads)) {
         wfDebugLog('connect', "All servers down");
     }
     if ($i !== false) {
         # Slave connection successful
         # Wait for the session master pos for a short time
         if ($this->mWaitForPos && $i > 0) {
             if (!$this->doWait($i)) {
                 $this->mServers[$i]['slave pos'] = $conn->getSlavePos();
             }
         }
         if ($this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group !== false) {
             $this->mReadIndex = $i;
         }
     }
     return $i;
 }