Ein FHIR-Server sammelt komplexe, tief strukturierte medizinische DatensĂ€tze aus allen angebundenen Systemen â und man geht leicht davon aus, dass sie alle 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 praktikabel. AuĂerdem validiert man selten nur einmal, denn Profile sind nicht statisch. Implementation Guides wie US Core und die HL7-Da-Vinci-LeitfĂ€den veröffentlichen regelmĂ€Ăig neue Versionen, die jeweils 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 erneut: Wie viel von dem, was Sie bereits gespeichert haben, ist noch konform? Wie prĂŒfen Sie also alles davon, wiederholt, ohne dass es zu einem eigenen Projekt wird?
Die FHIR-Operation $validate lieĂe sich theoretisch automatisieren, aber Sie mĂŒssten jede Ressource abrufen und per POST zurĂŒcksenden sowie die Ergebnisse sammeln und filtern.
Dieser Ansatz hat viele Nachteile und lÀsst sich nur schwer skalieren.
Im Idealfall möchten Sie den Server bitten, einen bestimmten Ressourcentyp zu validieren, und mit einem einzigen Aufruf erfahren, was nicht stimmt â mit echter Möglichkeit zur horizontalen und vertikalen Skalierung.
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 asynchrone Batch-Validierung, die ĂŒber eine Reihe von RPCs (aidbox.validation/batch-validation und verwandte) bereitgestellt wurde.
Es funktionierte, hatte aber mehrere Probleme.
Zum einen wurde jeder Validierungsfehler als eigene BatchValidationError-Ressource gespeichert.
AuĂerdem war es recht langsam, was die Validierung groĂer Datenmengen unnötig zeitaufwendig machte.
SchlieĂlich konnte die Validierung eines groĂen Datensatzes so viele Ergebnisse erzeugen, dass diese in ihrer GröĂe mit den Daten selbst konkurrierten. Hundert Gigabyte nicht-konformer Ressourcen konnten annĂ€hernd hundert Gigabyte an Fehlerressourcen erzeugen. Das Mittel, das Sie zur Analyse eines DatenqualitĂ€tsproblems einsetzten, verschĂ€rfte Ihr Speicherproblem.
Die Lösung war zudem nur asynchron, hatte die Form eines RPC statt einer FHIR-Operation und lieferte Ihnen einen Stapel von Fehlerressourcen zum Durchsuchen anstelle einer konkreten Antwort.
$batch-validate behĂ€lt das Gute â die parallele Validierung bereits gespeicherter Daten â und behebt alles andere.
| Alte Batch-Validierung | $batch-validate | |
|---|---|---|
| Schnittstelle | ProprietÀre RPCs | FHIR-Operation (Parameters als Ein- und Ausgabe) |
| Modi | Nur asynchron | Synchron oder asynchron |
| Ergebnisspeicherung | Eine BatchValidationError-Ressource pro Fehler | Eine Zeile pro eindeutigem Problem plus eine kleine Tabelle mit Ressourcen-IDs |
| Speicherkosten | WĂ€chst mit der Fehleranzahl | Begrenzt â Ressource-Bodies werden nie kopiert |
| Ausgabe | Ein Stapel von Fehlerressourcen zum Durchsuchen | Problemzusammenfassung nach Schweregrad mit bedarfsgerechter Detailansicht |
| Skalierung | Fest | Hash-partitionierte Chunks, gestreamt, parallel ĂŒber Knoten, indizierbar |
Die alten aidbox.validation/*-RPCs sowie die Ressourcen BatchValidationRun / BatchValidationError existieren nicht mehr. Dies ist eine Breaking Change; falls Sie diese verwendet haben, migrieren Sie bitte zur unten beschriebenen Operation.
Verwendung
$batch-validate wird fĂŒr einen einzelnen Ressourcentyp ausgefĂŒhrt.
Der einzige erforderliche Parameter ist _since, eine untere Schranke fĂŒr meta.lastUpdated.
Er zwingt jeden Lauf dazu, ein Zeitfenster zu deklarieren, anstatt versehentlich den gesamten Datensatz zu scannen â um alles zu validieren, ĂŒbergeben Sie den Epoch-Zeitstempel.
Um also jede Observation zu validieren, die im April 2026 aktualisiert wurde, können wir $batch-validate wie folgt aufrufen:
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 Gesamtzahlen und einem Eintrag pro eindeutigem Problem zurĂŒck, beginnend mit den schwerwiegendsten.
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 gibt die Anzahl der eindeutigen Ressourcen an, die von genau diesem Problem betroffen sind â der schnellste Weg, um zu erkennen, ob ein Problem sechs oder sechshunderttausend Ressourcen betrifft.
Synchrone Aufrufe eignen sich gut fĂŒr eine kleine Menge von Ressourcen, bei denen Sie sofort eine Antwort benötigen.
Die Arbeit lĂ€uft dennoch parallel: 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 steuern sie das VerhĂ€ltnis zwischen Chunk-GranularitĂ€t und Auslastung einer einzelnen Maschine â das ist Ihr Regler fĂŒr die vertikale Skalierung.
Wenn Sie jedoch einen wesentlich gröĂeren Datensatz validieren möchten, empfiehlt sich der asynchrone Modus.
Asynchron fĂŒr groĂe DatensĂ€tze
Um einen $batch-validate-Aufruf asynchron zu machen, genĂŒgt es, dem Aufruf den Header Prefer: respond-async hinzuzufĂŒgen.
Aidbox plant die Arbeit dann in seiner Task-Engine ein und verteilt sie ĂŒber 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 Grunde jede Observation 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.
Nach Abschluss 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 ihn 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 zeigt, dem Ressource-Body sowie 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 durchsetzen
Der hĂ€ufigste Anlass fĂŒr diesen Vorgang ist ein neues Profil: Sie möchten wissen, was scheitert, bevor Sie es zur Pflicht machen.
Ăbergeben Sie eine oder mehrere profile-Kanoniken, 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 werden konjunktiv (UND) verknĂŒpft: Eine Ressource ist nur konform, wenn sie allen entspricht, und die Probleme sind die Vereinigungsmenge ĂŒber alle Profile. Sie können US Core also aktivieren und genau wissen, was scheitern wĂŒrde â anstatt es erst in der Produktion herauszufinden.
Den Validator pro Lauf anpassen
Manchmal möchten Sie eine schnelle strukturelle PrĂŒfung 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 ausschlieĂlich fĂŒr diesen Lauf â wird er weggelassen, gilt das konfigurierte Verhalten.
| Parameter | Wirkung |
|---|---|
disable-terminology-validation | Kodierungs-/Terminology-PrĂŒfungen ĂŒberspringen |
disable-primitive-validation | PrĂŒfungen von primitiven Typen und Formaten ĂŒberspringen |
disable-slicing-validation | Slice-Validierung ĂŒberspringen |
disable-constraint-validation | Alle FHIRPath-Invarianten ĂŒberspringen (oder alle prĂŒfen bei false) |
disable-constraint | Bestimmte Invarianten nach SchlĂŒssel ĂŒberspringen (z. B. us-core-8) |
strict-profile-resolution | Ein nicht auflösbares Profil als Fehler behandeln, anstatt es zu ĂŒberspringen |
strict-extension-resolution | Eine nicht auflösbare Extension als Fehler behandeln |
strict-profile-resolution verdient besondere ErwÀhnung.
Ohne diese Einstellung wird eine Profil-URL, die nicht aufgelöst werden kann, stillschweigend ĂŒbersprungen â ein Tippfehler fĂŒhrt also zu einem âkonformen" Bericht, der leise falsch ist.
Aktivieren Sie diese Option, wenn der Lauf stattdessen laut scheitern soll.
FĂŒr Skalierung ausgelegt
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)-Anteil, und die Chunks werden zu einem Ergebnis aggregiert:
Jeder Chunk wird gestreamt, sodass der Heap 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 startet und nach Abschluss wieder beendet, dessen GröĂe durch die Einstellung scheduler-executors bestimmt wird und der von den Threads getrennt ist, die Ihren regulĂ€ren API-Verkehr bedienen.
Maximal so viele Chunks laufen gleichzeitig, sodass es keine Obergrenze fĂŒr number-of-chunks gibt â ein gröĂerer Wert fĂŒhrt lediglich zu einer Warteschlange, ohne den Heap zu vergröĂern.
Ein asynchroner Lauf verwendet diesen lokalen Pool nicht; stattdessen lĂ€uft er im eigenen Pool der Task-Engine und schreibt fĂŒr jeden Chunk eine Scheduler-Zeile, die ein beliebiger Knoten beanspruchen kann.
Da er weder Request-Threads noch Connection-Pool-Slots belegt, ist er sicher fĂŒr einen laufenden Server; CPU ist das, was Sie einsetzen â bevorzugen Sie daher Async fĂŒr eine erste groĂe DurchprĂŒfung.
Der asynchrone Pfad ermöglicht es auch, einen Lauf ĂŒber mehrere Maschinen zu skalieren. Jeder Chunk ist ein Job in einem Scheduler, der in dem gemeinsam genutzten Postgres lebt, sodass Sie mehrere Aidbox-Instanzen auf verschiedenen Maschinen gegen eine Datenbank betreiben und die Chunks unter ihnen 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; fĂ€llt eine Instanz wĂ€hrend des Laufs aus, gibt der Scheduler ihren Chunk frei und wiederholt ihn auf einem anderen Knoten â mit idempotenten SchreibvorgĂ€ngen, sodass ein zurĂŒckgeforderter Chunk nie doppelt gezĂ€hlt wird.
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, verwandelt ein passender Ausdrucksindex jeden Chunk von einem vollstĂ€ndigen Scan in einen selektiven Index-Scan:
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 ist 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:
| Tabelle | Inhalt |
|---|---|
issue | eine Zeile pro eindeutigem Fehler |
invalid_resource | eine kleine (issue, resource_id, version)-Zeile pro Ressource â nur IDs |
chunk_stat | eine Zeile pro Chunk mit dessen Metriken |
Alle Vorkommen, die dasselbe Profil, denselben Ressourcentyp, normalisierten Pfad, Code und dieselbe Constraint teilen, werden zu einem einzigen Problem zusammengefasst, dessen Anzahl die Zahl der eindeutigen betroffenen Ressourcen ist.
Aidbox kopiert niemals die Bodies ungĂŒltiger Ressourcen oder deren OperationOutcomes: Die Detailansicht liest jeden Body zur validierten Version aus der History erneut und rekonstruiert das Outcome aus den gespeicherten Feldern.
Die Speicherkosten richten sich nach der Anzahl eindeutiger Probleme, nicht nach dem Rohdatenvolumen.
Ausprobieren
$batch-validate ist in Aidbox 2607 verfĂŒgbar.
Richten Sie die Operation auf einen Ressourcentyp, ĂŒbergeben Sie einen Epoch-_since-Wert, und Sie erhalten mit einem einzigen Aufruf eine nach Schweregrad sortierte Ăbersicht Ihrer DatenqualitĂ€tsprobleme â und können dann die genauen Ressourcen hinter jedem Problem aufrufen.
Möchten Sie die Operation in Aktion sehen, ohne selbst etwas schreiben zu mĂŒssen? Wir haben ein interaktives Notebook veröffentlicht, das den gesamten Ablauf von Anfang bis Ende durchfĂŒhrt: Es lĂ€dt eine Reihe von Beispiel-Patienten, erstellt ein Profil nach dem Vorbild von US Core Patient, validiert sie mit einem einzigen Aufruf und stellt die Ergebnisse grafisch dar â die Aufteilung in gĂŒltige/ungĂŒltige Ressourcen, die ungĂŒltigen Patienten nach Problem und wo sich die Schwachstellen konzentrieren. Ăffnen Sie das Notebook Batch validation im Bereich Notebooks in Aidbox und fĂŒhren Sie es von oben nach unten aus.
VollstÀndige Referenz: Batch resource validation.



