Add a new worker implementation with a linear, predictable runtime
authorAlexander Ebert <ebert@woltlab.com>
Sat, 25 May 2024 13:12:05 +0000 (15:12 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 8 Jun 2024 10:19:40 +0000 (12:19 +0200)
wcfsetup/install/files/lib/system/worker/AbstractLinearRebuildDataWorker.class.php [new file with mode: 0644]

diff --git a/wcfsetup/install/files/lib/system/worker/AbstractLinearRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/AbstractLinearRebuildDataWorker.class.php
new file mode 100644 (file)
index 0000000..feecf25
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+
+namespace wcf\system\worker;
+
+use wcf\data\DatabaseObjectList;
+use wcf\system\event\EventHandler;
+use wcf\system\exception\ParentClassException;
+use wcf\system\exception\SystemException;
+use wcf\system\search\SearchIndexManager;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+
+/**
+ * Abstract implementation of a linear rebuild data worker that will try to
+ * process all objects starting with the id `1` up to the highest id.
+ *
+ * This differs from the previous rebuild data workers which use `LIMIT` and
+ * `OFFSET` which scales poorly with large amounts of rows. In contrast this
+ * worker will iterate over any possible id, causing some iterations to process
+ * less objects than requested by the limit.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+abstract class AbstractLinearRebuildDataWorker extends AbstractRebuildDataWorker
+{
+    #[\Override]
+    public function countObjects()
+    {
+        if ($this->count === null) {
+            if ($this->objectList === null) {
+                $this->initObjectList();
+            }
+
+            $sql = \sprintf(
+                "SELECT MAX(%s) FROM %s",
+                $this->objectList->getDatabaseTableIndexName(),
+                $this->objectList->getDatabaseTableName(),
+            );
+            $statement = WCF::getDB()->prepare($sql);
+            $statement->execute([]);
+
+            $this->count = $statement->fetchSingleColumn();
+        }
+    }
+
+    #[\Override]
+    public function execute()
+    {
+        $this->objectList->readObjects();
+
+        if (\count($this->objectList) === 0) {
+            return;
+        }
+
+        SearchIndexManager::getInstance()->beginBulkOperation();
+
+        EventHandler::getInstance()->fireAction($this, 'execute');
+    }
+
+    #[\Override]
+    protected function initObjectList()
+    {
+        if (empty($this->objectListClassName)) {
+            throw new SystemException('DatabaseObjectList class name not specified.');
+        }
+
+        if (!\is_subclass_of($this->objectListClassName, DatabaseObjectList::class)) {
+            throw new ParentClassException($this->objectListClassName, DatabaseObjectList::class);
+        }
+
+        $this->objectList = new $this->objectListClassName();
+        $this->objectList->getConditionBuilder()->add(
+            \sprintf(
+                "%s.%s BETWEEN ? AND ?",
+                $this->objectList->getDatabaseTableAlias(),
+                $this->objectList->getDatabaseTableIndexName(),
+            ),
+            [
+                $this->limit * $this->loopCount + 1,
+                $this->limit * $this->loopCount + $this->limit,
+            ]
+        );
+    }
+
+    #[\Override]
+    public function finalize()
+    {
+        if (\count($this->objectList) === 0) {
+            return;
+        }
+
+        SearchIndexManager::getInstance()->commitBulkOperation();
+        UserStorageHandler::getInstance()->shutdown();
+    }
+}