Partager les statistiques

Cette page explique comment détecter et déboguer les hotspots de votre base de données. Vous pouvez d'accéder à des statistiques sur les hotspots dans les fractionnements avec GoogleSQL et PostgreSQL.

Spanner stocke les données sous la forme d'un espace de clés contigu, trié par les clés primaires de vos tables et index. Une division est une plage de lignes un ensemble de tables ou un index. Le début du fractionnement est appelé début du fractionnement. La limite du fractionnement définit la fin du fractionnement. Le fractionnement inclut le début du fractionnement, mais pas la limite de fractionnement.

Dans Spanner, les hotspots sont des situations où un trop grand nombre de requêtes sont envoyées au même serveur, ce qui sature ses ressources et peut entraîner des latences élevées. Les divisions affectées par des hotspots sont appelées divisions actives ou divisions chaudes.

Statistique de point d'accès d'un fractionnement (identifié dans le système comme CPU_USAGE_SCORE) est une mesure de la charge sur un fractionnement limitées par les ressources disponibles sur le serveur. Cette mesure est fournie sous forme de pourcentage. Si plus de 50 % de la charge d'une division est limitée par les ressources disponibles, la division est considérée comme chaude. Si 100% de la charge lorsqu'une division est soumise à une contrainte, elle est considérée comme chaude.

Spanner utilise la répartition basée sur la charge pour répartir uniformément la charge de données sur les serveurs de l'instance. Les divisions chaudes et chaudes peuvent être déplacées entre les serveurs pour l'équilibrage de la charge ou peuvent être divisées en divisions plus petites. Toutefois, Spanner peut ne pas être en mesure d'équilibrer la charge, même après plusieurs tentatives de fractionnement, en raison d'anti-modèles dans l'application. Par conséquent, les points d'accès persistants qui durent au moins 10 minutes peuvent nécessiter un dépannage supplémentaire et des modifications d'application potentielles.

Les statistiques sur les divisions à chaud de Spanner vous aident à identifier les divisions où les hotspots se produisent. Vous pouvez ensuite modifier votre application ou schéma, si nécessaire. Vous pouvez récupérer ces statistiques Tables système SPANNER_SYS.SPLIT_STATS_TOP_MINUTE à l'aide d'instructions SQL

Disponibilité des statistiques de répartition à chaud

Spanner fournit les statistiques de division chaude dans le schéma SPANNER_SYS. Les données SPANNER_SYS ne sont disponibles que via les interfaces Google SQL et PostgreSQL. Toi peuvent utiliser les méthodes suivantes pour accéder à ces données:

Les API de lecture unique Spanner ne sont pas compatibles avec SPANNER_SYS.

Statistiques sur les divisions populaires

Le tableau suivant vous permet de suivre les fractionnements à chaud:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE : affiche les fractionnements actifs pendant des intervalles d'une minute.

Ces tables ont les propriétés suivantes :

  • Chaque table contient des données pour des intervalles de temps sans chevauchement de la durée spécifié par le nom de la table.
  • Les intervalles sont définis selon l'heure réelle :

    • Les intervalles d'une minute se terminent à la minute.
  • Après chaque intervalle, Spanner collecte les données de tous les serveurs, puis les met à disposition dans les tables SPANNER_SYS peu de temps après.

    Par exemple, à 11:59:30, les intervalles les plus récents disponibles pour les requêtes SQL sont les suivants :

    • 1 minute: 11:58:00 – 11:58:59
  • Spanner regroupe les statistiques par divisions.

  • Chaque ligne contient un pourcentage indiquant le degré de chaleur ou de chaleur d'un fractionnement, par chaque division pour laquelle Spanner enregistre des statistiques l'intervalle spécifié.

  • Si moins de 50% de la charge sur un fractionnement est limitée par la ressources, Spanner ne capture pas la statistique. Si Spanner ne peut pas stocker toutes les divisions à chaud pendant le le système donne la priorité aux divisions Pourcentage de CPU_USAGE_SCORE pendant l'intervalle spécifié. S'il y a aucune division renvoyée, cela indique l'absence de hotspots.

Schéma de la table

