Et si on passait au développement responsable et durable ?

ABAP

S’il est connu qu’un bon développement doit prendre en compte la maintenabilité (code clair, normé, commenté), il est moins fréquent que la gestion des ressources soit prise en compte, à savoir :

  1. Le temps de traitement total
  2. La quantité de requêtes vers la base de données
  3. La consommation mémoire sur le serveur applicatif

1/ Le temps de traitement

Il ne va attirer l'attention que s'il est pénalisant pour l'utilisateur ou s'il ne rentre pas dans le plan de production. Or ce n’est pas parce que le temps d’exécution parait acceptable qu’il l’est.
Par exemple, si un service passait 0,5 jour chaque semaine à faire manuellement un état synthétique très important sous Excel et que vous leur livrez un développement qui le fait en 1h, ils seront ravis et ne se demanderont pas si les 1h sont justifiés. Mais peut-être que le développement est particulièrement mal fait et qu'il aurait pu donner le même résultat en 1 min ?

De plus, un nouveau développement qui traite de nouvelles données a peut être un bon temps de réponse parce que la quantité de données à traiter est encore faible. Mais dans un an ou deux, il va peut-être devenir excessivement lent.

En quoi est-ce important de prendre cela en compte ?

Un temps de traitement inutilement long est un gaspillage de temps CPU au niveau serveur d’application, qui s’accompagne souvent d’une mauvaise gestion des accès à la base de données.

Comment procéder ?

Lancer le programme avec une grande quantité de données à traiter et utilisez la transaction SAT pour faire l'analyse du temps d'exécution. Portez votre attention sur les traitements qui prennent le plus de temps mais aussi sur les accès individuels à la base de données qui sont notablement long (vous avez peut-être des SELECT qui n'utilise pas la clef primaire (ou un index) alors qu'ils le pourraient).

2/ La quantité de requêtes vers la base de données

Chaque requête prend un temps minimal incompressible et occupe un canal d'échange avec la base de données.
Si vous récupérez un million de lignes avec un seul SELECT, cela sera beaucoup plus rapide et efficacement traité par la base de données que de faire une boucle applicative faisant un million de SELECT. Cela parait évident, mais on voit pourtant souvent ce 2ème cas dans des développements, que ce soit de manières évidentes (un SELECT / ENSELECT ou un SELECT dans une boucle LOOP) ou plus cachées (une boucle qui contient des appels – PERFORM, CALL FUNCTION – qui font des accès unitaires à la base de données).
Une solution classique est de charger dans des tables internes (donc dans la mémoire applicative) toutes les données nécessaires avec un minimum de requêtes puis de travailler uniquement avec les tables internes.
La limitation de cette solution c'est la mémoire disponible. Pour éviter un engorgement des I/O vers la base de données il ne faudrait pas entrainer une saturation de la mémoire.

Comment procéder ?

Utilisez le Code Inspector pour repérer tous les accès à la base de données problématiques.
Utilisez l'analyse de temps d'exécution (SAT) pour repérer les SELECT trop long.

3/ La consommation mémoire

Il est habituel de ne pas limiter (ou de mettre une limite très élevée) à la mémoire utilisable pour les traitements en arrière-plan et de mettre une limite assez basse pour les traitements en avant-plan. En effet, un traitement arrière-plan libère la mémoire à l'instant où il a fini son traitement, alors qu'en avant-plan la mémoire ne sera libérée que lorsque l'utilisateur quittera sa transaction (ou fermera sa session si le processus de travail associé a utilisé la Heap Memory et est passé en mode PRIVATE). Sans cette protection, une poignée d’utilisateur lançant des états très gourmands et les laissant ouvert pourraient monopoliser toute la mémoire du serveur.

Conséquences :

  • Les programmes utilisés en arrière-plan peuvent gaspiller une énorme quantité de mémoire sans que cela se voit. Jusqu'au jour où plusieurs traitements tournant en même temps arriveront à saturer complètement la mémoire et à planter.
  • Les programmes utilisés en avant-plan manipulant beaucoup de données risque de s'interrompre faute de mémoire. Dans le meilleur des cas, un incident sera ouvert, dans le pire les utilisateurs qui le peuvent se contenteront de lancer les états en arrière-plan.

