Les mégadonnées signifient généralement de gros problèmes et beaucoup de travail. L'un de ces problèmes est la façon de charger une grande quantité de données. Il existe généralement une source de mégadonnées, et le problème se pose sous forme de migration d'un système hérité ou de migration de données historiques vers un serveur FHIR.
La solution typique pour ce type de problème consiste à construire un pipeline ETL (extraction, transformation, chargement) :

Points clés de cette architecture :
- Transformer et téléverser les données vers le stockage. Cela nous permet de diviser l'ensemble du processus en deux étapes : la préparation des données et le téléversement des données.
- Compresser les données en stockage : les données FHIR contiennent de nombreuses structures similaires, p. ex. {:resourceType "Patient" … }, de sorte que la compression de ces données est assez efficace, et la charge réseau peut être réduite jusqu'à dix fois en conséquence.
La tâche de transformation et de stockage des données entrantes dépend fortement de l'état du système pour lequel l'importation est effectuée; cette information sera donc omise de cet article. Nous avions néanmoins besoin d'une source de données pour nos tests. Par conséquent, l'outil Synthea a été utilisé, ce qui a produit 40 lots contenant chacun environ 25 000 ressources Patient et leurs ressources associées. Le script de génération est accessible via le lien suivant : gen_synthea.sh.
Meilleures pratiques pour le chargement massif FHIR
La tâche de chargement d'une grande quantité de données n'est pas vraiment nouvelle et il existe un certain nombre de pratiques bien établies pour cette tâche qui se sont avérées efficaces.
Du point de vue de la transmission de données HTTP par le réseau, charger les données pièce par pièce n'est évidemment pas la meilleure approche, car cela entraîne une surcharge importante lors de l'initialisation de la connexion, de la réception de la réponse, etc. La transmission de données par lots peut réduire considérablement les coûts de surcharge et accélérer significativement le transfert de données. Des améliorations supplémentaires viennent avec la diffusion en continu, où tous les coûts de surcharge disparaissent.
Utilisez la commande PostgreSQL COPY pour charger toutes les lignes en une seule commande, plutôt qu'une série de commandes INSERT. La commande COPY est optimisée pour le chargement d'un grand nombre de lignes. Elle est moins flexible qu'INSERT, mais entraîne une surcharge nettement moindre pour les chargements de données importants.
Les index de base de données peuvent ralentir la vitesse d'insertion, car chaque insertion dans une table implique la mise à jour de tous les index de cette table pour maintenir la transactionnalité. Créer des index sur des données existantes est beaucoup plus rapide que les mises à jour incrémentielles à chaque insertion. Pour cette raison, il est recommandé de désactiver (ou supprimer) tous les index de table existants avant de charger des données, d'effectuer une insertion via la commande COPY, puis de réactiver (ou recréer) les index de table.
La particularité du stockage des données dans Aidbox sous forme de JSONB et les très grandes ressources FHIR peuvent causer des difficultés avec TOAST. En substance, le problème survient lorsque les données ne tiennent pas dans la page PostgreSQL et que le moteur de base de données commence à transférer une partie des données dans la table allouée par TOAST. En ce qui concerne la lecture et l'écriture subséquentes, celles-ci sont effectuées en interne pour deux tables : la table allouée par TOAST et la table standard.
Il existe un certain nombre de méthodes pour réduire le recours au TOAST :
- configuration explicite du stockage
- augmentation de la limite de compression
ALTER TABLE <target_table> ALTER COLUMN resource SET STORAGE MAIN; ALTER TABLE <target_table> SET (toast_tuple_target = 6000);
Opération de chargement depuis un compartiment
Aidbox dispose d'une opération load-from-bucket qui permet le téléversement asynchrone de ressources FHIR depuis un compartiment S3. Cette opération a été spécialement conçue pour le chargement par lots parallèles de grandes quantités de données FHIR.
Son fonctionnement :
- Récupérer la liste des fichiers du compartiment
- Désactiver et sauvegarder temporairement tous les index des tables de ressources cibles
- Exécuter n fils d'exécution de chargement en parallèle
- Pour chaque fil, ouvrir le flux depuis l'un des fichiers du compartiment S3 à charger, et de l'autre côté ouvrir le flux PostgreSQL COPY
- Convertir les ressources en parallèle dans le flux
- Après le chargement de toutes les ressources, réactiver tous les index
Remarque : La désactivation des index s'applique lorsque l'indicateur disable-idx? est spécifié. L'opération de chargement depuis un compartiment avec l'indicateur disable-idx? n'est pas recommandée en production active, car cette opération désactive tous les index pour les tables cibles.
Environnement
Infrastructure pour ce test :
- Aidbox installé sur une instance AWS m5.4xlarge avec 16 vCPU
- Base de données : AWS RDS Aurora sur une instance db.r5.4xlarge avec 16 vCPU
Expérimentation
Tout d'abord, essayons de charger environ 10 % des données et vérifions comment Aidbox réagira :
- Taille totale chargée (compressée) : 17 Go
- Nombre total de ressources : 93 millions
- Taille résultante de la base de données : 171 Go
Exemple d'appel de l'opération RPC load-from-bucket d'Aidbox :
POST /rpc content-type: text/yaml accept: text/yaml
method: aidbox.bulk/load-from-bucket
params:
bucket: s3://
Exécutons des importations avec différents nombres de fils d'exécution (1, 2, 4, ..., 16) et observons comment le temps d'exécution et les valeurs moyennes évolueront.

