# Transaktionssicherheit



## nrg (20. Dez 2012)

Hallo Zusammen,

z.B. wäre jetzt so eine Transaktion das Erstellen einer Ordnerstruktur, Schreiben einer Datei, Erstellen einer Signal-Datei und das Löschen einer Datei. Bei mir ist das meistens einfaches Exceptionhandling. Die Creates am Anfang gemerkt in einer Liste und am Ende der Delete - schlägt ein Schritt fehl kann ich im catch den Rollback machen. Das funktioniert auch ohne Probleme.

Jetzt wirds natürlich interessanter, wenn man mehrere schreibende bzw. modifizierende Transaktionsschritte hat. Mal ein ganz einfaches Beispiel: die Transaktion ist das Löschen von 2 Dateien. Schlägt der zweite Delete fehl, hat man natürlich beim Rollback schon ein Problem. Jetzt könnte man natürlich die Dateien erst umbenennen und danach löschen. Bisschen blöder wirds dann wieder bei Modifikationen. Klar, hier könnte man auch erst ein Backup erstellen.

Meine Frage ist eher, ob es Technologien gibt, die sowas "out-of-the-box" anbieten. Also eine Art try-catch-rollback-finally . Der Rollback ist im Grunde implizit auf alle durchgeführten Aktionen (file/db/whatever). Glaub aber fast, dass das wunschdenken ist


----------



## Bernd Hohmann (20. Dez 2012)

Ich schlage mich mit Deinem Problem schon seit Jahren in verschiedenen Sprachen herum und bislang hat mir jedes Dateisystem (FAT, HPFS, NTFS, EXT2..4, ReiserFS ...) immer einen Strich durch die Rechnung gemacht.

In einem Projekt (wo ich keine "richtige" Datenbank nutzen kann) werden zb. ständig Userdaten aus einer kleinen Datei "user@domain.tld" gelesen, modifiziert, in eine "$$user@domain.tld$$" zurückgeschrieben, das Originalfile gelöscht und die Tmp-Datei in die Originaldatei umbenannt. 

Dh. es liegt immer eine saubere Version der Datei herum (Originaldatei oder Temp-Datei, jeweils Grösse > 0)

Jetzt kommt es sporadisch zu irgendwelchen Hängern die selbst unter Linux nur mit hartem Reboot zu lösen sind. Ergebnis: die Originaldatei liegt mit 0 Bytes Grösse herum, von der Temp-Datei keine Spur. Von der Programmierung her darf dieser Zustand nicht vorkommen, passiert aber.

Dein Problem wäre ja noch halbwegs einfach abzuhandeln in dem man java.io.File nochmal als "nrg.io.File" schreibt wo alle del/ren Operationen in einer Queue landen und die Datei statt .delete() via .rename() nur temporär gelöscht wird.

Aber aufgrund meiner etwas schlechten Erfahrungen mit Dateisystemen habe ich etwas Zweifel dass das wirklich Transaktionssicher gestaltet werden kann.

Bernd


----------



## nrg (20. Dez 2012)

ja, sehe jetzt auch nicht das problem darin, das in diesem speziellen fall zu handeln. wie du ja auch schon sagst, hat man im grunde 3 aktionen, delete, modify, create. die lassen sich alle ohne größere probleme mit rename und co. transaktionssicher gestalten. wenn natürlich mittendrin die vm abraucht, wars das mit der transaktion aber sowas trifft einen ja überall.

mir ging es eher um eine allgemeine möglichkeit. ehrlich gesagt kann ich mir das auch schwer in einer library oder einem framework vorstellen, sondern muss die programmiersprache an sich mitbringen. es geht ja im weiteren auch um objektzustände

edit: bzw. bei dateioperationen, wie du schon sagst, das dateisystem/os


----------



## Bernd Hohmann (20. Dez 2012)

nrg hat gesagt.:


> mir ging es eher um eine allgemeine möglichkeit. ehrlich gesagt kann ich mir das auch schwer in einer library oder einem framework vorstellen, sondern muss die programmiersprache an sich mitbringen. es geht ja im weiteren auch um objektzustände



