|
7 min de lectura
|

Cómo cargar mil millones de recursos FHIR en Aidbox en 5 horas

Resumir este artículo con:
ChatGPTPerplexityClaudeGrok

Los grandes volúmenes de datos suelen implicar grandes problemas y mucho trabajo. Uno de esos problemas es cómo cargar una gran cantidad de datos. Generalmente existe alguna fuente de big data, y el problema se plantea como una migración de un sistema legado o una migración de datos históricos a un servidor FHIR.

La solución típica para este tipo de problemas consiste en construir un pipeline ETL (extraer, transformar, cargar):

Puntos clave de esta arquitectura:

  • Transformar y subir los datos al almacenamiento. Esto permite dividir el proceso completo en dos pasos: preparación de datos y carga de datos.
  • Comprimir los datos en el almacenamiento: los datos FHIR contienen muchas estructuras similares, p. ej. {:resourceType "Patient" … }, por lo que comprimir dichos datos resulta bastante eficiente y la carga neta puede reducirse hasta diez veces.

La tarea de transformación y almacenamiento de los datos entrantes depende en gran medida del estado del sistema para el que se realiza la importación, por lo que esta información se omitirá en este artículo. Sin embargo, necesitábamos una fuente de datos para nuestras pruebas. En consecuencia, se utilizó la herramienta Synthea, lo que generó 40 lotes, cada uno con aproximadamente 25.000 recursos Patient y sus recursos relacionados. El script de generación está disponible en el siguiente enlace: gen_synthea.sh.

Buenas prácticas de carga masiva FHIR

La tarea de cargar una gran cantidad de datos no es exactamente nueva y existen varias prácticas consolidadas para ello que han demostrado ser eficientes.

Desde la perspectiva de la transmisión de datos HTTP a través de la red, cargar los fragmentos de datos uno por uno no es evidentemente el mejor enfoque, ya que genera una sobrecarga significativa en la inicialización de la conexión, la recepción de la respuesta, etc. La transmisión de datos mediante lotes puede reducir drásticamente los costes de sobrecarga y acelerar considerablemente la transferencia de datos. Mejoras adicionales llegan con el streaming, donde todos los costes de sobrecarga desaparecen.

Utilice el comando PostgreSQL COPY para cargar todas las filas en un único comando, en lugar de emplear una serie de comandos INSERT. El comando COPY está optimizado para cargar grandes cantidades de filas. Es menos flexible que INSERT, pero genera una sobrecarga significativamente menor para cargas de datos grandes.

Los índices de base de datos pueden ralentizar la velocidad de inserción, ya que cada inserción en una tabla implica actualizar todos sus índices para mantener la transaccionalidad. Crear índices sobre datos existentes es mucho más rápido que las actualizaciones incrementales en cada inserción. Por este motivo, es una buena práctica desactivar (o eliminar) todos los índices de tabla existentes antes de cargar los datos, realizar la inserción mediante el comando COPY y reactivar (o recrear) los índices de tabla.

La particularidad de almacenar datos en Aidbox en forma de JSONB y los recursos FHIR de gran tamaño pueden ocasionar dificultades con TOAST. En esencia, el problema surge cuando los datos no caben en la página de PostgreSQL y el motor de base de datos comienza a transferir parte de los datos a la tabla asignada por TOAST. En cuanto a la lectura y escritura posteriores, estas se realizan internamente sobre dos tablas: la asignada por TOAST y la estándar.

Existen varios métodos para reducir el toasting:

  • configuración explícita del almacenamiento
  • aumento del límite de compresión

ALTER TABLE <target_table> ALTER COLUMN resource SET STORAGE MAIN; ALTER TABLE <target_table> SET (toast_tuple_target = 6000);

Operación de carga desde bucket

Aidbox dispone de la operación load-from-bucket que permite la carga asíncrona de recursos FHIR desde un bucket de S3. Esta operación ha sido diseñada específicamente para la carga paralela por lotes de grandes volúmenes de datos FHIR.

Cómo funciona:

  • Obtiene la lista de archivos del bucket
  • Desactiva y guarda temporalmente todos los índices de las tablas de recursos de destino
  • Ejecuta n hilos de carga en paralelo
  • Para cada hilo, abre el stream desde uno de los archivos del bucket S3 para la carga y, en el otro extremo, abre el stream de PostgreSQL COPY
  • Convierte los recursos en paralelo en el stream
  • Una vez cargados todos los recursos, reactiva todos los índices

Nota: La desactivación de índices se aplica cuando se especifica el indicador disable-idx?. No se recomienda utilizar la operación de carga desde bucket con el indicador disable-idx? en producción en vivo, ya que esta operación desactiva todos los índices de las tablas de destino.

Entorno

Infraestructura para esta prueba:

  • Aidbox instalado en una instancia AWS m5.4xlarge con 16 vCPU
  • Base de datos: AWS RDS Aurora en una instancia db.r5.4xlarge con 16 vCPU

Experimento

En primer lugar, intentemos cargar aproximadamente el 10 % de los datos y comprobar cómo responde Aidbox:

  • Tamaño total de carga (comprimido): 17 GB
  • Número total de recursos: 93 millones
  • Tamaño resultante de la base de datos: 171 GB

Ejemplo de llamada a la operación RPC load-from-bucket de Aidbox:

POST /rpc content-type: text/yaml accept: text/yaml

