|
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.