|
9 min de lectura
|

$batch-validate: Valide todos los recursos almacenados frente a sus perfiles, a escala

Resumen del artículo

$batch-validate comprueba todos los recursos de un tipo ya almacenados en Aidbox frente a su esquema FHIR y a cualquier perfil que usted indique, de forma síncrona o asíncrona. Los resultados se agregan en un formato compacto e indexado por incidencias, de modo que validar 100 GB de datos no conformes no añade 100 GB a su base de datos. Disponible desde Aidbox 2607.

Resumir este artículo con:
ChatGPTPerplexityClaudeGrok

Un servidor FHIR acumula registros médicos complejos y profundamente estructurados procedentes de todos los sistemas que lo alimentan, y es fácil asumir que todos son correctos. Confiar en los datos es una cosa; poder verificarlos —de forma robusta y a escala— es otra. Con millones de recursos, la validación manual de cada uno resulta tediosa y probablemente poco razonable. Y rara vez se valida una sola vez, porque los perfiles no son estáticos. Las guías de implementación como US Core y las guías HL7 Da Vinci publican nuevas versiones con regularidad, cada una de las cuales añade elementos, estrecha las cardinalidades o modifica los conjuntos de valores a los que se vinculan. Cada vez que adopta una nueva versión de perfil —o publica una propia— regresa la misma pregunta: qué parte de lo que ya almacena sigue siendo conforme. ¿Cómo verifica todo eso, de forma repetida, sin que se convierta en un proyecto en sí mismo?

La operación $validate de FHIR podría, en teoría, automatizarse, pero habría que obtener cada recurso y enviarlo de vuelta mediante POST, recogiendo y filtrando los resultados. Este enfoque tiene muchos problemas y es difícil de escalar. Lo ideal sería poder pedirle al servidor que valide un tipo de recurso específico e informarle de los errores en una sola llamada, con una capacidad adecuada para escalar tanto horizontal como verticalmente.

Eso es exactamente lo que hace $batch-validate. Se incluye en Aidbox 2607 y reemplaza por completo la anterior API de validación por lotes.

Por qué reconstruimos la validación por lotes

Aidbox ha contado con validación asíncrona por lotes durante años, expuesta a través de un conjunto de RPCs (aidbox.validation/batch-validation y similares). Funcionaba, pero tenía varios problemas. Por un lado, cada error de validación se almacenaba como su propio recurso BatchValidationError. Además, era bastante lento, lo que convertía la validación de grandes cantidades de recursos en una tarea innecesariamente larga.

Por último, validar un conjunto de datos de gran tamaño podía generar tantos resultados que estos rivalaban en tamaño con los propios datos. Cien gigabytes de recursos no conformes podían producir algo cercano a cien gigabytes de recursos de error. El mecanismo al que se recurría para comprender un problema de calidad de datos empeoraba el problema de almacenamiento.

Además, era exclusivamente asíncrono, tenía forma de RPC en lugar de operación FHIR, y le entregaba un montón de recursos de error para consultar en vez de una respuesta directa.

$batch-validate conserva la parte positiva —validar lo que ya está almacenado, en paralelo— y corrige el resto.

Validación por lotes anterior$batch-validate
InterfazRPCs propietariosOperación FHIR (Parameters de entrada y salida)
ModosSolo asíncronoSíncrono o asíncrono
Almacenamiento de resultadosUn recurso BatchValidationError por errorUna fila por incidencia distinta más una tabla reducida de IDs de recursos
Coste de almacenamientoCrece con el número de erroresAcotado — los cuerpos de los recursos nunca se copian
SalidaUn montón de recursos de error para consultarResumen de incidencias ordenado por gravedad con desglose bajo demanda
EscalabilidadFijaFragmentos particionados por hash, en streaming, paralelos entre nodos e indexables

