1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreAdminHome\Commands;
use Piwik\DataAccess\Model;
use Piwik\Date;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Site;
class ResetInvalidations extends ConsoleCommand
{
protected function configure()
{
$this->setName('core:reset-invalidations');
$this->setDescription('Resets invalidations that are stuck in the "in progress" state, allowing them to be reprocessed.');
$this->addRequiredValueOption(
'processing-host',
null,
'Restrict the reset to invalidations assigned to the specified host. Can be used multiple times to target multiple hosts.',
null,
true
);
$this->addRequiredValueOption(
'idsite',
null,
'Specify the site ID for which invalidations should be reset. Can be used multiple times to target multiple sites.',
null,
true
);
$this->addRequiredValueOption(
'older-than',
null,
'Only reset invalidations that were started before the given time. Accepts any date format parsable by `strtotime` (e.g. "1 day ago", "2024-01-01 12:00:00").'
);
$this->addRequiredValueOption(
'newer-than',
null,
'Only reset invalidations that were started after the given time. Accepts any date format parsable by `strtotime` (e.g. "1 hour ago", "2024-02-01").'
);
$this->addNoValueOption(
'dry-run',
null,
'Perform a dry run without making changes. Shows which invalidations would be reset without actually modifying them.'
);
$this->setHelp(
'This command allows administrators to reset stuck invalidations that are incorrectly marked as "in progress". '
. 'This can happen if an archiving process was interrupted, such as during a server crash or a deployment, leaving '
. 'invalidations in a stuck state. Resetting them ensures they can be reprocessed in the next archiving run.
⚠ Warning: Only reset invalidations when you are certain they are no longer being processed. ⚠
Resetting active invalidations can lead to incomplete archives, data inconsistencies and wasted processing resources.
Usage examples:
- Reset all stuck invalidations for site ID 1 that were started more than an hour ago:
`./console core:reset-invalidations --idsite=1 --older-than="1 hour ago"`
- Reset invalidations assigned to a specific host:
`./console core:reset-invalidations --processing-host=archiver1.example.com`
- Perform a dry run to check which invalidations would be reset:
`./console core:reset-invalidations --idsite=1 --older-than="1 hour ago" --dry-run`
- Reset invalidations for multiple sites and hosts:
`./console core:reset-invalidations --idsite=1 --idsite=10 --processing-host=archiver1 --processing-host=archiver2`
Use this command with caution, especially when resetting invalidations while archiving processes are still in progress.'
);
}
protected function doExecute(): int
{
$dryRun = $this->getInput()->getOption('dry-run');
try {
$startTime = $this->getStartTime();
} catch (\Exception $e) {
throw new \Exception('Invalid value for --newer-than provided.', $e->getCode(), $e);
}
try {
$endTime = $this->getEndTime();
} catch (\Exception $e) {
throw new \Exception('Invalid value for --older-than provided.', $e->getCode(), $e);
}
$model = new Model();
$invalidations = $model->getInvalidationsInProgress(
$this->getIdSites(),
$this->getProcessingHosts(),
$startTime,
$endTime
);
if (count($invalidations) === 0) {
$this->getOutput()->writeln('No invalidations found.');
} elseif ($dryRun) {
$this->getOutput()->writeln(count($invalidations) . ' invalidations found:');
if (count($invalidations) > 50) {
$invalidations = array_slice($invalidations, 0, 50);
$this->getOutput()->writeln('Output limited to oldest 50 records');
}
$header = [
'name', 'idsite', 'report', 'date1', 'date2', 'period',
'ts_invalidated', 'ts_started', 'processing_host', 'process_id',
];
$rows = [];
foreach ($invalidations as $invalidation) {
$rows[] = [
'name' => $invalidation['name'],
'idsite' => $invalidation['idsite'],
'report' => $invalidation['report'],
'date1' => $invalidation['date1'],
'date2' => $invalidation['date2'],
'period' => $invalidation['period'],
'ts_invalidated' => $invalidation['ts_invalidated'],
'ts_started' => $invalidation['ts_started'],
'processing_host' => $invalidation['processing_host'],
'process_id' => $invalidation['process_id'],
];
}
$this->renderTable($header, $rows);
} else {
$rowCount = $model->releaseInProgressInvalidations(array_column($invalidations, 'idinvalidation'));
$this->getOutput()->writeln('Number of invalidations that were reset: ' . $rowCount);
}
return self::SUCCESS;
}
private function getProcessingHosts(): array
{
$processingHosts = $this->getInput()->getOption('processing-host');
return !is_array($processingHosts) ? [$processingHosts] : $processingHosts;
}
private function getIdSites(): array
{
$idSites = $this->getInput()->getOption('idsite');
return Site::getIdSitesFromIdSitesString($idSites);
}
private function getStartTime(): ?Date
{
$startTime = $this->getInput()->getOption('newer-than');
if (empty($startTime)) {
return null;
}
return Date::factory($startTime);
}
private function getEndTime(): ?Date
{
$endTime = $this->getInput()->getOption('older-than');
if (empty($endTime)) {
return null;
}
return Date::factory($endTime);
}
}
|