Databases and Immutable Objects

Deutsch

A common approach in functional programming and even a good practice in object oriented programming is to prefer having objects immutable.

Especially in applications with multithreading this is extremely helpful, but in principal it helps having the information flow under control and avoiding unexpected side effects. I do not want to postulate this as a dogma, since there are actually legitimate uses of mutable objects, but it is a good idea to keep in mind what mutability means and implies where it is used and to question if it is really worth it in the particular case.

It is important to differentiate between really immutable objects and those that are handed out via a wrapper or a second interface in an immutable way while still being accessible for changes by those parts of the code that control the mutable interface.

Example:

import java.util.*;

public class A {
    List l = new ArrayList();
    
    public void addStr(String s) {
        l.add(s);
    }

    public List getList() {
        return Collections.unmodifiableList(l);
    }
}

Even though the list provided by getList() is by itself immutable, it can still be changed by the addStr(..)-method of A.

The whole story gets more interesting when involving databases.

Database contents are in some way mapped to objects. If this mapping is done via automatisms like OR-mappers or Active-Record or if it is done explicitely by using mechanisms like JDBC or DBI is secondary for the moment. These objects can be based on specifically written code for the particular usage or generic DB-content-objects or just plain old collections, which should be considered as an implementation detail for the moment. Important is that such objects or data structures expressing DB content exist in the software. For simplicity’s sake they will be referred to as objects throughout this text.

Now DB content can change. This can happen due to activities of the software with which we are dealing right now. But there can also be accesses to the database by other software or by an DB administrator which can cause changes in the database content while the software is running. This already results in some complications, because the objects processed by the software are already outdated while being processed. This would be acceptable if there were no caching mechanisms for database content on software and framework level, leaving the caching to the database software. Other than framework and software caches the caches of the database itself can be truly transaction conform. Off course it is good having the DB in terms of network connectivity not too far away from the application, maybe even on the same machine, depending on the access patterns. If the network connectivity is poor, this approach ends up having the cache on the wrong side of the network connection.

If the objects within the software have a life time that is so short that it can be avoided that they are out of sync with the database contents they are representing, things should be fine. This can be achieved by using the transaction isolation of „serializable“ or „phantom-read“ and by discarding all these objects before the end of the transaction. Within the transaction these objects that have been read during the same transaction are guaranteed to be up to date, as a result of the concept of transaction isolation used here. If certain OR-mapping patterns needed to map collections to database contents are not involved it is possible that „repeatable-read“ is already enough to guarantee this. As long as objects are only read from the database (SELECT or READ or FIND) immutable objects work just fine.

Even deleting (DELETE) can be imagined, but in this case it is important to ensure that the corresponding object is not in use in other parts of the software even though the database content represented by it have already been deleted. When ensuring that objects representing database content do not live longer than the corresponding transaction this should be possible to deal with. Also creating new objects (INSERT or CREATE) and the corresponding database content should be possible.

What is most problematic is changing of data (UPDATE). Common OR mapper do this by reading the object, changing it and then saving it, hoping that the framework will figure out the differences and run the appropriate update, possibly multiple updates in conjunction with inserts and deletes. This becomes difficult with immutable objects, because that blocks the typical approach just described. Ways can be found to accomplish this anyway, but they tend to get this done at the expense of the elegance of the framework. Even more important it becomes relevant to deal with the situation that objects expire even within the transaction and need to be replaced by new objects representing the changed database contents.

The provocative question is if updates are at all needed. Off course they are needed, since update is one of the four basic database operations. But it is worth taking a closer look in which cases updates really make sense and in which cases other approaches are attractive alternatives. A classic is an application that is dealing with some kind of bookings and some kind of subjects to which accounts are attached, typically this can be persons or companies or other entities, but we can go more abstract than that. For simplicity we can assume persons as an example. This can be a banking software with accounts, account owners and whatever is needed on top of that. Or a billing system of a phone company that calculates, creates and manages invoices to customers based on their phone usage and subscriptions. It is commonly seen that there is such a booking table to which records can be added, but never deleted or changed. That would falsify the book keeping. For canceling a booking, a cancel-entry can be added which is actually a booking in the reverse direction marked as cancel for the original booking. Maybe there is an archiving process that moves old data to a data warehouse, thus actually performing a delete in the booking table and replacing the old bookings by some summary entry, but that is really a detail not relevant for this article as long as this archiving process is done in a good way not compromising the regular usage of the software and database, maybe by actually taking the system down for maintenance during this archiving process once a year. Usually some kind of balance is needed quite often. In principal this can easily be calculated any time by just adding up the bookings of that particular account. This is in line with normalization requirements. Unfortunately this will slow down software to an extent that it is not useful for any practical purposes, but in theory this approach is extremely beautiful. So a more efficient way of finding balances is needed. Possibly each booking could contain the resulting balance reducing the task to looking for the newest booking of a particular account. That can be dangerous if two bookings generated simultaneously refer to the same previous balance. With transaction isolation of „serializable“ that would not happen, but this might slow down the database quite a bit because it reduces the performance of access to the largest table and thus worsening the bottle neck that might already exist there. It could be solved on database level for example with triggers or materialized views so that each insert to the booking table influences the balance automatically. This should work find for all kinds of accesses to the system by any software as long as it is constrained from doing any delete and update to the booking table. Actually granting only INSERT and SELECT rights to the booking table for the user under which the software is running is a very good idea. It could be seen as a disadvantage that part of the business logic is moved to the database. Maybe this is acceptable by just understanding the balance being the sum of the bookings of the account as some kind of high level constraint which is actively enforced by the database. Another approach would be to provide a functionality in the software that inserts a booking or more likely a set of bookings comprising a transaction and that always keeps the balance updated. This can go wrong if simultaneous bookings for the same account can happen, meaning that a transaction for a second booking start while the transaction of a first booking of the same account has not completed. This seems to be simple to avoid by just providing some queue for each account and working through the queue, but since the transactions usually include bookings on several different account this can become quite messy to implement while still avoiding deadlocks. It must be quite interesting to build such a system in a way that it will work fine even under high load. In any case objects containing the balance of an account tend to age extremely fast so they have to be handled with care as short lived objects within a transaction or even shorter, if that account is touched by bookings to that account in the same tranaction.

