Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/Cron/Cleanup.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ protected function run($argument): void {
$removedSessions = $this->sessionService->removeInactiveSessionsWithoutSteps();
$this->logger->debug('Removed ' . $removedSessions . ' inactive sessions');

$this->logger->debug('Run cleanup job for orphaned steps');
$removedSteps = $this->sessionService->removeOrphanedSteps();
$this->logger->debug('Removed ' . $removedSteps . ' orphaned steps');

$this->logger->debug('Run cleanup job for obsolete documents folders');
$this->documentService->cleanupOldDocumentsFolders();
}
Expand Down
45 changes: 45 additions & 0 deletions lib/Db/SessionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,51 @@ public function deleteInactiveWithoutSteps(?int $documentId = null): int {
return $deletedCount;
}

public function deleteOrphanedSteps(): int {
$startTime = microtime(true);
$maxExecutionSeconds = 30;
$batchSize = 1000;
$deletedCount = 0;
$safetyBufferTime = time() - 86400;

do {
$orphanedStepsQb = $this->db->getQueryBuilder();
$orphanedStepsQb->select('st.id')
->from('text_steps', 'st')
->leftJoin('st', 'text_sessions', 's', $orphanedStepsQb->expr()->eq('st.session_id', 's.id'))
->leftJoin('st', 'text_documents', 'd', $orphanedStepsQb->expr()->eq('st.document_id', 'd.id'))
->where($orphanedStepsQb->expr()->isNull('s.id'))
->andWhere($orphanedStepsQb->expr()->lt('st.timestamp', $orphanedStepsQb->createNamedParameter($safetyBufferTime)))
->andWhere(
$orphanedStepsQb->expr()->orX(
$orphanedStepsQb->expr()->isNull('d.id'),
$orphanedStepsQb->expr()->lt('st.id', 'd.last_saved_version')
)
)
->setMaxResults($batchSize);

$result = $orphanedStepsQb->executeQuery();
$stepIds = array_map(function ($row) {
return (int)$row['id'];
}, $result->fetchAll());
$result->closeCursor();

if (empty($stepIds)) {
break;
}

$deleteQb = $this->db->getQueryBuilder();
$batchDeleted = $deleteQb->delete('text_steps')
->where($deleteQb->expr()->in('id', $deleteQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY))
->setParameter('ids', $stepIds, IQueryBuilder::PARAM_INT_ARRAY)
->executeStatement();

$deletedCount += $batchDeleted;
} while ((microtime(true) - $startTime) < $maxExecutionSeconds);

return $deletedCount;
}

public function deleteByDocumentId(int $documentId): int {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
Expand Down
4 changes: 4 additions & 0 deletions lib/Service/SessionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ public function removeInactiveSessionsWithoutSteps(?int $documentId = null): int
return $this->sessionMapper->deleteInactiveWithoutSteps($documentId);
}

public function removeOrphanedSteps(): int {
return $this->sessionMapper->deleteOrphanedSteps();
}

public function getSession(int $documentId, int $sessionId, string $token): ?Session {
if ($this->session !== null) {
return $this->session;
Expand Down
64 changes: 63 additions & 1 deletion tests/unit/Db/SessionMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ class SessionMapperTest extends \Test\TestCase {

private SessionMapper $sessionMapper;
private StepMapper $stepMapper;
private DocumentMapper $documentMapper;

public function setUp(): void {
parent::setUp();
$this->sessionMapper = \OCP\Server::get(SessionMapper::class);
$this->stepMapper = \OCP\Server::get(StepMapper::class);

$this->documentMapper = \OCP\Server::get(DocumentMapper::class);
}

public function testDeleteInactiveWithoutSteps() {
Expand Down Expand Up @@ -98,4 +99,65 @@ public function testDeleteInactiveWithoutStepsMultiple() {

self::assertCount(0, $this->sessionMapper->findAll(1));
}

public function testDeleteOrphanedSteps() {
$this->documentMapper->clearAll();
$this->sessionMapper->clearAll();
$this->stepMapper->clearAll();

$oldTimestamp = time() - 86401;

// Create document
$document = $this->documentMapper->insert(Document::fromParams([
'id' => 1,
'currentVersion' => 0,
'lastSavedVersion' => 100,
'lastSavedVersionTime' => time()
]));

// Create Orphaned step without document (delete)
$this->stepMapper->insert(Step::fromParams([
'sessionId' => 99999,
'documentId' => 99999,
'data' => 'ORPHANED_NO_DOC',
'version' => 1
]));

// Orphaned "old" step with document and old version (delete)
$this->stepMapper->insert(Step::fromParams([
'id' => 1,
'sessionId' => 99999,
'documentId' => $document->getId(),
'data' => 'ORPHANED_OLD_VERSION',
'timestamp' => $oldTimestamp,
'version' => 1
]));

// Orphaned "new" step with document and current version (keep)
$this->stepMapper->insert(Step::fromParams([
'id' => 100,
'sessionId' => 99999,
'documentId' => $document->getId(),
'data' => 'ORPHANED_CURRENT_VERSION',
'version' => 2
]));

// Orphaned step with document and new version (keep)
$this->stepMapper->insert(Step::fromParams([
'id' => 101,
'sessionId' => 99999,
'documentId' => $document->getId(),
'data' => 'ORPHANED_NEW_VERSION',
'timestamp' => $oldTimestamp,
'version' => 3
]));

// Verify steps for document 1 and 99999
self::assertCount(3, $this->stepMapper->find(1, 0));
self::assertCount(1, $this->stepMapper->find(99999, 0));

// Verify orphan delete
$deletedCount = $this->sessionMapper->deleteOrphanedSteps();
self::assertEquals(2, $deletedCount);
}
}
Loading