Performances renommage utilisateurs Documentum

De EjnTricks

L'article Renommer Utilisateur a permis de décrine le fonctionnement du job pour renommer un utilisateur sous Documentum. Dans le cadre d'une intégration, un client avait beaucoup de modification de noms d'utilisateur, sur une docbase volumineuse, entraînant un temps de traitement supérieur à la journée. Une partie de amélioration de performances a été réalisée sur l'étude de cette méthode et son optimisation. Ce n'était certe pas les gains les plus importants, mais cela était assez spectaculaire pris individuellement, allant jusqu'à un facteur 10.


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote

Study icon.png Analyse version 6.7 SP1

Pour rappel, le job dm_UserRename exécute la méthode serveur de même nom, afin de traiter l'ensemble de demandes de renommage modélisées dans des instances de dm_job_request. La valeur de l'attribut method_veb est .\dmbasic -f..\install\admin\userrename.ebs -eUserRename permettant d'identifier:

Paramètre Valeur
Emplacement code source. ..\install\admin\userrename.ebs par rapport aux répertoire d'exécution.
Point d'entrée de la méthode. fonction UserRename.

Etant un code en DocBasic, il est très facile de l'étudier et de le modifier.

Run-icon.png Exécution

Le traitement débute par l'exécution de la fonction UserRename, dont le code est le suivant:

Cette fonction va permettre de récupérer les informations, mettre à jour le statut du job et enfin de sauvegarder les traces, principe qui se retrouve dans une grande majorité des méthodes serveurs d'administration de Documentum.

Icon-database-search.png Lecture instructions

L'apppel à la fonction DmGetRenameOptions va permettre de stocker temporairement les paramètres de renommage, instance de dm_job_request, dans des variables globales de la méthode. Sa lecture est uniquement rappeler pour information ici:

Ici, les variables suivantes sont peuplées pour chaque demande de changement de nom:

Variable Contenu
OldUserName Le nom à modifier.
NewUserName Le nouveau nom.
ReportOnly Indique si les modifications sont sauvegardées ou est ce que juste un rapport est réalisé.
UnlockLckObj Indique si les objets vérrouillés doivent être dévérouillés ou non.

Icon-database-process.png Modifications

Jusqu'à présent, seuls des rappels ont été présentés. Le modifications (ou rapport) sont réellement exécutées dans la fonction DmUserRename:


Le principe est d'exécuter une recherche, en fonction du type, pour identifier toutes les instances d'objets à modifier, puis d'effectuer le changement de valeur sur les attributs concernés. Pour certains types d'objet, la modification est réalisée directement en SQL car des objets ne sont théoriquement pas modifiables.

Les modifications apportées concernent:

Type d'objet Mode recherche Mode modification Attributs modifiés
dm_sysobject Requête DQL sur les objets vérouillés. API unlock. Enlève le verrou sur les objets vérrouillés qui doivent être modifiés.
dm_user API retrieve. API set puis save. user_name.
dm_federation_log   DQL. Injection d'une information de rename pour la fédération.
dm_acl   SQL. owner_name, r_accessor_name.
dm_alias_set Requête DQL. API set puis save. owner_name et alias_value.
dm_alias_set Requête DQL. API set puis save. owner_name et alias_value.
dm_user API retrieve. API set puis save. acl_domain.
dm_sysobject Requête DQL sur les objets non vérouillés. API set puis save, ou SQL. owner_name, r_creator_name, r_modifier, acl_domain, r_lock_owner en API.

r_modifier en SQL. En effet, la modification de r_modifier étant gérer par le serveur, il est impossible de mettre à jour la valeur en API.

dm_sysobject Requête DQL sur les objets vérouillés. API set puis save, ou SQL. owner_name, r_creator_name, r_modifier, acl_domain, r_lock_owner en API.

r_modifier en SQL. En effet, la modification de r_modifier étant gérer par le serveur, il est impossible de mettre à jour la valeur en API.

Informations sur les workflows.   SQL. La liste est trop importante pour être décrite dans le cadre de cet article. Et il n'y a aucune optimisation à apporter.
dm_group   SQL. users_names, owner_name et group_admin.
dmi_registry Requête DQL. API register.  
dm_registered Requête DQL.   Aucune modification n'est apportée. Seule un warning est affichée dans la log.
dm_category   SQL. category_owner.
dm_type Requête DQL.   Aucune modification n'est apportée. Seule un warning est affichée dans la log.
dmi_type_info Requête DQL.   Aucune modification n'est apportée. Seule un warning est affichée dans la log.

Icon-memory.png Optimisations

