|
9 Min. Lesezeit
|

$batch-validate: Jede gespeicherte Ressource gegen ihre Profile validieren – skalierbar

Zusammenfassung

$batch-validate prĂŒft jede Ressource eines Typs, die bereits in Aidbox gespeichert ist, gegen ihr FHIR-Schema und beliebige Profile – synchron oder asynchron. Die Ergebnisse werden in einer kompakten, nach Problemen indizierten Form aggregiert, sodass die Validierung von 100 GB nicht-konformer Daten nicht weitere 100 GB in Ihrer Datenbank beansprucht. VerfĂŒgbar ab Aidbox 2607.

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

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
SchnittstelleProprietÀre RPCsFHIR-Operation (Parameters als Ein- und Ausgabe)
ModiNur asynchronSynchron oder asynchron
ErgebnisspeicherungEine BatchValidationError-Ressource pro FehlerEine Zeile pro eindeutigem Problem plus eine kleine Tabelle mit Ressourcen-IDs
SpeicherkostenWĂ€chst mit der FehleranzahlBegrenzt – Ressource-Bodies werden nie kopiert
AusgabeEin Stapel von Fehlerressourcen zum DurchsuchenProblemzusammenfassung nach Schweregrad mit bedarfsgerechter Detailansicht
SkalierungFestHash-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.

ParameterWirkung
disable-terminology-validationKodierungs-/Terminology-PrĂŒfungen ĂŒberspringen
disable-primitive-validationPrĂŒfungen von primitiven Typen und Formaten ĂŒberspringen
disable-slicing-validationSlice-Validierung ĂŒberspringen
disable-constraint-validationAlle FHIRPath-Invarianten ĂŒberspringen (oder alle prĂŒfen bei false)
disable-constraintBestimmte Invarianten nach SchlĂŒssel ĂŒberspringen (z. B. us-core-8)
strict-profile-resolutionEin nicht auflösbares Profil als Fehler behandeln, anstatt es zu ĂŒberspringen
strict-extension-resolutionEine 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:

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

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.

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, 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:

TabelleInhalt
issueeine Zeile pro eindeutigem Fehler
invalid_resourceeine kleine (issue, resource_id, version)-Zeile pro Ressource – nur IDs
chunk_stateine 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.

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

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