MediaWiki  REL1_24
ExternalStoreDB.php
Go to the documentation of this file.
00001 <?php
00031 class ExternalStoreDB extends ExternalStoreMedium {
00038     public function fetchFromURL( $url ) {
00039         list( $cluster, $id, $itemID ) = $this->parseURL( $url );
00040         $ret = $this->fetchBlob( $cluster, $id, $itemID );
00041 
00042         if ( $itemID !== false && $ret !== false ) {
00043             return $ret->getItem( $itemID );
00044         }
00045 
00046         return $ret;
00047     }
00048 
00058     public function batchFetchFromURLs( array $urls ) {
00059         $batched = $inverseUrlMap = array();
00060         foreach ( $urls as $url ) {
00061             list( $cluster, $id, $itemID ) = $this->parseURL( $url );
00062             $batched[$cluster][$id][] = $itemID;
00063             // false $itemID gets cast to int, but should be ok
00064             // since we do === from the $itemID in $batched
00065             $inverseUrlMap[$cluster][$id][$itemID] = $url;
00066         }
00067         $ret = array();
00068         foreach ( $batched as $cluster => $batchByCluster ) {
00069             $res = $this->batchFetchBlobs( $cluster, $batchByCluster );
00071             foreach ( $res as $id => $blob ) {
00072                 foreach ( $batchByCluster[$id] as $itemID ) {
00073                     $url = $inverseUrlMap[$cluster][$id][$itemID];
00074                     if ( $itemID === false ) {
00075                         $ret[$url] = $blob;
00076                     } else {
00077                         $ret[$url] = $blob->getItem( $itemID );
00078                     }
00079                 }
00080             }
00081         }
00082 
00083         return $ret;
00084     }
00085 
00089     public function store( $cluster, $data ) {
00090         $dbw = $this->getMaster( $cluster );
00091         $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
00092         $dbw->insert( $this->getTable( $dbw ),
00093             array( 'blob_id' => $id, 'blob_text' => $data ),
00094             __METHOD__ );
00095         $id = $dbw->insertId();
00096         if ( !$id ) {
00097             throw new MWException( __METHOD__ . ': no insert ID' );
00098         }
00099 
00100         return "DB://$cluster/$id";
00101     }
00102 
00109     function getLoadBalancer( $cluster ) {
00110         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00111 
00112         return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
00113     }
00114 
00121     function getSlave( $cluster ) {
00122         global $wgDefaultExternalStore;
00123 
00124         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00125         $lb = $this->getLoadBalancer( $cluster );
00126 
00127         if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
00128             wfDebug( "read only external store\n" );
00129             $lb->allowLagged( true );
00130         } else {
00131             wfDebug( "writable external store\n" );
00132         }
00133 
00134         $db = $lb->getConnection( DB_SLAVE, array(), $wiki );
00135         $db->clearFlag( DBO_TRX ); // sanity
00136 
00137         return $db;
00138     }
00139 
00146     function getMaster( $cluster ) {
00147         $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
00148         $lb = $this->getLoadBalancer( $cluster );
00149 
00150         $db = $lb->getConnection( DB_MASTER, array(), $wiki );
00151         $db->clearFlag( DBO_TRX ); // sanity
00152 
00153         return $db;
00154     }
00155 
00162     function getTable( $db ) {
00163         $table = $db->getLBInfo( 'blobs table' );
00164         if ( is_null( $table ) ) {
00165             $table = 'blobs';
00166         }
00167 
00168         return $table;
00169     }
00170 
00181     function fetchBlob( $cluster, $id, $itemID ) {
00188         static $externalBlobCache = array();
00189 
00190         $cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
00191         if ( isset( $externalBlobCache[$cacheID] ) ) {
00192             wfDebugLog( 'ExternalStoreDB-cache',
00193                 "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
00194 
00195             return $externalBlobCache[$cacheID];
00196         }
00197 
00198         wfDebugLog( 'ExternalStoreDB-cache',
00199             "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
00200 
00201         $dbr = $this->getSlave( $cluster );
00202         $ret = $dbr->selectField( $this->getTable( $dbr ),
00203             'blob_text', array( 'blob_id' => $id ), __METHOD__ );
00204         if ( $ret === false ) {
00205             wfDebugLog( 'ExternalStoreDB',
00206                 "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
00207             // Try the master
00208             $dbw = $this->getMaster( $cluster );
00209             $ret = $dbw->selectField( $this->getTable( $dbw ),
00210                 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
00211             if ( $ret === false ) {
00212                 wfDebugLog( 'ExternalStoreDB',
00213                     "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
00214             }
00215         }
00216         if ( $itemID !== false && $ret !== false ) {
00217             // Unserialise object; caller extracts item
00218             $ret = unserialize( $ret );
00219         }
00220 
00221         $externalBlobCache = array( $cacheID => $ret );
00222 
00223         return $ret;
00224     }
00225 
00234     function batchFetchBlobs( $cluster, array $ids ) {
00235         $dbr = $this->getSlave( $cluster );
00236         $res = $dbr->select( $this->getTable( $dbr ),
00237             array( 'blob_id', 'blob_text' ), array( 'blob_id' => array_keys( $ids ) ), __METHOD__ );
00238         $ret = array();
00239         if ( $res !== false ) {
00240             $this->mergeBatchResult( $ret, $ids, $res );
00241         }
00242         if ( $ids ) {
00243             wfDebugLog( __CLASS__, __METHOD__ .
00244                 " master fallback on '$cluster' for: " .
00245                 implode( ',', array_keys( $ids ) ) );
00246             // Try the master
00247             $dbw = $this->getMaster( $cluster );
00248             $res = $dbw->select( $this->getTable( $dbr ),
00249                 array( 'blob_id', 'blob_text' ),
00250                 array( 'blob_id' => array_keys( $ids ) ),
00251                 __METHOD__ );
00252             if ( $res === false ) {
00253                 wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'" );
00254             } else {
00255                 $this->mergeBatchResult( $ret, $ids, $res );
00256             }
00257         }
00258         if ( $ids ) {
00259             wfDebugLog( __CLASS__, __METHOD__ .
00260                 " master on '$cluster' failed locating items: " .
00261                 implode( ',', array_keys( $ids ) ) );
00262         }
00263 
00264         return $ret;
00265     }
00266 
00273     private function mergeBatchResult( array &$ret, array &$ids, $res ) {
00274         foreach ( $res as $row ) {
00275             $id = $row->blob_id;
00276             $itemIDs = $ids[$id];
00277             unset( $ids[$id] ); // to track if everything is found
00278             if ( count( $itemIDs ) === 1 && reset( $itemIDs ) === false ) {
00279                 // single result stored per blob
00280                 $ret[$id] = $row->blob_text;
00281             } else {
00282                 // multi result stored per blob
00283                 $ret[$id] = unserialize( $row->blob_text );
00284             }
00285         }
00286     }
00287 
00292     protected function parseURL( $url ) {
00293         $path = explode( '/', $url );
00294 
00295         return array(
00296             $path[2], // cluster
00297             $path[3], // id
00298             isset( $path[4] ) ? $path[4] : false // itemID
00299         );
00300     }
00301 }