On the other hand the table with persons and addresses is commonly kept up to date using updates whenever data changes. People marry, change their phone number or move to another address. But is this really the best approach? In reality moving to another address could be known in advance and maybe it is not a good requirement that this change of address has to be entered on the exact date when the person actually moves. Or who wants to spend his wedding day updating all kind of name and address fields in all kinds of web applications? Ok, this can be cheated on because it is usually not a big deal if the change is done a little bit too late. But for some purposes exact data are really desirable. And then again, is this really the right approach? Sometimes it is required to be able to answer questions like name and address of the customer while booking 23571113a was being performed. Maybe it is a good idea to store address changes as some kind of bookings as well, with a valid-since field. For finding a customer’s current address it is sufficient to look for the newest entry for that particular customer that is not in the future. With this approach new entries need to be created for these changes and they can even be prepared in advance. Even deleting a customer can be accomplished by added a „deleted“-entry with a particular valid-since timestamp to that table.

The beatiful part is that now certain data in the database can be considered immutable since no updates can occur to them. So dealing with them as immutable in the software becomes a valable approach and actually also a must. These objects can even be kept in the system longer than the duration of a transaction. It remains important to be careful and to understand the OR-mapping since complex objects represeting collections can even change due to inserts. An example would be an account object containing the account and all its bookings. Customer address needs to be dealt with in conjunction with the timestamp that was used for acquiring it. So it is a good practice to freeze the „now“-timestamp at the beginning of a large operation and to consistently use it throughout that operation to have a consistent view of the data. If this timestamp gets too old, it might be a good idea to refresh it and to reread all data attached to the timestamp. Other than for bookings the times of address changes usually need not be accurate to the micro second as long as consistency is guaranteed and no weird mixture of old and new addresses ever occurs.

Share Button

Datenbanken und unveränderliche Objekte

English

Ein beliebter Ansatz in der funktionalen Programmierung, aber auch teilweise in der objektorientierten Programmierung ist es, Objekte nach Möglichkeit unveränderlich (engl. „immutable“) zu machen.

Speziell für Applikationen mit Multithreading ist das sehr nützlich, aber grundsätzlich erleichtert es auch im Griff zu haben, wie der Informationsfluss ist und unerwartete Seiteneffekte zu verhindern. Ich will das hier nicht als Dogma postulieren, denn es gibt durchaus legitime Verwendungen von veränderlichen Objekten, aber man sollte sich auf jeden Fall bewusst sein, was das bedeutet und ob sich die Veränderbarkeit (engl. „mutability“) in dem Fall überhaupt lohnt.

Unterscheiden muss man noch die echt unveränderlichen Objekte und diejenigen, die nur durch einen Wrapper oder ein zweites Interface unveränderlich weitergegeben werden, obwohl es noch für einen Teil des Codes Zugriff auf ein Interface gibt, das Veränderungen zulässt. Der typische Fall sind zum Beispiel Collections in Java, die in einem Objekt leben und mit Methoden dieses Objekts verändert werden können, aber die nur als immutable gewrappt herausgegeben werden.

Beispiel:

import java.util.*;

public class A {
    List l = new ArrayList();
    
    public void addStr(String s) {
        l.add(s);
    }

    public List getList() {
        return Collections.unmodifiableList(l);
    }
}

Obwohl also die mit getList() herausgegebenen Liste selbst immutable ist, kann sie mittels add aus A noch verändert werden.

Interessant wird es aber nun, wenn man eine Datenbank ins Spiel bringt.
Datenbank-Inhalte werden in irgendeiner Weise auf Objekte abgebildet. Ob das nun mit Automatismen passiert oder explizit mit Mechanismen wie JDBC, DBI o.ä. und ob es spezifische Objekte oder generische universelle DB-Content-Objekte oder Collections sind, spielt keine so große Rolle, es gibt solche Objekte oder Datenstrukturen.

Nun können sich Datenbankinhalte ändern. Das kann durch die Software, auf die wir uns hier beziehen, selbst passieren, aber auch von außen. Allein das ist schon eine gewisse Komplikation, wenn man es genau anschaut. Die Objekte, mit denen sich die Software befasst, sind dann nämlich schon veraltet. Das kann so akzeptabel sein, wenn man darauf verzichtet, auf Software- oder Frameworkebene ein Caching von solchen Objekten aufzubauen und das Caching allein der Datenbank überlässt, die das im Gegensatz zu Frameworks oder anderer Software, die die Datenbank nur nutzt, problemlos transaktionskonform kann. Schön ist es dann natürlich wieder, wenn die DB netzwerkmäßig nicht zu weit von der Applikation weg ist, sonst ist der Cache leider auf der falschen Seite der Netzwerkverbindung…

Wenn also in der Software die Objekte so kurzlebig sind, dass man ausschließen kann, dass sie veralten, dann kann man sagen, dass sie hinreichend mit dem Stand in der Datenbank übereinstimmen. Mit Transaktionsisolation „serializable“ oder „phantom-read“ und dann innerhalb von einer Transaktion sind diese Objekte, die man in derselben Transaktion gelesen hat, garantiert noch aktuell. Wenn nicht bestimmte OR-mapping-Muster für Collections vorkommen, ist das schon bei „repeatable-read“ der Fall. Man lebt also gut mit „immutable“-Objekten, solange diese nur aus der Datenbank gelesen (SELECT oder READ) werden. Auch das Löschen (DELETE) kann man sich noch vorstellen, aber in diesem Fall muss man schon darauf achten, dass das betreffende Objekt nicht noch an zu vielen Stellen der Software in Gebrauch ist, obwohl es in der Datenbank schon gelöscht ist. Wenn man aber sowieso sicherstellt, dass diese Objekte, die Datenbankinhalte darstellen, nicht länger leben als die entsprechenden Transaktionen, dann sollte auch das handhabbar sein. Auch das Erzeugen neuer Objekte und das Anlegen der entsprechenden Daten in der Datenbank (INSERT oder CREATE) ist möglich.

