MediaWiki 1.43.1
purgeParserCache.php
Go to the documentation of this file.
1<?php
21// @codeCoverageIgnoreStart
22require_once __DIR__ . '/Maintenance.php';
23// @codeCoverageIgnoreEnd
24
26use Wikimedia\Timestamp\ConvertibleTimestamp;
27
38class PurgeParserCache extends Maintenance {
39
41 private $lastProgress;
42
44 private $lastTimestamp;
45
47 private $tmpCount = 0;
49 private $usleep = 0;
50
51 public function __construct() {
52 parent::__construct();
53 $this->addDescription( "Remove old objects from the parser cache. " .
54 "This only works when the parser cache is in an SQL database." );
55 $this->addOption( 'expiredate', 'Delete objects expiring before this date.', false, true );
56 $this->addOption(
57 'age',
58 'Delete objects created more than this many seconds ago, assuming ' .
59 '$wgParserCacheExpireTime has remained consistent.',
60 false,
61 true );
62 $this->addOption( 'dry-run', 'Perform a dry run, to verify age and date calculation.' );
63 $this->addOption( 'msleep', 'Milliseconds to sleep between purge chunks of $wgUpdateRowsPerQuery.',
64 false,
65 true );
66 $this->addOption(
67 'tag',
68 'Purge a single server only. This feature is designed for use by large wiki farms where ' .
69 'one has to purge multiple servers concurrently in order to keep up with new writes. ' .
70 'This requires using the SqlBagOStuff "servers" option in $wgObjectCaches.',
71 false,
72 true );
73 }
74
75 public function execute() {
76 $inputDate = $this->getOption( 'expiredate' );
77 $inputAge = $this->getOption( 'age' );
78
79 if ( $inputDate !== null ) {
80 $timestamp = strtotime( $inputDate );
81 } elseif ( $inputAge !== null ) {
82 $expireTime = (int)$this->getConfig()->get( MainConfigNames::ParserCacheExpireTime );
83 $timestamp = time() + $expireTime - intval( $inputAge );
84 } else {
85 $this->fatalError( "Must specify either --expiredate or --age" );
86 }
87 $this->usleep = 1e3 * $this->getOption( 'msleep', 0 );
88 $this->lastTimestamp = microtime( true );
89
90 $humanDate = ConvertibleTimestamp::convert( TS_RFC2822, $timestamp );
91 if ( $this->hasOption( 'dry-run' ) ) {
92 $this->fatalError( "\nDry run mode, would delete objects having an expiry before " . $humanDate . "\n" );
93 }
94
95 $this->output( "Deleting objects expiring before " . $humanDate . "\n" );
96
97 $pc = $this->getServiceContainer()->getParserCache()->getCacheStorage();
98 $success = $pc->deleteObjectsExpiringBefore(
99 $timestamp,
100 [ $this, 'showProgressAndWait' ],
101 INF,
102 // Note that "0" can be a valid server tag, and must not be discarded or changed to null.
103 $this->getOption( 'tag', null )
104 );
105 if ( !$success ) {
106 $this->fatalError( "\nCannot purge this kind of parser cache." );
107 }
108 $this->showProgressAndWait( 100 );
109 $this->output( "\nDone\n" );
110 }
111
112 public function showProgressAndWait( $percent ) {
113 // Parser caches involve mostly-unthrottled writes of large blobs. This is sometimes prone
114 // to replication lag. As such, while our purge queries are simple primary key deletes,
115 // we want to avoid adding significant load to the replication stream, by being
116 // proactively graceful with these sleeps between each batch.
117 // The reason we don't explicitly wait for replication is that that would require the script
118 // to be aware of cross-dc replicas, which we prefer not to, and waiting for replication
119 // and confirmation latency might actually be *too* graceful and take so long that the
120 // purge script would not be able to finish within 24 hours for large wiki farms.
121 // (T150124).
122 usleep( $this->usleep );
123 $this->tmpCount++;
124
125 $percentString = sprintf( "%.1f", $percent );
126 if ( $percentString === $this->lastProgress ) {
127 // Only print a line if we've progressed >= 0.1% since the last printed line.
128 // This does not mean every 0.1% step is printed since we only run this callback
129 // once after a deletion batch. How often and how many lines we print depends on the
130 // batch size (SqlBagOStuff::deleteObjectsExpiringBefore, $wgUpdateRowsPerQuery),
131 // and on how many table rows there are.
132 return;
133 }
134 $now = microtime( true );
135 $sec = sprintf( "%.1f", $now - $this->lastTimestamp );
136
137 // Give a sense of how much time is spent in the delete operations vs the sleep time,
138 // by recording the number of iterations we've completed since the last progress update.
139 $this->output( "... {$percentString}% done (+{$this->tmpCount} iterations in {$sec}s)\n" );
140
141 $this->lastProgress = $percentString;
142 $this->tmpCount = 0;
143 $this->lastTimestamp = $now;
144 }
145}
146
147// @codeCoverageIgnoreStart
148$maintClass = PurgeParserCache::class;
149require_once RUN_MAINTENANCE_IF_MAIN;
150// @codeCoverageIgnoreEnd
A class containing constants representing the names of configuration variables.
Remove expired objects from the parser cache database.
showProgressAndWait( $percent)