Comme on peut le voir sur le graphique, avec l'augmentation des fils d'exécution de 1 à 4, la performance générale de chargement des données croît de façon presque linéaire. L'augmentation supplémentaire de 4 à 12 fils devient peu significative. L'augmentation additionnelle de 12 à 16 fils n'a aucun effet sur la croissance des performances. Les limites de performance peuvent être causées par la bande passante réseau, le débit de la base de données ou des facteurs similaires. Dans notre expérience, il n'y avait aucune limitation ni de la base de données ni de la bande passante réseau, et les performances Java constituaient le principal goulot d'étranglement.
Débit d'importation par seconde par type de ressource avec 16 fils d'exécution pour 10 % des données
D'après nos observations, nous concluons que la performance de chargement des données est influencée par la distribution des données par volume. Ainsi, d'après le graphique ci-dessus, il est clair que les petites ressources comme Procedure ou Observation peuvent être chargées à une vitesse allant jusqu'à 40 000 ressources par seconde. En revanche, les ressources volumineuses comme ExplanationOfBenefit ne peuvent être chargées qu'à une vitesse maximale de 5 300 ressources par seconde, représentant 20 % du volume total de l'ensemble de données. Cette irrégularité des données finit par affecter considérablement le débit et le temps total du processus d'importation des données, et peut donc varier grandement d'un projet à l'autre.
Curieux des détails ? Plongez plus en profondeur dans notre processus et découvrez comment vous pouvez optimiser le chargement de vos ressources FHIR. En savoir plus ici
Chargement de 1 milliard de ressources FHIR
Sur la base des expériences ci-dessus, nous avons choisi de charger les données avec 12 fils d'exécution pour l'importation finale des données. Le temps total pour compléter l'importation a été de 4 heures et 48 minutes. Environ 1 milliard de ressources ont été créées avec une taille totale de base de données de 1 649 Go.
Voici un résumé des résultats du téléchargement :
- 4 h 48 min de temps d'exécution
- 183 Go de taille totale des données compressées
- 1 649 Go de taille résultante de la base de données
- 1 milliard de ressources au total
- 57 000 ressources en moyenne par seconde
- 64 dossiers patients par seconde
Débit d'importation par seconde par type de ressource avec 12 fils d'exécution pour 100 % des données
Ce graphique illustre le débit général d'importation du téléversement des données. On peut observer que la vitesse du processus n'est pas constante et peut varier de 8 000 à 80 000 ressources par seconde à son pic. Le débit moyen était de 57 000 ressources par seconde, soit 10 % de plus que lors des tests précédents.
Les baisses de vitesse les plus significatives ont été détectées pour la ressource Provenance. La taille moyenne d'un enregistrement pour cette ressource était de 37 Ko, ce qui est très volumineux. Les enregistrements de cette taille sont traités lentement et causent des complexités liées à la technologie TOAST dans Postgres. Les statistiques finales de débit dépendent fortement de la structure et du volume des données : les petites ressources sont chargées presque instantanément, tandis que les grandes sont chargées très lentement.
Distribution du nombre de ressources et de la taille totale pour les 15 plus grandes tables
Le graphique de distribution est particulièrement intéressant. On peut observer qu'il y a environ 300 Observations par ressource Patient en moyenne, et que les ressources qui occupent le plus d'espace disque sont Claim, DiagnosticReport, DocumentReference et ExplanationOfBenefit. La ressource Provenance mérite une attention particulière. Avec un nombre total d'entrées de 1 million, elle occupe 40 Go d'espace disque avec une taille moyenne de ressource de 37 Ko.
Utilisation du processeur par Aidbox et Aurora
Le graphique de charge du processeur d'Aidbox et d'Aurora montre que pendant presque toute la durée de l'importation des données, la charge utilisait pleinement les 12 cœurs d'Aidbox. En même temps, la consommation moyenne du processeur pour la base de données Aurora était de 54 %. Sur le graphique, on peut voir une forte augmentation de la consommation du processeur de la base de données et une baisse pour Aidbox. Cela est dû au transfert des grandes ressources Provenance. À ce moment, les fils d'exécution d'Aidbox ont commencé à attendre une réponse de la base de données, tandis que la base de données elle-même était occupée à compresser les données pour les stocker dans TOAST.
Pour expérimenter le chargement de grands ensembles de données dans Aidbox et constater sa capacité de mise à l'échelle par vous-même, essayez la version gratuite d'Aidbox. Elle fournit un environnement robuste pour tester ces capacités, offrant tous les outils nécessaires sans aucune limitation de fonctionnalités.
Conclusion
La solution la plus évidente n'est pas toujours la plus optimale. Parfois, pour trouver une solution efficace, il faut poser le problème différemment ou faire certains compromis techniques. Dans cet article, nous proposons notre vision des meilleures pratiques pour téléverser d'immenses ensembles de données dans Aidbox.
Auteurs : Marat Surmashev, Georgii Ivannikov
Voir aussi : Mise à l'échelle horizontale et verticale d'Aidbox et Health Samurai Lab : Game of Pools.