Zustände sind das hier 

Ich denke nicht, dass sowas in den Aufgabenbereich einer Programmiersprache fällt  - es sei denn, sie heisst COBOL (oder eine andere Sprache die sich überwiegend um das herumschubsen von Records kümmert).

Aber eine Lib oder Framework... Vielleicht schreiben wir sowas einfach mal jetzt.

Bernd


----------



## tröööt (21. Dez 2012)

naja ... aber sind heutige daten-systeme nicht grundsätzlich journaled ?



			
				Wiki/NTFS hat gesagt.:
			
		

> Beim Speichern von Metadaten wird ein Journal geführt, das bedeutet, dass eine geplante Aktion zuerst in das Journal geschrieben wird. Dann wird der eigentliche Schreibzugriff auf die Daten ausgeführt und abschließend wird das Journal aktualisiert. Wenn ein Schreibzugriff nicht vollständig beendet wird, zum Beispiel wegen eines Absturzes, muss das Dateisystem nur die Änderungen im Journal zurücknehmen und befindet sich anschließend wieder in einem konsistenten Zustand.


sowie weiter unten


			
				Wiki/NTFS hat gesagt.:
			
		

> Erweiterungen seit Windows Vista [Bearbeiten]
> 
> Transactional NTFS (TxF) [Bearbeiten]
> 
> ...



außerdem zählt wikipedia in der liste der journaling-dateisysteme noch folgende auf

EXT3/4
HFS+
JFS
ReiserFS
XFS
ZFS

ich habe mir jetzt die einzelnen abschnitte zum journaling für die anderen FS jetzt nicht durchgelesen ... gehe aber von ähnlichem aus wie in der akutellen NTFS variante ...

ergo : die "transaktionssicherheit" von daten-ops sollte eigentlich im FS verankert und durch das OS bzw dessen kernel verwendet werden ...


----------



## Bernd Hohmann (21. Dez 2012)

tröööt hat gesagt.:


> naja ... aber sind heutige daten-systeme nicht grundsätzlich journaled ?



Was hast Du Vollspaten-Hobbyadmin an "Ich schlage mich mit Deinem Problem schon seit Jahren in verschiedenen Sprachen herum und bislang hat mir jedes Dateisystem (FAT, HPFS, NTFS, EXT2..4, ReiserFS ...) immer einen Strich durch die Rechnung gemacht." nicht verstanden?


----------



## Lumaraf (21. Dez 2012)

Bernd Hohmann hat gesagt.:


> Dh. es liegt immer eine saubere Version der Datei herum (Originaldatei oder Temp-Datei, jeweils Grösse > 0)
> 
> Jetzt kommt es sporadisch zu irgendwelchen Hängern die selbst unter Linux nur mit hartem Reboot zu lösen sind. Ergebnis: die Originaldatei liegt mit 0 Bytes Grösse herum, von der Temp-Datei keine Spur. Von der Programmierung her darf dieser Zustand nicht vorkommen, passiert aber.



Das klingt für mich danach das da vor dem umbenennen noch ein Aufruf von FileDescriptor#sync() bzw FileChannel#force(boolean) fehlt.


----------



## Bernd Hohmann (21. Dez 2012)

Lumaraf hat gesagt.:


> Das klingt für mich danach das da vor dem umbenennen noch ein Aufruf von FileDescriptor#sync() bzw FileChannel#force(boolean) fehlt.



Die Doku war etwas unklar dazu;: bezieht sich .sync() auf Filehandles innerhalb der aktuellen JVM oder auf das gesamte Dateisystem des OS?

Hatte das bei Einführung probiert und massive Performanceeinbrüche des gesamten Systems gehabt, danach nie wieder angefasst.

Da fällt mir unpassend ein, dass gerade dieses Projekt maximal gegen Java 1.2 (oder 1.4 - müsst ich nachsehen) kompiliert werden darf.

Bernd


----------



## nrg (21. Dez 2012)

