|
9 Min. Lesezeit
|

$batch-validate: Alle gespeicherten Ressourcen profilbasiert validieren – in großem Maßstab

Zusammenfassung

$batch-validate prüft jede Ressource eines Typs, die bereits in Aidbox gespeichert ist, gegen das FHIR-Schema und beliebig viele von Ihnen angegebene Profile – synchron oder asynchron. Ergebnisse werden in einer kompakten, nach Problemen indizierten Form aggregiert, sodass die Validierung von 100 GB nicht-konformer Daten keine weiteren 100 GB in Ihrer Datenbank verursacht. Verfügbar ab Aidbox 2607.

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

Ein FHIR-Server sammelt komplexe, tief strukturierte medizinische Datensätze aus jedem angebundenen System – und es liegt nahe anzunehmen, dass all diese Daten korrekt sind. Den Daten zu vertrauen ist eine Sache; sie robust und in großem Maßstab prüfen zu können, ist eine andere. Bei Millionen von Ressourcen ist eine manuelle Validierung jeder einzelnen mühsam und kaum praxistauglich. Und eine einmalige Validierung reicht selten aus, denn Profile sind nicht statisch. Implementation Guides wie US Core und die HL7 Da Vinci Guides veröffentlichen regelmäßig neue Versionen, die Elemente hinzufügen, Kardinalitäten verschärfen oder die gebundenen Value Sets ändern. Jedes Mal, wenn Sie eine neue Profilversion übernehmen – oder eine eigene veröffentlichen – stellt sich dieselbe Frage: Wie viel von dem, was Sie bereits gespeichert haben, ist noch konform? Wie prüfen Sie also alles davon, immer wieder, ohne dass es zum Projekt wird?

FHIRs $validate-Operation könnte theoretisch automatisiert werden, aber dafür müssten Sie jede Ressource abrufen und zurück-POSTen sowie Ergebnisse sammeln und filtern. Dieser Ansatz hat viele Nachteile und lässt sich schwer skalieren. Idealerweise möchten Sie den Server bitten, einen bestimmten Ressourcentyp zu validieren, und in einem einzigen Aufruf erfahren, was nicht stimmt – mit echter horizontaler und vertikaler Skalierbarkeit.

Genau das leistet $batch-validate. Die Operation ist in Aidbox 2607 enthalten und ersetzt die bisherige Batch-Validierungs-API vollständig.

Warum wir die Batch-Validierung neu gebaut haben

Aidbox verfügte seit Jahren über eine asynchrone Batch-Validierung, die über eine Reihe von RPCs (aidbox.validation/batch-validation und verwandte) bereitgestellt wurde. Sie funktionierte, hatte jedoch mehrere Probleme. Zum einen wurde jeder Validierungsfehler als eigene BatchValidationError-Ressource gespeichert. Außerdem war sie recht langsam, sodass die Validierung großer Datenmengen unnötig viel Zeit in Anspruch nahm.

Schließlich konnte die Validierung eines umfangreichen Datensatzes so viele Ergebnisse erzeugen, dass diese in ihrer Größe mit den Daten selbst vergleichbar waren. Hundert Gigabyte nicht-konformer Ressourcen konnten nahezu hundert Gigabyte an Fehlerressourcen erzeugen. Der Mechanismus, mit dem Sie ein Datenqualitätsproblem verstehen wollten, verschlimmerte Ihr Speicherproblem.

Die alte Lösung war zudem ausschließlich asynchron, hatte die Form eines RPCs statt einer FHIR-Operation und lieferte Ihnen einen Stapel von Fehlerressourcen zum Durchsuchen statt einer klaren Antwort.

$batch-validate behält das Gute bei – paralleles Validieren bereits gespeicherter Daten – und behebt den Rest.

Alte Batch-Validierung$batch-validate
SchnittstelleProprietäre RPCsFHIR-Operation (Parameters ein- und ausgehend)
ModiNur asynchronSynchron oder asynchron
ErgebnisspeicherEine BatchValidationError-Ressource pro FehlerEine Zeile pro eindeutigem Problem plus eine kleine Tabelle mit Ressourcen-IDs
SpeicherkostenWachsen mit der FehleranzahlBegrenzt – Ressourcen-Bodies werden nie kopiert
AusgabeEin Stapel Fehlerressourcen zum DurchsuchenProblemübersicht nach Schweregrad mit bedarfsweisem Drill-down
SkalierungFestHash-partitionierte Chunks, gestreamt, parallel über Knoten, indizierbar

