ApiParse: don't reparse language link titles
[mediawiki.git] / maintenance / pruneUnusedLinkTargetRows.php
blob9b703c6f81e8310b1e651a0480f9fb662b5e2c36
1 <?php
3 // @codeCoverageIgnoreStart
4 require_once __DIR__ . '/Maintenance.php';
5 // @codeCoverageIgnoreEnd
7 /**
8 * Maintenance script that cleans unused rows in linktarget table
10 * @ingroup Maintenance
11 * @since 1.39
13 class PruneUnusedLinkTargetRows extends Maintenance {
14 public function __construct() {
15 parent::__construct();
16 $this->addDescription(
17 'Clean unused rows in linktarget table'
19 $this->addOption(
20 'sleep',
21 'Sleep time (in seconds) between every batch. Default: 0',
22 false,
23 true
25 $this->addOption( 'dry', 'Dry run', false );
26 $this->addOption( 'start', 'Start after this lt_id', false, true );
27 $this->setBatchSize( 50 );
30 public function execute() {
31 $dbw = $this->getPrimaryDB();
32 $dbr = $this->getReplicaDB();
33 $maxLtId = (int)$dbr->newSelectQueryBuilder()
34 ->select( 'MAX(lt_id)' )
35 ->from( 'linktarget' )
36 ->caller( __METHOD__ )
37 ->fetchField();
38 // To avoid race condition of newly added linktarget rows
39 // being deleted before getting a chance to be used, let's ignore the newest ones.
40 $maxLtId = min( [ $maxLtId - 1, (int)( $maxLtId * 0.99 ) ] );
42 $ltCounter = (int)$this->getOption( 'start', 0 );
44 $this->output( "Deleting unused linktarget rows...\n" );
45 $deleted = 0;
46 $linksMigration = $this->getServiceContainer()->getLinksMigration();
47 while ( $ltCounter < $maxLtId ) {
48 $batchMaxLtId = min( $ltCounter + $this->getBatchSize(), $maxLtId ) + 1;
49 $this->output( "Checking lt_id between $ltCounter and $batchMaxLtId...\n" );
50 $queryBuilder = $dbr->newSelectQueryBuilder()
51 ->select( [ 'lt_id' ] )
52 ->from( 'linktarget' );
53 $queryBuilder->where( [
54 $dbr->expr( 'lt_id', '<', $batchMaxLtId ),
55 $dbr->expr( 'lt_id', '>', $ltCounter )
56 ] );
57 foreach ( $linksMigration::$mapping as $table => $tableData ) {
58 $queryBuilder->leftJoin( $table, null, $tableData['target_id'] . '=lt_id' );
59 $queryBuilder->andWhere( [
60 $tableData['target_id'] => null
61 ] );
63 $ltIdsToDelete = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
64 if ( !$ltIdsToDelete ) {
65 $ltCounter += $this->getBatchSize();
66 continue;
69 // Run against primary as well with a faster query plan, just to be safe.
70 // Also having a bit of time in between helps in cases of immediate removal and insertion of use.
71 $queryBuilder = $dbr->newSelectQueryBuilder()
72 ->select( [ 'lt_id' ] )
73 ->from( 'linktarget' )
74 ->where( [
75 'lt_id' => $ltIdsToDelete,
76 ] );
77 foreach ( $linksMigration::$mapping as $table => $tableData ) {
78 $queryBuilder->leftJoin( $table, null, $tableData['target_id'] . '=lt_id' );
79 $queryBuilder->andWhere( [
80 $tableData['target_id'] => null
81 ] );
83 $ltIdsToDelete = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
84 if ( !$ltIdsToDelete ) {
85 $ltCounter += $this->getBatchSize();
86 continue;
89 if ( !$this->getOption( 'dry' ) ) {
90 $dbw->newDeleteQueryBuilder()
91 ->deleteFrom( 'linktarget' )
92 ->where( [ 'lt_id' => $ltIdsToDelete ] )
93 ->caller( __METHOD__ )->execute();
95 $deleted += count( $ltIdsToDelete );
96 $ltCounter += $this->getBatchSize();
98 // Sleep between batches for replication to catch up
99 $this->waitForReplication();
100 $sleep = (int)$this->getOption( 'sleep', 0 );
101 if ( $sleep > 0 ) {
102 sleep( $sleep );
106 $this->output(
107 "Completed clean up linktarget table, "
108 . "$deleted rows deleted.\n"
111 return true;
116 // @codeCoverageIgnoreStart
117 $maintClass = PruneUnusedLinkTargetRows::class;
118 require_once RUN_MAINTENANCE_IF_MAIN;
119 // @codeCoverageIgnoreEnd