00001 <?php
00021 require_once( dirname(__FILE__) . '/Maintenance.php' );
00022
00023 class FixSlaveDesync extends Maintenance {
00024 public function __construct() {
00025 global $wgUseRootUser;
00026 $wgUseRootUser = true;
00027
00028 parent::__construct();
00029 $this->mDescription = "";
00030
00031 }
00032
00033 public function execute() {
00034 global $slaveIndexes, $wgDBservers;
00035 $slaveIndexes = array();
00036 for ( $i = 1; $i < count( $wgDBservers ); $i++ ) {
00037 if ( wfGetLB()->isNonZeroLoad( $i ) ) {
00038 $slaveIndexes[] = $i;
00039 }
00040 }
00041
00042 if ( $this->hasArg() ) {
00043 $this->desyncFixPage( $this->getArg() );
00044 } else {
00045 $dbw = wfGetDB( DB_MASTER );
00046 $maxPage = $dbw->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
00047 $corrupt = $this->findPageLatestCorruption();
00048 foreach ( $corrupt as $id => $dummy ) {
00049 $this->desyncFixPage( $id );
00050 }
00051 }
00052 }
00053
00058 private function findPageLatestCorruption() {
00059 $desync = array();
00060 $n = 0;
00061 $dbw = wfGetDB( DB_MASTER );
00062 $masterIDs = array();
00063 $res = $dbw->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ );
00064 $this->output( "Number of pages: " . $dbw->numRows( $res ) . "\n" );
00065 foreach ( $res as $row ) {
00066 $masterIDs[$row->page_id] = $row->page_latest;
00067 if ( !( ++$n % 10000 ) ) {
00068 $this->output( "$n\r" );
00069 }
00070 }
00071 $this->output( "\n" );
00072 $dbw->freeResult( $res );
00073
00074 global $slaveIndexes;
00075 foreach ( $slaveIndexes as $i ) {
00076 $db = wfGetDB( $i );
00077 $res = $db->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ );
00078 foreach ( $res as $row ) {
00079 if ( isset( $masterIDs[$row->page_id] ) && $masterIDs[$row->page_id] != $row->page_latest ) {
00080 $desync[$row->page_id] = true;
00081 $this->output( $row->page_id . "\t" );
00082 }
00083 }
00084 $db->freeResult( $res );
00085 }
00086 $this->output( "\n" );
00087 return $desync;
00088 }
00089
00094 private function desyncFixPage( $pageID ) {
00095 global $slaveIndexes;
00096
00097 # Check for a corrupted page_latest
00098 $dbw = wfGetDB( DB_MASTER );
00099 $dbw->begin();
00100 $realLatest = $dbw->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ),
00101 __METHOD__, 'FOR UPDATE' );
00102 #list( $masterFile, $masterPos ) = $dbw->getMasterPos();
00103 $found = false;
00104 foreach ( $slaveIndexes as $i ) {
00105 $db = wfGetDB( $i );
00106
00107
00108
00109
00110
00111
00112
00113 $latest = $db->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ), __METHOD__ );
00114 $max = $db->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ );
00115 if ( $latest != $realLatest && $realLatest < $max ) {
00116 $this->output( "page_latest corrupted in page $pageID, server $i\n" );
00117 $found = true;
00118 break;
00119 }
00120 }
00121 if ( !$found ) {
00122 $this->output( "page_id $pageID seems fine\n" );
00123 $dbw->commit();
00124 return;
00125 }
00126
00127 # Find the missing revisions
00128 $res = $dbw->select( 'revision', array( 'rev_id' ), array( 'rev_page' => $pageID ),
00129 __METHOD__, 'FOR UPDATE' );
00130 $masterIDs = array();
00131 foreach ( $res as $row ) {
00132 $masterIDs[] = $row->rev_id;
00133 }
00134 $dbw->freeResult( $res );
00135
00136 $res = $db->select( 'revision', array( 'rev_id' ), array( 'rev_page' => $pageID ), __METHOD__ );
00137 $slaveIDs = array();
00138 foreach ( $res as $row ) {
00139 $slaveIDs[] = $row->rev_id;
00140 }
00141 $db->freeResult( $res );
00142 if ( count( $masterIDs ) < count( $slaveIDs ) ) {
00143 $missingIDs = array_diff( $slaveIDs, $masterIDs );
00144 if ( count( $missingIDs ) ) {
00145 $this->output( "Found " . count( $missingIDs ) . " lost in master, copying from slave... " );
00146 $dbFrom = $db;
00147 $found = true;
00148 $toMaster = true;
00149 } else {
00150 $found = false;
00151 }
00152 } else {
00153 $missingIDs = array_diff( $masterIDs, $slaveIDs );
00154 if ( count( $missingIDs ) ) {
00155 $this->output( "Found " . count( $missingIDs ) . " missing revision(s), copying from master... " );
00156 $dbFrom = $dbw;
00157 $found = true;
00158 $toMaster = false;
00159 } else {
00160 $found = false;
00161 }
00162 }
00163
00164 if ( $found ) {
00165 foreach ( $missingIDs as $rid ) {
00166 $this->output( "$rid " );
00167 # Revision
00168 $row = $dbFrom->selectRow( 'revision', '*', array( 'rev_id' => $rid ), __METHOD__ );
00169 if ( $toMaster ) {
00170 $id = $dbw->selectField( 'revision', 'rev_id', array( 'rev_id' => $rid ),
00171 __METHOD__, 'FOR UPDATE' );
00172 if ( $id ) {
00173 $this->output( "Revision already exists\n" );
00174 $found = false;
00175 break;
00176 } else {
00177 $dbw->insert( 'revision', get_object_vars( $row ), __METHOD__, 'IGNORE' );
00178 }
00179 } else {
00180 foreach ( $slaveIndexes as $i ) {
00181 $db = wfGetDB( $i );
00182 $db->insert( 'revision', get_object_vars( $row ), __METHOD__, 'IGNORE' );
00183 }
00184 }
00185
00186 # Text
00187 $row = $dbFrom->selectRow( 'text', '*', array( 'old_id' => $row->rev_text_id ), __METHOD__ );
00188 if ( $toMaster ) {
00189 $dbw->insert( 'text', get_object_vars( $row ), __METHOD__, 'IGNORE' );
00190 } else {
00191 foreach ( $slaveIndexes as $i ) {
00192 $db = wfGetDB( $i );
00193 $db->insert( 'text', get_object_vars( $row ), __METHOD__, 'IGNORE' );
00194 }
00195 }
00196 }
00197 $this->output( "done\n" );
00198 }
00199
00200 if ( $found ) {
00201 $this->output( "Fixing page_latest... " );
00202 if ( $toMaster ) {
00203 #$dbw->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ );
00204 } else {
00205 foreach ( $slaveIndexes as $i ) {
00206 $db = wfGetDB( $i );
00207 $db->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ );
00208 }
00209 }
00210 $this->output( "done\n" );
00211 }
00212 $dbw->commit();
00213 }
00214 }
00215
00216 $maintClass = "FixSlaveDesync";
00217 require_once( DO_MAINTENANCE );