Skip to content

Commit 6ffb83a

Browse files
committed
Merge pull request #22269 from owncloud/issue-22243-avoid-deadlock-with-lots-of-entries-to-cleanup
Chunk the cleanup queries to make sure they don't time out
2 parents 39e6a18 + 0ebb205 commit 6ffb83a

3 files changed

Lines changed: 71 additions & 24 deletions

File tree

apps/files/command/deleteorphanedfiles.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
*/
3434
class DeleteOrphanedFiles extends Command {
3535

36+
const CHUNK_SIZE = 200;
37+
3638
/**
3739
* @var IDBConnection
3840
*/
@@ -50,13 +52,31 @@ protected function configure() {
5052
}
5153

5254
public function execute(InputInterface $input, OutputInterface $output) {
55+
$deletedEntries = 0;
56+
57+
$query = $this->connection->getQueryBuilder();
58+
$query->select('fc.fileid')
59+
->from('filecache', 'fc')
60+
->where($query->expr()->isNull('s.numeric_id'))
61+
->leftJoin('fc', 'storages', 's', $query->expr()->eq('fc.storage', 's.numeric_id'))
62+
->setMaxResults(self::CHUNK_SIZE);
63+
64+
$deleteQuery = $this->connection->getQueryBuilder();
65+
$deleteQuery->delete('filecache')
66+
->where($deleteQuery->expr()->eq('fileid', $deleteQuery->createParameter('objectid')));
5367

54-
$sql =
55-
'DELETE FROM `*PREFIX*filecache` ' .
56-
'WHERE NOT EXISTS ' .
57-
'(SELECT 1 FROM `*PREFIX*storages` WHERE `storage` = `numeric_id`)';
68+
$deletedInLastChunk = self::CHUNK_SIZE;
69+
while ($deletedInLastChunk === self::CHUNK_SIZE) {
70+
$deletedInLastChunk = 0;
71+
$result = $query->execute();
72+
while ($row = $result->fetch()) {
73+
$deletedInLastChunk++;
74+
$deletedEntries += $deleteQuery->setParameter('objectid', (int) $row['fileid'])
75+
->execute();
76+
}
77+
$result->closeCursor();
78+
}
5879

59-
$deletedEntries = $this->connection->executeUpdate($sql);
6080
$output->writeln("$deletedEntries orphaned file cache entries deleted");
6181
}
6282

apps/files/lib/backgroundjob/deleteorphaneditems.php

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
*/
3030
class DeleteOrphanedItems extends TimedJob {
3131

32+
const CHUNK_SIZE = 200;
33+
3234
/** @var \OCP\IDBConnection */
3335
protected $connection;
3436

@@ -66,19 +68,38 @@ public function run($argument) {
6668
/**
6769
* Deleting orphaned system tag mappings
6870
*
71+
* @param string $table
72+
* @param string $idCol
73+
* @param string $typeCol
6974
* @return int Number of deleted entries
7075
*/
7176
protected function cleanUp($table, $idCol, $typeCol) {
72-
$subQuery = $this->connection->getQueryBuilder();
73-
$subQuery->select($subQuery->expr()->literal('1'))
74-
->from('filecache', 'f')
75-
->where($subQuery->expr()->eq($idCol, 'f.fileid'));
77+
$deletedEntries = 0;
7678

7779
$query = $this->connection->getQueryBuilder();
78-
$deletedEntries = $query->delete($table)
80+
$query->select('t1.' . $idCol)
81+
->from($table, 't1')
7982
->where($query->expr()->eq($typeCol, $query->expr()->literal('files')))
80-
->andWhere($query->expr()->isNull($query->createFunction('(' . $subQuery->getSql() . ')')))
81-
->execute();
83+
->andWhere($query->expr()->isNull('t2.fileid'))
84+
->leftJoin('t1', 'filecache', 't2', $query->expr()->eq($query->expr()->castColumn('t1.' . $idCol, IQueryBuilder::PARAM_INT), 't2.fileid'))
85+
->groupBy('t1.' . $idCol)
86+
->setMaxResults(self::CHUNK_SIZE);
87+
88+
$deleteQuery = $this->connection->getQueryBuilder();
89+
$deleteQuery->delete($table)
90+
->where($deleteQuery->expr()->eq($idCol, $deleteQuery->createParameter('objectid')));
91+
92+
$deletedInLastChunk = self::CHUNK_SIZE;
93+
while ($deletedInLastChunk === self::CHUNK_SIZE) {
94+
$result = $query->execute();
95+
$deletedInLastChunk = 0;
96+
while ($row = $result->fetch()) {
97+
$deletedInLastChunk++;
98+
$deletedEntries += $deleteQuery->setParameter('objectid', (int) $row[$idCol])
99+
->execute();
100+
}
101+
$result->closeCursor();
102+
}
82103

83104
return $deletedEntries;
84105
}
@@ -111,8 +132,7 @@ protected function cleanUserTags() {
111132
* @return int Number of deleted entries
112133
*/
113134
protected function cleanComments() {
114-
$qb = $this->connection->getQueryBuilder();
115-
$deletedEntries = $this->cleanUp('comments', $qb->expr()->castColumn('object_id', IQueryBuilder::PARAM_INT), 'object_type');
135+
$deletedEntries = $this->cleanUp('comments', 'object_id', 'object_type');
116136
$this->logger->debug("$deletedEntries orphaned comments deleted", ['app' => 'DeleteOrphanedItems']);
117137
return $deletedEntries;
118138
}
@@ -123,8 +143,7 @@ protected function cleanComments() {
123143
* @return int Number of deleted entries
124144
*/
125145
protected function cleanCommentMarkers() {
126-
$qb = $this->connection->getQueryBuilder();
127-
$deletedEntries = $this->cleanUp('comments_read_markers', $qb->expr()->castColumn('object_id', IQueryBuilder::PARAM_INT), 'object_type');
146+
$deletedEntries = $this->cleanUp('comments_read_markers', 'object_id', 'object_type');
128147
$this->logger->debug("$deletedEntries orphaned comment read marks deleted", ['app' => 'DeleteOrphanedItems']);
129148
return $deletedEntries;
130149
}

lib/private/repair/repairinvalidshares.php

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
*/
3131
class RepairInvalidShares extends BasicEmitter implements \OC\RepairStep {
3232

33+
const CHUNK_SIZE = 200;
34+
3335
/**
3436
* @var \OCP\IConfig
3537
*/
@@ -83,18 +85,24 @@ private function removeSharesNonExistingParent() {
8385
->where($query->expr()->isNotNull('s1.parent'))
8486
->andWhere($query->expr()->isNull('s2.id'))
8587
->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'))
86-
->groupBy('s1.parent');
88+
->groupBy('s1.parent')
89+
->setMaxResults(self::CHUNK_SIZE);
8790

8891
$deleteQuery = $this->connection->getQueryBuilder();
8992
$deleteQuery->delete('share')
90-
->where($query->expr()->eq('parent', $deleteQuery->createParameter('parent')));
91-
92-
$result = $query->execute();
93-
while ($row = $result->fetch()) {
94-
$deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent'])
95-
->execute();
93+
->where($deleteQuery->expr()->eq('parent', $deleteQuery->createParameter('parent')));
94+
95+
$deletedInLastChunk = self::CHUNK_SIZE;
96+
while ($deletedInLastChunk === self::CHUNK_SIZE) {
97+
$deletedInLastChunk = 0;
98+
$result = $query->execute();
99+
while ($row = $result->fetch()) {
100+
$deletedInLastChunk++;
101+
$deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent'])
102+
->execute();
103+
}
104+
$result->closeCursor();
96105
}
97-
$result->closeCursor();
98106

99107
if ($deletedEntries) {
100108
$this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where the parent did not exist'));

0 commit comments

Comments
 (0)