Schwierig wird es aber mit dem Ändern von Daten in der Datenbank (UPDATE). Bei gängigen OR-Mappings funktioniert das so, dass man das entsprechende vorher gelesen Objekt ändert und dann speichert, in der Hoffnung, dass das Framework die Unterschiede herausfindet und ein entsprechendes Update, eventuell auch eine Kombination aus Updates, Inserts und Deletes initiiert. Das wird mit unveränderbaren (immutable) Objekten schwierig. Man kann auch da Wege finden, aber das geht dann leicht auf Kosten der Eleganz des Frameworks und man hat sich auf jeden Fall damit auseinanderzusetzen, dass die Objekte jetzt auch innerhalb der Transaktion veralten und durch solche mit den aktualisierten Inhalten ersetzt werden müssen.

Die provokante Frage ist, ob man das Update überhaupt braucht. Natürlich braucht man das, aber auch hier kann man genauer schauen, welches die Fälle sind, wo das wirklich so ist und wann andere Ansätze besser sind.
Ein klassisches Beispiel ist eine Software, die irgendwelche Buchungen und irgendwelche Personen verwaltet. Das kann eine Bankensoftware sein, wo man Konten hat. Oder ein Billingsystem einer Telefongesellschaft, wo man die Rechnungen für Kunden generiert und verwaltet. Man hat dort oft so eine Buchungstabelle, in der grundsätzlich nur Datensätze eingefügt werden und niemals Daten gelöscht oder verändert werden, wenn wir einmal Fragen der Archivierung sehr alter Daten ausklammern. Dazu hätte man gerne noch einen Kontostand. Den kann man strenggenommen immer berechnen, indem man einfach alle Buchungen für das Konto aufaddiert. Leider ist die Software dann nachher langsam und für keinerlei praktische Zwecke brauchbar, aber theoretisch sehr schön. Man braucht also schon einen effizienten Mechanismus, um Kontostände zu ermitteln. Vielleicht kann jede Buchung den aus ihr resultierenden Kontostand beinhalten und man muss nur nach der neuesten Buchung suchen. Das ist gefährlich, weil zwei etwa gleichzeitig erzeugte Buchungen sich auf denselben Vorgängerkontostand beziehen. Mit Transaction-Isolation „serializable“ wäre das nicht passiert, aber das bremst natürlich die Datenbank schon sehr aus, ausgerechnet bei der größten Tabelle, in der die ganze Arbeit läuft und wo es sowieso schon den Performance-Flaschenhals gibt. Man kann auch auf Datenbankebene eine Lösung implementieren, etwa mit Triggern oder Materialized Views, wo jedes Insert in der Buchungstabelle ein Update des entsprechenden Kontostands bewirkt. Das hat den Vorteil, dass beliebige DB-Zugriffe von beliebiger Software richtig verarbeitet werden, solange man in der betreffenden Buchungstabelle Delete- und Update-Zugriffe unterbindet. Der Nachteil ist aber, dass die Businesslogik sich jetzt auf die Datenbank und die Applikation verteilt. Das kann man akzeptieren, wenn man das als eine Art abstrakten Constraint ansieht, der von der Datenbank „aktiv“ eingehalten wird. Oder man kann es vermeiden, indem man auf Applikationsebene eine entsprechend Funktionalität zum Einfügen einer Buchung einführt, die den Kontostand anpasst. Wiederum wird das zu falschen Ergebnissen führen, wenn man erlaubt, dass eine zweite Buchung für dasselbe Konto eingefügt wird, während schon eine Transaktion für das Einfügen einer Buchung für das Konto läuft. Man sieht also, dass die richtige Lösung dieser Buchungstabelle nicht einfach ist. Wenn dann noch hinzukommt, dass die Buchungen wiederum zu Transaktionen gruppiert sind, also z.B. die Gutschrift auf einem Konto in derselben Transaktion wie die Abbuchung von einem anderen Konto laufen soll, dann wird es schon interessant, wie man das System so baut, dass es absolut zuverlässig und korrekt ist, auch unter Last, und doch auch performant ist. Und in der Applikation muss man nun mit Konto-Objekten, die den Kontostand enthalten, sehr vorsichtig sein, weil dieser Kontostand veraltet, sobald eine Buchung dazukommt.

Die Tabelle oder die Tabellen mit den Personen, Adressen werden gerne mit Updates aktualisiert, wenn sich Daten ändern. Leute heiraten, ändern die Telefonnummer oder ziehen um. Aber ist das wirklich der richtige Weg? In Wirklichkeit weiß man von dem Umzug schon etwas vorher und hat vielleicht am Umzugstag selbst keine Zeit, das zu erfassen. Und in Wirklichkeit muss das System auch Fragen beantworten können, wie den Namen und die Adresse des Kunden zu der Zeit, als Buchung 23571113a stattfand. Vielleicht kann man Adressänderungen auch als „Buchungen“ speichern, mit einem Gültigkeitsbeginn. Dann wird die Adresse für einen Kunden ermittelt, indem man den Adresseintrag für die Kundennummer sucht, der das größtmögliche Gültigkeitsdatum hat, das gerade noch nicht in der Zukunft liegt. Mit dem Ansatz muss man in dieser Tabelle vielleicht tatsächlich nur neue Datensätze einfügen, wenn sich etwas geändert hat. Und wenn ein Kunde gelöscht wird, dann fügt man einen Datensatz ein, der beinhaltet, dass der Kunde ab einem bestimmten Datum „gelöscht“ ist, aber man kann noch bei Bedarf Daten liefern.

Das Schöne ist nun aber, dass Daten, die in der Datenbank „immutable“ sind, also keine Updates erhalten dürfen, in der Software auch problemlos immutable sein können (und sollten) und dass man sie auch langlebiger als eine Transaktion machen kann. Vorsicht ist aber immer geboten, weil komplexe Objekte in der Applikation sich auch allein durch Inserts in einer Datenbanktabelle ändern würden, wenn etwa eine Collection enthalten ist. Man muss also das OR-Mapping genau kennen, egal wie faszinierend diese Automatismen auch sein mögen. Im Fall von einer Kundenadresse muss man die gültige Adresse immer in Verbindung mit einem Timestamp behandeln, der beim ermitteln der gültigen Adresse eingesetzt wurde. Wenn dieser Timestamp veraltet ist, muss man auch die Adresse neu abfragen, wobei es im Gegensatz zu den Buchungen die Adressänderungen selten auf die Mikrosekunde des Umzugszeitpunkts ankommt, solange man die Konsistenz wahrt und nicht in der Woche des Umzugs wilde Mischungen aus der alten und der neuen Adresse verwendet.

