From: Aaron Schulz Date: Wed, 10 Sep 2008 12:07:56 +0000 (+0000) Subject: ExternalStore tweaks: X-Git-Tag: 1.31.0-rc.0~45384 X-Git-Url: http://git.cyclocoop.org/%27.parametre_url%28%20%20%20generer_action_auteur%28%27charger_plugin%27%2C%20%27update_flux%27%29%2C%27update_flux%27%2C%20%27oui%27%29.%27?a=commitdiff_plain;h=7b61c1972ab9afd405115a027e0925bbb2632b6e;p=lhc%2Fweb%2Fwiklou.git ExternalStore tweaks: * On read, spend less time checking on dead slaves * Add randomInsert() to ES. This does the cluster picking for us * Make revision text use randomInsert(). On write, fails-over to other clusters as needed instead of throwing db errors --- diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php index e2b7856673..e73b9a1bd6 100644 --- a/includes/ExternalStore.php +++ b/includes/ExternalStore.php @@ -14,7 +14,7 @@ */ class ExternalStore { /* Fetch data from given URL */ - static function fetchFromURL($url) { + static function fetchFromURL( $url ) { global $wgExternalStores; if( !$wgExternalStores ) @@ -44,7 +44,7 @@ class ExternalStore { $class = 'ExternalStore' . ucfirst( $proto ); /* Any custom modules should be added to $wgAutoLoadClasses for on-demand loading */ - if( !class_exists( $class ) ){ + if( !class_exists( $class ) ) { return false; } @@ -66,4 +66,41 @@ class ExternalStore { return $store->store( $params, $data ); } } + + /** + * Like insert() above, but does more of the work for us. + * This function does not need a url param, it builds it by + * itself. It also fails-over to the next possible clusters. + * + * @param string $data + * Returns the URL of the stored data item, or false on error + */ + public static function randomInsert( $data ) { + global $wgDefaultExternalStore; + $tryStorages = (array)$wgDefaultExternalStore; + // Do not wait and do second retry per master if we + // have other active cluster masters to try instead. + $retry = count($tryStorages) > 1 ? false : true; + while ( count($tryStorages) > 0 ) { + $index = mt_rand(0, count( $tryStorages ) - 1); + $storeUrl = $tryStorages[$index]; + list( $proto, $params ) = explode( '://', $storeUrl, 2 ); + $store = self::getStoreObject( $proto ); + if ( $store === false ) { + throw new MWException( "Invalid external storage protocol - $storeUrl" ); + return false; + } else { + $url = $store->store( $params, $data, $retry ); // Try to save the object + if ( $url ) { + return $url; // Done! + } else { + unset( $tryStorages[$index] ); // Don't try this one again! + sort( $tryStorages ); // Must have consecutive keys + wfDebugLog( 'ExternalStorage', "Unable to store text to external storage $storeUrl" ); + } + } + } + throw new MWException( "Unable to store text to external storage" ); + return false; // All cluster masters dead :( + } } diff --git a/includes/ExternalStoreDB.php b/includes/ExternalStoreDB.php index 549412d18b..84d44a4018 100644 --- a/includes/ExternalStoreDB.php +++ b/includes/ExternalStoreDB.php @@ -34,13 +34,16 @@ class ExternalStoreDB { /** @todo Document.*/ function &getSlave( $cluster ) { $lb =& $this->getLoadBalancer( $cluster ); - return $lb->getConnection( DB_SLAVE ); + // Make only two connection attempts, since we still have the master to try + return $lb->getConnection( DB_SLAVE, array(), false, 2 ); } /** @todo Document.*/ - function &getMaster( $cluster ) { + function &getMaster( $cluster, $retry = true ) { $lb =& $this->getLoadBalancer( $cluster ); - return $lb->getConnection( DB_MASTER ); + // Make only two connection attempts if there are other clusters to try + $attempts = $retry ? false : 2; + return $lb->getConnection( DB_MASTER, array(), false, $attempts, LoadBalancer::GRACEFUL ); } /** @todo Document.*/ @@ -56,7 +59,7 @@ class ExternalStoreDB { * Fetch data from given URL * @param string $url An url of the form DB://cluster/id or DB://cluster/id/itemid for concatened storage. */ - function fetchFromURL($url) { + function fetchFromURL( $url ) { $path = explode( '/', $url ); $cluster = $path[2]; $id = $path[3]; @@ -119,15 +122,17 @@ class ExternalStoreDB { * * @param $cluster String: the cluster name * @param $data String: the data item + * @param $retry bool: allows an extra DB connection retry after 1 second * @return string URL */ - function store( $cluster, $data ) { - $fname = 'ExternalStoreDB::store'; - - $dbw =& $this->getMaster( $cluster ); - + function store( $cluster, $data, $retry = true ) { + if( !$dbw = $this->getMaster( $cluster, $retry ) ) { + return false; // failed, maybe another cluster is up... + } $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' ); - $dbw->insert( $this->getTable( $dbw ), array( 'blob_id' => $id, 'blob_text' => $data ), $fname ); + $dbw->insert( $this->getTable( $dbw ), + array( 'blob_id' => $id, 'blob_text' => $data ), + __METHOD__ ); $id = $dbw->insertId(); if ( $dbw->getFlag( DBO_TRX ) ) { $dbw->immediateCommit(); diff --git a/includes/Revision.php b/includes/Revision.php index eba7be4820..c6c8be85b8 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -719,20 +719,13 @@ class Revision { $flags = Revision::compressRevisionText( $data ); # Write to external storage if required - if ( $wgDefaultExternalStore ) { - if ( is_array( $wgDefaultExternalStore ) ) { - // Distribute storage across multiple clusters - $store = $wgDefaultExternalStore[mt_rand(0, count( $wgDefaultExternalStore ) - 1)]; - } else { - $store = $wgDefaultExternalStore; - } + if( $wgDefaultExternalStore ) { // Store and get the URL - $data = ExternalStore::insert( $store, $data ); - if ( !$data ) { - # This should only happen in the case of a configuration error, where the external store is not valid - throw new MWException( "Unable to store text to external storage $store" ); + $data = ExternalStore::randomInsert( $data ); + if( !$data ) { + throw new MWException( "Unable to store text to external storage" ); } - if ( $flags ) { + if( $flags ) { $flags .= ','; } $flags .= 'external'; diff --git a/includes/db/Database.php b/includes/db/Database.php index 0ec3974f3f..3c37f707a7 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -257,9 +257,11 @@ class Database { * @param failFunction * @param $flags * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php + * @param int $max, max connection attempts + ** After the first retry (second attempt), each retry waits 1 second */ function __construct( $server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) { + $failFunction = false, $flags = 0, $tablePrefix = 'get from global', $max = false ) { global $wgOut, $wgDBprefix, $wgCommandLineMode; # Can't get a reference if it hasn't been set yet @@ -293,7 +295,7 @@ class Database { } if ( $server ) { - $this->open( $server, $user, $password, $dbName ); + $this->open( $server, $user, $password, $dbName, $max ); } } @@ -311,7 +313,7 @@ class Database { * Usually aborts on failure * If the failFunction is set to a non-zero integer, returns success */ - function open( $server, $user, $password, $dbName ) { + function open( $server, $user, $password, $dbName, $max = false ) { global $wguname, $wgAllDBsAreLocalhost; wfProfileIn( __METHOD__ ); @@ -343,11 +345,11 @@ class Database { wfProfileIn("dbconnect-$server"); - # Try to connect up to three times + if( !$max ) { $max = 3; } + # Try to connect up to three times (by default) # The kernel's default SYN retransmission period is far too slow for us, # so we use a short timeout plus a manual retry. $this->mConn = false; - $max = 3; $this->installErrorHandler(); for ( $i = 0; $i < $max && !$this->mConn; $i++ ) { if ( $i > 1 ) { diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php index 42c4044df7..dd0c66b562 100644 --- a/includes/db/LoadBalancer.php +++ b/includes/db/LoadBalancer.php @@ -11,6 +11,8 @@ * @ingroup Database */ class LoadBalancer { + const GRACEFUL = 1; + /* private */ var $mServers, $mConns, $mLoads, $mGroupLoads; /* private */ var $mFailFunction, $mErrorConnection; /* private */ var $mReadIndex, $mLastIndex, $mAllowLagged; @@ -171,7 +173,7 @@ class LoadBalancer { * * Side effect: opens connections to databases */ - function getReaderIndex( $group = false, $wiki = false ) { + function getReaderIndex( $group = false, $wiki = false, $attempts = false ) { global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype; # FIXME: For now, only go through all this for mysql databases @@ -248,7 +250,7 @@ class LoadBalancer { } wfDebugLog( 'connect', __METHOD__.": Using reader #$i: {$this->mServers[$i]['host']}...\n" ); - $conn = $this->openConnection( $i, $wiki ); + $conn = $this->openConnection( $i, $wiki, $attempts ); if ( !$conn ) { wfDebugLog( 'connect', __METHOD__.": Failed connecting to $i/$wiki\n" ); @@ -399,8 +401,13 @@ class LoadBalancer { /** * Get a connection by index * This is the main entry point for this class. + * @param int $i Database + * @param array $groups Query groups + * @param string $wiki Wiki ID + * @param int $attempts Max DB connect attempts + * @param int $flags */ - public function &getConnection( $i, $groups = array(), $wiki = false ) { + public function &getConnection( $i, $groups = array(), $wiki = false, $attempts = false, $flags = 0 ) { global $wgDBtype; wfProfileIn( __METHOD__ ); @@ -432,7 +439,7 @@ class LoadBalancer { # Operation-based index if ( $i == DB_SLAVE ) { - $i = $this->getReaderIndex( false, $wiki ); + $i = $this->getReaderIndex( false, $wiki, $attempts ); } elseif ( $i == DB_LAST ) { # Just use $this->mLastIndex, which should already be set $i = $this->mLastIndex; @@ -444,12 +451,15 @@ class LoadBalancer { } # Couldn't find a working server in getReaderIndex()? if ( $i === false ) { + if( $flags && self::GRACEFUL ) { + return false; + } $this->reportConnectionError( $this->mErrorConnection ); } # Now we have an explicit index into the servers array - $conn = $this->openConnection( $i, $wiki ); - if ( !$conn ) { + $conn = $this->openConnection( $i, $wiki, $attempts ); + if ( !$conn && !($flags & self::GRACEFUL) ) { $this->reportConnectionError( $this->mErrorConnection ); } @@ -509,11 +519,12 @@ class LoadBalancer { * * @param integer $i Server index * @param string $wiki Wiki ID to open + * @param int $attempts Maximum connection attempts * @return Database * * @access private */ - function openConnection( $i, $wiki = false ) { + function openConnection( $i, $wiki = false, $attempts = false ) { wfProfileIn( __METHOD__ ); if ( $wiki !== false ) { $conn = $this->openForeignConnection( $i, $wiki ); @@ -525,7 +536,7 @@ class LoadBalancer { } else { $server = $this->mServers[$i]; $server['serverIndex'] = $i; - $conn = $this->reallyOpenConnection( $server ); + $conn = $this->reallyOpenConnection( $server, false, $attempts ); if ( $conn->isOpen() ) { $this->mConns['local'][$i][0] = $conn; } else { @@ -628,7 +639,7 @@ class LoadBalancer { * Returns a Database object whether or not the connection was successful. * @access private */ - function reallyOpenConnection( $server, $dbNameOverride = false ) { + function reallyOpenConnection( $server, $dbNameOverride = false, $attempts = false ) { if( !is_array( $server ) ) { throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' ); } @@ -643,7 +654,7 @@ class LoadBalancer { # Create object wfDebug( "Connecting to $host $dbname...\n" ); - $db = new $class( $host, $user, $password, $dbname, 1, $flags ); + $db = new $class( $host, $user, $password, $dbname, 1, $flags, 'get from global', $attempts ); if ( $db->isOpen() ) { wfDebug( "Connected\n" ); } else {