Files
itc.pidi-3-docs/chapters/execution/race-conditions.tex
T

63 lines
7.5 KiB
TeX

\section{Erkannte Grenze: Nebenläufigkeit im Lookup}
\label{sec:race-conditions}
\subsection{Ausgangslage}
Der Lesepfad des Kundenordner-Lookups ist unkritisch. Er fragt Metadaten ab und listet Objekte auf; gleichzeitige Aufrufe stören einander nicht. Der in Abschnitt~\ref{sec:folder-management} beschriebene dritte Schritt ist jedoch \emph{verändernd}: Er benennt Ordner um und legt sie an. Und er tut dies im Rahmen einer gewöhnlichen Seitenanfrage.
Erschwerend kommt hinzu, dass Houston in mehr als einer Instanz betrieben wird. Eine Sperre innerhalb einer Instanz — der naheliegende erste Gedanke — reicht deshalb nicht aus, weil die zweite Instanz von ihr nichts weiß.
Bei der Durchsicht des eigenen Codes im Anschluss an die Umsetzung wurden zwei konkrete Fehlerszenarien gefunden. Beide verletzen Akzeptanzkriterien, die im selben Backlog Item ausdrücklich formuliert worden waren.
\subsection{Szenario A: Datenverlust beim gleichzeitigen Umbenennen}
Das erste Szenario betrifft die Fehlerbehandlung des Umbenennens. Sie entfernt die eigenen Teilkopien, damit das Ziel leer bleibt und der Vorgang wiederholbar ist. Diese Annahme trifft nicht mehr zu, sobald eine zweite Anfrage dieselbe Umbenennung ausführt. Abbildung~\ref{fig:race-condition-a} zeigt den Ablauf.
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{figures/diagrams/race-condition-a.pdf}
\caption{Datenverlust bei gleichzeitigem Umbenennen}
\label{fig:race-condition-a}
\end{figure}
Beide Anfragen halten den Zielnamen für frei, weil beide prüfen, bevor eine von ihnen schreibt. Anfrage~A kopiert alle Objekte und löscht anschließend die Quelle. Anfrage~B, die langsamer ist, hat zu diesem Zeitpunkt erst einen Teil kopiert und findet beim nächsten Objekt die Quelle nicht mehr vor. Ihre Fehlerbehandlung greift und entfernt die von ihr erzeugten Kopien — das sind aber genau dieselben Objekte, die A soeben erfolgreich geschrieben hat. Da die Quelle bereits gelöscht ist, existiert von ihnen keine weitere Kopie.
Das Ergebnis ist endgültiger Datenverlust. Zusätzlich liefert B anschließend ein Präfix zurück, unter dem nichts mehr liegt. Die Fehlerbehandlung, die Datenverlust verhindern sollte, verursacht ihn.
\subsection{Szenario B: Vermischung zweier Mandanten}
Das zweite Szenario betrifft das Anlegen. Der Marker wird ohne Bedingung geschrieben. Haben zwei Organisationen denselben Firmennamen und greifen beide erstmals gleichzeitig zu, sehen beide den Zielnamen als frei an, und beide legen den Marker an. Der zuletzt geschriebene gewinnt.
Beide Anfragen arbeiten anschließend in demselben Ordner weiter, obwohl das Metadatum nur einer von ihnen gehört. Bis zum nächsten Aufruf sieht die unterlegene Organisation Dokumente in einem Ordner, der laut Metadatum der anderen zugeordnet ist. Der Folgeaufruf korrigiert sich zwar selbst — die unterlegene Organisation erkennt dann den fremden Marker und erhält den um die Organisations-ID ergänzten Namen —, doch was zwischenzeitlich abgelegt wurde, verbleibt im fremden Ordner und ist dort für den falschen Kunden sichtbar.
Von den beiden Szenarien ist dieses das schwerwiegendere: Datenverlust ist ein Betriebsproblem, die Offenlegung von Kundendokumenten gegenüber einem anderen Kunden ein Vertraulichkeitsproblem.
\subsection{Abgegrenzte Fälle}
Zur Eingrenzung wurde geprüft, welche nebenläufigen Abläufe \emph{nicht} betroffen sind. Zwei Anfragen derselben Organisation, die denselben Ordner anlegen, erzeugen identische Schlüssel mit identischen Metadaten und sind harmlos. Zwei Anfragen, die dieselbe Umbenennung vollständig ausführen, erzeugen inhaltsgleiche Kopien; ein Löschen bereits gelöschter Objekte ist unproblematisch. Auch der Fall, dass eine Anfrage liest, während eine andere verschiebt, ist abgedeckt: Da der Marker zuletzt kopiert wird und zusätzlich geprüft wird, ob am Ziel bereits Objekte liegen, wird ein halb gefülltes Ziel nicht als gültiger Kundenordner erkannt.
Diese Abgrenzung ist für die Bewertung wesentlich. Sie zeigt, dass die getroffenen Vorkehrungen wirken und das Problem auf den Fall zweier \emph{unterschiedlich weit fortgeschrittener} verändernder Vorgänge beschränkt ist.
\subsection{Erwogene Gegenmaßnahmen}
Vier Ansätze wurden formuliert:
\begin{enumerate}
\item \textbf{Absicherung der Fehlerbehandlung.} Vor dem Entfernen der Teilkopien wird geprüft, ob der Marker der Quelle noch existiert. Fehlt er, hat ein anderer Vorgang die Umbenennung bereits abgeschlossen, und es darf nichts gelöscht werden. Das beseitigt Szenario~A weitgehend und verschiebt den verbleibenden Fehler in die ungefährliche Richtung: Es bleiben überzählige Objekte zurück statt Daten verloren zu gehen.
\item \textbf{Bedingtes Schreiben.} Wird der Marker nur unter der Bedingung geschrieben, dass er noch nicht existiert, ist das Beanspruchen eines Namens unteilbar und Szenario~B ausgeschlossen \autocite{aws-conditional-writes}. Voraussetzung ist, dass der eingesetzte Speicher diese vergleichsweise junge Erweiterung unterstützt — was angesichts der Erfahrungen mit anderen Funktionen ausdrücklich nachzuweisen wäre.
\item \textbf{Instanzübergreifende Sperre.} Eine über die Datenbank realisierte Sperre je Organisation würde den verändernden Teil sauber serialisieren, kostet aber einen zusätzlichen Zugriff je Anfrage und erfordert ein Konzept für den Fall, dass eine Sperre nicht freigegeben wird.
\item \textbf{Verlagerung aus dem Anfragepfad.} Der verändernde Teil wird gar nicht mehr im Rahmen einer Benutzeranfrage ausgeführt. Bestandsordner werden einmalig kontrolliert migriert, und die Anwendung protokolliert lediglich, wenn ein Ordner vom Sollzustand abweicht. Damit verschwindet die Ursache vollständig statt abgesichert zu werden.
\end{enumerate}
Der erste Ansatz sollte in jedem Fall umgesetzt werden, unabhängig davon, wie die eigentliche Serialisierung gelöst wird. Der vierte ist insofern bemerkenswert, als er keine technische, sondern eine organisatorische Lösung darstellt — er beseitigt das Problem, indem er den verändernden Vorgang aus dem nebenläufigen Kontext herausnimmt.
\subsection{Umgang mit dem Befund}
Der Befund wurde als eigenes Backlog Item erfasst, mit erhöhter Priorität versehen und ausdrücklich vom laufenden Pull Request abgegrenzt. Diese Abgrenzung erfolgte nach Absprache im Team und wurde am Item dokumentiert. Das Team versah es mit einem Zeitrahmen von vier bis sechs Stunden.
Die Alternative wäre gewesen, den Pull Request so lange offenzuhalten, bis auch die Nebenläufigkeit gelöst ist. Dagegen sprach, dass die Wahl der Gegenmaßnahme selbst eine offene Frage ist: Ob bedingtes Schreiben zur Verfügung steht, ließ sich angesichts der Erfahrungen mit anderen Speicherfunktionen nicht ohne Rückfrage beim Betreiber beantworten, und deren Bearbeitungsdauer war zuvor mit mehreren Wochen bemessen worden. Der Lookup selbst — die eigentliche Verbesserung — wäre dadurch blockiert worden, obwohl er den Zustand gegenüber vorher bereits deutlich verbessert.
Hinzu kommt, dass der verändernde Pfad ausschließlich beim ersten Zugriff einer Organisation durchlaufen wird. Nach der einmaligen Selbstheilung ist er für diesen Kunden dauerhaft irrelevant. Das Risikofenster ist damit eng begrenzt — es besteht pro Kunde genau einmal.
Die Akzeptanzkriterien des Folgeitems halten fest, dass eine reine Sperre innerhalb einer Instanz ausdrücklich nicht als Lösung akzeptiert wird und dass beide Szenarien durch Unit-Tests nachzubilden sind, die ohne die Korrektur fehlschlagen.