Share Button

Mehrfache Interfaces mit inneren Klassen

Die im Beitrag über Multidispatch angedeutete Möglichkeit, mit inneren Klassen oder Wrappern anderen „equals()“ und „hashCode()“-Methoden anzubieten, sollte vielleicht etwas genauer gezeigt werden. Als Beispiel dient hier einmal Java, aber die Thematik ist auch in Scala, Ruby, Perl, C# und anderen Sprachen vorhanden, weil es gängig ist, dass die Hashtabellen gerne mit einer einer bestimmten Kombination aus hashCode() und eqals() zusammenarbeiten. Das zwingt dazu, diese beiden Methoden nach der gängigsten Verwendung in Hashtabellen zu definieren.

Die Idee lässt sich aber auch anwenden, um verschiedene Interfaces mit derselben Klasse zu implementieren.

Das Prinzip sieht so aus:


public interface A {
    public int fa(int x);
}

public interface B {
    public int fb(int x);
}

public class X implements A {

    ....
    public int fa(int x) {
        ....
    }
    private class Y implements B {
        public int fb(int x) {
             return fa(x) % 999;
        }
    }
    public B getAsB() {
        return new Y()
    }
}

Man kann also die Implementierung von B in der inneren Klasse Y vorsehen und dabei auf alle Methoden und Attribute der umgebenden Klasse zugreifen. Die Referenz auf die umgebende Klasse ist bei allen Instanzen der (nicht-statischen) inneren Klasse implizit dabei.

Hier ein Beispiel für verschiedene hashCode() und equals-Methoden:


public interface StringPair {
    public String getLhs();
    public String getRhs();
}

public class StringPairImpl implements StringPair {
    private final String lhs;
    private final String rhs;

    public StringPairImpl(String lhs, String rhs) {
        assert lhs != null;
        assert rhs != null;
        this.lhs = lhs;
        this.rhs = rhs;
    }

    public String getLhs() {
        return lhs;
    }

    public String getRhs() {
        return rhs;
    }

    public int hashCode() {
        return lhs.hashCode() * 91 + rhs.hashCode();
    }

    public boolean equals(Object obj) {
         if (this == obj) {
             return true;
         } else if (! obj instanceof StringPairImpl) {
             return false;
         } else {
             StringPairImpl other = (StringPairImpl) obj;
             return this.lhs.equals(other.lhs) && this.rhs.equals(other.rhs);
         }
    }

    private class IgnoreCase implements StringPair {

        public String getLhs() {
            return lhs;
        }

        public String getRhs() {
            return rhs;
        }

        public int hashCode() {
            return lhs.toUpperCase().hashCode() * 91 + rhs.toUpperCase().hashCode();
        }

        public boolean equals(Object obj) {
             if (this == obj) {
                 return true;
             } else if (! obj instanceof StringPairImpl) {
                 return false;
             } else {
                 StringPairImpl other = (StringPairImpl) obj;
                 return this.lhs.toUpperCase().equals(other.lhs.toUpperCase())
                     && this.rhs.toUpperCase().equals(other.rhs.toUpperCase());
             }
        }
    }

    public StringPair getWithCaseIgnored() {
        return new IgnoreCase();
    }
}

Die innere Klasse bietet also eine Implementierung desselben Interfaces, aber hashCode() und equals() ignorieren diesmal Groß- und Kleinschreibung.

Eine andere Möglichkeit wäre es bei Collections, eine immutable-Variante über eine innere Klasse anzubieten und eine getImmutable()-Methode einzubauen. Das ist aber bekanntlich nicht der Weg, den die JDK-library gewählt hat.

Was man aber dabei auch sieht: Leider muss man sehr viel „unnötigen“ Code schreiben, der eigentlich offensichtlich ist, aber eben doch nicht fehlen darf. Das erschwert sowohl das Schreiben als auch das Lesen des Codes, vor allem aber die Wartbarkeit. So wird die an sich gute Idee der inneren Klassen teilweise wieder verwässert.

Share Button

Snapshot too old – Behandlung langlaufender SELECTs

Wer größere Datenbank-Applikationen entwickelt, wird sich mit dem Problem auseinandersetzen müssen, was bei langlaufenden Abfragen eigentlich passiert.

Man hat also ein „SELECT“ am laufen, dass mehrere Sekunden oder sogar Minuten dauert, vielleicht sogar eine Stunde. Das kann durchaus sinnvoll sein, aber es lohnt sich natürlich, dieses mit gutem Wissen über die Datenbank-Software genau anzuschauen und zu optimieren.

Trotz aller Optimierungen muss man aber prinzipiell davon ausgehen, dass sich die Daten während des Lesezugriffs ändern. Es finden dauernd Schreibzugriffe statt, die natürlich erst nach Abschluss der Transaktion mit einem „COMMIT“ wirklich sichtbar werden. Nun können aber während des Lesezugriffs jede Menge Transaktionen stattfinden. Würde man diese berücksichtigen, müsste der Lesezugriff dauernd von vorne anfangen oder er würde inkonsistente Daten zurückliefern, die von verschiedenen Ständen der Datenbank stammen.

Die Lösung ist, den Stand der Datenbank vom Beginn des Lesezugriffs quasi einzufrieren und für die Dauer des Lesezugriffs zur Verfügung zu stellen. Zugriffe, die später beginnen, bekommen einen anderen Stand zu sehen, auch wenn sie früher fertig werden. Im Fall von Oracle-Datenbanken werden hierfür sogenannte Snapshots verwendet. Es werden also ähnliche Mechanismen wie für Transaktionen verwendet, um mehrere Stände der Datenbank parallel zu speichern.

