From 95000a1923965be285fa776e699b1fc7f8091f92 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 21 Nov 2024 12:20:11 +0100 Subject: [PATCH] enh: Enforce a max count of running indexer jobs (#77) Signed-off-by: Marcel Klehr --- lib/BackgroundJobs/IndexerJob.php | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/BackgroundJobs/IndexerJob.php b/lib/BackgroundJobs/IndexerJob.php index 16e892c..70276f8 100644 --- a/lib/BackgroundJobs/IndexerJob.php +++ b/lib/BackgroundJobs/IndexerJob.php @@ -19,6 +19,7 @@ use OCP\BackgroundJob\IJobList; use OCP\BackgroundJob\TimedJob; use OCP\DB\Exception; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Config\ICachedMountInfo; use OCP\Files\Config\IUserMountCache; use OCP\Files\File; @@ -26,12 +27,23 @@ use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\IDBConnection; use OCP\Lock\LockedException; use Psr\Log\LoggerInterface; +/** + * Indexer Job + * Makes use of the following app config settings: + * + * auto_indexing: bool = true The job only runs if this is true + * indexing_batch_size: int = 100 The number of files to index per run + * indexing_max_time: int = 30*60 The number of seconds to index files for per run, regardless of batch size + * indexing_max_jobs_count: int = 3 The maximum number of Indexer jobs allowed to run at the same time + */ class IndexerJob extends TimedJob { public const DEFAULT_MAX_INDEXING_TIME = 30 * 60; + public const DEFAULT_MAX_JOBS_COUNT = 3; public function __construct( ITimeFactory $time, @@ -44,6 +56,8 @@ public function __construct( private IRootFolder $rootFolder, private IAppConfig $appConfig, private DiagnosticService $diagnosticService, + private IDBConnection $db, + private ITimeFactory $timeFactory, ) { parent::__construct($time); $this->setInterval($this->getMaxIndexingTime()); @@ -66,6 +80,9 @@ public function run($argument): void { if ($this->appConfig->getAppValue('auto_indexing', 'true') === 'false') { return; } + if ($this->hasEnoughRunningJobs()) { + return; + } $this->logger->debug('Index files of storage ' . $storageId); try { $this->logger->debug('fetching ' . $this->getBatchSize() . ' files from queue'); @@ -122,6 +139,37 @@ protected function getMaxIndexingTime(): int { return $this->appConfig->getAppValueInt('indexing_max_time', self::DEFAULT_MAX_INDEXING_TIME); } + protected function hasEnoughRunningJobs(): bool { + // Sleep a bit randomly to avoid a scenario where all jobs are started at the same time and kill themselves directly + sleep(rand(1, 3 * 60)); + if (!$this->jobList->hasReservedJob(static::class)) { + // short circuit to false if no jobs are running, yet + return false; + } + $count = 0; + foreach ($this->jobList->getJobsIterator(static::class, null, 0) as $job) { + // Check if job is running + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from('jobs') + ->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 6 * 3600, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT))) + ->setMaxResults(1); + + try { + $result = $query->executeQuery(); + if ($result->fetch() !== false) { + // count if it's running + $count++; + } + $result->closeCursor(); + } catch (Exception $e) { + $this->logger->warning('Querying reserved jobs failed', ['exception' => $e]); + } + } + return $count >= $this->appConfig->getAppValueInt('indexing_max_jobs_count', self::DEFAULT_MAX_JOBS_COUNT); + } + /** * @param QueueFile[] $files * @return void