|
9 min de lectura
|

$batch-validate: Valide todos los recursos almacenados según sus perfiles, a escala

Resumen del artículo

$batch-validate comprueba cada recurso de un tipo ya almacenado en Aidbox frente a su esquema FHIR y los perfiles que usted indique, de forma síncrona o asíncrona. Los resultados se agregan en un formato compacto e indexado por incidencia, 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 provenientes 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, realizar la validación manual de cada uno resulta tedioso y probablemente poco razonable. Además, rara vez se valida una sola vez, porque los perfiles no son estáticos. Guías de implementación como US Core y las guías HL7 Da Vinci publican nuevas versiones con regularidad, y cada una añade elementos, ajusta 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 — surge la misma pregunta: cuánto de lo que ya almacena sigue siendo conforme. Entonces, ¿cómo verificar todo ello, de forma repetida, sin que se convierta en un proyecto en sí mismo?

La operación $validate de FHIR podría automatizarse en teoría, pero tendría que recuperar cada recurso y volver a enviarlo mediante POST, recopilando y filtrando los resultados. Este enfoque presenta numerosos problemas y es difícil de escalar. Lo ideal sería poder pedirle al servidor que valide un tipo de recurso específico y le informe de los errores en una sola llamada, con una capacidad de escalado tanto horizontal como vertical.

Eso es precisamente 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 por lotes asíncrona durante años, expuesta mediante 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 volúmenes de recursos en una tarea innecesariamente prolongada.

Por último, validar un conjunto de datos extenso podía generar tantos resultados que podían rivalizar con los propios datos en tamaño. 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 entender 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 conjunto de recursos de error para consultar en vez de una respuesta directa.

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

Validación por lotes antigua$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 compacta de IDs de recursos
Coste de almacenamientoCrece con el número de erroresAcotado — los cuerpos de los recursos nunca se copian
SalidaUn conjunto de recursos de error para consultarResumen de incidencias ordenado por gravedad con desglose bajo demanda
EscaladoFijoFragmentos con particionado por hash, transmitidos en flujo, paralelos entre nodos e indexables

Los RPCs aidbox.validation/* antiguos y los recursos BatchValidationRun / BatchValidationError ya no existen. Esto es un cambio disruptivo; si los utilizaba, migre a la operación que se describe a continuación.

Cómo usar

$batch-validate se ejecuta sobre un único tipo de recurso. El único parámetro obligatorio es _since, un límite inferior sobre meta.lastUpdated. Esto 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 Unix.

Así, para validar todas las Observations actualizadas en abril de 2026, podemos invocar $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 recuentos 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}   # recursos comprobados
  - {name: valid,     valueUnsignedInt: 1317494}   # sin incidencias
  - {name: invalid,   valueUnsignedInt: 487152}    # total de recursos no válidos
  - {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}   # recursos que presentan exactamente esta incidencia
      - {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 comprobar si un problema afecta a seis recursos o a seiscientos mil.

Las llamadas síncronas son adecuadas para un conjunto pequeño de recursos cuando se desea obtener la respuesta de inmediato. El trabajo se ejecuta igualmente 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 con la carga de trabajo de una sola máquina — ese es el control de escalado vertical.

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

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"} # básicamente validará cada Observation en la base de datos

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

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

Mientras se ejecuta, obtendrá un 202 con una cabecera X-Progress: 45%. Cuando finalice, 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 — filtre a una sola incidencia con _issue y navegue con _count / _page:

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

Cada recurso no válido se devuelve con una fullUrl específica de la versión que apunta a la versión exacta que fue validada, el cuerpo del recurso y un OperationOutcome que enumera 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 resultado lista el conjunto completo de incidencias de un recurso, incluso cuando _issue limita qué recursos se devuelven — de modo que nunca corregirá un problema para descubrir un segundo en el siguiente pase.

Probar un perfil antes de aplicarlo

La razón más habitual para ejecutar esta operación es un nuevo perfil: desea saber qué falla antes de convertirlo en un requisito. Pase uno o más canonicals de profile y cada recurso se validará frente a ellos además de 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 con todos ellos, y las incidencias son la unión de todos. Así puede activar US Core sabiendo exactamente qué fallaría, en lugar de descubrirlo en producción.

Ajustar el validador por ejecución

A veces se desea un análisis estructural rápido sin importar la terminología; otras veces se quiere ser más estricto que los valores predeterminados del sistema. Cada parámetro disable-* / strict-* sobreescribe un ajuste del validador únicamente para esa ejecución — si se omite, se mantiene el comportamiento configurado en el sistema.

ParámetroEfecto
disable-terminology-validationOmitir comprobaciones de vinculación de códigos y terminología
disable-primitive-validationOmitir comprobaciones de tipo primitivo y formato
disable-slicing-validationOmitir la validación de slices
disable-constraint-validationOmitir todos los invariantes FHIRPath (o comprobarlos todos si es false)
disable-constraintOmitir invariantes específicos por clave (p. ej. us-core-8)
strict-profile-resolutionTratar un perfil no resuelto como error en lugar de omitirlo
strict-extension-resolutionTratar una extensión no resuelta como error

Vale la pena destacar strict-profile-resolution. Sin él, una URL de perfil que no se resuelve se omite, por lo que un error tipográfico 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 transmite en flujo, 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 grupo de trabajadores dedicado — un grupo 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 normal de la API. Como máximo, ese número de fragmentos se ejecuta a la vez, por lo que no hay límite superior para number-of-chunks — un número mayor simplemente se encola en lugar de aumentar el uso de memoria. Una ejecución asíncrona no utiliza este grupo local; en su lugar, se ejecuta en el grupo propio del motor de tareas, escribiendo una fila de programador por fragmento para que cualquier nodo la reclame. Como no toma prestados hilos de solicitud ni ranuras del grupo de conexiones, es segura en un sistema en producción; la CPU es el recurso que se consume, por lo que se recomienda el modo asíncrono para una primera pasada de grandes volúmenes.

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 programador que reside en el Postgres compartido, por lo que puede ejecutar varias instancias de Aidbox en diferentes máquinas contra una misma base de datos y estas se repartirán los fragmentos entre sí — la ejecución se acelera a medida que se añaden nodos. Un fragmento es reclamado por exactamente una instancia, por lo que nunca se ejecuta dos veces; y si una instancia falla a mitad de una ejecución, el programador reclama su fragmento y lo reintenta en otra, con escrituras idempotentes para que un fragmento reclamado nunca duplique el conteo.

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 se puede indexar. 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á — confirme con EXPLAIN.

Compacto por diseño

He aquí por qué validar 100 GB de datos erróneos ya no le cuesta otros 100 GB. Aidbox almacena los resultados en un formato agregado e indexado por incidencia en un esquema dedicado aidbox_batch_validation:

TablaContiene
issueuna fila por error distinto
invalid_resourceuna fila compacta (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 consolidan en una única incidencia cuyo recuento es el número de recursos distintos que la presentan. Aidbox nunca copia los cuerpos de los recursos no válidos ni sus OperationOutcomes: el desglose vuelve a leer cada cuerpo desde el historial en la versión validada y reconstruye el resultado 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 Unix y obtendrá en una sola llamada un mapa de los problemas de calidad de sus datos ordenado por gravedad — para luego desglosar los recursos exactos detrás de cada uno.

¿Desea verlo funcionar sin escribir nada? Hemos publicado un notebook 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 representa gráficamente los resultados — la distribución de válidos e inválidos, los Patients no válidos por incidencia y dónde se concentran los problemas. Abra el notebook Batch validation desde la sección Notebooks en Aidbox y ejecútelo de arriba a abajo.

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.