Das bedeutet aber, dass bei einer sehr aktiven Datenbank immer größere Unterschiede zwischen den beiden Ständen entstehen. Bei vielen Transaktionen oder vielen parallelen langlaufenden Lesezugriffen sind sogar oft viel mehr als zwei Stände. Dafür gibt es Bereiche in der Datenbank, sogenannte Rollback-Segmente. Diese muss man definieren und bereitstellen und wenn sie zu klein sind, laufen sie irgendwann über. Dann kommt es im Fall von Oracle zu dem Fehler „ORA-01555: Snapshot too old“ und der lange Lesezugriff scheitert. Wenn man Pech hat nach ein paar Stunden. Wenn es ein Zugriff durch eine Software ist, wird es interessant, ob die Fehlerbehandlung für diesen Fall funktioniert oder ob es eine obskure „Exception“ in der Log-Datei gibt, die niemand bemerkt.

Mehr dazu findet man unter anderem hier:

Grundsätzlich muss diese Fragestellung bei allen ernsthaften transaktionalen Datenbanken gelöst werden und es ist sicher interessant, wie das im Detail aussieht. Vielleicht ist bei anderen Datenbanken so etwas wie eine „Lese-Transaktion“ („read transaction“) die Antwort. Als Isolationslevel muss man mindestens „repeatable read“ oder „serializable“ wählen. Wenn man mit niedrigeren Isolationsleveln arbeitet, muss man genau wissen, was man tut, da es sonst überraschende Fehler gibt.

Bei PostgreSQL muss man das mit
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
oder
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
explizit einstellen, sonst wird „READ COMMITTED“ gewählt.

Bei mariaDB und mysql hängt es von der DB-Engine ab. InnoDB verwendet als Default „REPEATABLE READ“, DB2 scheint eher auf „READ COMMITTED“ zu setzen, MS-SQL-Server auch auf READ COMMITTED.

Höhere Isolation-Level bedingen natürlich auch das Risiko von Verklemmungen („Deadlocks“), was erkannt werden sollte und zum Rollback einer der beiden Transaktionen führen sollte.

Es lohnt sich also, bei anspruchsvollen DB-Applikationen genau zu schauen, wie man mit diesen Transaktion-Isolationen umgehen will und ob man mit den Default-Eintstellungen der verwendeten Datenbank-Software zufrieden ist.

Vor einigen Jahren habe ich übrigens feststellen müssen, dass man diese niedrigeren Isolationslevel bei Oracle wohl einstellen konnte, dass die Datenbank oder der JDBC-Treiber sich dann aber fehlerhaft verhalten haben. Man sollte also auch herausfinden, ob die entsprechende Eintstellung von der verwendeten Datenbanksoftware unterstützt wird und zwar nicht nur auf dem Papier, sondern real und absolut zuverlässig. Eine unzuverlässige transaktionale Datenbank kann man billig durch einen Zufallszahlengenerator ersetzen.

Share Button

2014

Καλή Χρονια — Feliz ano novo — Gelukkig nieuwjaar — Šťastný nový rok — Boldog új évet — عام سعيد — bun di bun an — Onnellista uutta vuotta — С новым годом — 新年好 — Godt nytt år — Feliĉan novan jaron — Hääd uut aastat — Ath bhliain faoi mhaise — Akemashite omedetô — Срећна нова година — Gullukkig niuw jaar — Szczęśliwego nowego roku — laimīgu jauno gadu — Srechno novo leto — Gott nytt år — Bonne année — Щасливого нового року — Godt nytår — سال نو مبارک — Un an nou fericit — Felix sit annus novus — Felice anno nuovo — Feliz año nuevo — Sretna nova godina — Laimingų naujųjų metų — Šťastný nový rok — Frohes neues Jahr — Gleðilegt nýtt ár

Share Button

OR-mappings: Hibernate & Co.

Die meisten Software-Projekte verwenden eine Datenbank. Es ist immer eine Herausforderung, die Datenbank und die Software zusammenzubringen, weil das zwei verschiedene Welten sind, die ihre eigene Entwicklung genommen haben.

Hierzu haben sich ganz verschiedene Ansätze etabliert.

  • DB-zentrisches Vorgehen: Ein starker DBA oder ein starkes DB-Team fängt zunächst an, das Datenmodell zu erstellen. Die Software muss sich daran anpassen. Für die performance-kritischen Zugriffe erhalten die Entwickler Unterstützung von einem DBA.
  • Eine Variante davon ist die Verwendung des Active-Record-Patterns, wo die Objekte aus der Datenbankstruktur generiert werden. Dieses Active-Record-Pattern verwendet man bei Ruby on Rails.
  • OR-zentrisches Vorgehen: Man verwendet objekt-relationale Mapper und definiert Klassen in seiner Software und ein paar XML-Dateien oder Annotationen dazu und hofft dann, dass der OR-Mapper nicht nur relationale Daten in Objekte und Objekte in relationale Daten wandelt, sondern dass auch noch gleich das Datenmodell und die Zugriffe automatisch generiert werden. Das ist der typische Ansatz bei Hibernate, JDO, Eclipselink etc.
  • Auch in der Rails-Welt versucht man es oft und gerne ohne DBA und lässt das DB-Schema generieren.
  • Man verzichtet auf die relationale SQL-Datenbank und arbeitet mit NoSQL-Datenbanken, die oft etwas näher an der Software liegen

Letztlich muss man sich aber die Fragen beantworten, ob man die Datenbank in den Mittelpunkt stellt und die Software passend zur Datenbank entwickelt oder ob man die Software in den Mittelpunkt stellt und die Datenbank nur als eine Art Persistenzmechanismus dafür verwendet.

Dann kommt die zweite Frage ins Spiel, wie man die objektorientierte, prozedurale und funktionale Welt der Software mit der relationalen Welt der SQL-Datenbanken oder der jeweiligen Struktur der NoSQL-Datenbanken zusammenbringt.

Eine dritte Frage taucht auf, ob man auf Softwareebene Caching-Mechanismen einbaut. Das muss man nicht, aber Hibernate verspricht einem so etwas gut zu können und auch bietet sogar Caching auf der Platte an, nicht nur im Memory.

