fix(caldav): automatically delete outdated scheduling objects

Signed-off-by: Anna Larch <anna@nextcloud.com>
This commit is contained in:
Anna Larch 2024-05-08 20:38:51 +02:00
parent bf9b80fdab
commit 02b984debd
14 changed files with 183 additions and 1 deletions

View file

@ -16,6 +16,7 @@ return array(
'OCA\\DAV\\BackgroundJob\\CalendarRetentionJob' => $baseDir . '/../lib/BackgroundJob/CalendarRetentionJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
'OCA\\DAV\\BackgroundJob\\DeleteOutdatedSchedulingObjects' => $baseDir . '/../lib/BackgroundJob/DeleteOutdatedSchedulingObjects.php',
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => $baseDir . '/../lib/BackgroundJob/EventReminderJob.php',
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => $baseDir . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php',
@ -270,6 +271,7 @@ return array(
'OCA\\DAV\\Migration\\BuildSocialSearchIndexBackgroundJob' => $baseDir . '/../lib/Migration/BuildSocialSearchIndexBackgroundJob.php',
'OCA\\DAV\\Migration\\CalDAVRemoveEmptyValue' => $baseDir . '/../lib/Migration/CalDAVRemoveEmptyValue.php',
'OCA\\DAV\\Migration\\ChunkCleanup' => $baseDir . '/../lib/Migration/ChunkCleanup.php',
'OCA\\DAV\\Migration\\DeleteSchedulingObjects' => $baseDir . '/../lib/Migration/DeleteSchedulingObjects.php',
'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => $baseDir . '/../lib/Migration/FixBirthdayCalendarComponent.php',
'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => $baseDir . '/../lib/Migration/RefreshWebcalJobRegistrar.php',
'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => $baseDir . '/../lib/Migration/RegenerateBirthdayCalendars.php',

View file

@ -31,6 +31,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\BackgroundJob\\CalendarRetentionJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CalendarRetentionJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
'OCA\\DAV\\BackgroundJob\\DeleteOutdatedSchedulingObjects' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOutdatedSchedulingObjects.php',
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/EventReminderJob.php',
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php',
@ -285,6 +286,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Migration\\BuildSocialSearchIndexBackgroundJob' => __DIR__ . '/..' . '/../lib/Migration/BuildSocialSearchIndexBackgroundJob.php',
'OCA\\DAV\\Migration\\CalDAVRemoveEmptyValue' => __DIR__ . '/..' . '/../lib/Migration/CalDAVRemoveEmptyValue.php',
'OCA\\DAV\\Migration\\ChunkCleanup' => __DIR__ . '/..' . '/../lib/Migration/ChunkCleanup.php',
'OCA\\DAV\\Migration\\DeleteSchedulingObjects' => __DIR__ . '/..' . '/../lib/Migration/DeleteSchedulingObjects.php',
'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => __DIR__ . '/..' . '/../lib/Migration/FixBirthdayCalendarComponent.php',
'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => __DIR__ . '/..' . '/../lib/Migration/RefreshWebcalJobRegistrar.php',
'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/Migration/RegenerateBirthdayCalendars.php',

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\BackgroundJob;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use Psr\Log\LoggerInterface;
class DeleteOutdatedSchedulingObjects extends TimedJob {
public function __construct(
private CalDavBackend $calDavBackend,
private LoggerInterface $logger,
ITimeFactory $timeFactory,
) {
parent::__construct($timeFactory);
$this->setInterval(23 * 60 * 60);
$this->setTimeSensitivity(self::TIME_INSENSITIVE);
}
/**
* @param array $argument
*/
protected function run($argument): void {
$time = $this->time->getTime() - (60 * 60);
$this->calDavBackend->deleteOutdatedSchedulingObjects($time, 50000);
$this->logger->info("Removed outdated scheduling objects");
}
}

View file

@ -2722,6 +2722,44 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->executeStatement();
}
/**
* Deletes all scheduling objects last modified before $modifiedBefore from the inbox collection.
*
* @param int $modifiedBefore
* @param int $limit
* @return void
*/
public function deleteOutdatedSchedulingObjects(int $modifiedBefore, int $limit): void {
$query = $this->db->getQueryBuilder();
$query->select('id')
->from('schedulingobjects')
->where($query->expr()->lt('lastmodified', $query->createNamedParameter($modifiedBefore)))
->setMaxResults($limit);
$result = $query->executeQuery();
$count = $result->rowCount();
if($count === 0) {
return;
}
$ids = array_map(static function (array $id) {
return (int)$id[0];
}, $result->fetchAll(\PDO::FETCH_NUM));
$result->closeCursor();
$numDeleted = 0;
$deleteQuery = $this->db->getQueryBuilder();
$deleteQuery->delete('schedulingobjects')
->where($deleteQuery->expr()->in('id', $deleteQuery->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY));
foreach(array_chunk($ids, 1000) as $chunk) {
$deleteQuery->setParameter('ids', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
$numDeleted += $deleteQuery->executeStatement();
}
if($numDeleted === $limit) {
$this->logger->info("Deleted $limit scheduling objects, continuing with next batch");
$this->deleteOutdatedSchedulingObjects($modifiedBefore, $limit);
}
}
/**
* Creates a new scheduling object. This should land in a users' inbox.
*

View file

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\Migration;
use OCA\DAV\BackgroundJob\DeleteOutdatedSchedulingObjects;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class DeleteSchedulingObjects implements IRepairStep {
public function __construct(private IJobList $jobList,
private ITimeFactory $time,
private CalDavBackend $calDavBackend
) {
}
public function getName(): string {
return 'Handle outdated scheduling events';
}
public function run(IOutput $output): void {
$output->info('Cleaning up old scheduling events');
$time = $this->time->getTime() - (60 * 60);
$this->calDavBackend->deleteOutdatedSchedulingObjects($time, 50000);
if (!$this->jobList->has(DeleteOutdatedSchedulingObjects::class, null)) {
$output->info('Adding background job to delete old scheduling objects');
$this->jobList->add(DeleteOutdatedSchedulingObjects::class, null);
}
}
}

View file

@ -383,6 +383,7 @@ class Version1004Date20170825134824 extends SimpleMigrationStep {
]);
$table->setPrimaryKey(['id']);
$table->addIndex(['principaluri'], 'schedulobj_principuri_index');
$table->addIndex(['lastmodified'], 'schedulobj_lastmodified_idx');
}
if (!$schema->hasTable('cards_properties')) {

View file

@ -79,6 +79,7 @@ return array(
'OCA\\Settings\\SetupChecks\\NeedsSystemAddressBookSync' => $baseDir . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
'OCA\\Settings\\SetupChecks\\PhpDefaultCharset' => $baseDir . '/../lib/SetupChecks/PhpDefaultCharset.php',
'OCA\\Settings\\SetupChecks\\PhpOutputBuffering' => $baseDir . '/../lib/SetupChecks/PhpOutputBuffering.php',
'OCA\\Settings\\SetupChecks\\SchedulingTableSize' => $baseDir . '/../lib/SetupChecks/SchedulingTableSize.php',
'OCA\\Settings\\SetupChecks\\SupportedDatabase' => $baseDir . '/../lib/SetupChecks/SupportedDatabase.php',
'OCA\\Settings\\UserMigration\\AccountMigrator' => $baseDir . '/../lib/UserMigration/AccountMigrator.php',
'OCA\\Settings\\UserMigration\\AccountMigratorException' => $baseDir . '/../lib/UserMigration/AccountMigratorException.php',

View file

@ -94,6 +94,7 @@ class ComposerStaticInitSettings
'OCA\\Settings\\SetupChecks\\NeedsSystemAddressBookSync' => __DIR__ . '/..' . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
'OCA\\Settings\\SetupChecks\\PhpDefaultCharset' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpDefaultCharset.php',
'OCA\\Settings\\SetupChecks\\PhpOutputBuffering' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpOutputBuffering.php',
'OCA\\Settings\\SetupChecks\\SchedulingTableSize' => __DIR__ . '/..' . '/../lib/SetupChecks/SchedulingTableSize.php',
'OCA\\Settings\\SetupChecks\\SupportedDatabase' => __DIR__ . '/..' . '/../lib/SetupChecks/SupportedDatabase.php',
'OCA\\Settings\\UserMigration\\AccountMigrator' => __DIR__ . '/..' . '/../lib/UserMigration/AccountMigrator.php',
'OCA\\Settings\\UserMigration\\AccountMigratorException' => __DIR__ . '/..' . '/../lib/UserMigration/AccountMigratorException.php',

View file

@ -68,6 +68,7 @@ use OCA\Settings\SetupChecks\LegacySSEKeyFormat;
use OCA\Settings\SetupChecks\PhpDefaultCharset;
use OCA\Settings\SetupChecks\PhpOutputBuffering;
use OCA\Settings\SetupChecks\SupportedDatabase;
use OCA\Settings\SetupChecks\SchedulingTableSize;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataDisplayResponse;
@ -902,6 +903,7 @@ Raw output
$supportedDatabases = new SupportedDatabase($this->l10n, $this->connection);
$ldapInvalidUuids = new LdapInvalidUuids($this->appManager, $this->l10n, $this->serverContainer);
$needsSystemAddressBookSync = new NeedsSystemAddressBookSync($this->config, $this->l10n);
$schedulingTableSize = new SchedulingTableSize($this->l10n, $this->connection);
return new DataResponse(
[
@ -958,6 +960,7 @@ Raw output
'temporaryDirectoryWritable' => $this->isTemporaryDirectoryWritable(),
LdapInvalidUuids::class => ['pass' => $ldapInvalidUuids->run(), 'description' => $ldapInvalidUuids->description(), 'severity' => $ldapInvalidUuids->severity()],
NeedsSystemAddressBookSync::class => ['pass' => $needsSystemAddressBookSync->run(), 'description' => $needsSystemAddressBookSync->description(), 'severity' => $needsSystemAddressBookSync->severity()],
SchedulingTableSize::class => ['pass' => $schedulingTableSize->run(), 'description' => $schedulingTableSize->description(), 'severity' => $schedulingTableSize->severity()],
]
);
}

View file

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\SetupChecks;
use OCP\IDBConnection;
use OCP\IL10N;
class SchedulingTableSize {
private IL10N $l10n;
private IDBConnection $connection;
public function __construct(
IL10N $l10n,
IDBConnection $connection
) {
$this->l10n = $l10n;
$this->connection = $connection;
}
public function description(): string {
return $this->l10n->t('You have more than 500 000 rows in the scheduling objects table. Please run the expensive repair jobs via occ maintenance:repair --include-expensive');
}
public function severity(): string {
return 'warning';
}
public function run(): bool {
$qb = $this->connection->getQueryBuilder();
$qb->select($qb->func()->count('id'))
->from('schedulingobjects');
$query = $qb->executeQuery();
$count = $query->fetchOne();
$query->closeCursor();
return $count <= 500000;
}
}

View file

@ -201,6 +201,10 @@ class Application extends App {
if (!$table->hasIndex('schedulobj_principuri_index')) {
$subject->addHintForMissingSubject($table->getName(), 'schedulobj_principuri_index');
}
$table = $schema->getTable('schedulingobjects');
if (!$table->hasIndex('schedulobj_lastmodified_idx')) {
$subject->addHintForMissingSubject($table->getName(), 'schedulobj_lastmodified_idx');
}
}
if ($schema->hasTable('properties')) {

View file

@ -403,8 +403,18 @@ class AddMissingIndices extends Command {
$output->writeln($sqlQueries);
}
$updated = true;
$output->writeln('<info>schedulingobjects table updated successfully.</info>');
}
if (!$table->hasIndex('schedulobj_lastmodified_idx')) {
$output->writeln('<info>Adding schedulobj_lastmodified_idx index to the schedulingobjects table, this can take some time...</info>');
$table->addIndex(['lastmodified'], 'schedulobj_lastmodified_idx');
$sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
if ($dryRun && $sqlQueries !== null) {
$output->writeln($sqlQueries);
}
$updated = true;
}
$output->writeln('<info>schedulingobjects table updated successfully.</info>');
}
$output->writeln('<info>Check indices of the oc_properties table.</info>');

View file

@ -543,6 +543,7 @@
OC.SetupChecks.addGenericSetupCheck(data, 'OCA\\Settings\\SetupChecks\\SupportedDatabase', messages)
OC.SetupChecks.addGenericSetupCheck(data, 'OCA\\Settings\\SetupChecks\\LdapInvalidUuids', messages)
OC.SetupChecks.addGenericSetupCheck(data, 'OCA\\Settings\\SetupChecks\\NeedsSystemAddressBookSync', messages)
OC.SetupChecks.addGenericSetupCheck(data, 'OCA\\Settings\\SetupChecks\\SchedulingTableSize', messages)
} else {
messages.push({
msg: t('core', 'Error occurred while checking server setup'),

View file

@ -81,6 +81,7 @@ use OC\Repair\RepairInvalidShares;
use OC\Repair\RepairMimeTypes;
use OC\Repair\SqliteAutoincrement;
use OC\Template\JSCombiner;
use OCA\DAV\Migration\DeleteSchedulingObjects;
use OCP\AppFramework\QueryException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Collaboration\Resources\IManager;
@ -227,6 +228,7 @@ class Repair implements IOutput {
return [
new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()),
\OC::$server->get(ValidatePhoneNumber::class),
\OC::$server->get(DeleteSchedulingObjects::class),
];
}