Die alten aidbox.validation/*-RPCs sowie die Ressourcentypen BatchValidationRun und BatchValidationError existieren nicht mehr. Dies ist ein Breaking Change; wenn Sie diese genutzt haben, migrieren Sie bitte zur unten beschriebenen Operation.

Verwendung

$batch-validate wird für einen einzelnen Ressourcentyp ausgeführt. Der einzige Pflichtparameter ist _since, eine untere Schranke für meta.lastUpdated. Damit ist jeder Lauf gezwungen, ein Zeitfenster zu deklarieren, anstatt versehentlich den gesamten Datensatz zu scannen – um alles zu validieren, übergeben Sie einfach die Epoche.

Um also alle Observations zu validieren, die im April 2026 aktualisiert wurden, kann $batch-validate wie folgt aufgerufen werden:

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"}

Standardmäßig ist der Aufruf synchron: Er blockiert und gibt eine Parameters-Zusammenfassung mit den wichtigsten Kennzahlen und einem Eintrag pro eindeutigem Problem zurück, sortiert nach Schweregrad.

resourceType: Parameters
parameter:
  - {name: task-id,   valueString: "b1f9..."}
  - {name: validated, valueUnsignedInt: 1804646}   # geprüfte Ressourcen
  - {name: valid,     valueUnsignedInt: 1317494}   # keine Probleme
  - {name: invalid,   valueUnsignedInt: 487152}    # insgesamt ungültige Ressourcen
  - {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}   # Ressourcen mit genau diesem Problem
      - {name: diagnostics, valueString: "Observation.category: element count is outside the allowed range"}

count ist die Anzahl der eindeutigen Ressourcen, bei denen genau dieses Problem aufgetreten ist – der schnellste Weg zu erkennen, ob ein Problem sechs oder sechshunderttausend Ressourcen betrifft.

Synchrone Aufrufe eignen sich gut für eine kleine Menge von Ressourcen, bei der Sie sofort eine Antwort benötigen. Die Arbeit läuft dennoch parallel ab: number-of-chunks (pro Aufruf festgelegt) teilt die Ressourcen in entsprechend viele Chunks auf, und die Einstellung scheduler-executors steuert, wie viele davon gleichzeitig auf dem Knoten ausgeführt werden. Gemeinsam bestimmen sie das Verhältnis von Chunk-Granularität zu Auslastung einer einzelnen Maschine – das ist Ihr Regler für die vertikale Skalierung.

Wenn Sie jedoch einen wesentlich größeren Datensatz validieren möchten, bietet sich die asynchrone Variante an.

Asynchron für große Datensätze

Um einen beliebigen $batch-validate-Aufruf asynchron zu machen, fügen Sie dem Aufruf lediglich den Header Prefer: respond-async hinzu. Aidbox plant die Arbeit dann in seiner Task-Engine ein und verteilt sie über mehrere Knoten, was sowohl horizontale als auch vertikale Skalierung ermöglicht.

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

resourceType: Parameters
parameter:
  - {name: _since, valueInstant: "1970-01-01T00:00:00Z"} # validiert im Wesentlichen alle Observations in der Datenbank

Sie erhalten eine 202-Antwort mit einem Content-Location-Header, den Sie abfragen können, um den Fortschritt zu verfolgen:

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

Während der Ausführung erhalten Sie 202 mit einem X-Progress: 45%-Header. Wenn der Vorgang abgeschlossen ist, erhalten Sie dieselbe Parameters-Zusammenfassung wie bei einem synchronen Aufruf. Sowohl synchrone als auch asynchrone Aufrufe speichern ihre Ergebnisse unter einer task-id, sodass es keinen Unterschied in der Analyse der Ergebnisse gibt.

Ungültige Ressourcen untersuchen

Die Zusammenfassung zeigt Ihnen, welche Probleme vorhanden sind und wie viele Ressourcen jeweils betroffen sind. Um die tatsächlichen Ressourcen einzusehen, folgen Sie dem Link invalid-resources – filtern Sie mit _issue auf ein einzelnes Problem und blättern Sie mit _count / _page:

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

Jede ungültige Ressource wird mit einer versionsspezifischen fullUrl zurückgegeben, die auf die exakte validierte Version verweist, dem Ressourcen-Body und einem OperationOutcome, das alle Probleme dieser Ressource auflistet:

- 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: "..."}

Das Outcome listet den vollständigen Problemsatz einer Ressource auf, auch wenn _issue einschränkt, welche Ressourcen zurückgegeben werden – so entdecken Sie ein zweites Problem nie erst beim nächsten Durchlauf, nachdem Sie das erste behoben haben.

Ein Profil testen, bevor Sie es verbindlich machen

Der häufigste Anlass für diesen Aufruf ist ein neues Profil: Sie möchten wissen, was nicht mehr funktioniert, bevor Sie es zur Anforderung machen. Übergeben Sie eine oder mehrere profile-Canonicals, und jede Ressource wird zusätzlich zu ihrem Basisschema gegen diese validiert.

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"}

Mehrere Profile sind konjunktiv (UND): Eine Ressource ist nur dann konform, wenn sie jedem einzelnen entspricht, und die Probleme bilden die Vereinigung aller Profile. So können Sie US Core aktivieren und genau wissen, was scheitern würde, anstatt es in der Produktion herauszufinden.

Den Validator pro Lauf anpassen

Manchmal möchten Sie einen schnellen strukturellen Sweep durchführen und sich noch nicht um Terminology kümmern; manchmal möchten Sie strenger sein als die Standardkonfiguration. Jeder disable-*- / strict-*-Parameter überschreibt eine Validator-Einstellung nur für diesen Lauf – lassen Sie ihn weg, um das konfigurierte Verhalten beizubehalten.

ParameterWirkung
disable-terminology-validationTerminologieprüfungen für codierte Bindungen überspringen
disable-primitive-validationPrüfungen primitiver Typen und Formate überspringen
disable-slicing-validationSlice-Validierung überspringen
disable-constraint-validationAlle FHIRPath-Invarianten überspringen (oder alle prüfen, wenn false)
disable-constraintBestimmte Invarianten nach Schlüssel überspringen (z. B. us-core-8)
strict-profile-resolutionEin nicht auflösbares Profil als Fehler behandeln statt es zu überspringen
strict-extension-resolutionEine nicht auflösbare Extension als Fehler behandeln

strict-profile-resolution verdient besondere Erwähnung. Ohne diesen Parameter wird eine Profil-URL, die nicht aufgelöst werden kann, übersprungen – ein Tippfehler führt dann zu einem „konformen" Bericht, der stillschweigend falsch ist. Aktivieren Sie diesen Parameter, wenn der Lauf stattdessen laut scheitern soll.

Für Skalierung gebaut

Intern teilt $batch-validate die Ressourcen per Hash-Partitionierung in eine feste Anzahl von Chunks auf (number-of-chunks, Standard 12). Jeder Chunk validiert seinen mod(hash(id), N)-Slice, und die Chunks werden zu einem Gesamtergebnis zusammengeführt:

POST /fhir/Observation/$batch-validate Hash-Partitionierung nach id Chunk 0 Chunk 1 Chunk ... Aggregierte Ergebnisse

Jeder Chunk wird gestreamt, sodass der Heap-Verbrauch unabhängig von der Datensatzgröße begrenzt bleibt. Ein synchroner Lauf führt seine Chunks in einem dedizierten Worker-Pool aus – einem festen Thread-Pool, den Aidbox für diesen Lauf hochfährt und danach wieder abbaut. Dieser wird durch die Einstellung scheduler-executors dimensioniert und ist von den Threads getrennt, die Ihren regulären API-Traffic bedienen. Maximal so viele Chunks laufen gleichzeitig, es gibt also keine Obergrenze für number-of-chunks – eine größere Anzahl wird einfach in die Warteschlange gestellt, ohne den Heap zu vergrößern. Ein asynchroner Lauf verwendet diesen lokalen Pool nicht; stattdessen läuft er auf dem eigenen Pool der Task-Engine und schreibt eine Scheduler-Zeile pro Chunk, die jeder Knoten beanspruchen kann. Da er weder Request-Threads noch Connection-Pool-Slots belegt, ist er für einen laufenden Produktivbetrieb sicher; CPU ist die einzige Ressource, die verbraucht wird – bevorzugen Sie daher Async für einen großen Erst-Sweep.

Der asynchrone Pfad ermöglicht auch die Skalierung über mehrere Maschinen. Jeder Chunk ist ein Job in einem Scheduler, der im gemeinsamen Postgres gespeichert ist, sodass mehrere Aidbox-Instanzen auf verschiedenen Maschinen gegen eine Datenbank arbeiten und sich die Chunks aufteilen können – ein Lauf wird schneller, je mehr Knoten Sie hinzufügen. Ein Chunk wird von genau einer Instanz beansprucht und läuft daher nie doppelt; stirbt eine Instanz mitten in einem Lauf, gibt der Scheduler ihren Chunk frei und wiederholt ihn auf einer anderen, mit idempotenten Schreibvorgängen, sodass ein zurückgeforderter Chunk nie doppelt gezählt wird.

beanspruchen beanspruchen beanspruchen POST + Prefer: respond-async Gemeinsame Postgres-Chunk-Warteschlange Aidbox-Knoten 1 Aidbox-Knoten 2 Aidbox-Knoten ... Aggregierte Ergebnisse

Da N für einen Lauf fest ist, ist das Partitionierungsprädikat ein konstanter Ausdruck, der indiziert werden kann. Bei einem sehr großen Datensatz, der mit einer hohen Chunk-Anzahl validiert wird, wandelt ein passender Expression-Index jeden Chunk von einem Full Scan in einen selektiven Index Scan um:

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

Führen Sie dann den Lauf mit number-of-chunks: 10000 aus. Der Index-Modulus muss mit der Chunk-Anzahl übereinstimmen, sonst verwendet PostgreSQL den Index nicht – prüfen Sie dies mit EXPLAIN.

Kompakt by design

Hier der Grund, warum die Validierung von 100 GB schlechter Daten Sie nicht weitere 100 GB kostet. Aidbox speichert Ergebnisse in aggregierter, nach Problemen indizierter Form in einem dedizierten Schema aidbox_batch_validation:

TabelleInhalt
issueeine Zeile pro eindeutigem Fehler
invalid_resourceeine kleine (issue, resource_id, version)-Zeile pro Ressource – nur IDs
chunk_stateine Zeile pro Chunk mit seinen Metriken

Alle Vorkommen, die dasselbe Profil, denselben Ressourcentyp, denselben normalisierten Pfad, denselben Code und dieselbe Constraint teilen, werden zu einem einzigen Issue zusammengefasst, dessen Anzahl der Anzahl der eindeutigen betroffenen Ressourcen entspricht. Aidbox kopiert weder die Bodies der ungültigen Ressourcen noch ihre OperationOutcomes: Der Drill-down liest jeden Body erneut aus der History in der validierten Version und rekonstruiert das Outcome aus den gespeicherten Feldern. Die Speicherkosten hängen von der Anzahl eindeutiger Probleme ab, nicht vom rohen Datenvolumen.

Jetzt ausprobieren

$batch-validate ist in Aidbox 2607 verfügbar. Richten Sie die Operation auf einen Ressourcentyp, übergeben Sie ein epochales _since, und Sie erhalten in einem einzigen Aufruf eine nach Schweregrad sortierte Übersicht Ihrer Datenqualitätsprobleme – mit der Möglichkeit, anschließend in die genauen Ressourcen hinter jedem Problem einzutauchen.

Möchten Sie es in Aktion sehen, ohne etwas schreiben zu müssen? Wir haben ein interaktives Notebook veröffentlicht, das den gesamten Ablauf von Ende zu Ende durchführt: Es lädt eine Reihe von Beispiel-Patients, erstellt ein an US Core Patient angelehntes Profil, validiert sie in einem einzigen Aufruf und stellt die Ergebnisse grafisch dar – die Aufteilung in gültige/ungültige Einträge, die ungültigen Patients nach Problem und wo sich die Probleme häufen. Lesen Sie das Batch-Validierung-Notebook in der Dokumentation, öffnen Sie es dann in Ihrem eigenen Aidbox und führen Sie es von oben bis unten aus.

Vollständige Referenz: Batch resource validation.

Diesen Artikel teilen
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

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