Die erste Frage ist legitim und kennt wohl keine allgemeingültige Antwort. Man kann durchaus von einer Datenbank im Mittelpunkt ausgehen, wenn diese von mehreren Applikationen genutzt wird oder wenn man davon ausgeht, dass die Daten wesentlich langlebiger als die Software sind. Das könnte bei einer Kundendatenbank so sein. Oft behilft man sich mit dem Versprechen, dass es eine Export-Funktionalität gibt und wenn man dann irgendwann die Software umstellen will, kann man die Daten als XML exportieren und gleich in die neue Software übernehmen. Das kann Routine sein, ich wäre aber skeptisch und würde diese Migration sorgfältig planen und testen. Wahrscheinlich wird es nicht so einfach und so billig, wie man gehofft hat.

Die zweite Frage ist auch recht relevant. Man hat ja nicht nur Objekte in Relationen zu übersetzen, sondern muss sich auch mit Transaktionen auseinandersetzen und mit der Frage, wie tief man große Objekte mit vielen Unterobjekten wirklich lesen und schreiben will. Hier werden gute Antworten schwierig. Liest man sie nur in kleine Portionen, stößt man später auf Probleme, weil Unterobjekte fehlen, z.B. LazyLoadingException in Hibernate. Man muss hierfür eine Lösung finden und diese konsequent umsetzen. Ein radikaler Ansatz, der etwas umständlich ist, aber recht zuverlässig funktioniert, ist die aggregierten Objekte zu entkoppeln und stattdessen nur Schlüsselfelder zu speichern. Beim Zugriff muss man sie dann explizit mit dem Schlüsselfeld holen und stellt so sicher, dass sie aktuell sind. Hier stellen sich natürlich auch interessante Konsistenzfragen, wenn man Daten aus der Datenbank miteinander kombiniert, die von verschiedenen Zeitpunkten stammen und die deshalb nicht mehr notwendigerweise miteinander konsistent sind.

Ein beliebter Ansatz ist, recht viel in den Hauptspeicher zu laden und darauf zuzugreifen. Damit landen wir bei dem dritten Thema, dem Caching. Eines sollte zunächst einmal klargestellt werden: Wenn man eine relationale SQL-Datenbank benutzt und sich dabei auf das Caching des OR-Mapping-Frameworks verlässt, dann verlässt man die transaktionale Welt. Man handelt sich sehr viele Probleme ein, weil Fragen der Konsistenz nicht mehr einfach handhabbar sind, schon gar nicht auf DB-Ebene. Das lässt sich scheinbar in den Griff bekommen, wenn man sagt, alle Zugriffe laufen über die Applikation und die Datenbank ist nur noch eine Persistenzschicht. Nun laufen aber mehrere Threads gleichzeitig und man stößt dann auch noch in diesen heiklen Bereichen auf Hibernate-Fehler. Eclipselink, JDO, etc. sind da wohl nicht wesentlich anders. Kurz gesagt, das Caching ist für veränderliche Daten bestenfalls kaum handhabbar, aber ich gehe sogar soweit, dass das konzeptionell nicht korrekt ist. Nun kann man natürlich so etwas wie „Stammdaten“ haben, die sich selten verändern. Die lassen sich tatsächlich im Memory halten, wenn es einen Prozess gibt, diesen Cache aufzufrischen, wenn sich die Stammdaten ändern, vielleicht sogar mit einem Neustart der Applikation. Ja, das will niemand zugeben, dass man so etwas heute braucht, aber besser zweimal im Jahr die Applikation neu starten als zwei Monate im Jahr obskure Fehler suchen, die durch unsauberes Caching und Fehler im OR-Mapping-Framework entstehen.

Zusammenfassend kann man sagen, dass diese OR-Mapper durchaus zumindest in der Java-Welt ihren Bereich haben, wo sie nützlich sind. Aufpassen sollte man aber bei sehr großen Tabellen und bei sehr stark miteinander verknüpften Tabellen und vor allem beim Caching von veränderlichen Daten. Hier lohnt es sich, etwas mehr Aufwand zu treiben, denn der scheinbare Komfortgewinn durch den OR-Mapper existiert hier nicht oder nur zu einem unverhältnismäßig hohen Preis.

Share Button

Weihnachten — Christmas — Jul — Navidad 2013

καλά Χριστούγεννα — Feliz Natal — Zalig Kerstfeest! — Veselé Vánoce — Kellemes Karácsonyi Ünnepeket — ميلاد مجيد — Bella Festas daz Nadal! — Hyvää Joulua! — С Рождеством — 圣诞快乐 — God Jul! — Feliĉan Kristnaskon — Häid jõule — Nollaig Shona Dhuit! — クリスマスおめでとう ; メリークリスマス — Срећан Божић — Prettige Kerstdagen — Wesołych Świąt Bożego Narodzenia — Priecîgus Ziemassvçtkus — Vesele bozicne praznike! — God Jul — Joyeux Noël — З Рiздвом Христовим — Glædelig Jul — کريسمس مبارک — Crăciun fericit — Natale hilare — Buon Natale — Feliz Navidad — Sretan božić — Merry Christmas — Su Šventom Kalėdom — Vesele Vianoce! — Fröhliche Weihnachten — Gleðileg jól

Weihnachtsbaum bei Nacht
Quelle Wikimedia commons
Weihnachtsbaum bei Nacht 1981
Quelle: Wikimedia
© Karl Brodowsky 1981-2013
Share Button

Carry Bit: How does it work?

Deutsch

Most of us know from elementary school how to add multi-digit numbers on paper. Usage of the carry bit is the same concept, but not for base 10, not even for base 2, but for base 256 (in the old 8-bit-days), base 65536 (in the almost as old 16-bit-days), base 4294967296 (32 bit) or base 18446744073709551616 (64 bit), whatever is the word width of the CPU. Always using powers of two is common today, but it is quite possible that this will change from bits to trits (having three possible values, -1, 0 and 1) in the far future.

I do not think that application development should be dealing with low level stuff like bits and bytes, but currently common programming languages like Java, C, C++, C# and more would not let you get away with that, you have to be aware of the bits underlying their numeric types to some extent. So it is a good idea to spend some effort on understanding this. Unfortunately all of these languages are lacking the carry bit, but it is anyway useful to understand the concept.

