diff --git a/VERSIONS_HOTLINE/105091 b/VERSIONS_HOTLINE/105091
new file mode 100644
index 0000000000000000000000000000000000000000..e15a2d398cdb902e978609f9e822b33d7d6c981f
--- /dev/null
+++ b/VERSIONS_HOTLINE/105091
@@ -0,0 +1 @@
+ - ticket #105091 : Performance : Correction de la perte de performance suite à la livraison des nouveaux groupes dynamiques
\ No newline at end of file
diff --git a/library/Class/UserGroup.php b/library/Class/UserGroup.php
index 5c2ede4bb4552dc79082c8244f644dd87918d580..82eba663a208cd513aa03bd153c5724be8ecc782 100644
--- a/library/Class/UserGroup.php
+++ b/library/Class/UserGroup.php
@@ -22,6 +22,17 @@
 class UserGroupLoader extends Storm_Model_Loader {
   use Trait_MemoryCleaner;
 
+  protected $_dynamics_of_user_cache = [];
+
+
+  public function save($model) {
+    $result = parent::save($model);
+    if ($model->isDynamic())
+      $this->_dynamics_of_user_cache = [];
+
+    return $result;
+  }
+
 
   public function getUsersIdsOf($group) {
     $ids = [];
@@ -99,6 +110,23 @@ class UserGroupLoader extends Storm_Model_Loader {
   public function findAllDynamics() {
     return Class_UserGroup::findAllBy(['group_type' => Class_UserGroup::TYPE_DYNAMIC]);
   }
+
+
+  public function findAllDynamicsOf($user) {
+    if ($user->isNew())
+      return $this->_realFindAllDynamicsOf($user);
+
+    $key = $user->getId();
+    return array_key_exists($key, $this->_dynamics_of_user_cache)
+      ? $this->_dynamics_of_user_cache[$key]
+      : ($this->_dynamics_of_user_cache[$key] = $this->_realFindAllDynamicsOf($user));
+  }
+
+
+  protected function _realFindAllDynamicsOf($user) {
+    return array_filter(Class_UserGroup::findAllDynamics(),
+                        function($group) use($user) { return $group->hasUser($user); });
+  }
 }
 
 
diff --git a/library/Class/Users.php b/library/Class/Users.php
index 705a8e3410c196f1d6f2226086797546d537a7ba..8f6a8558eb019059f35d12843e44484afbfe3c25 100644
--- a/library/Class/Users.php
+++ b/library/Class/Users.php
@@ -827,8 +827,7 @@ class Class_Users extends Storm_Model_Abstract {
 
 
   public function getDynamicUserGroups() {
-    return array_filter(Class_UserGroup::findAllDynamics(),
-                        function($group) { return $group->hasUser($this); });
+    return Class_UserGroup::findAllDynamicsOf($this);
   }
 
 
diff --git a/tests/application/modules/opac/controllers/AbonneControllerFicheTest.php b/tests/application/modules/opac/controllers/AbonneControllerFicheTest.php
index 2614eb80d4592e942a57ce76578493d15e3efce8..ae2c3447670fb5387b966304b5a12d853bef07ab 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerFicheTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerFicheTest.php
@@ -28,6 +28,8 @@ abstract class AbstractAbonneControllerFicheTest extends AbstractControllerTestC
     $this->fixture('Class_Bib', ['id' => 1]);
     $this->fixture('Class_Bib', ['id' => 2]);
 
+    $this->onLoaderOfModel('Class_UserGroup');
+
     $this->marcus = $this->fixture('Class_Users',
                                    ['id' => 10,
                                     'login' => 'MC',
@@ -121,11 +123,18 @@ class AbonneControllerFicheAsAbonneTest extends AbstractAbonneControllerFicheTes
     $this->assertXPathContentContains('//div[@class="abonneTitre"]//span[@data-name="last-name"]', 'Miller');
   }
 
+
   /** @test */
   public function LoansHistoryShouldNotBeDisplayed() {
     $this->assertNotXPathContentContains('//a', 'Voir mon historique de prêts');
   }
 
+
+  // @see http://forge.afi-sa.fr/issues/105091
+  /** @test */
+  public function callToFindAllDynamicsShouldBeDoneOnlyOnce() {
+    $this->assertEquals(1, Class_UserGroup::methodCallCount('findAllDynamics'));
+  }
 }