Comment limiter la quantité de mémoire utilisée ?

a) Ne récupérer que les champs nécessaires

Il est aisé de créer une structure à l'image de la table source puis de faire un SELECT *. Mais il sera plus rapide et plus économique en mémoire de créer la structure avec uniquement les champs nécessaires et de ne ramener que ceux-là dans le SELECT. Cela évite aussi nombre de bug dû à l'accès à un mauvais champ au nom proche de celui attendu.

b) Ne ramenez que les entrées nécessaires

Un SELECT est parfois suivi d'une boucle de DELETE pour nettoyer la sélection sur un critère additionnel.
Il faut s’assurer que ce critère ne peut pas être ajouté directement dans le SELECT.

c) Jointures

Il est tentant de copier les tables standards en tables internes individuellement. Mais si une table interne ne sert qu'une fois dans un FOR ALL ENTRIESS, alors il y avait peut-être moyen de faire une seule sélection avec jointure.
La puissance des jointures peut permettre de se passer de récupérer les informations intermédiaires et cela économise de la mémoire.

d) Libérer la mémoire

A chaque fois que vous n'avez plus besoin d'une table interne, pensez à libérer la mémoire avec l'instruction FREE. L’instruction CLEAR vide les données de la table, mais ne libère pas la mémoire.

e) Eviter les copies de table

Quelques situations qui se rencontrent fréquemment dans les développements :

  • Vous avez besoin de faire une boucle LOOP sur une sous partie d'une table interne ?

Plutôt que de la copier et de faire un DELETE dans la copie, peut-être est-il possible de faire un LOOP avec un critère de sélection ou de faire un LOOP avec un test pour sauter les lignes inutiles (surtout si la partie de la table qui est supprimée est petite par rapport à la taille de la table).

  • Vous avez besoin de faire un FOR ALL ENTRIES sur une sous partie d'une table interne ?

Envisagez de recopier uniquement les colonnes nécessaires au SELECT FOR ALL ENTRIES (souvent 1 seule) dans une table interne temporaire.

  • Pour la performance, vous utilisez une table triée avec clef unique et vous avez besoin de la passer en paramètre à un module de fonction qui n'accepte que les table de type STANDARD.

Vous pouvez déclarer la table comme STANDARD et utiliser une clef secondaire unique et triée.

TYPES: BEGIN OF ty_mara,
       matnr TYPE vbrp-matnr,
       ...
     END OF ty_mara.
DATA lt_mara TYPE STANDARD TABLE OF ty_mara WITH UNIQUE SORTED KEY by matnr COMPONENTS matnr.

f) Utiliser une taille de paquet

Vous avez optimisé vos opérations, mais vous avez quand même un pic dans l'usage de la mémoire qui dépasse la mémoire disponible pendant le processus, alors que la taille du résultat reste dans les limites.
Vous pouvez définir une taille de paquet "n" et l'utiliser pour limiter soit la sélection des données primaires, soit les données récupérées à partir de ces données primaires via un SELECT ... INTO ... PACKAGE SIZE n ... / ENDSELECT.
Il faudra faire quelques tests pour trouver la bonne taille de paquet qui permettra de réduire suffisamment la consommation mémoire sans augmenter de trop le temps d'exécution.

g) Ne pas charger en mémoire ce qui y est déjà

Quand on prend l'habitude de faire de travailler avec les tables internes, il arrive qu'on en fasse trop.
Il ne faut pas oublier que certaines tables sont bufferisées et qu'un SELECT sur ces tables est aussi rapide qu'un READ sur une table interne. C'est surtout le cas dans le standard pour les tables de paramétrages et quelques petites tables de données très souvent accédées.
Mais peut-être avez-vous également des tables spécifiques de données qui interviennent dans beaucoup d’états et qui ont été bufferisées. Il convient de s'en assurer.


Poster un commentaire

Suivant A propos de ABAP 7.50 et 7.51

SAP News, tips and tricks