Le tableau suivant présente le schéma de la table pour les statistiques suivantes:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE
Nom de la colonne Type Description
INTERVAL_END TIMESTAMP Fin de l'intervalle de temps pendant lequel la division était active
SPLIT_START STRING Clé de début de la plage de lignes dans la division. Le début de la division peut également être <begin>, ce qui indique le début de l'espace de clés.
SPLIT_LIMIT STRING Clé de limite pour la plage de lignes de l'écran fractionné. La limite: clé peut également être <end>, indiquant la fin de l'espace clé|
CPU_USAGE_SCORE INT64 Pourcentage de divisions : CPU_USAGE_SCORE. Un pourcentage de CPU_USAGE_SCORE de 50 % indique la présence de divisions chaudes ou actives.
AFFECTED_TABLES STRING ARRAY Tables dont les lignes peuvent se trouver dans la division

Clés de début et de limite de fractionnement

Une division est une plage de lignes contiguës d'une base de données. Elle est définie par son début et les clés de limite. Une division peut être une seule ligne, une plage de lignes étroite ou une plage de lignes large, et peut inclure plusieurs tables ou index.

Les colonnes SPLIT_START et SPLIT_LIMIT identifient les clés primaires d'une division à chaud ou à chaud.

Exemple de schéma

Le schéma suivant est un exemple de table pour les sujets de cette page.

GoogleSQL

CREATE TABLE Users (
  UserId INT64 NOT NULL,
  FirstName STRING(MAX),
  LastName STRING(MAX),
) PRIMARY KEY(UserId);

CREATE INDEX UsersByFirstName ON Users(FirstName DESC);

CREATE TABLE Threads (
  UserId INT64 NOT NULL,
  ThreadId INT64 NOT NULL,
  Starred BOOL,
) PRIMARY KEY(UserId, ThreadId),
  INTERLEAVE IN PARENT Users ON DELETE CASCADE;

CREATE TABLE Messages (
  UserId INT64 NOT NULL,
  ThreadId INT64 NOT NULL,
  MessageId INT64 NOT NULL,
  Subject STRING(MAX),
  Body STRING(MAX),
) PRIMARY KEY(UserId, ThreadId, MessageId),
  INTERLEAVE IN PARENT Threads ON DELETE CASCADE;

CREATE INDEX MessagesIdx ON Messages(UserId, ThreadId, Subject),
INTERLEAVE IN Threads;

PostgreSQL

CREATE TABLE users
(
   userid    BIGINT NOT NULL PRIMARY KEY,-- INT64 to BIGINT
   firstname VARCHAR(max),-- STRING(MAX) to VARCHAR(MAX)
   lastname  VARCHAR(max)
);

CREATE INDEX usersbyfirstname
  ON users(firstname DESC);

CREATE TABLE threads
  (
    userid   BIGINT NOT NULL,
    threadid BIGINT NOT NULL,
    starred  BOOLEAN, -- BOOL to BOOLEAN
    PRIMARY KEY (userid, threadid),
    CONSTRAINT fk_threads_user FOREIGN KEY (userid) REFERENCES users(userid) ON
    DELETE CASCADE -- Interleave to Foreign Key constraint
  );

CREATE TABLE messages
  (
    userid    BIGINT NOT NULL,
    threadid  BIGINT NOT NULL,
    messageid BIGINT NOT NULL PRIMARY KEY,
    subject   VARCHAR(max),
    body      VARCHAR(max),
    CONSTRAINT fk_messages_thread FOREIGN KEY (userid, threadid) REFERENCES
    threads(userid, threadid) ON DELETE CASCADE
  -- Interleave to Foreign Key constraint
  );

CREATE INDEX messagesidx ON messages(userid, threadid, subject), REFERENCES
threads(userid, threadid);

Imaginez que votre espace clé ressemble à ceci:

PRIMARY KEY
<begin>
Users()
Threads()
Users(2)
Users(3)
Threads(3)
Threads(3,"a")
Messages(3,"a",1)
Messages(3,"a",2)
Threads(3, "aa")
Users(9)
Users(10)
Threads(10)
UsersByFirstName("abc")
UsersByFirstName("abcd")
<end>

Exemple de fractionnement

Vous trouverez ci-dessous quelques exemples de fractionnements pour vous aider à comprendre à quoi ils ressemblent.

Les éléments SPLIT_START et SPLIT_LIMIT peuvent indiquer la ligne d'un tableau ou d'un index, ou <begin> et <end>, qui représentent les limites de la clé de la base de données. Les SPLIT_START et SPLIT_LIMIT peuvent également contenir des clés tronquées, qui sont des clés précédant toute clé complète dans la table. Par exemple, Threads(10) est un préfixe pour toute ligne Threads entrelacée dans Users(10).

