initEditCount.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <?php
  2. /**
  3. * Init the user_editcount database field based on the number of rows in the
  4. * revision table.
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. * http://www.gnu.org/copyleft/gpl.html
  20. *
  21. * @file
  22. * @ingroup Maintenance
  23. */
  24. require_once __DIR__ . '/Maintenance.php';
  25. use MediaWiki\MediaWikiServices;
  26. class InitEditCount extends Maintenance {
  27. public function __construct() {
  28. parent::__construct();
  29. $this->addOption( 'quick', 'Force the update to be done in a single query' );
  30. $this->addOption( 'background', 'Force replication-friendly mode; may be inefficient but
  31. avoids locking tables or lagging replica DBs with large updates;
  32. calculates counts on a replica DB if possible.
  33. Background mode will be automatically used if multiple servers are listed
  34. in the load balancer, usually indicating a replication environment.' );
  35. $this->addDescription( 'Batch-recalculate user_editcount fields from the revision table' );
  36. }
  37. public function execute() {
  38. global $wgActorTableSchemaMigrationStage;
  39. $dbw = $this->getDB( DB_MASTER );
  40. // Autodetect mode...
  41. if ( $this->hasOption( 'background' ) ) {
  42. $backgroundMode = true;
  43. } elseif ( $this->hasOption( 'quick' ) ) {
  44. $backgroundMode = false;
  45. } else {
  46. $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
  47. $backgroundMode = $lb->getServerCount() > 1;
  48. }
  49. $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
  50. $needSpecialQuery = ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
  51. $wgActorTableSchemaMigrationStage !== MIGRATION_NEW );
  52. if ( $needSpecialQuery ) {
  53. foreach ( $actorQuery['joins'] as &$j ) {
  54. $j[0] = 'JOIN'; // replace LEFT JOIN
  55. }
  56. unset( $j );
  57. }
  58. if ( $backgroundMode ) {
  59. $this->output( "Using replication-friendly background mode...\n" );
  60. $dbr = $this->getDB( DB_REPLICA );
  61. $chunkSize = 100;
  62. $lastUser = $dbr->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
  63. $start = microtime( true );
  64. $migrated = 0;
  65. for ( $min = 0; $min <= $lastUser; $min += $chunkSize ) {
  66. $max = $min + $chunkSize;
  67. if ( $needSpecialQuery ) {
  68. // Use separate subqueries to collect counts with the old
  69. // and new schemas, to avoid having to do whole-table scans.
  70. $result = $dbr->select(
  71. [
  72. 'user',
  73. 'rev1' => '('
  74. . $dbr->selectSQLText(
  75. [ 'revision', 'revision_actor_temp' ],
  76. [ 'rev_user', 'ct' => 'COUNT(*)' ],
  77. [
  78. "rev_user > $min AND rev_user <= $max",
  79. 'revactor_rev' => null,
  80. ],
  81. __METHOD__,
  82. [ 'GROUP BY' => 'rev_user' ],
  83. [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
  84. ) . ')',
  85. 'rev2' => '('
  86. . $dbr->selectSQLText(
  87. [ 'revision' ] + $actorQuery['tables'],
  88. [ 'actor_user', 'ct' => 'COUNT(*)' ],
  89. "actor_user > $min AND actor_user <= $max",
  90. __METHOD__,
  91. [ 'GROUP BY' => 'actor_user' ],
  92. $actorQuery['joins']
  93. ) . ')',
  94. ],
  95. [ 'user_id', 'user_editcount' => 'COALESCE(rev1.ct,0) + COALESCE(rev2.ct,0)' ],
  96. "user_id > $min AND user_id <= $max",
  97. __METHOD__,
  98. [],
  99. [
  100. 'rev1' => [ 'LEFT JOIN', 'user_id = rev_user' ],
  101. 'rev2' => [ 'LEFT JOIN', 'user_id = actor_user' ],
  102. ]
  103. );
  104. } else {
  105. $revUser = $actorQuery['fields']['rev_user'];
  106. $result = $dbr->select(
  107. [ 'user', 'rev' => [ 'revision' ] + $actorQuery['tables'] ],
  108. [ 'user_id', 'user_editcount' => "COUNT($revUser)" ],
  109. "user_id > $min AND user_id <= $max",
  110. __METHOD__,
  111. [ 'GROUP BY' => 'user_id' ],
  112. [ 'rev' => [ 'LEFT JOIN', "user_id = $revUser" ] ] + $actorQuery['joins']
  113. );
  114. }
  115. foreach ( $result as $row ) {
  116. $dbw->update( 'user',
  117. [ 'user_editcount' => $row->user_editcount ],
  118. [ 'user_id' => $row->user_id ],
  119. __METHOD__ );
  120. ++$migrated;
  121. }
  122. $delta = microtime( true ) - $start;
  123. $rate = ( $delta == 0.0 ) ? 0.0 : $migrated / $delta;
  124. $this->output( sprintf( "%s %d (%0.1f%%) done in %0.1f secs (%0.3f accounts/sec).\n",
  125. wfWikiID(),
  126. $migrated,
  127. min( $max, $lastUser ) / $lastUser * 100.0,
  128. $delta,
  129. $rate ) );
  130. wfWaitForSlaves();
  131. }
  132. } else {
  133. $this->output( "Using single-query mode...\n" );
  134. $user = $dbw->tableName( 'user' );
  135. if ( $needSpecialQuery ) {
  136. $subquery1 = $dbw->selectSQLText(
  137. [ 'revision', 'revision_actor_temp' ],
  138. [ 'COUNT(*)' ],
  139. [
  140. 'user_id = rev_user',
  141. 'revactor_rev' => null,
  142. ],
  143. __METHOD__,
  144. [],
  145. [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
  146. );
  147. $subquery2 = $dbw->selectSQLText(
  148. [ 'revision' ] + $actorQuery['tables'],
  149. [ 'COUNT(*)' ],
  150. 'user_id = actor_user',
  151. __METHOD__,
  152. [],
  153. $actorQuery['joins']
  154. );
  155. $dbw->query(
  156. "UPDATE $user SET user_editcount=($subquery1) + ($subquery2)",
  157. __METHOD__
  158. );
  159. } else {
  160. $subquery = $dbw->selectSQLText(
  161. [ 'revision' ] + $actorQuery['tables'],
  162. [ 'COUNT(*)' ],
  163. [ 'user_id = ' . $actorQuery['fields']['rev_user'] ],
  164. __METHOD__,
  165. [],
  166. $actorQuery['joins']
  167. );
  168. $dbw->query( "UPDATE $user SET user_editcount=($subquery)", __METHOD__ );
  169. }
  170. }
  171. $this->output( "Done!\n" );
  172. }
  173. }
  174. $maintClass = InitEditCount::class;
  175. require_once RUN_MAINTENANCE_IF_MAIN;