das journaling bietet dem os selbst die transaktionssicherheit beim schreiben einer datei, um eine datenkonsistenz zu gewährleisten. jetzt ist die frage, ob es einer programmiersprache bzw. auf lowlevel der maschinensprache möglich ist, das journaling asynchron darzustellen.


----------



## tröööt (21. Dez 2012)

Bernd Hohmann hat gesagt.:


> Was hast Du Vollspaten-Hobbyadmin an "Ich schlage mich mit Deinem Problem schon seit Jahren in verschiedenen Sprachen herum und bislang hat mir jedes Dateisystem (FAT, HPFS, NTFS, EXT2..4, ReiserFS ...) immer einen Strich durch die Rechnung gemacht." nicht verstanden?



den fakt das es einfach mal alle modernen daten-systeme und OS anbieten und auch nutzen ...

wenn linux crashed während man auf platte schreibt stellt fschk nach nem crash den stand wieder her der erfolgreich abgeschlossen wurde bevor "write()" gecallt wird ... zumindest auf nem Ext4 unter OpenSuSE 12.x ...

warum es also mit java ... oder wie du sagst : anderen sprachen ... nicht funktioniert kann doch nur den schluss zu lassen das diese die vom OS genutzte fähigkeit das journaling zu nutzen eben nicht nutzen (können) ... warum auch immer ...

bevor du dich also mal wieder über jemanden auslässt der sich lustig drüber macht wie du dir selbst in deinen posts/threads immer mal wieder gerne widersprichst versuch es einfach mal zu verstehen ...


@nrg
tja ... man könnte mal gucken ob man was über NIO.2 File-API rausbekommt ob diese auf bestimmten OS mit passendem FS in der lage dazu ist das journaling auch zu nutzen ...
unter windows und NTFS dürfte das sicher schwer werden ... aber unter unix mit Ext4 sollte es definitiv machbar sein ...
wenn nich kann man ja mal versuchen den fs-treiber des kernels direkt anzuzapfen


----------



## Bleiglanz (21. Dez 2012)

Ich versteh ehrlich gesagt eure Diskussion nicht ganz, ein Unix 
	
	
	
	





```
rm file
```
 ist in etwa so wie das Abschiessen einer Rakete, ich kann mir nicht recht vorstellen wie man da von einer höheren Programmiersprache aus eine Transaktion mit Rollback drumherum basteln soll/kann.

Journaling auf OS-Ebene ist doch ganz was anderes, das ist doch Welten von der Java JTA entfernt. Eure Diskussion klingt fast so, als wolltet ihr einen Java-Transaktionsmanager für Operationen auf dem Dateisystem schreiben? Das ist wahrscheinlich SEHR schwierig - wenn nicht unlösbar.


----------



## KSG9|sebastian (21. Dez 2012)

Zudem ist das Problem doch damit nicht gelöst:

TX: 2 Dateien schreiben 1 löschen 1 ändern

1. schreiben - ok
2. schreiben - ok
3. löschen - ok
4. ändern - fail

Wie soll durch Journaling jetzt bitte der alte Zustand wiederhergestellt werden? 1, 2 und 3 waren ja erfolgreich...


----------



## Bernd Hohmann (21. Dez 2012)

Bleiglanz hat gesagt.:


> Ich versteh ehrlich gesagt eure Diskussion nicht ganz, ein Unix
> 
> 
> 
> ...



In dem man den Abschuss der Rakete nur simuliert.

Ich bin da ganz bei nrg weil ich sein Problem auch immer mal wieder habe: man bearbeitet einen Satz Dateien und irgendwo ganz hinten klemmt es und man möchte alle Bearbeitungsschritte ungeschehen machen.

Vielleicht wäre "Snapshot" eine bessere Bezeichnung als Transaktion. Also alles, was man anfassen möche vorher beiseite kopieren, Änderungen machen und im Fehlerfall den Ursprungszustand restaurieren.

Singlethreaded kann ich mir das noch gut vorstellen (zb über ein eigenes File-Objekt), Multithreaded? Uhhh..... :reflect:

Bernd


----------



## Bernd Hohmann (21. Dez 2012)

tröööt hat gesagt.:


> warum es also mit java ... oder wie du sagst : anderen sprachen ... nicht funktioniert kann doch nur den schluss zu lassen das diese die vom OS genutzte fähigkeit das journaling zu nutzen eben nicht nutzen (können) ... warum auch immer ...



Journaling ist fester Bestandteil des Dateisystems. Ich habe arge Zweifel daran, dass sich irgendjemand die Mühe gemacht hat, die Java-Runtime ausgerechnet am Journaling vorbei arbeiten zu lassen.



tröööt hat gesagt.:


> bevor du dich also mal wieder über jemanden auslässt der sich lustig drüber macht wie du dir selbst in deinen posts/threads immer mal wieder gerne widersprichst versuch es einfach mal zu verstehen ...



Du hast einen Knall, der ausreicht das Universum nochmal von der Singularität zur Unendlichkeit anzutreiben.


----------



## Bleiglanz (21. Dez 2012)

Na dann:

1) Bearbeite Datei1
2) Bearbeite Datei2
...
47) Bearbeite Datei47: FEHLER! HILFE

Probrammierer: WTF, ab ins Wochenende.
Chef: Nein, du machst das so

1) KOPIERE Datei1
1') Bearbeite KOPIE1
2) KOPIERE Datei2
2') Bearbeite KOPIE2
...
WENN ERFOLGREICH
1) Überschreibe Datei1 mit KOPIE1
2) Überschreibe Datei2 mit KOPIE2
...
47) Überschreibe Datei47 mit KOPIE 47: FEHLER Hilfe

..
kann man beliebig iterieren, da wird nie eine Transaktion im Sinne von ACID draus, das kann so nicht funktionieren. Man muss das in eine Datenbank schieben.


----------



## Bernd Hohmann (21. Dez 2012)

Bleiglanz hat gesagt.:


> kann man beliebig iterieren, da wird nie eine Transaktion im Sinne von ACID draus, das kann so nicht funktionieren. Man muss das in eine Datenbank schieben.



Das klingt jetzt aber so, als ob eine Datenbank ein mysteriöse Black-Box ist, die deus ex machina  alle Probleme eines Dateisystems abgestreift hat. Wenn die Datenbank das schafft, müsste man es auch im Dateisysstem schaffen - oder irre ich hier?

Bernd


----------



## tfa (21. Dez 2012)

Bernd Hohmann hat gesagt.:


> Wenn die Datenbank das schafft, müsste man es auch im Dateisysstem schaffen - oder irre ich hier?


Sicherlich geht das. Ich hatte vor langer Zeit auch mal dieses Problem. Wenn ich mich recht erinnere, habe ich versucht, das so zu lösen:

1. Kopiere Datei -> Datei.work
2. Bearbeite Datei.work
3. Benenne Datei -> Datei.recover
4. Benenne Datei.work -> Datei
5. Lösche Datei.recover

Wenn der Prozess zwischen 1. und 4. stirbt, kann man beim Neustart "Datei.recover" wieder herstellen und "Datei.work" löschen, sozusagen einen Rollback machen. Zusammen mit den Zeitstempeln sollte das möglich sein.


----------



## Shadoka (21. Dez 2012)

Wenn ich die Lösung von tfa richtig verstanden habe, ist das im Prinzip ja ein optimistisches Sperrverfahren, wo man auf lokalen Kopien arbeitet und durch Validierung am Ende per Timestamp feststellt, ob alle Änderungen gemacht werden dürfen.
Diese Vorgehensweise dürfte wahrscheinlich funktionieren, aber es ist von Nutzungskontext abhängig, ob es sinnvoll ist.

Die Validierung lässt sich auch beliebig komplex gestalten, je nachdem ob Konsistenzbedingungen an diese oder jene Datei, Ordnerstruktur, o.ä. geknüpft sind.


----------



## Empire@Work (21. Dez 2012)

