---
{
  "title": "$batch-validate: Alle gespeicherten Ressourcen profilbasiert validieren – in großem Maßstab",
  "description": "Aidbox 2607 ersetzt die bisherige Batch-Validierungs-API durch die Operation $batch-validate – validieren Sie einen gesamten Ressourcentyp in Ihrer Datenbank, synchron oder asynchron, und ermitteln Sie gezielt, 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 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.",
  "seo-tags": ["FHIR validation", "batch validation", "FHIR profile validation", "Aidbox", "FHIR conformance", "healthcare data quality"]
}
---

> For the complete documentation index, see [llms.txt](https://www.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---

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`                                                        |
|--------------------|----------------------------------------------------|--------------------------------------------------------------------------|
| Schnittstelle      | Proprietäre RPCs                                   | FHIR-Operation (`Parameters` ein- und ausgehend)                         |
| Modi               | Nur asynchron                                      | Synchron **oder** asynchron                                              |
| Ergebnisspeicher   | Eine `BatchValidationError`-Ressource pro Fehler   | Eine Zeile pro **eindeutigem** Problem plus eine kleine Tabelle mit Ressourcen-IDs |
| Speicherkosten     | Wachsen mit der Fehleranzahl                       | Begrenzt – Ressourcen-Bodies werden nie kopiert                          |
| Ausgabe            | Ein Stapel Fehlerressourcen zum Durchsuchen        | Problemübersicht nach Schweregrad mit bedarfsweisem Drill-down           |
| Skalierung         | Fest                                               | Hash-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:

```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 wichtigsten Kennzahlen und einem Eintrag pro eindeutigem Problem zurück, sortiert nach Schweregrad.

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

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

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

```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 verweist, dem Ressourcen-Body und 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 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.

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

| Parameter                        | Wirkung                                                                      |
|----------------------------------|------------------------------------------------------------------------------|
| `disable-terminology-validation` | Terminologieprüfungen für codierte Bindungen überspringen                    |
| `disable-primitive-validation`   | Prüfungen primitiver Typen und Formate überspringen                          |
| `disable-slicing-validation`     | Slice-Validierung überspringen                                               |
| `disable-constraint-validation`  | **Alle** FHIRPath-Invarianten überspringen (oder alle prüfen, wenn `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 statt es zu überspringen   |
| `strict-extension-resolution`    | Eine 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:

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

```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, wandelt ein passender Expression-Index jeden Chunk von einem Full Scan in einen selektiven Index Scan um:

```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 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 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 `OperationOutcome`s: 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](/docs/aidbox/notebooks/64c853e6-f13a-4246-bb0b-044020b3b01a) 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](https://www.health-samurai.io/docs/aidbox/modules/profiling-and-validation/batch-resource-validation).