3 require_once __DIR__
. '/Maintenance.php';
6 * Maintenance script that cleans unused rows in linktarget table
11 class PruneUnusedLinkTargetRows
extends Maintenance
{
12 public function __construct() {
13 parent
::__construct();
14 $this->addDescription(
15 'Clean unused rows in linktarget table'
19 'Sleep time (in seconds) between every batch. Default: 0',
23 $this->addOption( 'dry', 'Dry run', false );
24 $this->addOption( 'start', 'Start after this lt_id', false, true );
25 $this->setBatchSize( 50 );
28 public function execute() {
29 $dbw = $this->getDB( DB_PRIMARY
);
30 $dbr = $this->getDB( DB_REPLICA
);
31 $maxLtId = (int)$dbr->newSelectQueryBuilder()
32 ->select( 'MAX(lt_id)' )
33 ->from( 'linktarget' )
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 ) ] );
39 $ltCounter = (int)$this->getOption( 'start', 0 );
41 $this->output( "Deleting unused linktarget rows...\n" );
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 'lt_id < ' . $dbr->addQuotes( $batchMaxLtId ),
52 'lt_id > ' . $dbr->addQuotes( $ltCounter )
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
60 $ltIdsToDelete = $queryBuilder->caller( __METHOD__
)->fetchFieldValues();
61 if ( !$ltIdsToDelete ) {
62 $ltCounter +
= $this->getBatchSize();
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' )
72 'lt_id' => $ltIdsToDelete,
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
80 $ltIdsToDelete = $queryBuilder->caller( __METHOD__
)->fetchFieldValues();
81 if ( !$ltIdsToDelete ) {
82 $ltCounter +
= $this->getBatchSize();
86 if ( !$this->getOption( 'dry' ) ) {
87 $dbw->delete( 'linktarget', [ 'lt_id' => $ltIdsToDelete ], __METHOD__
);
89 $deleted +
= count( $ltIdsToDelete );
90 $ltCounter +
= $this->getBatchSize();
92 // Sleep between batches for replication to catch up
93 $this->waitForReplication();
94 $sleep = (int)$this->getOption( 'sleep', 0 );
101 "Completed clean up linktarget table, "
102 . "$deleted rows deleted.\n"
110 $maintClass = PruneUnusedLinkTargetRows
::class;
111 require_once RUN_MAINTENANCE_IF_MAIN
;