# Watermarks (watermark)

> Die watermark-Tabelle speichert den letzten synchronisierten Stand pro Job — und verhindert so doppelte Übertragungen.

---

Site [llms.txt](/llms.txt)

---

Die Tabelle `watermark` ist die Gedächtnisfunktion der DataBridge. Nach jedem
Job-Lauf wird hier gespeichert, bis zu welchem Stand die Daten gelesen wurden.
Beim nächsten Lauf startet der Quellabruf genau an diesem Punkt.

Diese Tabelle wird vom Deployment angelegt und zur Laufzeit automatisch befüllt —
manuelle Einträge sind normalerweise nicht nötig. Es gibt jedoch Situationen, in denen
ein gezielter Eingriff sinnvoll ist.

---

## Tabellenstruktur

| Spalte | Wert | Beschreibung |
|---|---|---|
| **PartitionKey** | `watermark` | Fest vorgegeben — immer `watermark` |
| **RowKey** | Jobname | Entspricht dem RowKey des zugehörigen Jobs in der `jobs`-Tabelle |
| **Timestamp** | automatisch | Systemseitig gesetzt, nicht bearbeitbar |
| **Watermark** | Letzter Stand | Format abhängig vom `WatermarkType` des Jobs (siehe unten) |
| **LastCronRun** | Zeitstempel | Beginn der letzten Ausführung des Jobs. Basis für die CRON-Intervallprüfung. |

---

## Watermark-Typen

Der Typ wird im Job über das Feld `WatermarkType` festgelegt und bestimmt,
wie der gespeicherte Wert interpretiert wird.

### `datetime` — Vollständiger Zeitstempel

Für Quellsysteme, die Änderungen über ein Datum-/Uhrzeit-Feld tracken.
Typisch für Business Central (`lastModifiedDateTime`).

**Format:** `yyyy-MM-ddTHH:mm:ss.fffZ`

**Beispielwert:** `2026-05-31T08:15:00.000Z`

Der Filter im Job sieht dann so aus:
```
{{lastModifiedDateTime gt %watermark%}}
```

### `date` — Nur Datum

Für Quellsysteme, bei denen nur das Datum (ohne Uhrzeit) relevant ist.
Typisch für NAV-Systeme mit datumbasiertem Tracking.

**Format:** `yyyy-MM-dd`

**Beispielwert:** `2026-05-31`

### `string` — Change Tracking Token

Für Dataverse / D365CE: Die Change Tracking API gibt bei jedem Abruf einen
opaken Token zurück, der den aktuellen Änderungsstand repräsentiert.
Dieser Token wird als `string` gespeichert und beim nächsten Lauf direkt
an die API übergeben — kein Datumsvergleich, kein Risiko fehlender Änderungen.

**Format:** opake Zeichenkette (von Dataverse vergeben)

**Beispielwert:** `892290!08/10/2026 10:00:00`

> **Empfehlung für Dataverse**
>
> Für alle D365CE-Jobs `WatermarkType = string` verwenden. Die Dataverse
> Change Tracking API garantiert lückenlose Erkennung aller Änderungen —
> auch wenn der Timer während einer Änderung ausgeführt wird.



### Schreibweise und unbekannte Typen

Der Typ wird ohne Beachtung von Groß-/Kleinschreibung und ohne umgebende Leerzeichen
erkannt — `datetime`, `DateTime` und ` DATETIME ` sind gleichwertig.

> **Ein unbekannter Typ legt die Änderungserkennung still**
>
> Steht im Feld `WatermarkType` ein Wert, der weder `date` noch `datetime` noch `string`
> ergibt (z. B. ein Tippfehler), gilt der Typ als unbekannt. Dann passiert Folgendes:
> 
> - Die gespeicherte Watermark bleibt **unangetastet** — sie wird nicht geleert
> - Sie wird aber auch **nie fortgeschrieben**
> - Im Log erscheint eine **Warnung** mit Jobnamen und dem hinterlegten Rohwert
> 
> Der Job liest damit bei jedem Lauf dieselbe Menge erneut. Die Warnung im Log ist der
> einzige Hinweis darauf — es entsteht kein Fehler.
> 
> Bis einschließlich v2.0 war das Verhalten deutlich schlimmer: Der Vergleich war
> zeichengenau, ein abweichend geschriebenes `DateTime` **leerte** die Watermark, und der
> Job las bei **jedem** Lauf das komplette Quellsystem — ohne jede Meldung.



---