Wuie wäre es statt datei gefrikel zu machen das ganze sauber zu versionieren?

Erst die neu datei erstellen, dann validieren dass sie existiert mit inhalt, danach dann austauschen. (Btw kann es hier durch fehlerhafte schreibblockaden der hardware unabhängig was und wie immer mal probleme geben, da hilft dann auch kein anderes system mehr.)

Workaround?
Hardcore lösung, inner vm mit snapshoting fürs image machen. Solange der host stabil läuft, was eigentlich unter linux gegeben ist, kann die vm den nicht zum absturz bringen, somit kann man die vm ntfalls abschiessen, snapshot resetten und gut is.


Zudem sollte man jegliche art von schreibcaches auschalten wenn man sichergehen muss das datien geschreiben sind wenns das system returnt. (evtl extra platte dafür nehmen, dass nicht alles plötzlich extrem langsam wird)


----------



## tröööt (22. Dez 2012)

KSG9|sebastian hat gesagt.:


> Zudem ist das Problem doch damit nicht gelöst:
> 
> TX: 2 Dateien schreiben 1 löschen 1 ändern
> 
> ...



ind dem man bis zum ende von 4) alles im RAM oder in kopien macht und erst nach abschluss ALLER schritte wirklich daten auf platte schreibt ... und erst wenn hier alles wirklich fehlerfrei geschrieben wurde die sicherungsdaten entfernen ... DANN kann journaling schon helfen ... zumindest wenn der gesamte block als "eine" operation eingetragen wird


----------



## Ark (22. Dez 2012)

Ich habe mir auch schon mal Gedanken zu dem Thema gemacht. Momentan bin ich bei folgendem Ansatz (gewählte Dateinamen dienen hier mehr der Illustration):


Erzeuge atomar(!) eine neue(!) Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
. (Wenn das fehlschlägt, versucht gerade jemand anderes sich an einer Transaktion.)
Wenn du eine Datei 
	
	
	
	





```
TRANSAKTION_FAST_BEENDET
```
 findest, schließe zunächst die vormals begonnene Transaktion sauber ab (siehe unten). Wenn du sie aber nicht vorfindest, mache die vormals begonnene Transaktion rückgängig (siehe unten).
Erzeuge Dummy-Dateien so, dass klar ist, was passieren soll, und arbeite auf diesen Dateien, etwa 
	
	
	
	





```
Name~
```
 für eine Datei, die nach Abschluss der Transaktion die Datei 
	
	
	
	





```
Name
```
 ersetzt/erzeugt, oder 
	
	
	
	





```
Name$
```
 als Hinweis dafür, dass die Datei 
	
	
	
	





```
Name
```
 nach der Transaktion gelöscht sein wird. (Die Zuordnung zu den tatsächlichen Dateinamen muss natürlich eineindeutig sein, was die Wahl der Dateinamen für die "richtigen" Dateien einschränkt.)
Synchronisiere alle Dateiinhalte bzw. Metadaten.
Erzeuge atomar(!) eine neue(!) Datei 
	
	
	
	





```
TRANSAKTION_FAST_BEENDET
```
.
Schließe die Transaktion ab (siehe unten).
Lösche atomar(!) die Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
.

Der eigentliche Abschluss der Transaktion sieht dabei wie folgt aus:

Vorbedingung: Man muss gerade selbst die Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
 wie oben beschrieben angelegt haben.
Benenne jede Datei 
	
	
	
	





```
Name~
```
 in 
	
	
	
	





```
Name
```
 um und lösche für jede Datei 
	
	
	
	





```
Name$
```
 die dazugehörige 
	
	
	
	





```
Name
```
, lösche danach alle 
	
	
	
	





```
Name$
```
.
Synchronisiere alle Dateiinhalte bzw. Metadaten.
Lösche atomar(!) die Datei 
	
	
	
	





```
TRANSAKTION_FAST_BEENDET
```
.

Das Rückgängigmachen einer Transaktion sieht wie folgt aus:

Vorbedingung: Man muss gerade selbst die Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
 wie oben beschrieben angelegt haben.