SPLIT_START SPLIT_LIMIT AFFECTED_TABLES EXPLICATION
Users(3) Users(10) UsersByFirstName, Users, Threads, Messages et MessagesIdx La division commence à la ligne avec UserId=3 et se termine à la ligne précédente avec UserId = 10. La division contient les lignes de la table Users et toutes les lignes de ses tables entrelacées pour UserId=3 à 10.
Messages(3,"a",1) Threads(3,"aa") Threads, Messages, MessagesIdx Le fractionnement commence à la ligne contenant UserId=3, ThreadId="a" et MessageId=1 et se termine à la ligne précédant la ligne avec la clé UserId=3 et ThreadsId = "aa". La répartition contient toutes les tables entre Messages(3,"a",1) et Threads(3,"aa"). Comme les éléments split_start et split_limit sont entrelacés dans la même ligne de table racine, la division contient les lignes entrelacées entre le début et la limite. Consultez schemas-overview pour comprendre comment les tables entrelacées sont colocalisées.
Messages(3,"a",1) <end> UsersByFirstName, Users, Threads, Messages et MessagesIdx La division commence dans la table des messages à la ligne avec la clé UserId=3, ThreadId="a" et MessageId=1. La division héberge toutes les lignes de split_start à <end>, soit la fin de l'espace de clés de la base de données. Toutes les lignes des tables suivant la split_start, comme Users(4), sont incluses dans la division.
<begin> Users(9) UsersByFirstName, Users, Threads, Messages et MessagesIdx La division commence à <begin>, au début de l'espace clé de la base de données, et se termine à la ligne précédant la ligne Users par UserId=9. La division comprend donc toutes les lignes de la table précédant Users, toutes les lignes de la table Users précédant UserId=9 et les lignes de ses tables entrelacées.
Messages(3,"a",1) Threads(10) UsersByFirstName, Users, Threads, Messages et MessagesIdx La division commence à Messages(3,"a", 1) entrelacée dans Users(3) et se termine à la ligne précédant Threads(10). Threads(10) est une clé de fractionnement tronquée qui est un préfixe de n'importe quelle clé de la table de threads entrelacée dans Users(10).
Users() <end> UsersByFirstName, Users, Threads, Messages et MessagesIdx La division commence à la clé de division tronquée Users(), qui précède toute clé complète de la table Users. La division s'étend jusqu'à la fin de l'espace clé possible dans la base de données. Les tables concernées couvrent donc la table Users, ses index et ses tables entrelacées, ainsi que toutes les tables qui peuvent apparaître après les utilisateurs.
Threads(10) UsersByFirstName("abc") UsersByFirstName, Users, Threads, Messages et MessagesIdx La division commence à la ligne Threads avec UserId = 10 et se termine à l'index, UsersByFirstName à la clé précédant "abc".

Exemples de requêtes pour trouver des divisions populaires

L'exemple suivant montre une instruction SQL que vous pouvez utiliser pour récupérer les statistiques de répartition à chaud. Vous pouvez exécuter ces instructions SQL les bibliothèques clientes, gcloud ou la console Google Cloud.

GoogleSQL

SELECT t.split_start,
       t.split_limit,
       t.cpu_usage_score,
       t.affected_tables,
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end =
  (SELECT MAX(interval_end)
  FROM    SPANNER_SYS.SPLIT_STATS_TOP_MINUTE)
ORDER BY  t.cpu_usage_score DESC;

PostgreSQL

SELECT t.split_start,
       t.split_limit,
       t.cpu_usage_score,
       t.affected_tables
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end = (
  SELECT MAX(interval_end)
  FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE
)
ORDER BY t.cpu_usage_score DESC;

La sortie de la requête se présente comme suit :

SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE AFFECTED_TABLES
Users(13) Users(76) 82 Messages,Users,Threads
Users(101) Users(102) 90 Messages,Users,Threads
Threads(10, "a") Threads(10, "aa") 100 Messages,Threads
Messages(631, "abc", 1) Messages(631, "abc", 3) 100 Messages
Threads(12, "zebra") Users(14) 76 Messages,Users,Threads
Users(620) <end> 100 Messages,Users,Threads

Conservation des données pour les statistiques de répartition à chaud

Spanner conserve au minimum les données de chaque table pour les éléments suivants : période:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: intervalles qui couvrent au cours des 6 dernières heures.

Résoudre les problèmes liés aux points d'accès à l'aide des statistiques de fractionnement à chaud

Cette section explique comment détecter les zones cliquables et résoudre les problèmes associés.

Sélectionnez une période à examiner

