]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/jobqueue/utils/BacklinkJobUtils.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / jobqueue / utils / BacklinkJobUtils.php
1 <?php
2 /**
3  * Job to update links for a given title.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @ingroup JobQueue
22  */
23
24 /**
25  * Class with Backlink related Job helper methods
26  *
27  * When an asset changes, a base job can be inserted to update all assets that depend on it.
28  * The base job splits into per-title "leaf" jobs and a "remnant" job to handle the remaining
29  * range of backlinks. This recurs until the remnant job's backlink range is small enough that
30  * only leaf jobs are created from it.
31  *
32  * For example, if templates A and B are edited (at the same time) the queue will have:
33  *     (A base, B base)
34  * When these jobs run, the queue will have per-title and remnant partition jobs:
35  *     (titleX,titleY,titleZ,...,A remnant,titleM,titleN,titleO,...,B remnant)
36  *
37  * This works best when the queue is FIFO, for several reasons:
38  *   - a) Since the remnant jobs are enqueued after the leaf jobs, the slower leaf jobs have to
39  *        get popped prior to the fast remnant jobs. This avoids flooding the queue with leaf jobs
40  *        for every single backlink of widely used assets (which can be millions).
41  *   - b) Other jobs going in the queue still get a chance to run after a widely used asset changes.
42  *        This is due to the large remnant job pushing to the end of the queue with each division.
43  *
44  * The size of the queues used in this manner depend on the number of assets changes and the
45  * number of workers. Also, with FIFO-per-partition queues, the queue size can be somewhat larger,
46  * depending on the number of queue partitions.
47  *
48  * @ingroup JobQueue
49  * @since 1.23
50  */
51 class BacklinkJobUtils {
52         /**
53          * Break down $job into approximately ($bSize/$cSize) leaf jobs and a single partition
54          * job that covers the remaining backlink range (if needed). Jobs for the first $bSize
55          * titles are collated ($cSize per job) into leaf jobs to do actual work. All the
56          * resulting jobs are of the same class as $job. No partition job is returned if the
57          * range covered by $job was less than $bSize, as the leaf jobs have full coverage.
58          *
59          * The leaf jobs have the 'pages' param set to a (<page ID>:(<namespace>,<DB key>),...)
60          * map so that the run() function knows what pages to act on. The leaf jobs will keep
61          * the same job title as the parent job (e.g. $job).
62          *
63          * The partition jobs have the 'range' parameter set to a map of the format
64          * (start:<integer>, end:<integer>, batchSize:<integer>, subranges:((<start>,<end>),...)),
65          * the 'table' parameter set to that of $job, and the 'recursive' parameter set to true.
66          * This method can be called on the resulting job to repeat the process again.
67          *
68          * The job provided ($job) must have the 'recursive' parameter set to true and the 'table'
69          * parameter must be set to a backlink table. The job title will be used as the title to
70          * find backlinks for. Any 'range' parameter must follow the same format as mentioned above.
71          * This should be managed by recursive calls to this method.
72          *
73          * The first jobs return are always the leaf jobs. This lets the caller use push() to
74          * put them directly into the queue and works well if the queue is FIFO. In such a queue,
75          * the leaf jobs have to get finished first before anything can resolve the next partition
76          * job, which keeps the queue very small.
77          *
78          * $opts includes:
79          *   - params : extra job parameters to include in each job
80          *
81          * @param Job $job
82          * @param int $bSize BacklinkCache partition size; usually $wgUpdateRowsPerJob
83          * @param int $cSize Max titles per leaf job; Usually 1 or a modest value
84          * @param array $opts Optional parameter map
85          * @return Job[] List of Job objects
86          */
87         public static function partitionBacklinkJob( Job $job, $bSize, $cSize, $opts = [] ) {
88                 $class = get_class( $job );
89                 $title = $job->getTitle();
90                 $params = $job->getParams();
91
92                 if ( isset( $params['pages'] ) || empty( $params['recursive'] ) ) {
93                         $ranges = []; // sanity; this is a leaf node
94                         $realBSize = 0;
95                         wfWarn( __METHOD__ . " called on {$job->getType()} leaf job (explosive recursion)." );
96                 } elseif ( isset( $params['range'] ) ) {
97                         // This is a range job to trigger the insertion of partitioned/title jobs...
98                         $ranges = $params['range']['subranges'];
99                         $realBSize = $params['range']['batchSize'];
100                 } else {
101                         // This is a base job to trigger the insertion of partitioned jobs...
102                         $ranges = $title->getBacklinkCache()->partition( $params['table'], $bSize );
103                         $realBSize = $bSize;
104                 }
105
106                 $extraParams = isset( $opts['params'] ) ? $opts['params'] : [];
107
108                 $jobs = [];
109                 // Combine the first range (of size $bSize) backlinks into leaf jobs
110                 if ( isset( $ranges[0] ) ) {
111                         list( $start, $end ) = $ranges[0];
112                         $iter = $title->getBacklinkCache()->getLinks( $params['table'], $start, $end );
113                         $titles = iterator_to_array( $iter );
114                         /** @var Title[] $titleBatch */
115                         foreach ( array_chunk( $titles, $cSize ) as $titleBatch ) {
116                                 $pages = [];
117                                 foreach ( $titleBatch as $tl ) {
118                                         $pages[$tl->getArticleID()] = [ $tl->getNamespace(), $tl->getDBkey() ];
119                                 }
120                                 $jobs[] = new $class(
121                                         $title, // maintain parent job title
122                                         [ 'pages' => $pages ] + $extraParams
123                                 );
124                         }
125                 }
126                 // Take all of the remaining ranges and build a partition job from it
127                 if ( isset( $ranges[1] ) ) {
128                         $jobs[] = new $class(
129                                 $title, // maintain parent job title
130                                 [
131                                         'recursive'     => true,
132                                         'table'         => $params['table'],
133                                         'range'         => [
134                                                 'start'     => $ranges[1][0],
135                                                 'end'       => $ranges[count( $ranges ) - 1][1],
136                                                 'batchSize' => $realBSize,
137                                                 'subranges' => array_slice( $ranges, 1 )
138                                         ],
139                                         // Track how many times the base job divided for debugging
140                                         'division'      => isset( $params['division'] )
141                                                 ? ( $params['division'] + 1 )
142                                                 : 1
143                                 ] + $extraParams
144                         );
145                 }
146
147                 return $jobs;
148         }
149 }