Lösche jede Datei 
	
	
	
	





```
Name~
```
 und jede Datei 
	
	
	
	





```
Name$
```
.

Probleme meines Ansatzes:

Nebenläufiges Lesen funktioniert nur insofern, als dass zwar nur "komplette" Dateien vorliegen, aber zwischendurch könnten Dateien gelöscht oder erzeugt worden sein.
Wenn man "sicher" lesen will (also nur abgeschlossene Transaktionen vorfinden will), muss man selbst eine Transaktion starten, auch wenn man nichts verändert. Das erfordert, dass das Dateisystem beschreibbar sein muss bzw. man selbst Schreibrechte hat, obwohl man nur lesen will.
Es ist viel Platz auf der Platte nötig und dauert lange.
Es könnte noch eine Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
 sowie Dummydateien wie 
	
	
	
	





```
Name~
```
 oder 
	
	
	
	





```
Name$
```
 herumspuken, obwohl wirklich kein Prozess eine Transaktion durchführen will.

Ich muss aber zugeben, dass ich das noch nie praktisch umgesetzt habe, insofern ist das alles mit Vorsicht zu genießen.

Ark


----------



## Ark (23. Dez 2012)

[WR]Mein im letzten Beitrag beschreibenes Verfahren ist im Umgang mit zu löschenden Dateien grob fehlerhaft. Hier nun eine (hoffentlich) weniger problematische Version:[/WR]

Erzeuge atomar(!) eine neue(!) Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
. (Wenn das fehlschlägt, versucht gerade jemand anderes sich an einer Transaktion.)
Wenn du eine Datei 
	
	
	
	





```
TRANSAKTION_FAST_BEENDET
```
 findest, schließe zunächst die vormals begonnene Transaktion sauber ab (siehe unten). Wenn du sie aber nicht vorfindest, mache die vormals begonnene Transaktion rückgängig (siehe unten).
Erzeuge Dummy-Dateien so, dass klar ist, was passieren soll, und arbeite auf diesen Dateien, etwa 
	
	
	
	





```
Name~
```
 für eine Datei, die nach Abschluss der Transaktion die Datei 
	
	
	
	





```
Name
```
 ersetzt/erzeugt. Benenne atomar(!) eine zu löschende Datei 
	
	
	
	





```
Name
```
 in 
	
	
	
	





```
Name$
```
 um. (Die Zuordnung zu den tatsächlichen Dateinamen muss natürlich eineindeutig sein, was die Wahl der Dateinamen für die "richtigen" Dateien einschränkt.)
Synchronisiere alle Dateiinhalte bzw. Metadaten.
Erzeuge atomar(!) eine neue(!) Datei 
	
	
	
	





```
TRANSAKTION_FAST_BEENDET
```
.
Schließe die Transaktion ab (siehe unten).
Lösche atomar(!) die Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
.

Der eigentliche Abschluss der Transaktion sieht dabei wie folgt aus:

Vorbedingung: Man muss gerade selbst die Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
 wie oben beschrieben angelegt haben.
Benenne jede Datei 
	
	
	
	





```
Name~
```
 in 
	
	
	
	





```
Name
```
 um und lösche jede Datei 
	
	
	
	





```
Name$
```
.
Synchronisiere alle Dateiinhalte bzw. Metadaten.
Lösche atomar(!) die Datei 
	
	
	
	





```
TRANSAKTION_FAST_BEENDET
```
.

Das Rückgängigmachen einer Transaktion sieht wie folgt aus:

Vorbedingung: Man muss gerade selbst die Datei 
	
	
	
	





```
TRANSAKTION_LAEUFT
```
 wie oben beschrieben angelegt haben.
Lösche jede Datei 
	
	
	
	





```
Name~
```
 und benenne atomar(!) jede Datei 
	
	
	
	





```
Name$
```
 zu 
	
	
	
	





```
Name
```
 um.

Die oben beschriebenen Probleme des Ansatzes bleiben natürlich bestehen.

Ark


----------

