diff --git a/lib/classes/StudipTreeNodeCachableTrait.php b/lib/classes/StudipTreeNodeCachableTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..31823acee79b07ab7c52e17d7b25ee1e97290cc6
--- /dev/null
+++ b/lib/classes/StudipTreeNodeCachableTrait.php
@@ -0,0 +1,59 @@
+<?php
+trait StudipTreeNodeCachableTrait
+{
+    protected static $descendants_cache_array = null;
+
+    protected static function getDescendantsCacheArray(): StudipCachedArray
+    {
+        if (self::$descendants_cache_array === null) {
+            self::$descendants_cache_array = new StudipCachedArray(
+                static::class . '/descendants',
+                30 * 60
+            );
+        }
+        return self::$descendants_cache_array;
+    }
+
+    protected static function registerCachableCallbacks(array $config): array
+    {
+        if (!isset($config['registered_callbacks'])) {
+            $config['registered_callbacks'] = [];
+        }
+
+        if (!isset($config['registered_callbacks']['before_store'])) {
+            $config['registered_callbacks']['before_store'] = [];
+        }
+        $config['registered_callbacks']['before_store'][] = function ($node): void {
+            self::getDescendantsCacheArray()->expire();
+        };
+
+        if (!isset($config['registered_callbacks']['after_delete'])) {
+            $config['registered_callbacks']['after_delete'] = [];
+        }
+        $config['registered_callbacks']['after_delete'][] = function ($node): void {
+            self::getDescendantsCacheArray()->expire();
+        };
+
+        return $config;
+    }
+
+    protected function getDescendantIds(): array
+    {
+        $cache = self::getDescendantsCacheArray();
+
+        if (isset($cache[$this->id])) {
+            return $cache[$this->id];
+        }
+
+        $ids = [];
+
+        foreach ($this->getChildNodes() as $child) {
+            $ids = array_merge($ids, [$child->id], $child->getDescendantIds());
+        }
+
+        $cache[$this->id] = $ids;
+
+        return $ids;
+    }
+
+}
diff --git a/lib/models/RangeTreeNode.php b/lib/models/RangeTreeNode.php
index 4d52fedaf718b57f137e28e2bc625b48959a09f8..c142579e05d0d16aac5db8bdf8ed522dd8803e75 100644
--- a/lib/models/RangeTreeNode.php
+++ b/lib/models/RangeTreeNode.php
@@ -27,6 +27,8 @@
  */
 class RangeTreeNode extends SimpleORMap implements StudipTreeNode
 {
+    use StudipTreeNodeCachableTrait;
+
     protected static function configure($config = [])
     {
         $config['db_table'] = 'range_tree';
@@ -41,12 +43,13 @@ class RangeTreeNode extends SimpleORMap implements StudipTreeNode
         ];
         $config['has_many']['children'] = [
             'class_name'  => RangeTreeNode::class,
-            'foreign_key' => 'item_id',
             'assoc_foreign_key' => 'parent_id',
             'order_by' => 'ORDER BY priority, name',
-            'on_delete' => 'delete'
+            'on_delete' => 'delete',
         ];
 
+        $config = self::registerCachableCallbacks($config);
+
         parent::configure($config);
     }
 
@@ -240,17 +243,6 @@ class RangeTreeNode extends SimpleORMap implements StudipTreeNode
         return DBManager::get()->fetchAll($query, $parameters, 'Course::buildExisting');
     }
 
-    public function getDescendantIds()
-    {
-        $ids = [];
-
-        foreach ($this->children as $child) {
-            $ids = array_merge($ids, [$child->id], $child->getDescendantIds());
-        }
-
-        return $ids;
-    }
-
     public function getAncestors(): array
     {
         $path = [
diff --git a/lib/models/StudipStudyArea.class.php b/lib/models/StudipStudyArea.class.php
index 1ea7722fc47cc0feb89c3f60a3f2f35f9954f502..518e6c1dcfa41e9b21b08666a87a971a4dc51ba9 100644
--- a/lib/models/StudipStudyArea.class.php
+++ b/lib/models/StudipStudyArea.class.php
@@ -30,6 +30,8 @@
 
 class StudipStudyArea extends SimpleORMap implements StudipTreeNode
 {
+    use StudipTreeNodeCachableTrait;
+
     /**
      * This constant represents the key of the root area.
      */
@@ -53,6 +55,9 @@ class StudipStudyArea extends SimpleORMap implements StudipTreeNode
             'class_name' => StudipStudyArea::class,
             'foreign_key' => 'parent_id',
         ];
+
+        $config = self::registerCachableCallbacks($config);
+
         parent::configure($config);
     }
 
@@ -64,7 +69,7 @@ class StudipStudyArea extends SimpleORMap implements StudipTreeNode
     /**
      * Returns the children of the study area with the specified ID.
      */
-    static function findByParent($parent_id)
+    public static function findByParent($parent_id)
     {
         return self::findByparent_id($parent_id, "ORDER BY priority,name");
     }
@@ -601,17 +606,6 @@ class StudipStudyArea extends SimpleORMap implements StudipTreeNode
         return $path;
     }
 
-    private function getDescendantIds()
-    {
-        $ids = [];
-
-        foreach ($this->_children as $child) {
-            $ids = array_merge($ids, [$child->id], $child->getDescendantIds());
-        }
-
-        return $ids;
-    }
-
     /**
      * Constructs an index from the level hierarchy, This index is a number,
      * containing the "depth" level and the priority on this level. For example,