Zeichenketten in Java, Ruby und Perl

Eigentlich sind die Zeichenketten in allen Programmiersprachen das gleiche, man kann sie als Literale angeben, irgend woher lesen oder zusammensetzen und dann vergleichen und ausgeben.

Aber es gibt einen großen Unterschied. In Java und in den JVM-Sprachen Scala und Clojure, die sich nicht speziell dagegen gewehrt haben, sind die normalen Zeichenketten unveränderlich. Das bedeutet, dass bei jeder Operation, die eine Zeichenkette zu verändern scheint, in Wirklichkeit jeweils eine neue Zeichenkette generiert wird. Das erschwert die Handhabung der Zeichenketten etwas, hat aber den Vorteil, dass man nicht durch Manipulationen an einer Zeichenkette anderen Programmteilen den Teppich wegziehen kann. Bei Multi-Thread-Programmen ist das besonders gefährlich, aber auch ohne mehrere Threads zu verwenden könnte das zu unerwarteten Fehlern führen, aber für Manipulationen kann es etwas unpraktischer sein, weil jedes Mal eine neue Zeichenkette generiert werden muss. Für diese Zwecke gibt es aber StringBuilder und StringBuffer, die explizit veränderliche Zeichenketten darstellen und für längere Manipulationen als Zwischenergebnis gut geeignet sind, aber man sollte diese am Schluss mit toString() in eine normale Zeichenkette umwandeln, die nicht mehr veränderlich ist. Da Scala und Clojure das Lied der unveränderlichen Objekte singen und als funktionale Sprachen oder teilweise funktionale Sprachen solche unveränderlichen Objekte der Normalfall sind, passt dieses Konzept dort sehr gut hinein.

In Perl und Ruby kann man sehr viele Manipulationen mit Zeichenketten machen, man könnte sogar fast sagen, dass diese Manipulationen von Zeichenketten mit regulären Ausdrücken die Stärke von Perl sind und deshalb auch sehr häufig vorkommen. Aber auch Perl und Ruby haben sich davor geschützt, dass man den Schlüssel (engl. key) einer Map verändert:

Beispiel in Ruby:

$ irb
irb(main):001:0> x="abc"
=> "abc"
irb(main):002:0> y="def"
=> "def"
irb(main):003:0> m={x=>3, y=>4}
=> {"abc"=>3, "def"=>4}
irb(main):004:0> x += "x"
=> "abcx"
irb(main):005:0> x
=> "abcx"
irb(main):006:0> m
=> {"abc"=>3, "def"=>4}
irb(main):007:0> y.gsub!(/d/, "x")
=> "xef"
irb(main):008:0> m
=> {"abc"=>3, "def"=>4}
irb(main):009:0>

Man sieht also, dass auch hier sichergestellt wird, dass das Verändern der Zeichenkette nicht die Schlüssel der Map verändert.

In Perl verhält es sich ähnlich:

$ perl
$x = "abc";
$y = "def";
%m = ($x, 3, $y, 4);
$x =~ s/a/A/;
print "x=$x y=$y\n";
print "m{x}=", $m{$x}, " m{'abc'}=", $m{'abc'}, " m(y)=", $m{$y},"\n";

Ctrl-D

x=Abc y=def
m{x}= m{'abc'}=3 m{y}=4

Die Zeichenketten werden beim Anlegen der Map %m kopiert und deshalb passiert nichts, wenn man nachträglich noch $x und $y ändert. Bei Ruby ist das entsprechend.

Man kann dies aber austricksen, zum Beispiel in Ruby:

$ irb
irb(main):001:0> class X
irb(main):002:1> attr_accessor :x
irb(main):003:1> def initialize(x)
irb(main):004:2> @x = x
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> x = X.new("abc")
=> #
irb(main):008:0> y = X.new("def")
=> #
irb(main):009:0> m={x=>3, y=>4}
=> {#=>3, #=>4}
irb(main):010:0> m
=> {#=>3, #=>4}
irb(main):011:0> x.x="abcd"
=> "abcd"
irb(main):012:0> x
=> #
irb(main):013:0> m
=> {#=>3, #=>4}
irb(main):014:0> y.x.gsub!(/d/, "D")
=> "Def"
irb(main):015:0> y
=> #
irb(main):016:0> m
=> {#=>3, #=>4}

Das lässt sich entsprechend in Perl und Java auch tun, führt eher zu überraschenden als erwünschten Ergebnissen und ist nicht zu empfehlen. In Ruby kann man die Schlüssel einer Map mit freeze() schützen:

irb(main):017:0> x.freeze
=> #
irb(main):018:0> x.x="u"
RuntimeError: can't modify frozen object
from (irb):18
from /usr/local/bin/irb:12:in `

'

Das ist ein recht eleganter Mechanismus, weil man ein Objekt mit einigen komplexeren Schritten initialisieren kann und dann durch freeze() schützen kann. Aber Vorsicht, es ist kein deepfreeze(), dies muss man explizit sicherstellen:

irb(main):019:0> x.x.gsub!(/a/, "u")
=> "ubcd"
irb(main):020:0> x
=> #
irb(main):021:0> x.x.freeze
=> "ubcd"
irb(main):022:0> x.x.gsub!(/a/, "u")
RuntimeError: can't modify frozen string
from (irb):22:in `gsub!'
from (irb):22
from /usr/local/bin/irb:12:in `
'

Eine interessante Frage ist oft, ob zwei Zeichenketten mit demselben Inhalt in Wirklichkeit dieselbe Zeichenkette sind oder ob es identische Kopien sind. Normalerweise ist das ja egal, aber es spielt eine Rolle bei Vergleichen. Diese sind billiger, wenn man sofort erkennt, ob es dasselbe Objekt ist und nicht erst zeichenweise vergleichen muss. Wenn große Datenmengen verarbeitet werden, ist es aber auch manchmal wegen des Speicherverbrauchs relevant.

Man kann in allen drei Sprachen beide Fälle haben. Mit zwei Variablen dieselbe Zeichenkette zu referenzieren ist in Java und Ruby einfach.

x = "abc";
y = x;

(in Ruby darf man die „;“ weglassen.)
In Perl sind die Variablen nicht Referenzen, sondern es werden wirklich Werte kopiert. Man muß dazu also explizit Referenzen verwenden:

$ perl
$x="abc";
$u=\$x;
$v=\$y;
print "x=$x u=$u v=$v\n";
$$u =~ s/a/A/;
print "x=$x u=$u v=$v\n";
print '$$u=', $$u, ' $$v=', $$v, ' $x=', $x,"\n";
Ctrl-D

x=abc u=SCALAR(0x8069820) v=SCALAR(0x80698ac)
x=Abc u=SCALAR(0x8069820) v=SCALAR(0x80698ac)
$$u=Abc $$v= $x=Abc

Umgekehrt kann man aber auch echte Kopien erzwingen, wenn man das will:
In Java geht das so:

String x = "abc";
String y = new String(x);

Oder in Ruby:

x = "abc"
y = String.new(x)

und in Perl ist es trivial:

$x = "abc";
$y = $x;

reicht schon aus.

Nun gewinnt man beim Vergleichen und beim Speicherverbrauch schon etwas, wenn man möglichst oft bei Vergleichen, die am Ende „true“ ergeben, schon an der Objektidentität und nicht erst am Vergleich aller Zeichen die Gleichheit erkennt. Aber wenn bei der Mehrheit der Vergleiche zwar die Länge gleich ist, aber die Zeichen sich irgendwo weit hinten unterscheiden, arbeitet das Programm vielleicht immer noch zu viel für diese Vergleiche. Wenn man also große Datenmengen verarbeiten will oder einfach nur meint, dass der Vergleich über die Objektidentität „richtiger“ ist, lässt sich das in Java und Ruby recht gut erzwingen.

In Java etwa mit

import java.util.IdentityHashMap;

public class MyMap extends IdentityHashMap {
public V put(String key, V value) {
assert key != null;
String uniqueKey = key.intern();
return super.put(uniqueKey, value);
}

public V get(Object key) {
if (key instanceof String) {
String str = (String) key;
return super.get(str.intern());
} else {
return null;
}
}
//..

public static void main(String args[]) {
MyMap map = new MyMap();
System.out.println(map.put("abc", "uvw"));
System.out.println(map.put(new String("abc"), "def"));
System.out.println(map.get("abc"));
System.out.println(map.get(new String("abc")));
}
}

ergibt die Ausgabe:

null
uvw
def
def

und verwendet intern nur die billigen Vergleiche mit == statt mit .equals(..).

In Ruby verwendet man dafür einfach „Symbole“ statt Zeichenketten, etwa

m = { :a => 3, :b => 4, :c => 5 }

Vorsicht ist aber geboten, sobald man Frameworks benutzt, die serialisieren, um Daten zu persistieren oder über das Netz zu schieben. Beim Deserialisieren geht diese Identität der Zeichenketten leider ganz schnell verloren, vor allem, wenn man „über das Netz“ vergleicht, was ja vorkommen kann.

Nun sei noch ein Grund erwähnt, warum man Java-Zeichenketten überhaupt mit so etwas wie y=new String(x) kopiert, wo sie doch unveränderbar (immutable) sind. Hier sollte man eine Optimierung in der Implementierung von String kennen. Wenn man substring() aufruft, wird ein neues String-Objekt für die Unterzeichenkette angelegt, dieses referenziert aber die Zeichensequenz der ursprünglichen Zeichenkette, nur mit anderen Anfangs- und Endzeigern. Dadurch wird in der Regel Speicher gespart und auch der Aufwand für das Allozieren von neuem Speicher vermieden. Wenn man nun aber von einer sehr langen, kurzlebigen Zeichenkette mit substring() eine sehr kurze, aber langlebige Zeichenkette extrahiert, verhindert man damit, dass der Speicher der eigentlichen Zeichensequenz der langen Zeichenkette freigegeben wird. So raffiniert und richtig es also in den allermeisten Fällen ist, die Optimierung der Library zu nutzen und zu schreiben

String y = x.substring(r, s);

so sollte man doch beachten, dass es in seltenen Fällen richtig und sogar wichtig ist, stattdessen

String y = new String(x.substring(r, s));

zu schreiben. Im Zweifelsfalls sollte man aber immer bei der ersten Schreibweise bleiben. Programme, die wegen ein paar Zeichenketten Memoryprobleme bekommen, die sich nicht durch Erhöhen der Speicherparameter leicht lösen lassen, sind zum Glück sehr selten.

Interessant ist sicher noch die Frage, in welcher Codierung die Zeichenketten gespeichert werden und wie man sie bei Ein- und Ausgabe richtig konvertiert. Das ist aber sicher genug Stoff für einen eigenen Artikel. Hier sei nur soviel gesagt: Java speichert die Zeichenketten mit utf-16, braucht also zwei Byte pro Zeichen, auch bei europäischen Sprachen.

Share Button

Jolla will noch dieses Jahr Mobiltelefone mit SailfishOS bringen

Share Button

Ein paar Gedanken zu ssh

English

Früher, als man noch jedem im Internet und sogar im Intranet vertraute, hat man sich ja einfach mit telnet in andere Rechner eingeloggt. Oder mit rlogin, damit die Umlaute auch funktioniert haben. Leider ging dabei das Password im Klartext über das Netz, was natürlich unverantwortlich ist.

Nun hat man also stattdessen ssh und es funktioniert recht ähnlich wie damals telnet, kann aber gleich noch sehr viel mehr, auch wesentlich mehr als ich hier in ein paar Zeilen schreiben kann. Wichtig ist nicht nur, dass man das Password verschlüsselt überträgt, sondern auch, dass man sicherstellt, dass die Gegenseite wirklich der gewünschte Rechner ist, sonst kan ein „man in the middle“ das Password abfangen und man ist wieder da, wo wir mit telnet standen.

Dazu gibt es im .ssh-Verzeichnis diese Zertifikate, also solche Dateien wie id_rsa und id_rsa.pub. Die id_rsa sollte man hüten wie seinen Augapfel, da die Sicherheit des ganzen Protokolls davon abhängt. Mit ssh-keygen kann man sich solche Zertifikate auch anlegen, wahlweise auch so, dass man bei deren Verwendung ein Password eingeben muss (das nur auf dem lokalen Rechner bleibt). Die Zertifikate haben sogenannte „Fingerprints“, die man sich mit ssh-keygen -l anzeigen lassen kann. Genau dieser Fingerprint wird beim ersten mal angezeigt und man muss ihn bestätigen. Man sollte diesen also auf einem sicheren Kanal vorher überprüfen, dass er auch wirklich stimmt, zum Beispiel wenn man zwischen zwei Rechnern in einem verkabelten Netz, dem man traut, aufeinander zugreift, oder indem an sich den Fingerprint notiert oder ausdruckt. Wenn man ihn einmal bestätigt hat, wird er in .ssh/known_hosts eingetragen und man kann dort natürlich auch aufräumen, indem man unnötige Einträge löscht. Oder sogar mit USB-Stick übertragene Fingerprints reineditieren, wenn man das Format verstanden hat. Solange known_hosts keine falschen Einträge enthält, hat ein „man in the middle“ es sehr schwer und das Verfahren bietet eine vernünftige Sicherheit.

Nun lässt sich der „public key“ aus id_rsa.pub des eigenen Rechners in .ssh/authorized_keys des Rechners, auf dem man sich einloggen will, eintragen. Damit erreicht man, dass das ohne Password-Abfrage möglich ist, außer das lokale Zertifikat braucht eine Password-Abfrage. Die kann man aber auch einmalig pro Session mittels ssh-add durchführen.

ssh wird auch für andere Zwecke verwendet, weil man andere Protokolle darüber tunneln kann, z.B. Subversion oder git.

Sehr schön ist es auch, wenn man unter X11 ein

ssh -X user@host

aufruft. Dann kann man auf dem entfernten Rechner grafische Applikationen starten und das Display wird über den ssh-Tunnel umgeleitet.

Display umleiten ist in der Unix- und Linux-Welt schon seit über 25 Jahren gängige Praxis, aber mit ssh geht es nun auch sicherer als mit den unverschlüsselten Protokollen, die man damals gerne verwendet hat. Wer kennt noch xhost +?

Diese Überlegungen beziehen sich auf ssh unter Linux, gelten aber auch für alle möglichen Unix-Systeme, wie z.B. Solaris. Auf MS-Windows-Rechnern gibt es putty als verbreitete SSH-Client-Implementierung. Mit Putty findet man sicher vieles von dem wieder, was das Linux/Unix ssh kann, nur in etwas anderer Verpackung. Ich bevorzuge aber auf MS-Windowsrechnern cygwin und dessen ssh-Implementierung, die sehr ähnlich funktioniert. Es ist sogar möglich, mit cygwin einen ssh-Server aufzusetzen. Allerdings ist der Vorgang nicht ganz einfach.

Share Button

MS-Windows-Bug oder Feature mit CMD

English

Jeder der mit MS-Windows-Rechnern zu tun hat, kennt diese schwarzen Fenster, mit dem cmd-Kommamdozeileninterpreter, wenn auch kaum jemand sie mag. Die Unix- und Linux-Leute mögen sie nicht, weil cmd einfach verglichen mit typischen Linux- und Unix-Shells lächerlich wenig kann und die MS-Windows-Leute wollen nicht in der Kommandozeile arbeiten, jedenfalls nicht in dieser. Es gibt Alternativen wie Powershell und cygwin mit bash, aber das schwarze Fenster wird doch meist mit cmd assoziiert. Unter Linux gibt es auch solche Fenster für die Shell, z.B. xterm, aber dort sind sie meistens weiß. Dabei kann man auf beiden Systemen die Farben konfigurieren, wie man will.

NT-basierende MS-Windows-Systeme (NT 3.x, 4.x, 2000, XP, Vista, 7, 8, 10) haben jeweils verschiedene Subsysteme und Programme laufen in diesen Subsystemen, z.B. Win64, Win32, Win16, cygwin, DOS. Weil nun Programme für das DOS-Subsystem typischerweise im CMD-Fenster gestartet wurden und einige der Kommandos, die im CMD angeboten werden, ihren gleichnamigen Vorläufern aus der DOS-Ära von vor 30 Jahren nachempfunden wurden, wurde dieses CMD-Fenster früher (und vielleicht gelegentlich noch heute) oft fälschlicherweise als DOS-Fenster bezeichnet. Dabei kommt dieses schwarze Fenster eigentlich in vielen Situationen zum Vorschein, wenn z.B. ein Win32-Programm gestartet wird, das Ein- und Ausgabe (stdin, stdout, stderr) hat. Wenn diese umgeleitet sind, z.B. in eine Datei oder an ein anderes Programm, kann das Programm unsichtbar ohne schwarzes Fenster starten und gegebenenfalls graphisch in Erscheinung treten. Wenn es keine Umleitung der Ausgabe gibt, wird dem Programm ein solches schwarzes Fenster für die Anzeige der Ausgabe automatisch zur Verfügung gestellt. Und eines dieser Programme mit stdin und stdout ist nun einmal cmd, deshalb wird beim starten von cmd das schwarze Fenster drumherum dazu geliefert. Unter Linux (und Unix mit X11) ist es umgekehrt, man startet xterm bekommt darin die übliche Shell, außer man gibt explizit an, dass etwas anderes darin gestartet werden soll.

Nun empfehle ich einmal ein kleines Experiment. Wir brauchen dazu einen beliebigen graphischen Editor wie z.B. emacs, gvim, ultraedit, textpad, scite, es darf sogar notepad sein. Und ein cmd-Fenster.

  • Diese Befehle abschreiben nicht mit Copy&Paste übertragen.
  • Im cmd-Fenster mit cd in ein Verzeichnis wechseln, in dem Dateien angelegt werden können (Schreibrecht), falls nötig..
  • echo "xäöüx" > filec.txt
  • Die dabei angelegte Datei filec.txt mit dem graphischen Editor öffnen. Wie sehen die Umlaute aus??
  • Mit dem Editor im selben Verzeichnis eine neue Datei fileg.txt anlegen, die etwa folgende Zeile enthält: yäöüy.
  • Im CMD-Fenster diese ausgeben:
  • type fileg.txt
  • Wie sehen jetzt die Umlaute aus?

Es ist ein Feature oder ein Fehler aller gängigen MS-Windows-Versionen, dass diese schwarzen Fenster in der Standardeinstellung die Umlaute auf andere Positionen legen als die graphischen Editoren. Vielleicht weiß jemand, wie man das ändern kann, es würde mich interessieren.

Was ist hier genau passiert? Als die ersten DOS-Versionen Anfang der 1980er-Jahre aufkamen, gab es nur einen halbwegs etablierten Standard für Zeichensatzcodierungen, das war ASCII oder ISO-646-IRV, was immerhin ein großer Fortschritt gegenüber EBCDIC war. Aber dieser normierte nur die unteren 128 Zeichen (7 Bit) und enthielt keine Umlaute oder in Varianten Umlaute statt „irrelevanter“ Zeichen wie „@“, „[„, „~“ u.s.w. So fingen Hersteller und Softwaresysteme an, für die oberen 128 Plätze im Zeichensatz ihre eigene Lösungen zu erfinden. Commodore, Atari, MS-DOS, Apple, NeXT, TeX und überhaupt „alle“ hatten im Laufe der Jahre ihre „Lösungen“ für verschiedene Sprachregionen gefunden, die natürlich jeweils inkompatibel miteinander waren, meist sogar noch zwischen verschiedenen Produktgenerationen oder Ländereinstellungen desselben Herstellers inkompatibel. In Zeiten, wo man sowieso noch kaum Netzwerke hatte, wo es auch eine Sammlung von verschiedenen herstellerspezifischen Formaten für Disketten und herstellerspezifische Netzwerktechnologien gab, fiel das noch nicht so auf. Aber relativ früh wurde es bei X11 (dem gängigen graphischen System für Unix und Linux) üblich, Standardzeichensätze aus der ISO-8859-x-Familie oder später sogar Unicode mit utf-8 und utf-16 zu unterstützen. Linux hat schon in den 0.99-Versionen auch ohne graphische Oberfläche diese ISO-8859-1-Zeichensätze verwendet und nie versucht, seine eigene Zeichensatzcodierung zu erfinden.

Inzwischen sind wohl alle relevanten Systeme auf zum Unicode-Standard kompatible Zeichensatzcodierungen wie ISO-8859-x, utf-8 und utf-16 umgeschwenkt. Aber MS-Windows hat dies nur teilweise umgesetzt. Während das graphische System nur die modernen Codierungen verwendet oder zumindest mit Cp1252 dem recht nahe kommt, hat man für das textbasierte System (schwarze Fenster wie bei cmd) die Codierungen aus der DOS-Zeit von vor über 30 Jahren, wie z.B. Cp850 beibehalten und so einen Bruch innerhalb des System in Kauf genommen, der sehr ärgerlich ist, wenn man mit Daten mit Umlauten in cygwin oder cmd-Fenstern arbeiten will.

Wenn man mutig ist, kann man dieses Verhalten in der Registry ändern. Man muss dazu in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage die Einträge OEMCP und OEMHAL gleichzeitig ändern. Einer ist für die Eingabe und einer für die Ausgabe zuständig, wenn man die also nicht gleichzeitig ändert, wird es sogar innerhalb des schwarzen Fensters inkonsistent. Sucht man danach im Internet, finden sich Beispiele, wo jemand versucht haben will, utf-8 (CP65001) einzustellen und das Ergebnis war, dass er nicht mehr booten konnte. Ich habe nicht überprüft, ob das manipulierte Behauptungen sind, um einer beliebten Firma zu schaden, oder ob es reale Erfahrungen sind. Man kann es mit Virtualisierung relativ risikofrei probieren, indem man sich ein funktionierendes System kopiert und es mit der Kopie testet. Auf jeden Fall ist das Editieren in der Registry nicht ganz risikofrei und geschieht „auf eigene Gefahr“…. Und in vielen Umgebungen ist es nicht möglich, weil die Berechtigungen dafür nicht ausreichen. Man kann es noch mit chcp im schwarzen Fenster selber ändern, vielleicht muss man dort auch noch so etwas wie chhal oder so eingeben, damit der Font passend zur Eingabe codiert ist.

Ob man das als „Bug“ oder lieber wegen der Kompatibilität zu älteren Versionen bis 30 Jahre zurück als Feature bezeichnen sollte, überlasse ich dem Leser.

Share Button

Wo bleiben die Transaktionen bei den NoSQL-Datenbanken?

Jetzt war mal wieder so ein Vortrag bei einer der vielen User-Groups, in der ich drin bin. Diesmal ging es um Riak und der Vortrag war von einem der Entwickler und war wirklich gut. Ein Stück weit wurde die Grundsatzthematik der NoSQL-Datenbanken behandelt, wobei natürlich Riak im Zentrum stand.

Während es bei den SQL-Datenbanken vielleicht etwa vier gibt, die einigermaßen austauschbar sind, was die Funktionalität betrifft (Oracle, MS-SQL-Server, DB2 und PostgreSQL, mit Einschränkungen auch mySQL und MariaDB), ist hier die Frage relevant, das richtige Werkzeug für die richtige Aufgabe zu verwenden. Und das richtige Werkzeug sind oft die relationalen transaktionalen Datenbanken. Die Austauschbarkeit der relationalen Datenbankprodukte gilt natürlich nur, wenn man eine neue Applikation entwickelt, nicht wenn man Software nachträglich umschreiben muss und die Datenbankadministrationsprozesse nachträglich ändern muss.

Nun sind die Daten ja immer sehr wichtig, sonst könnte man sich den Aufwand ja sparen, sie zu speichern. Und wenn die Datenbank nicht transaktional ist, dann ist das gruselig, weil die Daten nicht zuverlässig und nicht genau genug gespeichert werden. Das Versprechen der transaktionalen Datenbanken für Transaktionen wird ja kurz mit „ACID“ bezeichnet:

  • „Atomic“ bedeutet, dass die Transaktion entweder komplett oder gar nicht ausgeführt wird
  • „Consistent“ bedeutet, dass die Daten immer in einem konsistenten Zustand sind.
  • „Isolated“ bedeutet, dass verschiedene gleichzeitig laufende Transaktionen sich nicht gegenseitig beeinflussen.
  • „Durability“ bedeutet, dass die Daten nach Abschluss der Transaktion garantiert dauerhaft gespeichert sind.

Super, da kann nichts mehr schief gehen. Und Software von Oracle oder IBM ist serienmäßig komplett fehlerfrei, von Microsoft sowieso und bei PostgreSQL bauen die Entwickler der Software, die die Datenbank verwendet, allfällige Fehler der DB-Software selbst noch kurz in der Nacht aus… Aber fairerweise muss man sagen, dass die Kernfunktionalität der Datenbankprodukte tatsächlich recht zuverlässig funktioniert und die nervigen Fehler eher am Rande bei unwichtigen Dingen wie Installern oder Security-Features aufgetreten sind, die hier nicht das Thema sind. 😉

Also gut, man kann also mal annehmen, dass die DB-Software vernünftige Zuverlässigkeit hat. Was ist mit der Hardware? In einem großen Rechenzentrum muss rein rechnerisch immer irgendwo ein Hardwaredefekt sein. Ist aber kein Problem, man baut die Hardware und damit die Datenbankinstallation einfach redundant auf. Und es gibt tolle Mechanismen, die mit erheblichem Aufwand sicherstellen, dass ACID immer noch gilt. Man hat also eine Datenbank, die auf mehreren Rechnern läuft und sich gegen außen wie einer verhält. Eine Transaktion kann auch mehrere dieser Rechner involvieren. Egal was passiert, soll immer ACID-Transaktionalität gelten. Mit „Two-Phase-Commit“ und solchen Werkzeugen kann man das hinbekommen. Zumindest sagt man, dass es in der Praxis zuverlässig funktioniert. Vielleicht bis zu einer gewissen Größe, denn wenn eine einzige Datenbank ein ganzes großes Rechenzentrum beansprucht, kann man sich sicher auf einen hohen Stromverbrauch verlassen, aber mehr will ich dazu hier nicht sagen. Reale Rechenzentren, die transaktionale Datenbanken betreiben, haben erfahrungsgemäß auch viele mehr oder weniger unabhängige Datenbanken am Laufen.

Man kann also mit recht großen ACID-transaktionalen Datenbanken die Unzuverlässigkeit der Hardware recht gut in den Griff bekommen. Das ist nicht billig und es lohnt sich gute Datenbankberater heranzuziehen.

Wie sieht es mit der Applikationssoftware aus? Die wird heutzutage ja oft in Java geschrieben und läuft deshalb in der Sandbox, was ja alle Probleme verhindert, weil die Sandbox unterbindet, dass irgendwas gefährliches gemacht wird… 😉

Ist die Applikationssoftware fehlerfrei? Oder zumindest so fehlerfrei, dass nie mit den Daten etwas schief gehen kann? Vielleicht, wenn man optimistisch ist? Wenn wir schon bis hierher Optimismus aufbauen konnten… Die transaktionale Datenbank ist ein nütziches Werkzeug, wenn man sie mit hinreichend korrekter Software benutzt. Aber das machen wir alle und auf dem Laptop des Entwicklers hat es wirklich funktioniert, es sind halt die Benutzer schuld, die mehrere Requests gleichzeitig abschicken. Was ist mit dem „I“ aus ACID passiert? Ja, die Datenbank macht es schon richtig, aber die Applikation stürzt ab oder erzeugt korrumpierte Daten. Oder verwendet zu kleine oder zu große Transaktionen. Schade…

Nun kommt aber noch die Software- und Systemarchitektur ins Spiel. Man fängt an, ein großes System über mehrere Server zu verteilen, Caching wird verwendet, Teilbereiche der Daten werden über Services angesprochen. Natürlich arbeitet man mit tollen Frameworks und die Datenbank ist für den Applikationsentwickler schon recht weit weg, aber immer noch macht man irgendwo implizit oder explizit schöne Transaktionen auf und beendet sie mit commit oder rollback. Niemand weiß mehr, in welcher Schachtelungstiefe von solchen Methoden, die implizit Transaktionen durchführen, man sich befindet und mit bestimmten Mustern kann man das aus Versehen austricksen, aber so etwas passiert natürlich nicht. Wie sieht es jetzt mit ACID aus?

Kurz gesagt, für reale Applikationen muss man genauer hinschauen, ob ein etwas schwächeres Modell als ACID wirklich ein Nachteil ist.

Nun muss man aber noch einmal auf den Ausgangspunkt zurückkommen. Transaktionen lassen sich sehr gut für nicht-relationale Datenbanken definieren und auch implementieren. Umgekehrt kann man für manche Zwecke durchaus relationale SQL-Datenbanken verwenden, die nicht transaktional sind. Oder man hat wie bei MongoDB Transaktionen für einzelne DB-Operationen, kann diese aber nicht zusammenfassen.

Share Button

MongoDB im RAM

Hier ist eine interessante Beschreibung, wie man MongoDB (unter Linux) komplett im RAM betreiben kann:

How to use MongoDB as a pure in-memory DB (Redis style)

Die Idee ist einfach und interessant, weil sie sich auch für viele andere, ähnlich gelagerte Anwendungsfälle eignet.

Für die Entwicklung von Mongo-DB-basierenden Applikationen und vor allem für die Unit-Tests kann das auch eine sehr nützliche Sache sein.

Share Button

Responsive Design

Neu ist dieser Blog mit „responsive design“ ausgestattet. Das bedeutet, dass das Layout sich von selber kleinere Bildschirme von Mobiltelefonen anpasst.

Auch die Seite IT Sky Consulting GmbH hat responsive Design. Dies ist nur mit CSS umgesetzt, die HTML-Seiten existieren nur einmal.

Share Button

Ein paar Besonderheiten von Linux/Unix-Filesystemen

Unix und in der Folge auch Linux liegt ein gewisses Verständnis darüber zugrunden, wie die Dateisysteme (Filesysteme) funktionieren. Man kann zwar unter Linux auch Filesysteme wie FAT32 einbinden, die diese Eigenschaften nicht haben, sollte das aber nur tun, um Daten mit einem Medium auszutauschen, das auch von anderen Betriebssystemen verwendet wird, nicht aber für die eigentliche Linux-Installation, nicht einmal für das Home-Verzeichnis.

Man hat aber heute eine Vielzahl von guten Filesystemen zur Verfügung, die die benötigten Features haben.

Grundsätzlich ist ein Verzeichnis (engl. Directory) selbst eine spezielle Datei, die die darin vorkommenden Dateinamen und jeweils eine Referenz zu einem sogenannten I-Node (inode) enthält, wo u.a. gespeichert ist, welche Berechtigungen, welche Änderungs- und Zugriffsdaten und welche Größte die Datei hat und wo man deren Inhalte finden kann. Normalerweise sind die Dateien in „Blöcken“ gespeichert, die z.B. 1024 Bytes groß sind, wobei der letzte Block normalerweise nicht vollständig ausgenutzt wird. Zusätzlich benötigt man bei größeren Dateien mit vielen Blöcken eine Struktur, um diese zu finden, die im I-Node selbst keinen Platz hat.

Nun kann man sogenannte „hard-links“ anlegen, z.B. mit ln. Das bedeutet, dass ein I-Node von mehreren Verzeichnissen referenziert wird. Der hard-link und sein
Original sind dabei völlig gleichberechtigt, es gibt keinen Unterschied zwischen dem zuerst vorhandenen Eintrag und dem neuen, als Hardlink angelegten. Deutlich zu unterscheiden ist das von sogenannten Softlinks, die auf einen anderen Verzeichniseintrag verweisen und damit indirekt auf eine Datei, wenn dieser Verzeichniseintrag tatsächlich existiert. Wegen der hard-links kann eine Datei also beliebig oft im Dateisystem eingetragen sein und wenn man sie mit rm löscht, dann wird in Wirklichkeit nur der eine Eintrag im Verzeichnis gelöscht. Nur wenn diese Datei wirklich nirgendwo sonst mehr referenziert wird, wird sie auch selber gelöscht. Dazu führt der I-Node noch einen Referenzzähler.

Was passiert nun, wenn man eine Datei mit einem Programm öffnet und löscht, bevor das Programm beendet ist? Etwa so etwas:

In xterm1:

$ sort grosse-datei.txt > sorted.txt

Und in xterm2 direkt nachdem das sort gestartet wurde, aber bevor es beendet wurde:

$ rm grosse-datei.txt

?
Die Datei wird wirklich aus dem Verzeichnis gelöscht, ist also mit ls nicht mehr zu sehen.

Das „sort“ greift aber über den I-Node darauf zu und hat noch weiterhin zugriff, zählt also auch als eine Referenz in der Zählung. So kann sort seine Arbeit noch beenden, aber kein anderes Programm mehr auf diese „halb-gelöschte“ Datei zugreifen. Man kann das mit df -k erkennen. Sobald das sort fertig ist, wird der Referenzzähler im I-Node heruntergezählt und wenn nicht noch Hardlinks oder andere Programme darauf zugreifen, dann geht er auf 0 runter und die Datei wird wirklich gelöscht, erkennbar mit df.

Vorsicht ist natürlich geboten bei Programmen, die eine Datei zwar lesen, aber dabei eine raffinierte File-Handle-Verwaltung benutzen, also die Datei immer wieder kurz zum Lesen öffnen, ein paar Daten lesen und wieder schließen, um aufwendige Berechnungen mit den Daten zu machen, bis der nächste Lesezugriff erfolgt. Bei so einem Programm würde das Wiederöffnen der Datei in unserem obigen Szenario zu einem Fehler führen, weil die Datei vom System als nicht mehr referenziert erkannt und
definitiv gelöscht worden wäre.

Die typischen Unix/Linux/Posix-Tools wie grep, sort, uniq, cat,…. öffnen aber die betreffende Datei nur einmal und lesen alles sequentiell. Dieses raffinierte Öffnen und Schließen ist eher bei Datenbanksytemen und datenbankähnlicher Software zu üblich.

Wenn man dieses Verhalten kennt, kann man damit viel anfangen, wenn man es nicht kennt, gibt es wohl gelegentlich Überraschungen.

Share Button

Design-Patterns: Singleton

English

Dieses Singleton-Pattern hat den Vorteil, dass man es sich merken kann und damit beim Thema Design-Patterns dabei ist.

Interessant ist höchstens die Frage der Initialisierung („lazy“ oder „eager“) und vielleicht noch die Frage der Abhängigkeiten, wenn man sehr viele Singletons hat.

Aber ich möchte hier einmal auf zwei Verallgemeinerungen eingehen.

Ein Singleton ist „einmal“ „im ganzen Programm“ vorhanden. Verallgemeinerungen können also bei dem „einmal“ oder bei dem Universum (hier „im ganzen Programm“) ansetzen.

Das „einmal“ auf eine bestimmte endliche Anzahl zu erweitern ist für viele Routine. Bei den Java-Entwicklern nennt sich das enum, aber in anderen Programmiersprachen gibt es oft ähnliche Konstrukte oder man kann sie sich leicht selber aufbauen, indem man die Erzeugung bestimmter Objekte einschränkt und nur für die endlich vielen „enum-Einträge“ Instanzen anlegt und entsprechend erreichbar macht.

Die andere, etwas komplexere Verallgemeinerung setzt bei dem „Universum“ an. Eine Applikation kann verteilt sein oder mehrere parallele Prozesse (nicht nur Threads) verwenden, womit man ein potentiell größeres Universum als beim klassischen Singleton hat. Frameworks kennen so etwas oft und es sind dort Komponenten oder „Beans“ mit „Application-Scope“. Entsprechend gbit es kleinere Universen, zum Beispiel „Session Scope“.

Wenn man also nur diese Komponenten richtig als Verallgemeinerungen des Singleton-Patterns liest, werden diese obskuren Frameworks vielleicht etwas verständlicher und nützlicher. Und die Investion, mal mit diesem einfachen Design-Pattern anzufangen, hat sich gelohnt. Irgendwann werden wir sehen, ob sich noch andere Patterns lohnen… 😉

Share Button

SSD billiger als magnetische Festplatten?

In der vergangenen Woche war ein interessanter Vortrag bei der Elastic Search User Group in Zürich, vom Gründer der Firma und des Projekts

Eine Bemerkung war, dass es in typischen Elastic-Search-Anwendungen billiger sein kann, SSDs als magnetische Festplatten einzubauen. Magnetische Festplatten sind natürlich für das gespeicherte Byte viel billiger, aber wenn man Durchsatz in Bytes/sec braucht, seien SSDs billiger.

Ein interessanter Gedanke, erst einmal verwirrend.

Aber man baut letztlich eine Serverfarm auf, um die nötige Leistung zu erbringen. Wenn nun die Hauptaufgabe dieser Server in geschickten Lesezugriffen auf ihren Daten besteht, dann braucht man mit vollständiger Verwendung von SSDs weniger Server. Das könnte kostengünstiger sein. Und gerade für überwiegendes Lesen sind SSDs unumstritten gut.

Share Button