I have been writing software since the beginning of the 80es. The computers available to me at that time where 8-bit with a 6502- or 6510-CPU and 1 MHz clock speed. Yes, it was 1 MHz, not 1 GHz. It was possible to program them in some BASIC-dialect, but that was quite useless for many purposes because it was simply too slow. Compiled languages existed, but were too clumsy and too big to be handled properly on those computers, at least the ones that I have seen. So assembly language was the way to go. In later years I have also learned to use the 680×0 assembly language and the 80×86 assembly language, but after the mid 90es that has not happened any more. An 8-bit CPU can add two 8-bit numbers and yield an 8-bit result. For this two variants need to be distinguished, namely signed and unsigned integers. For signed numbers it is common to use 2’s complement. That means that the highest bit encodes the sign. So all numbers from 0 to 127 are positive integers as expected. 127 has the 8-bit representation 01111111. Now it would be tempting to assume that 10000000 stands for the next number, which would be +128, but it does not. Having the highest bit 1 makes this a negative number, so this is -128. Those who are familiar with modular arithmetic should find this easily understandable, it is just a matter of choosing the representatives for the residue classes. But this should not disturb you, if you have no recent experience with modular arithmetic, just accept the fact that 10000000 stands for -128. Further increments of this number make it less negative, so 10000001 stands for -127 and 11111111 for -1. For unsigned numbers, the 8 bits are used to express any number from 0 to 255.

For introducing the carry bit let us start with unsigned integral numbers. The possible values of a word are 0 to 2^n-1 where n is the word width in bits, which would be 8 in our example. Current CPUs have off course 64-bit word width, but that does not change the principle, so we stick with 8-bit to make it more readable. Just use your imagination for getting this to 32, 64, 96 or 128 bits.

So now the bit sequence 11111111 stands for 255. Using an assembly language command that is often called ADD or something similar, it is possible to add two such numbers. This addition can typically be performed by the CPU within one or two clock cycles. The sum of two 8-bit numbers is in the range from 0 through 510 (111111110 in binary), which is a little bit too much for one byte. One bit more would be sufficient to express this result. The workaround is to accept the lower 8 bits as the result, but to retain the upper ninth bit, which can be 0 or 1, in the so called carry bit or carry flag. It is possible to query it and use a different program flow depending on it, for example for handling overflows, in case successive operation cannot handle more than 8 bit. But there is also an elegant solution for adding numbers that are several bytes (or several machine words) long. From the second addition onwards a so called ADC („add with carry“) is used. The carry bit is included as third summand. This can create results from 0 to 511 (111111111 in binary). Again we are getting a carry bit. This can be continued until all bytes from both summands have been processed, just using 0 if one summand is shorter than the other one. If the carry bit is not 0, one more addition with both summand 0 and the carry bit has to be performed, yielding a result that is longer than the longer summand. This can off course also be achieved by just assuming 1, but this is really an implementation detail.

So it is possible to write a simple long integer addition in assembly language. One of the most painful design mistakes of current programming languages, especially of C is not providing convenient facilities to access the carry bit, so a lot of weird coding is used to work around this when writing a long integer arithmetic. Usually 64-bit arithemetic is used to do 32-bit calculations and the upper 32 bits are used for the carry bit. Actually, it is not that hard to recover the carry bit, but it is anyway a bit annoying.

Subtraction of long integers can be done in a quite similar way, using something like SBC („subtract with carry“) or SBB („subtract with borrow“), depending on how the carry bit is interpreted when subtracting.

For signed integer special care has to be taken for the highest bit of the highest word of each summand, which is the sign. Often a so called overflow but comes in handy, which allows to recognize if an additional machine word is needed for the result.

Within the CPU of current 64 bit hardware it could theoretically be possible to do the 64-bit addition internally bit-wise or byte-wise one step after the other. I do not really know the implementation details of ARM, Intel and AMD, but I assume that much more parallelism is used for performing such operation within one CPU cycle for all 64 bits. It is possible to use algorithms for long integer addition that make use of parallel computations and that can run much faster than what has been described here. They work for the bits and bytes within the CPU, but they can also be used for very long numbers when having a large number of CPUs, most typically in a SIMD fashion that is available on graphics devices misused for doing calculations. I might be willing to write about this, if interest is indicated by readers.

It is quite interesting to look how multiplication, division, square roots, cube roots and more are calculated (or approximated). I have a lot of experience with that so it would be possible to write about hat. In short these operations can be done quite easily on modern CPUs, because they have already quite sophisticated multiplication and division functions in the assembly language level, but I have off course been able to write such operations even for 8-bit CPUs lacking multiplication and division commands. Even that I could cover, but that would be more for nostalgic reasons. Again there are much better algorithms than the naïve ones for multiplication of very long integers.

Links

Share Button

Kein kommerzieller Support mehr für Glassfish

Oracle stellt den kommerziellen Support für Glassfish ein und empfiehlt Kunden, die an solchem Support interessiert sind, auf WebLogic umzustellen:

Oracle stellt kommerziellen Support für GlassFish ein.

Die Frage ist natürlich, ob man dann nicht ein paar andere Umstellungen überdenkt:

  • Wenn es EJB sein muss, ist vielleicht auch der WildFly-Applikationsserver von JBoss eine Option
  • Wenn es Java sein muss, ist vielleicht auch Spring eine Alternative
  • Wenn es die JVM sein muss, sind Scala, Clojure oder JRuby einige weitere Möglichkeiten. Diese bringen in der Regel ihre eigenen Frameworks mit. Wer möchte noch EJB benutzen, wenn er schon bei JRuby, Scala oder Clojure angelangt ist? Ja, theoretisch ginge das wohl, EJBs in diesen Sprachen zu entwickeln.
  • Wenn nicht es nicht einmal die JVM sein muss, gibt es viele Möglichkeiten, die ich der Phantasie des Lesers überlasse.

Kurz gesagt, ich will hier nicht den Ruhm von EJB schmälern. Das tue ich vielleicht einmal in anderen Blog-Beiträgen. Aber niemand muss von Glassfish auf WebLogic umstellen. Und schon gar nicht muss man sich bei dem prall gefüllten Satz an Alternativen von Oracle bei den Verhandlungen über den Tisch ziehen lassen.