method: aidbox.bulk/load-from-bucket params: bucket: s3:// prefixes: ["fhir/1/", "fhir/2/", "fhir/3/", "fhir/4/"] thread-num: 16 disable-idx?: true account: access-key-id: secret-access-key: region:

Ejecutemos las importaciones con distintos números de hilos (1, 2, 4, …, 16) y observemos cómo varían el tiempo de ejecución y los valores medios.

Como se aprecia en el gráfico, al aumentar el número de hilos de 1 a 4, el rendimiento general de carga de datos crece de forma casi lineal. El aumento adicional de hilos de 4 a 12 resulta poco significativo. El incremento adicional de 12 a 16 no afecta al crecimiento del rendimiento. Las limitaciones de rendimiento pueden deberse al ancho de banda de red, al rendimiento de la base de datos o a factores similares. En nuestro experimento no existían limitaciones ni de la base de datos ni del ancho de banda de red, siendo el rendimiento de Java el principal cuello de botella.

Rendimiento de importación por segundo según el tipo de recurso ejecutando 16 hilos para el 10 % de los datos

A partir de nuestras observaciones, concluimos que el rendimiento de carga de datos se ve afectado por la distribución de los datos según su volumen. Así, como se aprecia en el gráfico anterior, los recursos pequeños como Procedure u Observation pueden cargarse a una velocidad de hasta 40.000 recursos por segundo. Por otro lado, los recursos de gran tamaño como ExplanationOfBenefit solo pueden cargarse a una velocidad de hasta 5.300 recursos por segundo, lo que representa el 20 % del volumen total del conjunto de datos. Esta irregularidad en los datos acaba afectando considerablemente al rendimiento y al tiempo total del proceso de importación, por lo que puede variar enormemente entre proyectos.

¿Le interesan los detalles? Profundice en nuestro proceso y descubra cómo puede optimizar la carga de sus recursos FHIR. Más información aquí

Cargar mil millones de recursos FHIR

Basándonos en los experimentos anteriores, optamos por cargar los datos en 12 hilos para la importación final. El tiempo total para completar la importación fue de 4 horas y 48 minutos. Se crearon aproximadamente mil millones de recursos con un tamaño total de base de datos de 1.649 GB.

A continuación se muestra un resumen del resultado de la carga:

  • 4 h 48 min de tiempo de ejecución
  • 183 GB de tamaño total de datos comprimidos
  • 1.649 GB de tamaño resultante de la base de datos
  • Mil millones de recursos en total
  • 57.000 recursos por segundo de media
  • 64 datos de paciente por segundo

Rendimiento de importación por segundo según el tipo de recurso ejecutando 12 hilos para el 100 % de los datos

Este gráfico ilustra el rendimiento general de importación de la carga de datos. Puede observarse que la velocidad del proceso no es constante y puede variar entre 8.000 y 80.000 recursos por segundo en su punto máximo. El rendimiento medio fue de 57.000 recursos por segundo, un 10 % superior al de las pruebas anteriores.

Las caídas de velocidad más significativas se detectaron en el recurso Provenance. El tamaño medio de registro para este recurso era de 37 KB, lo cual es muy elevado. Los registros de este tamaño se procesan lentamente y generan complejidades relacionadas con la tecnología TOAST en Postgres. Las estadísticas finales de rendimiento dependen en gran medida de la estructura y el volumen de los datos: los recursos pequeños se cargan casi de forma instantánea, mientras que los grandes se cargan muy lentamente.

Distribución del recuento de recursos y tamaño total en las 15 tablas más grandes

El gráfico de distribución resulta especialmente interesante. Podemos observar que hay aproximadamente 300 Observations por cada recurso Patient de media, y los recursos que ocupan más espacio en disco son Claim, DiagnosticReport, DocumentReference y ExplanationOfBenefit. El recurso Provenance merece una atención especial. Con un número total de entradas de 1 millón, ocupa 40 GB de espacio en disco con un tamaño medio de recurso de 37 KB.

Utilización de CPU de Aidbox y Aurora

El gráfico de carga de CPU de Aidbox y Aurora muestra que durante prácticamente toda la duración de la importación de datos, la carga utilizó al máximo los 12 núcleos de Aidbox. Al mismo tiempo, el consumo medio de CPU para la base de datos Aurora fue del 54 %. En el gráfico puede apreciarse un aumento brusco en el consumo de CPU de la base de datos y una caída en el de Aidbox. Esto se debe a la transferencia de los grandes recursos Provenance. En ese momento, los hilos de Aidbox comenzaron a esperar la respuesta de la base de datos, mientras que esta estaba ocupada comprimiendo los datos para almacenarlos en TOAST.

Para experimentar con la carga de grandes conjuntos de datos en Aidbox y comprobar de primera mano su escalabilidad, pruebe la versión gratuita de Aidbox. Proporciona un entorno robusto para probar estas capacidades, ofreciendo todas las herramientas necesarias sin ninguna limitación de funcionalidades.

Conclusión

La solución más obvia no siempre es la óptima. A veces, para encontrar una solución eficaz, es necesario plantear el problema de forma diferente o asumir ciertos compromisos técnicos. En esta publicación ofrecemos nuestra visión de las mejores prácticas para cargar grandes conjuntos de datos en Aidbox.

Autores: Marat Surmashev, Georgii Ivannikov

Aidbox FHIR Platform Free Trial

Véase también: Escalado horizontal y vertical de Aidbox y Health Samurai Lab: Game of Pools.

Compartir este artículo
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

Get the latest articles on FHIR, interoperability, and healthcare IT.