/** * 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; }
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; }
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; }
/** * 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); }
/** * 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; }
/** * 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; }