Vérifiez les métriques de latence de votre base de données Spanner pour trouver la période pendant laquelle votre application a enregistré une latence et une utilisation du processeur élevées. Par exemple, il peut vous indiquer qu'un problème a commencé vers 22h50 le 18 mai 2024.

Identifier le hotspotting persistant

Étant donné que Spanner équilibre la charge avec la répartition basée sur la charge, nous vous recommandons de déterminer si le hotspotting s'est produit pendant plus de 10 minutes. Pour ce faire, interrogez la table SPANNER_SYS.SPLIT_STATS_TOP_MINUTE, comme illustré dans l'exemple suivant :

GoogleSQL

SELECT Count(DISTINCT t.interval_end)
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.utilization >= 50
  AND  t.interval_end >= "interval_end_date_time"
  AND  t.interval_end <= "interval_end_date_time";

Remplacez interval_end_date_time par la date et l'heure du intervalle, au format 2024-05-18T17:40:00Z.

PostgreSQL

SELECT COUNT(DISTINCT t.interval_end)
FROM   SPLIT_STATS_TOP_MINUTE t
WHERE  t.utilization >= 50
  AND  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

Remplacez interval_end_date_time par la date et l'heure du intervalle, au format 2024-05-18T17:40:00Z.

Si le résultat de la requête précédente est égal à 10, cela signifie que votre base de données présente un point chaud qui peut nécessiter un débogage supplémentaire.

Trouver les écrans fractionnés ayant le niveau CPU_USAGE_SCORE le plus élevé

Pour cet exemple, nous exécutons la requête SQL suivante pour trouver les plages de lignes avec le niveau CPU_USAGE_SCORE le plus élevé :

GoogleSQL

SELECT t.split_start,
       t.split_limit,
       t.affected_tables,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.cpu_usage_score >= 50
  AND  t.interval_end = "interval_end_date_time";

Remplacez interval_end_date_time par la date et l'heure du intervalle, au format 2024-05-18T17:40:00Z.

PostgreSQL

SELECT t.split_start,
       t.split_limit,
       t.affected_tables,
       t.cpu_usage_score
FROM   SPLIT_STATS_TOP_MINUTE t
WHERE  t.cpu_usage_score = 100
  AND  t.interval_end = 'interval_end_date_time'::timestamptz;

Remplacez interval_end_date_time par la date et l'heure de l'intervalle, au format 2024-05-18T17:40:00Z.

Le code SQL précédent génère le résultat suivant:

SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE AFFECTED_TABLES
Users(180) <end> 85 Messages,Users,Threads
Users(24) Users(76) 76 Messages,Users,Threads

Ce tableau de résultats montre que des hotspots sont apparus sur deux écrans fractionnés. La répartition basée sur la charge dans Spanner peut tenter de résoudre les hotspots. sur ces écrans fractionnés. Toutefois, il est possible qu'elle ne puisse pas le faire des schémas problématiques dans le schéma ou la charge de travail. Pour détecter les divisions nécessitant une intervention, nous vous recommandons de suivre les temps de passage minutes. Par exemple, la requête SQL suivante suit la première division au cours des dix dernières minutes.

GoogleSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.split_start = "users(180)"
  AND  t.split_limit = "<end>"
  AND  t.interval_end >= "interval_end_date_time"
  AND  t.interval_end <= "interval_end_date_time";

Remplacez interval_end_date_time par la date et l'heure du intervalle, au format 2024-05-18T17:40:00Z.

PostgreSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.split_start = 'users(180)'
  AND  t.split_limit = ''
  AND  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

Remplacez interval_end_date_time par la date et l'heure de l'intervalle, au format 2024-05-18T17:40:00Z.

Le code SQL précédent génère le résultat suivant:

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-18T17:46:00Z Users(180) <end> 85
2024-05-18T17:47:00Z Users(180) <end> 85
2024-05-18T17:48:00Z Users(180) <end> 85
2024-05-18T17:49:00Z Users(180) <end> 85
2024-05-18T17:50:00Z Users(180) <end> 85

On dirait que la fracture brûle ces dernières minutes. Vous pouvez observer la division plus longtemps pour déterminer que la division basée sur la charge de Spanner atténue le point chaud. Il peut y avoir des cas où Spanner ne peut plus équilibrer la charge.

Par exemple, interrogez SPANNER_SYS.SPLIT_STATS_TOP_MINUTE. tableau. Consultez les exemples de scénarios suivants.

GoogleSQL

SELECT t.interval_end,
      t.split_start,
      t.split_limit,
      t.cpu_usage_score