Pour plusieurs modifications, la récupération des objets à modifier est effectuée à l'aide d'une requête DQL avec plusieurs critères de filtre séparés par l'opérateur OR. Et c'est cette organisation des filtres qui est le source des potentielles lenteurs d'exécution. De manière générale, il faut éviter cet opérateur quand cela est possible, ce qui est largement le cas dans cette méthode.

Pour ce faire, il suffit de modifier les requêtes en préférant l'utilisation de sous requête imbriquées avec l'opérateur UNION.

Unlock dm_sysobject

La actions sur les instances de dm_sysobject utilise la requête suivante:

	QueryStr$ = "query,c,select r_object_id from " & _
				"dm_sysobject (all) where " & _ 
				"(owner_name = '" & DqlOldUserName & "' or " & _
				"r_creator_name = '" & DqlOldUserName & "' or " & _ 
				"r_modifier = '" & DqlOldUserName & "' or " & _ 
				"acl_domain = '" & DqlOldUserName & "' or " & _
			        "r_lock_owner = '" & DqlOldUserName & "') and " & _
				"r_lock_owner <> ' '"

Elle est transformée ainsi:

	QueryStr$ = "query,c,select r_object_id from dm_sysobject (all) where owner_name = '" & DqlOldUserName & "' and r_lock_owner <> ' ' " & _
				"union select r_object_id from dm_sysobject (all) where r_creator_name = '" & DqlOldUserName & "' and r_lock_owner <> ' ' " & _
				"union select r_object_id from dm_sysobject (all) where r_modifier = '" & DqlOldUserName & "' and r_lock_owner <> ' ' " & _
				"union select r_object_id from dm_sysobject (all) where acl_domain = '" & DqlOldUserName & "' and r_lock_owner <> ' ' " & _
				"union select r_object_id from dm_sysobject (all) where r_lock_owner = '" & DqlOldUserName & "'"

Il est évité de chercher les objets donc la valeur de r_lock_owner est l'ancien nom et est différent de chaîne vide, le second étant forcément vrai.

Modification dm_alias_set

La requête à modifier se trouve dans la fonction DmUpdateAliasSets:

  DmQuery = dmAPIGet("query,c,select r_object_id from dm_alias_set where " & _
                      "owner_name = '" & DqlOldUserName & "' or " & _
                      "any alias_value = '" & DqlOldUserName & "'")

Elle est transformée ainsi:

  DmQuery = dmAPIGet("query,c,select r_object_id from dm_alias_set where owner_name = '" & DqlOldUserName & "' " & _
                      "union select r_object_id from dm_alias_set where any alias_value = '" & DqlOldUserName & "'")

Modification dm_sysobject non vérrouillé

Les instances de dm_sysobject non vérouillées sont cherchées à partir de la requête suivante:

  QueryStr$ = "query,c,select r_object_id from " & _
              "dm_sysobject (all) where " & _
              "(owner_name = '" & DqlOldUserName & "' or " & _
              "r_creator_name = '" & DqlOldUserName & "' or " & _
              "r_modifier = '" & DqlOldUserName & "' or " & _
	      "acl_domain = '" & DqlOldUserName & "' or " & _
	      "r_lock_owner = '" & DqlOldUserName & "') and " & _
              "r_lock_owner = ' '"

Il est assez drôle de voir qu'un des filtres porte sur le nom à modifier dans l'attribut r_lock_owner, alors qu'il y a un filtre global indiquant que r_lock_owner doit être une chaîne vide. Bien entendu, cette erreur, sans grand impact, ne sera pas reproduite dans le cadre de cette optimisation.

La requête est transformée ainsi:

  QueryStr$ = "query,c,select r_object_id from dm_sysobject (all) where owner_name = '" & DqlOldUserName & "' and r_lock_owner = ' ' " & _
              "union select r_object_id from dm_sysobject (all) where r_creator_name = '" & DqlOldUserName & "' and r_lock_owner = ' ' " & _
              "union select r_object_id from dm_sysobject (all) where r_modifier = '" & DqlOldUserName & "' and r_lock_owner = ' ' " & _
              "union select r_object_id from dm_sysobject (all) where acl_domain = '" & DqlOldUserName & "' and r_lock_owner = ' '"

Modification dm_sysobject vérrouillé

Les instances de dm_sysobject vérouillées sont cherchées à partir de la requête suivante:

  QueryStr$ = "query,c,select r_object_id from " & _
              "dm_sysobject (all) where " & _ 
              "(owner_name = '" & DqlOldUserName & "' or " & _
              "r_creator_name = '" & DqlOldUserName & "' or " & _ 
              "r_modifier = '" & DqlOldUserName & "' or " & _ 
	      "acl_domain = '" & DqlOldUserName & "' or " & _
	      "r_lock_owner = '" & DqlOldUserName & "') and " & _
              "r_lock_owner <> ' '"

