Strategien für mobile Apps, die auf mehreren Geräten gleichzeitig offline arbeiten
Sobald zwei Geräte denselben Datensatz offline bearbeiten und beide anschließend synchronisieren, entsteht zwangsläufig ein Konflikt, den die App auflösen muss. Dieser Artikel zeigt, wie solche Konflikte technisch entstehen, welche Auflösungsstrategien sich in der Praxis bewährt haben und wo automatische Lösungen an ihre Grenzen stoßen.
Inhaltsverzeichnis
- 1. Wie Synchronisationskonflikte technisch entstehen
- 2. Last-Write-Wins: einfache Regel mit stillen Datenverlusten
- 3. Feldbasierte Merge-Strategien statt ganzer Datensätze
- 4. Konflikterkennung mit Versionsvektoren und Änderungszählern
- 5. Operational Transformation und CRDTs im mobilen Kontext
- 6. Benutzergesteuerte Konfliktauflösung: wenn Automatik nicht ausreicht
- 7. Praktische Sync-Engines: WatermelonDB, RxDB und Automerge im Vergleich
- 8. Konfliktszenarien gezielt testen statt hoffen
- 9. Grenzen automatischer Auflösung in der Praxis
- 10. Zusammenfassung
- 11. FAQ
1. Wie Synchronisationskonflikte technisch entstehen
Ein Synchronisationskonflikt entsteht, sobald zwei Geräte offline denselben Datensatz verändern und beide Änderungen anschließend gegen denselben Server-Zustand ausgespielt werden. Der Server kennt zum Zeitpunkt der ersten eingehenden Änderung nur eine Version, sieht aber kurz darauf eine zweite Anfrage, die von genau derselben Ausgangsversion ausgeht, obwohl der Datensatz inzwischen bereits verändert wurde.
Bei mobilen Apps tritt dieses Szenario deutlich häufiger auf als bei reinen Web-Anwendungen, weil Flugmodus, U-Bahn-Fahrten und schwankende Netzabdeckung längere Offline-Fenster erzeugen. Zwei Außendienstmitarbeiter, die zeitgleich denselben Kundendatensatz auf ihrem Gerät bearbeiten, produzieren dabei fast zwangsläufig widersprüchliche Änderungen, die erst beim nächsten erfolgreichen Sync-Vorgang sichtbar werden.
2. Last-Write-Wins: einfache Regel mit stillen Datenverlusten
Last-Write-Wins ist die einfachste Konfliktlösung: Der Änderung mit dem höchsten Zeitstempel wird der Vorzug gegeben, die andere wird stillschweigend verworfen. Die Regel lässt sich mit wenigen Zeilen Code implementieren und funktioniert für unkritische Werte wie einen zuletzt geöffneten Ordner oder eine Sortiereinstellung meist völlig ausreichend.
Problematisch wird Last-Write-Wins, sobald Geräteuhren nicht exakt synchron laufen oder ein Gerät zeitweise ohne Netzverbindung war und seine Uhrzeit erst später korrigiert. In diesem Fall gewinnt nicht die tatsächlich zuletzt getroffene Entscheidung, sondern die Änderung mit der zufällig höheren Client-Uhrzeit, was bei geschäftskritischen Feldern zu unbemerktem Datenverlust führt.
// Naive Last-Write-Wins-Implementierung im Sync-Layer
type Record = { id: string; updatedAt: number; payload: Record<string, unknown> };
function resolveLastWriteWins(local: Record, remote: Record): Record {
// Achtung: verlässt sich vollständig auf die Client-Uhrzeit,
// Uhren-Drift zwischen Geräten wird hier nicht erkannt.
return local.updatedAt >= remote.updatedAt ? local : remote;
}
3. Feldbasierte Merge-Strategien statt ganzer Datensätze
Statt einen kompletten Datensatz zu überschreiben, vergleicht ein feldbasiertes Merge jeden einzelnen Wert mit seinem eigenen Änderungszeitstempel. Ändert ein Gerät nur die Telefonnummer eines Kontakts und ein anderes Gerät zeitgleich nur die Notiz, bleiben beide Änderungen erhalten, weil sie sich schlicht nicht überschneiden.
Diese Strategie reduziert die Anzahl echter Konflikte erheblich, weil in vielen Formularen die meisten Felder unabhängig voneinander bearbeitet werden. Der Preis dafür ist zusätzlicher Speicheraufwand, da pro Feld ein eigener Zeitstempel und teilweise ein eigener Änderungszähler mitgeführt werden muss, statt nur eine einzige Spalte für den gesamten Datensatz zu pflegen.
// Feldbasiertes Merge: nur tatsächlich kollidierende Felder werden geprüft
type FieldMeta = { value: unknown; changedAt: number };
type Entity = Record<string, FieldMeta>;
function mergeFields(local: Entity, remote: Entity): Entity {
const result: Entity = { ...local };
for (const key of Object.keys(remote)) {
const remoteField = remote[key];
const localField = local[key];
if (!localField || remoteField.changedAt > localField.changedAt) {
result[key] = remoteField;
}
}
return result;
}
4. Konflikterkennung mit Versionsvektoren und Änderungszählern
Reine Zeitstempel verraten nur, welche Änderung später erfolgt ist, nicht aber, ob zwei Änderungen tatsächlich gleichzeitig und unabhängig voneinander entstanden sind. Versionsvektoren lösen dieses Problem, indem jedes Gerät einen eigenen Zähler pro Datensatz führt und diesen bei jeder lokalen Änderung erhöht.
Vergleicht der Server zwei Versionsvektoren, erkennt er zuverlässig, ob eine Änderung linear auf der anderen aufbaut oder ob beide unabhängig voneinander, also nebenläufig, entstanden sind. Nur im zweiten Fall liegt ein echter Konflikt vor, der eine Auflösungsstrategie benötigt, während eine rein sequentielle Änderungskette automatisch und ohne Risiko übernommen werden kann.
5. Operational Transformation und CRDTs im mobilen Kontext
Operational Transformation stammt aus kollaborativen Texteditoren und transformiert eingehende Operationen so, dass sie unabhängig von der Reihenfolge zum gleichen Endergebnis führen. Für klassische mobile CRUD-Apps mit Formularen ist dieser Ansatz meist überdimensioniert, weil er eine zentrale Transformationslogik voraussetzt, die für jede Feldkombination korrekt definiert sein muss.
Conflict-free Replicated Data Types, kurz CRDTs, garantieren dagegen mathematisch, dass mehrere Replikate ohne zentrale Koordination zum selben Ergebnis konvergieren. Bibliotheken wie Automerge oder Yjs eignen sich gut für Zähler, Mengen oder kollaborativen Text, bringen aber deutlich größere Metadaten mit sich und erschweren die Fehlersuche in einfachen Geschäftsdatensätzen spürbar.
6. Benutzergesteuerte Konfliktauflösung: wenn Automatik nicht ausreicht
Manche Konflikte dürfen nicht automatisch entschieden werden, etwa wenn zwei Vertriebsmitarbeiter offline unterschiedliche Rabatte für denselben Auftrag hinterlegt haben. In solchen Fällen sollte die App den Konflikt sichtbar machen, beide Versionen nebeneinander anzeigen und dem Nutzer die aktive Entscheidung überlassen, statt eine der beiden Änderungen stillschweigend zu verwerfen.
Praktisch bewährt sich dafür eine dedizierte Konflikt-Warteschlange, die betroffene Datensätze so lange zurückhält, bis der Nutzer eine Entscheidung getroffen hat. Erst nach dieser Entscheidung wird der Datensatz endgültig synchronisiert, während alle anderen, konfliktfreien Änderungen weiterhin automatisch im Hintergrund verarbeitet werden.
function ConflictBanner({ local, remote, onResolve }: ConflictProps) {
return (
<View style={styles.banner}>
<Text style={styles.title}>Widersprüchliche Änderung erkannt</Text>
<Text>Lokal: {local.discount}% Rabatt</Text>
<Text>Server: {remote.discount}% Rabatt</Text>
<View style={styles.actions}>
<Button title="Lokale Version behalten" onPress={() => onResolve(local)} />
<Button title="Server-Version übernehmen" onPress={() => onResolve(remote)} />
</View>
</View>
);
}
7. Praktische Sync-Engines: WatermelonDB, RxDB und Automerge im Vergleich
WatermelonDB folgt dem Muster pullChanges und pushChanges und liefert standardmäßig eine einfache Last-Write-Wins-Logik, erlaubt aber, einen eigenen Resolver zwischen dem Pull- und dem Push-Schritt einzuhängen. RxDB arbeitet mit einer Revisionskette pro Dokument und erkennt Konflikte, sobald die lokale Revision nicht mehr zur erwarteten Server-Revision passt.
Automerge geht einen grundsätzlich anderen Weg und führt Änderungen als CRDT automatisch zusammen, ganz ohne expliziten Konfliktschritt, was für strukturierte Dokumente wie Notizen oder Konfigurationen gut funktioniert. Für klassische relationale Geschäftsdaten mit Fremdschlüsseln bleibt in der Praxis meist eine Kombination aus feldbasiertem Merge und einer schmalen manuellen Eskalationsschicht die robustere Wahl.
8. Konfliktszenarien gezielt testen statt hoffen
Konfliktlösung lässt sich nicht durch manuelles Ausprobieren zuverlässig absichern, weil das Zusammenspiel aus Netzwerktiming und lokalem Zustand kaum reproduzierbar ist. Sinnvoller ist ein Integrationstest, der zwei simulierte Clients denselben Ausgangsdatensatz offline verändern lässt und anschließend beide Änderungen nacheinander gegen denselben Server-Endpunkt schickt.
Ergänzend lohnt sich gezieltes Chaos-Testing mit zufälligen Offline-Fenstern und verzögerten Antworten, etwa über eine gemockte NetInfo-Implementierung in React Native. Auf Serverseite gehört zusätzlich ein Idempotenz-Check dazu, der doppelt eingehende Änderungen erkennt und verhindert, dass eine wiederholte Sync-Anfrage nach einem Verbindungsabbruch versehentlich einen weiteren Konflikt erzeugt.
9. Grenzen automatischer Auflösung in der Praxis
Bei Lagerbeständen, Preisen oder Zahlungsstatus ist automatisches Merge riskant, weil ein falsch zusammengeführter Wert direkte finanzielle Folgen haben kann. Für solche Felder sollte die Automatisierung bewusst eskalieren, statt eine plausibel klingende, aber möglicherweise falsche Annahme zu treffen und den Konflikt intransparent im Hintergrund zu verstecken.
In der Praxis bewährt sich ein hybrider Ansatz: automatische Auflösung für risikoarme Felder wie Notizen oder Ansichtspräferenzen, eine manuelle Prüfungswarteschlange für geschäftskritische Felder sowie ein durchgängiges Änderungsprotokoll, das jede Konfliktentscheidung nachvollziehbar dokumentiert und im Streitfall als Nachweis dient. Diese Kombination aus Automatisierung und gezielter menschlicher Kontrolle lässt sich schrittweise einführen und muss nicht von Anfang an jedes Feld abdecken, sondern kann zunächst auf die offensichtlich riskantesten Datenpunkte beschränkt bleiben, bevor sie auf weitere Bereiche der App ausgeweitet wird.
| Strategie | Funktionsweise | Geeignet für | Risiko |
|---|---|---|---|
| Last-Write-Wins | Höchster Zeitstempel gewinnt, unterlegene Änderung wird verworfen | Unkritische Einzelwerte wie Ansichtspräferenzen | Stille Datenverluste bei Uhren-Drift |
| Feldbasiertes Merge | Jedes Feld erhält einen eigenen Zeitstempel, unabhängige Felder bleiben erhalten | Formulare mit mehreren unabhängigen Feldern | Erhöhter Speicher- und Prüfaufwand pro Feld |
| CRDT-basierte Typen | Mathematisch garantierte Konvergenz ohne zentrale Instanz | Kollaborative Texte, Zähler, Mengen | Größere Metadaten, komplexere Fehlersuche |
| Versionsvektoren | Erkennung echter Konflikte statt reiner Zeitreihenfolge | Verteilte Systeme mit mehreren Schreibern | Zusätzliche Serverlogik zur Auswertung |
| Benutzergesteuerte Auflösung | Nutzer erhält Diff-Ansicht und entscheidet aktiv | Geschäftskritische Felder wie Preise oder Mengen | Unterbricht den Workflow, erfordert UI-Aufwand |
Mironsoft
React-Native-App-Entwicklung und Magento-Anbindung
Eine mobile App zum Magento-Shop, die wirklich rund läuft?
Wir entwickeln React-Native-Apps, die sauber an die Magento REST- oder GraphQL-API angebunden sind, von der ersten Codezeile bis zur Veröffentlichung im App Store und bei Google Play.
App-Konzeption
Architektur und Feature-Umfang einer Magento-angebundenen App gemeinsam planen.
Magento-API-Integration
Produktkatalog, Warenkorb und Checkout sauber an die Shop-API anbinden.
Store-Veröffentlichung
App Store- und Google-Play-Freigabeprozess ohne Stolperfallen begleiten.
10. Zusammenfassung
Offline-Sync-Konfliktlösung: Das Wichtigste auf einen Blick
Konflikte sind unvermeidbar
Sobald mehrere Geräte offline denselben Datensatz bearbeiten, entsteht bei der Synchronisation zwangsläufig ein Konflikt, den die App aktiv behandeln muss.
Last-Write-Wins reicht selten
Reine Zeitstempel-Vergleiche verursachen bei Uhren-Drift stille Datenverluste, besonders bei geschäftskritischen Feldern.
Feldbasiertes Merge reduziert Kollisionen
Wer Konflikte pro Feld statt pro Datensatz behandelt, verliert deutlich seltener unabhängige Änderungen.
Automatik hat Grenzen
Bei hohem Risiko sollte die App eskalieren und den Nutzer aktiv entscheiden lassen, statt zu raten.