FROM  SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE t.interval_end >= "interval_end_date_time"
      AND t.interval_end <= "interval_end_date_time";

Remplacez interval_end_date_time par la date et l'heure du intervalle, au format 2024-05-18T17:40:00Z.

PostgreSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t._cpu_usage
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

Remplacez interval_end_date_time par la date et l'heure de l'intervalle, au format 2024-05-18T17:40:00Z.

Ligne active unique

Dans l'exemple suivant, il semble que Threads(10,"spanner") se trouve dans une seule division de ligne qui est restée active pendant plus de 10 minutes. Cela peut se produire lorsque il y a une charge persistante sur une ligne populaire.

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:41:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:42:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:43:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:44:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:45:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:46:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:47:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:48:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:49:00Z Threads(10,"spanner") Threads(10,"spanner1") 100
2024-05-16T20:50:00Z Threads(10,"spanner") Threads(10,"spanner1") 100

Spanner ne peut pas équilibrer la charge pour cette seule clé, car elle ne peut pas être divisée davantage.

Déplacement du point d'accès...

Dans l'exemple suivant, la charge passe par des divisions contiguës au fil du temps, et passe à une nouvelle division à des intervalles de temps.

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(1,"a") Threads(1,"aa") 100
2024-05-16T20:41:00Z Threads(1,"aa") Threads(1,"ab") 100
2024-05-16T20:42:00Z Threads(1,"ab") Threads(1,"c") 100
2024-05-16T20:43:00Z Threads(1,"c") Threads(1,"ca") 100

Cela peut se produire, par exemple, si une charge de travail lit ou écrit des clés dans un ordre croissant de façon monotone. Spanner ne peut pas équilibrer la charge pour atténuer les effets de ce comportement de l'application.

Équilibrage de charge normal

Spanner tente d'équilibrer la charge en ajoutant d'autres divisions ou en les déplaçant. L'exemple suivant montre à quoi cela pourrait ressembler.

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(1000,"zebra") <end> 82
2024-05-16T20:41:00Z Threads(1000,"zebra") <end> 90
2024-05-16T20:42:00Z Threads(1000,"zebra") <end> 100
2024-05-16T20:43:00Z Threads(1000,"zebra") Threads(2000,"spanner") 100
2024-05-16T20:44:00Z Threads(1200,"c") Threads(2000) 92
2024-05-16T20:45:00Z Threads(1500,"c") Threads(1700,"zach") 76
2024-05-16T20:46:00Z Threads(1700) Threads(1700,"c") 76
2024-05-16T20:47:00Z Threads(1700) Threads(1700,"c") 50
2024-05-16T20:48:00Z Threads(1700) Threads(1700,"c") 39

Ici, la partition la plus large à 2024-05-16T17:40:00Z a été divisée en une plus petite fractionnement. Par conséquent, la statistique CPU_USAGE_SCORE a diminué. Spanner peut ne pas créer de divisions en lignes individuelles. Les écrans fractionnés mettez en miroir la charge de travail à l'origine de la statistique CPU_USAGE_SCORE élevée.

Si vous avez observé une division chaude persistante pendant plus de 10 minutes, consultez les bonnes pratiques pour atténuer les hotspots.

Bonnes pratiques pour atténuer les points chauds

Si l'équilibrage de charge ne réduit pas la latence, l'étape suivante consiste à identifier la cause des points chauds. Ensuite, vous pouvez réduire la charge de travail de point chaud ou optimiser le schéma et la logique de l'application pour éviter les points chauds.

Identifier la cause

  • Utilisez les insights sur les verrouillages et les transactions pour rechercher les transactions dont le temps d'attente de verrouillage est élevé, où la clé de début de la plage de lignes se trouve dans la division active.

  • Utilisez Query Insights pour rechercher les requêtes qui lisent des données depuis la table qui contient la division à chaud, et ont récemment augmenté la latence ou un ratio plus élevé de latence au processeur.

  • Utilisez Requêtes actives les plus anciennes pour rechercher les requêtes qui lisent à partir du tableau contenant la division chaude et qui ont une latence supérieure à celle attendue.

Voici quelques cas particuliers à prendre en compte :

  • Vérifiez si la valeur TTL (Time To Live) a été activée récemment. S'il y a de nombreuses divisions à partir d'anciennes données, la valeur TTL peut augmenter CPU_USAGE_SCORE lors des suppressions en masse. Dans ce cas, le problème devrait se résoudre lui-même une fois les suppressions initiales sont terminées.

Optimiser la charge de travail

Étape suivante