---
{
  "title": "$batch-validate: Jede gespeicherte Ressource gegen ihre Profile validieren – skalierbar",
  "description": "Aidbox 2607 ersetzt die alte Batch-Validierungs-API durch die Operation $batch-validate – validieren Sie einen ganzen Ressourcentyp direkt in Ihrer Datenbank, synchron oder asynchron, und analysieren Sie genau, welche Ressourcen nicht konform sind und warum.",
  "date": "2026-07-13",
  "author": "Andrew Listopadov",
  "reading-time": "9 min read",
  "tags": ["Aidbox", "FHIR Profiling", "Compliance", "Database"],
  "utm-campaign": "feature",
  "utm-content": "batch-validate",
  "tldr": "$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.",
  "seo-tags": ["FHIR validation", "batch validation", "FHIR profile validation", "Aidbox", "FHIR conformance", "healthcare data quality"]
}
---

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:

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

```yaml
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`](https://www.health-samurai.io/docs/aidbox/reference/all-settings#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.

```yaml
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:

```http
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`:

```http
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:

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

```yaml
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:

```mermaid
flowchart LR
    A["POST /fhir/Observation/$batch-validate"] --> B{Hash-Partitionierung nach id}
    B --> C[chunk 0]
    B --> D[chunk 1]
    B --> E[chunk ...]
    C --> F[(Aggregierte Ergebnisse)]
    D --> F
    E --> F
```

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`](https://www.health-samurai.io/docs/aidbox/reference/all-settings#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.

```mermaid
flowchart LR
    A["POST + Prefer: respond-async"] --> Q[(Gemeinsame Postgres-Chunk-Warteschlange)]
    Q -->|beanspruchen| N1[Aidbox-Knoten 1]
    Q -->|beanspruchen| N2[Aidbox-Knoten 2]
    Q -->|beanspruchen| N3[Aidbox-Knoten ...]
    N1 --> R[(Aggregierte Ergebnisse)]
    N2 --> R
    N3 --> R
```

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:

```sql
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 `OperationOutcome`s: 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](https://www.health-samurai.io/docs/aidbox/modules/profiling-and-validation/batch-resource-validation).