From c23d64eb423733536758c63ccb9299d55fcc4170 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Thu, 3 Sep 2026 14:53:04 +0200 Subject: [PATCH 1/3] perf(cleanup): Optimize cleanup job Signed-off-by: Kostiantyn Miakshyn --- lib/Cron/Cleanup.php | 19 ++++++++++++++++++- lib/Db/StepMapper.php | 18 ++++++++++++++++++ lib/Service/DocumentService.php | 4 ++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/Cron/Cleanup.php b/lib/Cron/Cleanup.php index aadcb115a9e..7a83a3a0fc1 100644 --- a/lib/Cron/Cleanup.php +++ b/lib/Cron/Cleanup.php @@ -9,6 +9,7 @@ namespace OCA\Text\Cron; +use OCA\Text\Exception\DocumentHasUnsavedChangesException; use OCA\Text\Service\AttachmentService; use OCA\Text\Service\DocumentService; use OCA\Text\Service\SessionService; @@ -17,6 +18,8 @@ use Psr\Log\LoggerInterface; class Cleanup extends TimedJob { + private const ABANDONED_UNSAVED_CHANGES_AGE = 30 * 24 * 60 * 60; + public function __construct( ITimeFactory $time, private readonly SessionService $sessionService, @@ -34,7 +37,21 @@ public function __construct( protected function run($argument): void { $this->logger->debug('Run cleanup job for text documents'); foreach ($this->documentService->getAllWithNoActiveSession() as $document) { - $this->attachmentService->cleanupAttachments($document->getId()); + $documentId = $document->getId(); + try { + $this->documentService->resetDocument($documentId); + } catch (DocumentHasUnsavedChangesException) { + $lastStepTime = $this->documentService->getLatestStepTimestamp($documentId); + if ($lastStepTime === null || $lastStepTime >= $this->time->getTime() - self::ABANDONED_UNSAVED_CHANGES_AGE) { + continue; + } + $this->documentService->resetDocument($documentId, true); + $this->logger->warning('Force reset document with abandoned unsaved changes', [ + 'documentId' => $documentId, + 'lastStepTime' => $lastStepTime, + ]); + } + $this->attachmentService->cleanupAttachments($documentId); } $this->logger->debug('Run cleanup job for text sessions'); diff --git a/lib/Db/StepMapper.php b/lib/Db/StepMapper.php index 26339b0912b..637830a159e 100644 --- a/lib/Db/StepMapper.php +++ b/lib/Db/StepMapper.php @@ -56,6 +56,24 @@ public function getLatestVersion(int $documentId): ?int { return $data['id']; } + public function getLatestTimestamp(int $documentId): ?int { + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('timestamp') + ->from($this->getTableName()) + ->where($qb->expr()->eq('document_id', $qb->createNamedParameter($documentId))) + ->setMaxResults(1) + ->orderBy('id', 'DESC') + ->executeQuery(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + return null; + } + + return (int)$data['timestamp']; + } + public function getBeforeVersion(int $documentId, int $version, int $offset): int { $qb = $this->db->getQueryBuilder(); $result = $qb->select('id') diff --git a/lib/Service/DocumentService.php b/lib/Service/DocumentService.php index 91e510b08b4..982342ba3cc 100644 --- a/lib/Service/DocumentService.php +++ b/lib/Service/DocumentService.php @@ -428,6 +428,10 @@ public function autosave(Document $document, File $file, int $version, string $a return $document; } + public function getLatestStepTimestamp(int $documentId): ?int { + return $this->stepMapper->getLatestTimestamp($documentId); + } + /** * @throws DocumentHasUnsavedChangesException * @throws Exception From 1834904a4ae5d66f1eafabdfd075fef23211c92c Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Sun, 13 Sep 2026 16:31:24 +0200 Subject: [PATCH 2/3] perf(cleanup): Optimize cleanup job (fix pipeline) Signed-off-by: Kostiantyn Miakshyn --- lib/Cron/Cleanup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cron/Cleanup.php b/lib/Cron/Cleanup.php index 7a83a3a0fc1..2429cb15629 100644 --- a/lib/Cron/Cleanup.php +++ b/lib/Cron/Cleanup.php @@ -18,7 +18,7 @@ use Psr\Log\LoggerInterface; class Cleanup extends TimedJob { - private const ABANDONED_UNSAVED_CHANGES_AGE = 30 * 24 * 60 * 60; + private const int ABANDONED_UNSAVED_CHANGES_AGE = 30 * 24 * 60 * 60; public function __construct( ITimeFactory $time, From 0a05ff343fa5afea04a51f57722588544134a6b2 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Sun, 13 Sep 2026 16:36:00 +0200 Subject: [PATCH 3/3] perf(cleanup): Optimize cleanup job (refactoring) Signed-off-by: Kostiantyn Miakshyn --- lib/Cron/Cleanup.php | 5 +++-- lib/Db/StepMapper.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Cron/Cleanup.php b/lib/Cron/Cleanup.php index 2429cb15629..bbd6afca545 100644 --- a/lib/Cron/Cleanup.php +++ b/lib/Cron/Cleanup.php @@ -18,7 +18,7 @@ use Psr\Log\LoggerInterface; class Cleanup extends TimedJob { - private const int ABANDONED_UNSAVED_CHANGES_AGE = 30 * 24 * 60 * 60; + private const string ABANDONED_UNSAVED_CHANGES_AGE = '-30 days'; public function __construct( ITimeFactory $time, @@ -36,13 +36,14 @@ public function __construct( */ protected function run($argument): void { $this->logger->debug('Run cleanup job for text documents'); + $cutoff = $this->time->getDateTime(self::ABANDONED_UNSAVED_CHANGES_AGE)->getTimestamp(); foreach ($this->documentService->getAllWithNoActiveSession() as $document) { $documentId = $document->getId(); try { $this->documentService->resetDocument($documentId); } catch (DocumentHasUnsavedChangesException) { $lastStepTime = $this->documentService->getLatestStepTimestamp($documentId); - if ($lastStepTime === null || $lastStepTime >= $this->time->getTime() - self::ABANDONED_UNSAVED_CHANGES_AGE) { + if ($lastStepTime === null || $lastStepTime >= $cutoff) { continue; } $this->documentService->resetDocument($documentId, true); diff --git a/lib/Db/StepMapper.php b/lib/Db/StepMapper.php index 637830a159e..83d6e4ed6d2 100644 --- a/lib/Db/StepMapper.php +++ b/lib/Db/StepMapper.php @@ -65,7 +65,7 @@ public function getLatestTimestamp(int $documentId): ?int { ->orderBy('id', 'DESC') ->executeQuery(); - $data = $result->fetch(); + $data = $result->fetchAssociative(); $result->closeCursor(); if ($data === false) { return null;