Los antiguos RPCs aidbox.validation/* y los recursos BatchValidationRun / BatchValidationError ya no existen. Se trata de un cambio que rompe la compatibilidad; si los utilizaba, migre a la operación descrita a continuación.

Cómo utilizarlo

$batch-validate se ejecuta sobre un único tipo de recurso. El único parámetro obligatorio es _since, un límite inferior sobre meta.lastUpdated. Obliga a que cada ejecución declare una ventana temporal en lugar de analizar todo el conjunto de datos por accidente — para validar todo, pase la época.

Por tanto, para validar todas las Observation actualizadas en abril de 2026, podemos llamar a $batch-validate de la siguiente manera:

POST /fhir/Observation/$batch-validate
Content-Type: application/json

resourceType: Parameters
parameter:
  - {name: _since, valueInstant: "2026-04-01T00:00:00Z"}
  - {name: _until, valueInstant: "2026-05-01T00:00:00Z"}

Por defecto, la llamada es síncrona: bloquea y devuelve un resumen Parameters con los conteos principales y una entrada por incidencia distinta, ordenadas de mayor a menor gravedad.

resourceType: Parameters
parameter:
  - {name: task-id,   valueString: "b1f9..."}
  - {name: validated, valueUnsignedInt: 1804646}   # resources checked
  - {name: valid,     valueUnsignedInt: 1317494}   # no issues
  - {name: invalid,   valueUnsignedInt: 487152}    # total invalid resources
  - {name: invalid-resources, valueUrl: "/fhir/$batch-validate/b1f9.../invalid-resources"}
  - name: issue
    part:
      - {name: code,        valueCode: invalid-slice-cardinality}
      - {name: expression,  valueString: category}
      - {name: profile,     valueString: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab"}
      - {name: count,       valueUnsignedInt: 486018}   # resources hitting this exact issue
      - {name: diagnostics, valueString: "Observation.category: element count is outside the allowed range"}

count es el número de recursos distintos que presentan exactamente esa incidencia — la forma más rápida de saber si un problema afecta a seis recursos o a seiscientos mil.

Las llamadas síncronas son adecuadas para un conjunto reducido de recursos, cuando se desea la respuesta de inmediato. El trabajo sigue ejecutándose en paralelo: number-of-chunks (configurado por llamada) divide los recursos en ese número de fragmentos, y el parámetro scheduler-executors controla cuántos se ejecutan simultáneamente en el nodo. Juntos permiten equilibrar la granularidad de los fragmentos frente a la carga de una sola máquina — ese es su control de escalado vertical.

Sin embargo, cuando se desea validar un conjunto de datos considerablemente mayor, puede ser conveniente hacerlo de forma asíncrona.

Modo asíncrono para grandes conjuntos de datos

Para que cualquier llamada a $batch-validate sea asíncrona, basta con añadir la cabecera Prefer: respond-async. Aidbox programa entonces el trabajo en su motor de tareas, distribuyéndolo entre nodos y permitiendo tanto el escalado horizontal como el vertical.

POST /fhir/Observation/$batch-validate
Prefer: respond-async

resourceType: Parameters
parameter:
  - {name: _since, valueInstant: "1970-01-01T00:00:00Z"} # basically will validate every Observation in the database

Recibirá un 202 con una cabecera Content-Location que puede consultar para ver el progreso:

GET /fhir/$batch-validate/b1f9...

Mientras se ejecuta, obtendrá 202 con una cabecera X-Progress: 45%. Al finalizar, recibirá el mismo resumen Parameters que devuelve una llamada síncrona. Tanto las llamadas síncronas como las asíncronas persisten sus resultados bajo un task-id, por lo que no hay diferencia en la forma de analizar los resultados.

Explorar los recursos no válidos

El resumen le indica qué incidencias existen y cuántos recursos presenta cada una. Para ver los recursos reales, siga el enlace invalid-resources — filtrelo a una sola incidencia con _issue y pagine con _count / _page:

GET /fhir/$batch-validate/b1f9.../invalid-resources?_count=50&_page=1

Cada recurso no válido se devuelve con un fullUrl específico de versión que apunta a la versión exacta que fue validada, el cuerpo del recurso y un OperationOutcome que lista todas las incidencias de ese recurso:

- name: resource
  part:
    - {name: fullUrl, valueUrl: "/Observation/obs-42/_history/7"}
    - name: resource
      resource: {resourceType: Observation}
    - name: outcome
      resource:
        resourceType: OperationOutcome
        issue:
          - {severity: fatal, code: invalid, expression: [Observation.category], diagnostics: "..."}

El outcome lista el conjunto completo de incidencias de un recurso, incluso cuando _issue restringe qué recursos se devuelven — de modo que nunca se corrige un problema solo para descubrir un segundo en la siguiente pasada.

Pruebe un perfil antes de aplicarlo

La razón más habitual para ejecutar esto es un nuevo perfil: quiere saber qué falla antes de convertirlo en un requisito. Pase uno o más canónicos de profile, y cada recurso se validará contra ellos además de frente a su esquema base.

resourceType: Parameters
parameter:
  - {name: _since,  valueInstant: "1970-01-01T00:00:00Z"}
  - {name: profile, valueCanonical: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab"}

Varios perfiles son conjuntivos (AND): un recurso es conforme solo si cumple todos ellos, y las incidencias son la unión de todas. Así puede activar US Core sabiendo exactamente qué fallaría, en lugar de descubrirlo en producción.

Ajuste el validador por ejecución

A veces se desea un análisis estructural rápido sin preocuparse aún por la terminología; otras veces se quiere ser más estricto que los valores predeterminados del sistema. Cada parámetro disable-* / strict-* sobreescribe una configuración del validador solo para esa ejecución — omítalo para mantener el comportamiento configurado en el sistema.

ParámetroEfecto
disable-terminology-validationOmite las comprobaciones de enlaces codificados / terminología
disable-primitive-validationOmite las comprobaciones de tipos primitivos y formatos
disable-slicing-validationOmite la validación de slices
disable-constraint-validationOmite todos los invariantes FHIRPath (o los comprueba todos si es false)
disable-constraintOmite invariantes específicos por clave (p. ej. us-core-8)
strict-profile-resolutionTrata un perfil no resuelto como un error en lugar de omitirlo
strict-extension-resolutionTrata una extensión no resuelta como un error

strict-profile-resolution merece especial atención. Sin él, una URL de perfil que no se resuelve se omite, de modo que una errata se convierte en un informe de «conformidad» que es silenciosamente incorrecto. Actívelo cuando desee que la ejecución falle de forma explícita.

Diseñado para escalar

Internamente, $batch-validate particiona los recursos por hash en un número fijo de fragmentos (number-of-chunks, por defecto 12). Cada fragmento valida su porción mod(hash(id), N), y los fragmentos se agregan en un único resultado:

POST /fhir/Observation/$batch-validate Hash-partition by id chunk 0 chunk 1 chunk ... Aggregated results

Cada fragmento se procesa en streaming, por lo que el uso de memoria permanece acotado independientemente del tamaño del conjunto de datos. Una ejecución síncrona ejecuta sus fragmentos en un pool de trabajadores dedicado — un pool de hilos fijo que Aidbox crea para esa ejecución y destruye al finalizar, dimensionado por el parámetro scheduler-executors y separado de los hilos que atienden el tráfico regular de la API. Como máximo, ese número de fragmentos se ejecuta simultáneamente, por lo que no hay límite superior en number-of-chunks — un número mayor simplemente encola en lugar de aumentar el uso de memoria. Una ejecución asíncrona no utiliza este pool local; en su lugar, se ejecuta en el pool propio del motor de tareas, escribiendo una fila de planificador por fragmento para que cualquier nodo pueda reclamarla. Dado que no toma prestados hilos de solicitud ni conexiones del pool, es seguro frente a un sistema en producción; la CPU es el recurso que se consume, por lo que se recomienda el modo asíncrono para un primer análisis de gran envergadura.

La ruta asíncrona es también la que permite que una ejecución escale entre máquinas. Cada fragmento es un trabajo en un planificador que reside en el Postgres compartido, de modo que se pueden ejecutar varias instancias de Aidbox en diferentes máquinas contra una misma base de datos y estas se reparten los fragmentos entre ellas — una ejecución se acelera a medida que se añaden nodos. Un fragmento es reclamado exactamente por una instancia, por lo que nunca se ejecuta dos veces; y si una instancia falla a mitad de la ejecución, el planificador reclama su fragmento y lo reintenta en otra, con escrituras idempotentes para que un fragmento reclamado nunca cuente doble.

claim claim claim POST + Prefer: respond-async Shared Postgres chunk queue Aidbox node 1 Aidbox node 2 Aidbox node ... Aggregated results

Dado que N es fijo para una ejecución, el predicado de partición es una expresión constante que puede indexarse. Para un conjunto de datos muy grande validado con un número elevado de fragmentos, un índice de expresión coincidente convierte cada fragmento de un escaneo completo en un escaneo de índice selectivo:

CREATE INDEX CONCURRENTLY observation_batch_validate_10000
  ON observation (mod(abs(hashtextextended(id, 0)), 10000));

Luego ejecute con number-of-chunks: 10000. El módulo del índice debe coincidir con el número de fragmentos, o PostgreSQL no lo utilizará — compruébelo con EXPLAIN.

Compacto por diseño

Esta es la razón por la que validar 100 GB de datos incorrectos ya no le cuesta otros 100 GB. Aidbox almacena los resultados en un formato agregado e indexado por incidencias en un esquema dedicado aidbox_batch_validation:

TablaContiene
issueuna fila por error distinto
invalid_resourceuna pequeña fila (issue, resource_id, version) por recurso — solo IDs
chunk_statuna fila por fragmento con sus métricas

Todas las ocurrencias que comparten perfil, tipo de recurso, ruta normalizada, código y restricción se colapsan en una única incidencia cuyo conteo es el número de recursos distintos que la presentan. Aidbox nunca copia los cuerpos de los recursos no válidos ni sus OperationOutcome: el desglose vuelve a leer cada cuerpo desde el historial en la versión validada y reconstruye el outcome a partir de los campos almacenados. Son los problemas distintos, no el volumen bruto, los que determinan el coste de almacenamiento.

Pruébelo

$batch-validate está disponible en Aidbox 2607. Apúntelo a un tipo de recurso, pase un _since de época, y obtendrá un mapa ordenado por gravedad de los problemas de calidad de sus datos en una sola llamada — y luego podrá desglosar los recursos exactos detrás de cada uno.

¿Quiere verlo funcionar sin escribir nada? Hemos publicado un cuaderno interactivo que ejecuta el flujo completo de principio a fin: carga un conjunto de Patients de muestra, crea un perfil basado en US Core Patient, los valida en una sola llamada y muestra los resultados — la distribución entre válidos e inválidos, los Patients no válidos por incidencia y dónde se concentran los problemas. Lea el cuaderno de validación por lotes aquí en la documentación y ábralo en su propio Aidbox para ejecutarlo de principio a fin.

Referencia completa: Validación de recursos por lotes.

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

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