Cette requête est similaire à celle pour les instances non vérouillées. De plus, comme pour la suppression du verrou, il sera évité de chercher les objets donc la valeur de r_lock_owner est l'ancien nom et est différente de chaîne vide, le second étant forcément vrai.

La requête est transformée ainsi:

  QueryStr$ = "query,c,select r_object_id from dm_sysobject (all) where owner_name = '" & DqlOldUserName & "' r_lock_owner <> ' ' " & _
              "union select r_object_id from dm_sysobject (all) where r_creator_name = '" & DqlOldUserName & "' r_lock_owner <> ' ' " & _
              "union select r_object_id from dm_sysobject (all) where r_modifier = '" & DqlOldUserName & "' r_lock_owner <> ' ' " & _
              "union select r_object_id from dm_sysobject (all) where acl_domain = '" & DqlOldUserName & "' r_lock_owner <> ' ' " & _
              "union select r_object_id from dm_sysobject (all) where r_lock_owner = '" & DqlOldUserName & "'"

Modification dm_router

Les instances de dm_router sont identifiées dans la méthode DmUpdateRouters:

 DmQuery = dmAPIGet("query,c,select r_object_id from dm_router (all) " & _
              "where supervisor_name = '" & DqlOldUserName & "' or" & _
                     " any task_owner = '" & DqlOldUserName & "' or" & _
                     " any r_task_user = '" & DqlOldUserName & "'")

