/** * @param bool|string $wiki * @return LoadBalancer */ public function getMainLB($wiki = false) { if (!isset($this->mainLB)) { $this->mainLB = $this->newMainLB($wiki); $this->mainLB->parentInfo(array('id' => 'main')); $this->chronProt->initLB($this->mainLB); } return $this->mainLB; }
public function getLagTimes($serverIndexes, $wiki) { if (count($serverIndexes) == 1 && reset($serverIndexes) == 0) { // Single server only, just return zero without caching return array(0 => 0); } $expiry = 5; $requestRate = 10; $cache = $this->cache; $masterName = $this->parent->getServerName(0); $memcKey = wfMemcKey('lag_times', $masterName); $times = $cache->get($memcKey); if (is_array($times)) { # Randomly recache with probability rising over $expiry $elapsed = time() - $times['timestamp']; $chance = max(0, ($expiry - $elapsed) * $requestRate); if (mt_rand(0, $chance) != 0) { unset($times['timestamp']); // hide from caller return $times; } wfIncrStats('lag_cache.miss.expired'); } else { wfIncrStats('lag_cache.miss.absent'); } # Cache key missing or expired if ($cache->lock($memcKey, 0, 10)) { # Let this process alone update the cache value $unlocker = new ScopedCallback(function () use($cache, $memcKey) { $cache->unlock($memcKey); }); } elseif (is_array($times)) { # Could not acquire lock but an old cache exists, so use it unset($times['timestamp']); // hide from caller return $times; } $times = array(); foreach ($serverIndexes as $i) { if ($i == 0) { # Master $times[$i] = 0; } elseif (false !== ($conn = $this->parent->getAnyOpenConnection($i))) { $times[$i] = $conn->getLag(); } elseif (false !== ($conn = $this->parent->openConnection($i, $wiki))) { $times[$i] = $conn->getLag(); // Close the connection to avoid sleeper connections piling up. // Note that the caller will pick one of these DBs and reconnect, // which is slightly inefficient, but this only matters for the lag // time cache miss cache, which is far less common that cache hits. $this->parent->closeConnection($conn); } } # Add a timestamp key so we know when it was cached $times['timestamp'] = time(); $cache->set($memcKey, $times, $expiry + 10); unset($times['timestamp']); // hide from caller return $times; }
public function getLagTimes($serverIndexes, $wiki) { if (count($serverIndexes) == 1 && reset($serverIndexes) == 0) { // Single server only, just return zero without caching return array(0 => 0); } $section = new ProfileSection(__METHOD__); $expiry = 5; $requestRate = 10; global $wgMemc; if (empty($wgMemc)) { $wgMemc = wfGetMainCache(); } $masterName = $this->parent->getServerName(0); $memcKey = wfMemcKey('lag_times', $masterName); $times = $wgMemc->get($memcKey); if (is_array($times)) { # Randomly recache with probability rising over $expiry $elapsed = time() - $times['timestamp']; $chance = max(0, ($expiry - $elapsed) * $requestRate); if (mt_rand(0, $chance) != 0) { unset($times['timestamp']); // hide from caller return $times; } wfIncrStats('lag_cache_miss_expired'); } else { wfIncrStats('lag_cache_miss_absent'); } # Cache key missing or expired if ($wgMemc->add("{$memcKey}:lock", 1, 10)) { # Let this process alone update the cache value $unlocker = new ScopedCallback(function () use($wgMemc, $memcKey) { $wgMemc->delete($memcKey); }); } elseif (is_array($times)) { # Could not acquire lock but an old cache exists, so use it unset($times['timestamp']); // hide from caller return $times; } $times = array(); foreach ($serverIndexes as $i) { if ($i == 0) { # Master $times[$i] = 0; } elseif (false !== ($conn = $this->parent->getAnyOpenConnection($i))) { $times[$i] = $conn->getLag(); } elseif (false !== ($conn = $this->parent->openConnection($i, $wiki))) { $times[$i] = $conn->getLag(); } } # Add a timestamp key so we know when it was cached $times['timestamp'] = time(); $wgMemc->set($memcKey, $times, $expiry + 10); unset($times['timestamp']); // hide from caller return $times; }
/** * Fetch data from given URL * @param string $url An url */ function &getLoadBalancer($cluster) { global $wgExternalServers; if (!array_key_exists($cluster, $this->loadBalancers)) { $this->loadBalancers[$cluster] = LoadBalancer::newFromParams($wgExternalServers[$cluster]); } return $this->loadBalancers[$cluster]; }
/** @todo Document.*/ function &getLoadBalancer($cluster) { global $wgExternalServers, $wgExternalLoadBalancers; if (!array_key_exists($cluster, $wgExternalLoadBalancers)) { $wgExternalLoadBalancers[$cluster] = LoadBalancer::newFromParams($wgExternalServers[$cluster]); } $wgExternalLoadBalancers[$cluster]->allowLagged(true); return $wgExternalLoadBalancers[$cluster]; }
/** * Clears the list of sites stored in the database. * * @see SiteStore::clear() * * @return bool Success */ public function clear() { $dbw = $this->dbLoadBalancer->getConnection(DB_MASTER); $dbw->startAtomic(__METHOD__); $ok = $dbw->delete('sites', '*', __METHOD__); $ok = $dbw->delete('site_identifiers', '*', __METHOD__) && $ok; $dbw->endAtomic(__METHOD__); $this->reset(); return $ok; }
/** * @param array $params An associative array with one member: * - connection: An IDatabase connection object */ public function __construct(array $params) { if (!isset($params['connection'])) { throw new InvalidArgumentException("Missing 'connection' argument."); } $this->db = $params['connection']; parent::__construct(['servers' => [['type' => $this->db->getType(), 'host' => $this->db->getServer(), 'dbname' => $this->db->getDBname(), 'load' => 1]], 'trxProfiler' => isset($params['trxProfiler']) ? $params['trxProfiler'] : null, 'srvCache' => isset($params['srvCache']) ? $params['srvCache'] : null, 'wanCache' => isset($params['wanCache']) ? $params['wanCache'] : null]); if (isset($params['readOnlyReason'])) { $this->db->setLBInfo('readOnlyReason', $params['readOnlyReason']); } }
/** * Notify the ChronologyProtector that the LoadBalancer is about to shut * down. Saves replication positions. * * @param LoadBalancer $lb * @return void */ public function shutdownLB(LoadBalancer $lb) { if (session_id() == '' || $lb->getServerCount() <= 1) { return; // don't start a session; don't bother with non-replicated setups } $masterName = $lb->getServerName(0); if (isset($this->shutdownPositions[$masterName])) { return; // already done } // Only save the position if writes have been done on the connection $db = $lb->getAnyOpenConnection(0); $info = $lb->parentInfo(); if (!$db || !$db->doneWrites()) { wfDebug(__METHOD__ . ": LB {$info['id']}, no writes done\n"); return; } $pos = $db->getMasterPos(); wfDebug(__METHOD__ . ": LB {$info['id']} has master pos {$pos}\n"); $this->shutdownPositions[$masterName] = $pos; }
/** * @param $serverIndexes * @param $wiki * @return array */ function getLagTimes($serverIndexes, $wiki) { if (count($serverIndexes) == 1 && reset($serverIndexes) == 0) { // Single server only, just return zero without caching return array(0 => 0); } wfProfileIn(__METHOD__); $expiry = 5; $requestRate = 10; global $wgMemc; if (empty($wgMemc)) { $wgMemc = wfGetMainCache(); } $masterName = $this->parent->getServerName(0); $memcKey = wfMemcKey('lag_times', $masterName); $times = $wgMemc->get($memcKey); if ($times) { # Randomly recache with probability rising over $expiry $elapsed = time() - $times['timestamp']; $chance = max(0, ($expiry - $elapsed) * $requestRate); if (mt_rand(0, $chance) != 0) { unset($times['timestamp']); wfProfileOut(__METHOD__); return $times; } wfIncrStats('lag_cache_miss_expired'); } else { wfIncrStats('lag_cache_miss_absent'); } # Cache key missing or expired $times = array(); foreach ($serverIndexes as $i) { if ($i == 0) { # Master $times[$i] = 0; } elseif (false !== ($conn = $this->parent->getAnyOpenConnection($i))) { $times[$i] = $conn->getLag(); } elseif (false !== ($conn = $this->parent->openConnection($i, $wiki))) { $times[$i] = $conn->getLag(); } } # Add a timestamp key so we know when it was cached $times['timestamp'] = time(); $wgMemc->set($memcKey, $times, $expiry); # But don't give the timestamp to the caller unset($times['timestamp']); $lagTimes = $times; wfProfileOut(__METHOD__); return $lagTimes; }
/** * @return DatabaseBase */ protected function getDB() { if (!isset($this->db)) { # If server connection info was given, use that if ($this->serverInfo) { $this->lb = new LoadBalancer(array('servers' => array($this->serverInfo))); $this->db = $this->lb->getConnection(DB_MASTER); $this->db->clearFlag(DBO_TRX); } else { # We must keep a separate connection to MySQL in order to avoid deadlocks # However, SQLite has an opposite behaviour. # @todo Investigate behaviour for other databases if (wfGetDB(DB_MASTER)->getType() == 'sqlite') { $this->db = wfGetDB(DB_MASTER); } else { $this->lb = wfGetLBFactory()->newMainLB(); $this->db = $this->lb->getConnection(DB_MASTER); $this->db->clearFlag(DBO_TRX); } } } return $this->db; }
/** * Fetch data from given URL * @param string $url An url */ function fetchFromURL($url) { global $wgExternalServers; # # URLs have the form DB://cluster/id, e.g. # DB://cluster1/3298247 # $path = explode('/', $url); $cluster = $path[2]; $id = $path[3]; $lb = LoadBalancer::NewFromParams($wgExternalServers[$cluster]); $db = $lb->getConnection(DB_SLAVE); $ret = $db->selectField('blobs', 'blob_text', array('blob_id' => $id)); return $ret; }
/** * Get a connection to the specified database * * @param $serverIndex integer * @return DatabaseBase */ protected function getDB( $serverIndex ) { global $wgDebugDBTransactions; if ( !isset( $this->conns[$serverIndex] ) ) { if ( $serverIndex >= $this->numServers ) { throw new MWException( __METHOD__ . ": Invalid server index \"$serverIndex\"" ); } # Don't keep timing out trying to connect for each call if the DB is down if ( isset( $this->connFailureErrors[$serverIndex] ) && ( time() - $this->connFailureTimes[$serverIndex] ) < 60 ) { throw $this->connFailureErrors[$serverIndex]; } # If server connection info was given, use that if ( $this->serverInfos ) { if ( $wgDebugDBTransactions ) { wfDebug( "Using provided serverInfo for SqlBagOStuff\n" ); } $info = $this->serverInfos[$serverIndex]; $type = isset( $info['type'] ) ? $info['type'] : 'mysql'; $host = isset( $info['host'] ) ? $info['host'] : '[unknown]'; wfDebug( __CLASS__ . ": connecting to $host\n" ); $db = DatabaseBase::factory( $type, $info ); $db->clearFlag( DBO_TRX ); } else { /* * We must keep a separate connection to MySQL in order to avoid deadlocks * However, SQLite has an opposite behavior. And PostgreSQL needs to know * if we are in transaction or no */ if ( wfGetDB( DB_MASTER )->getType() == 'mysql' ) { $this->lb = wfGetLBFactory()->newMainLB(); $db = $this->lb->getConnection( DB_MASTER ); $db->clearFlag( DBO_TRX ); // auto-commit mode } else { $db = wfGetDB( DB_MASTER ); } } if ( $wgDebugDBTransactions ) { wfDebug( sprintf( "Connection %s will be used for SqlBagOStuff\n", $db ) ); } $this->conns[$serverIndex] = $db; } return $this->conns[$serverIndex]; }
/** * Get a connection to the specified database * * @param int $serverIndex * @return IDatabase * @throws MWException */ protected function getDB($serverIndex) { if (!isset($this->conns[$serverIndex])) { if ($serverIndex >= $this->numServers) { throw new MWException(__METHOD__ . ": Invalid server index \"{$serverIndex}\""); } # Don't keep timing out trying to connect for each call if the DB is down if (isset($this->connFailureErrors[$serverIndex]) && time() - $this->connFailureTimes[$serverIndex] < 60) { throw $this->connFailureErrors[$serverIndex]; } # If server connection info was given, use that if ($this->serverInfos) { $info = $this->serverInfos[$serverIndex]; $type = isset($info['type']) ? $info['type'] : 'mysql'; $host = isset($info['host']) ? $info['host'] : '[unknown]'; $this->logger->debug(__CLASS__ . ": connecting to {$host}"); // Use a blank trx profiler to ignore expections as this is a cache $info['trxProfiler'] = new TransactionProfiler(); $db = DatabaseBase::factory($type, $info); $db->clearFlag(DBO_TRX); } else { /* * We must keep a separate connection to MySQL in order to avoid deadlocks * However, SQLite has an opposite behavior. And PostgreSQL needs to know * if we are in transaction or no */ $index = $this->slaveOnly ? DB_SLAVE : DB_MASTER; if (wfGetDB($index)->getType() == 'mysql') { $this->lb = wfGetLBFactory()->newMainLB(); $db = $this->lb->getConnection($index); $db->clearFlag(DBO_TRX); // auto-commit mode } else { $db = wfGetDB($index); } } $this->logger->debug(sprintf("Connection %s will be used for SqlBagOStuff", $db)); $this->conns[$serverIndex] = $db; } return $this->conns[$serverIndex]; }
/** * Reset the notification timestamp of this entry * * @param User $user * @param Title $title * @param string $force Whether to force the write query to be executed even if the * page is not watched or the notification timestamp is already NULL. * 'force' in order to force * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed. * * @return bool success */ public function resetNotificationTimestamp(User $user, Title $title, $force = '', $oldid = 0) { // Only loggedin user can have a watchlist if ($this->loadBalancer->getReadOnlyReason() !== false || $user->isAnon()) { return false; } $item = null; if ($force != 'force') { $item = $this->loadWatchedItem($user, $title); if (!$item || $item->getNotificationTimestamp() === null) { return false; } } // If the page is watched by the user (or may be watched), update the timestamp $job = new ActivityUpdateJob($title, ['type' => 'updateWatchlistNotification', 'userid' => $user->getId(), 'notifTime' => $this->getNotificationTimestamp($user, $title, $item, $force, $oldid), 'curTime' => time()]); // Try to run this post-send // Calls DeferredUpdates::addCallableUpdate in normal operation call_user_func($this->deferredUpdatesAddCallableUpdateCallback, function () use($job) { $job->run(); }); $this->uncache($user, $title); return true; }
/** * @return DatabaseBase */ protected function getDB() { global $wgDebugDBTransactions; # Don't keep timing out trying to connect for each call if the DB is down if ($this->connFailureError && time() - $this->connFailureTime < 60) { throw $this->connFailureError; } if (!isset($this->db)) { # If server connection info was given, use that if ($this->serverInfo) { if ($wgDebugDBTransactions) { wfDebug(sprintf("Using provided serverInfo for SqlBagOStuff\n")); } $this->lb = new LoadBalancer(array('servers' => array($this->serverInfo))); $this->db = $this->lb->getConnection(DB_MASTER); $this->db->clearFlag(DBO_TRX); } else { /* * We must keep a separate connection to MySQL in order to avoid deadlocks * However, SQLite has an opposite behaviour. And PostgreSQL needs to know * if we are in transaction or no */ if (wfGetDB(DB_MASTER)->getType() == 'mysql') { $this->lb = wfGetLBFactory()->newMainLB(); $this->db = $this->lb->getConnection(DB_MASTER); $this->db->clearFlag(DBO_TRX); // auto-commit mode } else { $this->db = wfGetDB(DB_MASTER); } } if ($wgDebugDBTransactions) { wfDebug(sprintf("Connection %s will be used for SqlBagOStuff\n", $this->db)); } } return $this->db; }
/** * builds a new Node object * * @param LoadBalancer $lb the parent LB object * @param mixed $info either an ID or an array of values * @returns void */ public function __construct(LoadBalancer $lb, $info = NULL) { $this->_lb = $lb; parent::__construct($lb->Service(), $info); }
/** * Clean up the connection when out of scope */ function __destruct() { if ($this->conn !== null) { $this->lb->reuseConnection($this->conn); } }
private function getLagTimeCacheKey() { # Lag is per-server, not per-DB, so key on the master DB name return wfForeignMemcKey($this->parent->getServerName(0), '', 'lag_times'); }
/** * @param LoadBalancer $lb * @param string $prefix * @return void */ public static function changeLBPrefix($lb, $prefix) { $lb->forEachOpenConnection(array('CloneDatabase', 'changeDBPrefix'), array($prefix)); }
/** * @param array $params */ public function __construct(array $params) { $this->db = $params['connection']; parent::__construct(['servers' => [['type' => $this->db->getType(), 'host' => $this->db->getServer(), 'dbname' => $this->db->getDBname(), 'load' => 1]], 'trxProfiler' => $this->trxProfiler]); if (isset($params['readOnlyReason'])) { $this->db->setLBInfo('readOnlyReason', $params['readOnlyReason']); } }
/** * Notify the ChronologyProtector that the LoadBalancer is about to shut * down. Saves replication positions. * * @param LoadBalancer $lb */ function shutdownLB($lb) { if (session_id() != '' && $lb->getServerCount() > 1) { $masterName = $lb->getServerName(0); if (!isset($this->shutdownPos[$masterName])) { $pos = $lb->getMasterPos(); $info = $lb->parentInfo(); wfDebug(__METHOD__ . ": LB " . $info['id'] . " has master pos {$pos}\n"); $this->shutdownPos[$masterName] = $pos; } } }
/** * @return IDatabase * @throws MWException */ private function getConnection() { return $this->loadBalancer->getConnectionRef(DB_REPLICA, ['watchlist']); }
session_name($wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session'); } if (!$wgCommandLineMode && (isset($_COOKIE[session_name()]) || isset($_COOKIE[$wgCookiePrefix . 'Token']))) { wfIncrStats('request_with_session'); User::SetupSession(); $wgSessionStarted = true; } else { wfIncrStats('request_without_session'); $wgSessionStarted = false; } wfProfileOut($fname . '-SetupSession'); wfProfileIn($fname . '-database'); if (!$wgDBservers) { $wgDBservers = array(array('host' => $wgDBserver, 'user' => $wgDBuser, 'password' => $wgDBpassword, 'dbname' => $wgDBname, 'type' => $wgDBtype, 'load' => 1, 'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT)); } $wgLoadBalancer = LoadBalancer::newFromParams($wgDBservers, false, $wgMasterWaitTimeout); $wgLoadBalancer->loadMasterPos(); wfProfileOut($fname . '-database'); wfProfileIn($fname . '-language1'); require_once "{$IP}/languages/Language.php"; function setupLangObj($langclass) { global $IP; if (!class_exists($langclass)) { # Default to English/UTF-8 $baseclass = 'LanguageUtf8'; require_once "{$IP}/languages/{$baseclass}.php"; $lc = strtolower(substr($langclass, 8)); $snip = "\n\t\t\tclass {$langclass} extends {$baseclass} {\n\t\t\t\tfunction getVariants() {\n\t\t\t\t\treturn array(\"{$lc}\");\n\t\t\t\t}\n\n\t\t\t}"; eval($snip); }
private function getLagTimeCacheKey() { # Lag is per-server, not per-DB, so key on the master DB name return wfGlobalCacheKey('lag-times', $this->parent->getServerName(0)); }
private function getLagTimeCacheKey() { $writerIndex = $this->parent->getWriterIndex(); // Lag is per-server, not per-DB, so key on the master DB name return $this->srvCache->makeGlobalKey('lag-times', $this->parent->getServerName($writerIndex)); }
/** * Execute checks for a single database server * @param string $databaseName Database name to use for connection * @param LoadBalancer $loadBalancer Load Balancer instance for the given cluster * @param int $index Server index to test * @return bool Is server healthy? * @throws MWException */ private function testHost($databaseName, LoadBalancer $loadBalancer, $index) { $serverInfo = $loadBalancer->getServerInfo($index); $master = $index == 0; // connection check try { $db = wfGetDB($index, array(), $databaseName); } catch (DBError $e) { $this->addError("could not connect to server: " . $e->getMessage()); return false; } // lag check if (!$master && isset($serverInfo['max lag'])) { try { $maxLag = $serverInfo['max lag']; $lag = $db->getLag(); if ($lag > $maxLag) { $this->addError("lag (%d) is greater than configured \"max lag\" (%d)", $lag, $maxLag); $db->close(); return false; } } catch (DBError $e) { $this->addError("could not fetch lag time"); $db->close(); return false; } } // read_only check on master try { $res = $db->query("SHOW VARIABLES LIKE 'read_only';"); $row = $res->fetchRow(); $res->free(); $readWrite = $row['Value'] != 'ON'; if ($master && !$readWrite) { $this->addMessage("read_only is set on master host"); } else { if (!$master && $readWrite) { $this->addMessage("read_only is unset on slave host"); } } } catch (DBError $e) { $this->addError("could not check read_only flag"); $db->close(); return false; } $db->close(); return true; }
/** * @param $params array */ function __construct($params) { $this->db = $params['connection']; parent::__construct(array('servers' => array(array('type' => $this->db->getType(), 'host' => $this->db->getServer(), 'dbname' => $this->db->getDBname(), 'load' => 1)))); }
/** * Notify the ChronologyProtector that the LoadBalancer is about to shut * down. Saves replication positions. * * @param LoadBalancer $lb * @return void */ public function shutdownLB(LoadBalancer $lb) { if (!$this->enabled || $lb->getServerCount() <= 1) { return; // non-replicated setup or disabled } $info = $lb->parentInfo(); $masterName = $lb->getServerName($lb->getWriterIndex()); // Only save the position if writes have been done on the connection $db = $lb->getAnyOpenConnection($lb->getWriterIndex()); if (!$db || !$db->doneWrites()) { wfDebugLog('replication', __METHOD__ . ": LB {$info['id']}, no writes done\n"); return; // nothing to do } $pos = $db->getMasterPos(); wfDebugLog('replication', __METHOD__ . ": LB {$info['id']} has master pos {$pos}\n"); $this->shutdownPositions[$masterName] = $pos; }
/** * @param LoadBalancer $lb * @param string $prefix * @return void */ public static function changeLBPrefix($lb, $prefix) { $lb->forEachOpenConnection(['CloneDatabase', 'changeDBPrefix'], [$prefix]); }
function newFromParams($servers, $failFunction = false, $waitTimeout = 10) { $lb = new LoadBalancer(); $lb->initialise($servers, $failFunction, $waitTimeout); return $lb; }