MediaWiki master
pruneUnusedLinkTargetRows.php
Go to the documentation of this file.
1<?php
2
3require_once __DIR__ . '/Maintenance.php';
4
12 public function __construct() {
13 parent::__construct();
14 $this->addDescription(
15 'Clean unused rows in linktarget table'
16 );
17 $this->addOption(
18 'sleep',
19 'Sleep time (in seconds) between every batch. Default: 0',
20 false,
21 true
22 );
23 $this->addOption( 'dry', 'Dry run', false );
24 $this->addOption( 'start', 'Start after this lt_id', false, true );
25 $this->setBatchSize( 50 );
26 }
27
28 public function execute() {
29 $dbw = $this->getPrimaryDB();
30 $dbr = $this->getReplicaDB();
31 $maxLtId = (int)$dbr->newSelectQueryBuilder()
32 ->select( 'MAX(lt_id)' )
33 ->from( 'linktarget' )
34 ->fetchField();
35 // To avoid race condition of newly added linktarget rows
36 // being deleted before getting a chance to be used, let's ignore the newest ones.
37 $maxLtId = min( [ $maxLtId - 1, (int)( $maxLtId * 0.99 ) ] );
38
39 $ltCounter = (int)$this->getOption( 'start', 0 );
40
41 $this->output( "Deleting unused linktarget rows...\n" );
42 $deleted = 0;
43 $linksMigration = $this->getServiceContainer()->getLinksMigration();
44 while ( $ltCounter < $maxLtId ) {
45 $batchMaxLtId = min( $ltCounter + $this->getBatchSize(), $maxLtId ) + 1;
46 $this->output( "Checking lt_id between $ltCounter and $batchMaxLtId...\n" );
47 $queryBuilder = $dbr->newSelectQueryBuilder()
48 ->select( [ 'lt_id' ] )
49 ->from( 'linktarget' );
50 $queryBuilder->where( [
51 $dbr->expr( 'lt_id', '<', $batchMaxLtId ),
52 $dbr->expr( 'lt_id', '>', $ltCounter )
53 ] );
54 foreach ( $linksMigration::$mapping as $table => $tableData ) {
55 $queryBuilder->leftJoin( $table, null, $tableData['target_id'] . '=lt_id' );
56 $queryBuilder->andWhere( [
57 $tableData['target_id'] => null
58 ] );
59 }
60 $ltIdsToDelete = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
61 if ( !$ltIdsToDelete ) {
62 $ltCounter += $this->getBatchSize();
63 continue;
64 }
65
66 // Run against primary as well with a faster query plan, just to be safe.
67 // Also having a bit of time in between helps in cases of immediate removal and insertion of use.
68 $queryBuilder = $dbr->newSelectQueryBuilder()
69 ->select( [ 'lt_id' ] )
70 ->from( 'linktarget' )
71 ->where( [
72 'lt_id' => $ltIdsToDelete,
73 ] );
74 foreach ( $linksMigration::$mapping as $table => $tableData ) {
75 $queryBuilder->leftJoin( $table, null, $tableData['target_id'] . '=lt_id' );
76 $queryBuilder->andWhere( [
77 $tableData['target_id'] => null
78 ] );
79 }
80 $ltIdsToDelete = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
81 if ( !$ltIdsToDelete ) {
82 $ltCounter += $this->getBatchSize();
83 continue;
84 }
85
86 if ( !$this->getOption( 'dry' ) ) {
87 $dbw->newDeleteQueryBuilder()
88 ->deleteFrom( 'linktarget' )
89 ->where( [ 'lt_id' => $ltIdsToDelete ] )
90 ->caller( __METHOD__ )->execute();
91 }
92 $deleted += count( $ltIdsToDelete );
93 $ltCounter += $this->getBatchSize();
94
95 // Sleep between batches for replication to catch up
96 $this->waitForReplication();
97 $sleep = (int)$this->getOption( 'sleep', 0 );
98 if ( $sleep > 0 ) {
99 sleep( $sleep );
100 }
101 }
102
103 $this->output(
104 "Completed clean up linktarget table, "
105 . "$deleted rows deleted.\n"
106 );
107
108 return true;
109 }
110
111}
112
113$maintClass = PruneUnusedLinkTargetRows::class;
114require_once RUN_MAINTENANCE_IF_MAIN;
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
output( $out, $channel=null)
Throw some output to the user.
waitForReplication()
Wait for replica DBs to catch up.
getServiceContainer()
Returns the main service container.
getBatchSize()
Returns batch size.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
setBatchSize( $s=0)
Maintenance script that cleans unused rows in linktarget table.