La requête est transformée ainsi:

 DmQuery = dmAPIGet("query,c,select r_object_id from dm_router (all) where supervisor_name = '" & DqlOldUserName & "' " & _
              "union select r_object_id from dm_router (all) where any task_owner = '" & DqlOldUserName & "' " & _
              "union select r_object_id from dm_router (all) where any r_task_user = '" & DqlOldUserName & "'"


Recherche dm_workflow

Les instances de dm_workflow sont identifiées dans la méthode DmUpdateDm_Wrkflws:

    DmQuery = dmAPIGet("query,c,select r_object_id " & _
                         "from dm_workflow where " & _
                         "r_creator_name = '" & DqlOldUserName & "' or " & _
                         "supervisor_name = '" & DqlOldUserName & "' or " & _
                         "any r_performers = '" & DqlOldUserName & "' or " & _
                         "any r_last_performer = '" & DqlOldUserName & "'")

La requête est transformée ainsi:

    DmQuery = dmAPIGet("query,c,select r_object_id from dm_workflow where r_creator_name = '" & DqlOldUserName & "' " & _
                         "union select r_object_id from dm_workflow where supervisor_name = '" & DqlOldUserName & "' " & _
                         "union select r_object_id from dm_workflow where any r_performers = '" & DqlOldUserName & "' " & _
                         "union select r_object_id from dm_workflow where any r_last_performer = '" & DqlOldUserName & "'")

Recherche dmi_workitem

Les instances de dmi_workitem sont identifiées dans la méthode DmUpdateDm_Wrkitm:

 DmQuery = dmAPIGet("query,c,select r_object_id " & _
                     "from dmi_workitem where " & _
                     "r_performer_name = '" & DqlOldUserName & "' or " & _
                     "any r_ext_performer = '" & DqlOldUserName & "'")

La requête est transformée ainsi:

 DmQuery = dmAPIGet("query,c,select r_object_id from dmi_workitem where r_performer_name = '" & DqlOldUserName & "' " & _
                     "union select r_object_id from dmi_workitem where any r_ext_performer = '" & DqlOldUserName & "'")

Recherche dmi_queue_item

Les instances de dmi_queue_item sont identifiées dans la méthode DmUpdateDmiQueue:

 DmQuery = dmAPIGet("query,c,select r_object_id from dmi_queue_item " & _
                     "where " & _ 
                     "name            = '" & DqlOldUserName & "' or " & _
                     "sent_by         = '" & DqlOldUserName & "' or " & _
                     "supervisor_name = '" & DqlOldUserName & "' or " & _
                     "dequeued_by     = '" & DqlOldUserName & "' or " & _
                     "sign_off_user   = '" & DqlOldUserName & "'")

A noter, il est amusant de constater le formattage de la requête juste dans cette méthode, afin d'avoir le signe = sur la même colonne. Ce principe n'est quasiment repris nulle part, à croire qu'un développeur s'est amusé un jour... Mais bon ceci n'est pas très important.

La requête est transformée ainsi:

 DmQuery = dmAPIGet("query,c,select r_object_id from dmi_queue_item where name = '" & DqlOldUserName & "' " & _
                     "union select r_object_id from dmi_queue_item where sent_by = '" & DqlOldUserName & "' " & _
                     "union select r_object_id from dmi_queue_item where supervisor_name = '" & DqlOldUserName & "' " & _
                     "union select r_object_id from dmi_queue_item where dequeued_by = '" & DqlOldUserName & "' " & _
                     "union select r_object_id from dmi_queue_item where sign_off_user = '" & DqlOldUserName & "'")


Optimisation annexe

A la lecture du code, une modification est possible mais ne concernant pas les requêtes. Dans ce cas, c'est unique un principe de bonne pratique dans le language de programmation. Le code de la fonction DmUpdateDmiRegistry est le suivant:

Sub DmUpdateDmiRegistry()
  Dim ret As Integer
  Dim NumOfObj As String
  
  NumOfObj = countUsrRegistryObjs(DqlOldUserName)
  Call DmPrint("dmi_registry objects " & _
    "referencing user " & DqlOldUserName & " are " & countUsrRegistryObjs(DqlOldUserName) , "information")      
 
  If NumOfObj <> "0" Or NumOfObj <> "" Then
    Call DmRegisterForNewUser()
    Call DmPrint("dmi_registry objects " & _
      "referencing user " & DqlNewUserName & " are " & countUsrRegistryObjs(DqlNewUserName) , "information")      
  End If
  
End Sub

Il paraît évident que le deuxième appel à countUsrRegistryObjs(DqlOldUserName) est inutile puisque son résultat a déjà été stocké dans la variable NumOfObj. La fonction est transformée ainsi:

Sub DmUpdateDmiRegistry()
  Dim ret As Integer
  Dim NumOfObj As String
  
  NumOfObj = countUsrRegistryObjs(DqlOldUserName)
'  Call DmPrint("dmi_registry objects " & _
'    "referencing user " & DqlOldUserName & " are " & countUsrRegistryObjs(DqlOldUserName) , "information")      
  Call DmPrint("dmi_registry objects " & _
    "referencing user " & DqlOldUserName & " are " & NumOfObj , "information")      
 
  If NumOfObj <> "0" Or NumOfObj <> "" Then
    Call DmRegisterForNewUser()
    Call DmPrint("dmi_registry objects " & _
      "referencing user " & DqlNewUserName & " are " & countUsrRegistryObjs(DqlNewUserName) , "information")      
  End If
  
End Sub


System-Install-icon.png Installation optimisation

Le fichier modifié est disponible, pour la version 6.7 SP1, ici userrename optimisé.ebs.gz. Attention, ce fichier n'est pas officiel et je ne pourrai être tenu responsable en cas de corruption des données, même si je ne vois pas pourquoi cela se produirait.

Il y a deux possibilités pour l'installer:

  • Soit le fichier vient remplacer celui installé en standard, en ayant fait une sauvegarde au préalable.
  • Soit, afin de conserver le support Editeur, il faut faire des copies du job dm_UserRename et de la méthode dm_UserRename.

La deuxième solution est conseillée et détaillée, ainsi il est possible de conserver le fonctionnement standard.

Copie du fichier

Après téléchargement du fichier userrename optimisé.ebs.gz, il faut en extratire le fichier userrename.ebs et le déposer sous le nom userrename_optimized.ebs dans le répertoire <DCTM INSTALL DIR>\install\admin\, dans le même emplacement que le fichier original.

Copie de la méthode

Il faut ensuite créer une nouvelle méthode serveur qui va utiliser ce nouveau fichier. Ceci s'effectue simplement en API:

API> set,c,l,object_name
SET> dm_UserRename_Optimized
...
OK
API> set,c,l,method_verb
SET> ..\install\admin\userrenam_optimized.ebd -eUserRename
...
OK
API> saveasnew,c,l
...
1000000180021121

Copie du job

Il faut ensuite créer un nouveau job lié avec cette nouvelle méthode serveur. Ceci s'effectue simplement en API:

API> retrieve,c,dm_job where object_name='dm_UserRename'
...
0800000180000373
API> get,c,l,method_name
...
dm_UserRename
API> set,c,l,object_name
SET> dm_UserRename_Optimized
...
OK
API> set,c,l,method_name
SET> dm_UserRename_Optimized
...
OK
API> saveasnew,c,l
...
080000018002112a

Par contre, il ne sera pas pris en compte lors de la demande de renommage sous DA. En effet, lorsqu'il est demandé d'effectuer le renommage tout de suite après la création de la demande, le job est recherché avec le nom dm_UserRename.

Enfin, il suffit de désactiver le job standard:

API> retrieve,c,dm_job where object_name='dm_UserRename'
...
0800000180000373
API> set,c,l,is_inactive
SET> T
...
OK
API> save,c,l
...
080000018002112a