## Wann wird der Watermark aktualisiert?

Der Watermark wird am Ende der **Erkennungsphase** fortgeschrieben — also sobald alle
gefundenen Änderungen in die Queue geschrieben sind, und **bevor** das Zielsystem
beschrieben wurde. Für die Zustellung ins Ziel sorgen danach die Queue-Mechanismen
(Wiederholung, Dead-Letter).

Im Einzelnen:

1. Beim Lesen führt die DataBridge den Änderungsmarker (`ChangeTracker`) der verarbeiteten
   Datensätze mit
2. Bei `date` und `datetime` wird jeweils der **höhere** der beiden Werte behalten, bei
   `string` gewinnt immer der zuletzt gelesene Token — ein opaker Token hat keine
   Ordnung
3. Nach dem Leeren des letzten Batches wird der Endstand in die Tabelle geschrieben
4. `LastCronRun` wird auf den Zeitpunkt gesetzt, zu dem der Job **begonnen** hat

> **`LastCronRun` wird bei jedem Lauf geschrieben**
>
> Auch dann, wenn der Job keine Änderungen gefunden hat oder gar keinen eigenen `Schedule`
> besitzt. Der Wert ist damit ein verlässlicher Indikator dafür, ob ein Job überhaupt
> gelaufen ist.



Bricht ein Job mit einem Fehler ab, bleibt der Watermark auf dem zuletzt geschriebenen
Stand. Beim nächsten Lauf wird ab diesem Punkt erneut gelesen.

### Zeitzonen

Zeitstempel werden zeitzonenunabhängig verglichen. Bis v2.0 wurde die Zeitangabe in die
lokale Zeit des Servers umgerechnet — beim Typ `date` konnte das über eine Tagesgrenze
springen und das Vergleichsergebnis je nach Betriebsstandort verändern.

---

## Erster Lauf — kein Eintrag vorhanden

Existiert für einen Job noch kein Watermark-Eintrag, legt die DataBridge ihn
beim ersten Lauf automatisch an. Als Startpunkt wird ein möglichst früher Wert
verwendet:

| Typ | Initialwert |
|---|---|
| `datetime` | `0001-01-01T00:00:00.000Z` |
| `date` | `0001-01-01` |
| `string` | *(leer)* |

Das bedeutet: der **erste Lauf lädt alle vorhandenen Datensätze** aus dem Quellsystem.
Bei großen Datenmengen sollte dies zu einem Zeitpunkt mit geringer Last geschehen.

---

## Manueller Eingriff — Wann und warum

> **Kein Schutz gegen gleichzeitige Änderungen**
>
> Die DataBridge schreibt Watermark-Einträge ohne ETag-Prüfung — es gilt
> **Last-Writer-Wins**. Wird ein Wert von Hand geändert, während der zugehörige Job gerade
> läuft, überschreibt der Job die manuelle Änderung am Ende seines Laufs kommentarlos.
> 
> Vor einem Eingriff daher die betroffene Funktion deaktivieren
> (`AzureWebJobs.GetChanges.Disabled = true`) oder einen Zeitpunkt wählen, zu dem der Job
> nachweislich nicht läuft.



### Vollständigen Neulauf erzwingen

Den Wert in der `Watermark`-Spalte **leeren** oder auf den Initialwert zurücksetzen.
Beim nächsten Lauf werden alle Datensätze erneut übertragen.

**Typische Anwendungsfälle:**
- Mapping wurde grundlegend geändert und alle Datensätze sollen neu gemappt werden
- Zielsystem wurde zurückgesetzt oder neu aufgebaut
- Datenverlust im Zielsystem

### Partiellen Neulauf ab bestimmtem Datum

Den Watermark-Wert manuell auf ein Datum setzen, ab dem neu gelesen werden soll:

```
2026-01-01T00:00:00.000Z
```

Damit werden nur Datensätze übertragen, die seit diesem Datum geändert wurden.

Bei `WatermarkType = string` ist das **nicht** möglich — der Token stammt aus Dataverse
und lässt sich nicht sinnvoll von Hand konstruieren. Dort bleibt nur das vollständige
Leeren.

### LastCronRun zurücksetzen

Wenn ein Job mit eigenem Schedule nicht zum erwarteten Zeitpunkt ausgeführt wurde,
kann `LastCronRun` manuell auf einen früheren Wert gesetzt werden. Die DataBridge
erkennt dann, dass das Intervall abgelaufen ist, und führt den Job beim nächsten
Timer-Aufruf aus.