Nur benötigen diese Umstellungen immer viel Zeit und Geld und sie müssen gut geplant werden. Deshalb ist es sicher gut, längerfristig die Alternativen vorzubereiten und nicht erst wenn es schon zu spät ist. Sonst geht mit einem virtuellen Schild um den Hals in die Verhandlungen mit Oracle, auf dem steht, dass man gerne über den Tisch gezogen werden möchte.

Es sei dem Leser überlassen, seine Schlussfolgerungen für andere technisch interessante Softwareprodukte aus demselben Haus zu ziehen, z.B. mySQL oder Solaris. Und ja, man könnte theoretisch für diese Produkte ähnliche Listen anfertigen.

Share Button

Multidispatch (Multimethoden): verpassen wir etwas

Objektorientierte Programmiersprachen werden nach meiner Meinung eigentlich erst wirklich objektorientiert, wenn sie Polymorphie unterstützen. Das bedeutet, dass ein Stück Progammcode mit einem Objekt gewisse Manipulationen durchführt und dabei Methoden des Objekts aufruft. Diese tun etwas verschiedenes, je nach dem welches Objekt hier angesprochen wird, denn es ist eine Eigenschaft des Objekts, welche Methode wirklich aufgerufen wird. In vielen Objektorientierten Programmiersprachen, wie zum Beispiel Java oder C# oder C++ ist das so gelöst, dass das Objekt einer Klasse angehört und die Klasse die Methodenimplementierung enthält, aber es können natürlich auch Methoden direkt an einzelnen Objekten aufgehängt sein. Für die Freunde von C# und C++ sei daran erinnert, dass „virtual“ hier nicht fehlen darf.

Das ist alles wunderbar und man kann damit sehr viel anfangen. Nun wird die heile Welt gestört, wenn man sich irgendwelche Konzepte aus nie wirklich Mainstream gewordenen Programmiersprachen wie Common Lisp mit CLOS anschaut, wo es sogenanntes Multidispatch gibt. Die aufgerufene Methode hängt also nicht nur vom ersten speziellen Parameter, also dem Objekt, das oft vor dem „.“ steht, ab, sondern von allen Parametern. Das macht es unübersichtlich und niemand braucht so etwas, könnte man meinen. Das mag ja alles stimmen, aber wir werden doch mit Situationen konfrontiert, wo das Thema sehr relevant wird.

Nehmen wir an, man führt einen neuen numerischen Typ ein, z.B. Rational für rationale Zahlen \Bbb Q=\{ \frac{a}{b} : a, b \in \Bbb Z \wedge b > 0 \}. Nun braucht man Methoden für die Grundrechenarten, was sehr schön funktioniert, wenn man z.B. zwei rationale Zahlen addieren will. Nun soll es aber auch funktionieren, eine ganze Zahl zu einer rationalen Zahl zu addieren. Dafür kann man die „+“-Methode oder weniger schön die „add“-Methode noch einmal mehr überladen und einen ganzzahligen Parameter verstehen oder innerhalb der Methode diesen Fall zur Laufzeit erkennen. Der erste Ansatz greift leider zu kurz, weil wegen Vererbungshierarchie und gerade wegen der Polymorphie der genaue Type zur Compilezeit nicht bekannt ist und daher auf die allgemeine Methode, etwa so etwas wie

+(x : Numeric) ,

zurückgefallen wird, die wiederum abhängig vom realen Typ von x auf die richtige Methode oder den richtigen Methodenteil führen sollte.
Das ist alles noch gut machbar, man darf es nur nicht vergessen.

Aber was passiert nun, wenn q rational und n ganzzahlig ist und wir nicht q+n, sondern n+q schreiben? Nun müsste man Integer (oder BigInteger) oder was auch immer der Typ ist, hoffentlich ohne diesen absurden Überlauf bei 2^{31}-1 wie die java-ints, nachträglich beibrigen, rational zu verstehen. In Ruby kann man zumindest noch nachträglich die Methode + von Integer ändern und durch eine eigene ersetzen, die auch Rational versteht, aber der Ansatz stößt irgendwann an Grenzen. In Ruby behilft man sich oft damit, dass die +-Methode der linken Seite eine „coerce“-Methode der rechten Seite mit der linken Seite als Parameter aufruft, um Konvertierungen zu machen. Damit kommt man recht weit, aber man muss sehr genau hinschauen, was man tut.

Nun kommt sicher der Einwand, dass Operatorüberladen sowieso „böse“ ist. Das hilft aber hier nichts, die Probleme treten in identischer Form auf, wenn man es „add“ statt „+“ nennt.

Der zweite Einwand ist vielleicht, dass man nicht jeden Tag neue numerische Typen schreibt. Aber man schreibt doch hoffentlich ab und zu neue Klassen und nutzt Vererbung. Da muss man dann eine equals-Methode oder so etwas ähnliches implementieren. Und schon ist das Problem sehr relevant. In der Regel habe ich das dadurch gelöst, dass equals zuerst die Klassen von linker Seite (self oder this) und rechter Seite vergleicht und nur wenn die gleich sind, überhaupt weiter vergleicht. Das ist meistens ein guter Ansatz. Aber manchmal muss man wirklich Gleichheit zwischen Objekten zulassen, die nicht derselben Klasse angehören. Dann ist es schon eine Herausforderung, das korrekt und halbwegs solide zu lösen. Man sieht also, dass wir hier wirklich etwas verpassen… Aber solange man die Konzepte kennt, kann man sie grundsätzlich in jeder Programmiersprache anwenden, der Aufwand ist nur etwas größer.

Java ist hier tatsächlich etwas schlechter gestellt als z.B. Scala oder Ruby, weil man sehr schlechte Möglichkeiten hat, die eingebaute equals-Methode zu ersetzen. Während es bei SortedSet die Wahlmöglichkeit gibt, Comparable oder einen Comparator zu verwenden, setzen HashSet und HashMap fast immer auf die interne hashCode() und equals()-Methode und man muss sich mit Wrappern oder geeigneten inneren Klassen behelfen, um eine zweites Interface für dasselbe Objekt mit anderen equals()- und hashCode()-Methoden zu liefern.

Share Button