Kolumne

EnterpriseTales: JDBC Next – asynchron in die Zukunft?

Sven Kölpin

© SuS_Media

Bislang ist mit JDBC in Java nur ein synchrones und blockierendes Datenbank-API spezifiziert. Seit einiger Zeit gibt es aber konkrete Pläne für ein zusätzliches, asynchrones API. Welche Vorteile hat diese Alternative, und wie wird sie aussehen?

Guter Wein braucht seine Zeit

Das Java-SE-10-Release ist für Entwickler eher unspektakulär. Es gibt nur wenige neue Sprachfeatures und geringfügige Erweiterungen bestehender APIs. Das hätte aber unter Umständen auch anders kommen können. Zeitweise war für Java 10 nämlich eine ganz besondere Neuerung im Gespräch: das Asynchronous Java Database Access API (ADBA), auch als JDBC Next bekannt.

Die JDBC-Spezifikation ist ein Urgestein in Java. Sie existiert in ihren Grundzügen bereits seit Java SE 1.1 und ermöglicht den standardisierten Zugriff auf relationale Datenbanken. JDBC liefert dazu zahlreiche Interfaces, die von den jeweiligen Datenbankherstellern implementiert und dann in Form von Treibern zur Verfügung gestellt werden.

Auch das Asynchronous Java Database Access API definiert implementierungsunabhängige Interfaces und Klassen, die zur Interaktion mit relationalen Datenbanken bestimmt sind. Das API ist aber weder ein Ersatz noch eine Erweiterung von JDBC – auch wenn der initial gewählte Name „JDBC Next“ etwas anderes vermuten lassen könnte. ADBA ist eine eigenständige Spezifikation, die, im Gegensatz zu JDBC, ausschließlich auf die asynchrone Datenbankinteraktion ausgelegt ist.

Die initiale Ankündigung von ADBA liegt fast zwei Jahre zurück. Zuerst wurde das API auf der JavaOne-Konferenz 2016 vorgestellt. Daraufhin passierte aber, zumindest in der Öffentlichkeit, ein Jahr lang nichts. Seit der JavaOne im Oktober 2017 ist das grundlegende API für asynchrone Datenbankzugriffe auch für die Community einsehbar. Der Plan, ADBA bereits in Java SE 10 zu integrieren, wurde zwar wieder verworfen, doch das API soll noch immer Teil einer kommenden Java-Version werden.

JDBCs Schwachstelle

Das JDBC-API bildet seit Jahrzehnten das Herzstück unzähliger Enterprise-Anwendungen. Warum soll nun also ein weiterer Weg für den Zugriff auf relationale Datenbanken geschaffen werden? Relationale Datenbanken speichern Datensätze typischerweise auf der Festplatte. Die Interaktion mit dieser ist, zeitlich gesehen, vergleichsweise teuer. Vereinfacht gesagt müssen die benötigten Daten beim Festplattenzugriff schließlich mithilfe eines motorbetriebenen Arms auf sich drehenden Metallplatten gesucht werden. Festplattenzugriffe sind deshalb um ein Vielfaches langsamer als etwa CPU oder Hauptspeicheroperationen. Zwar können moderne SSD-Festplatten die Zugriffszeiten deutlich verringern, trotzdem bleibt stets ein zeitlicher Mehraufwand.

Das aktuelle JDBC-API sieht vor, dass der Thread, der eine Datenbankinteraktion initiiert (Userthread), so lange wartet, bis die Datenbank eine Antwort geliefert hat. Der Userthread wird bei JDBC also während einer Datenbankoperation blockiert. Unglücklicherweise sind Datenbankabfragen zeitlich aufwendig, nicht zuletzt, weil sie zumeist Festplatteninteraktionen nach sich ziehen. Ein Thread wartet deshalb unter Umständen relativ lange auf eine Rückmeldung. Bei datenbankgetriebenen Anwendungen mit vielen konkurrierenden Zugriffen, wie es z.B. viele Webanwendungen sind, führen blockierende Datenbankinteraktionen so zu einer Vielzahl wartender Userthreads.

Ein CPU Core ist ohne Weiteres in der Lage, Hunderte von Threads parallel zu betreiben. Allerdings ist diese Parallelität nur eine Illusion des Betriebssystems. Beispielsweise kann ein Single-Core-Prozessor streng genommen nur einen Thread zur selben Zeit ausführen. Mithilfe von Time Slicing wird deshalb die verfügbare Prozessorzeit auf die verschiedenen Threads aufgeteilt, um so Kontinuität und damit Parallelität zu simulieren. Leider verursacht das Hin- und Herwechseln zwischen den Threads, auch als Context Switching bekannt, einen gewissen Overhead. Je mehr Context Switching betrieben werden muss, desto geringer ist die Wahrscheinlichkeit, dass die für den aktuellen Thread benötigten Daten (Kontextdaten) in dem CPU-Cache verfügbar sind. Die Kontextdaten müssen deshalb beim Kontextwechsel zunächst aufwendig aus dem Hauptspeicher geladen und später wieder zurückgeschrieben werden. Neben dem zeitlichen Mehraufwand steigt durch zu viele parallele Threads also auch der Speicherverbrauch an.

JDBC ist ein synchrones und blockierendes Datenbank-API, das das Problem konkurrierender Zugriffe mit der Erstellung neuer Threads löst. Dieses Vorgehen führt aber zu einer Verschwendung der Ressourcen CPU und Hauptspeicher. Für Anwendungen, die einen hohen Durchsatz benötigen oder bei hoch skalierbaren Applikationen ist der Einsatz von JDBC also nicht optimal.

Der Non-blocking-Ansatz

In einer perfekten Welt wäre es sinnvoll, die Anzahl der Userthreads auf die Zahl der verfügbaren Hardwarethreads zu minimieren, um so eine optimale Ressourcenauslastung zu erreichen. Eine Minimierung von Threads beim Datenbankzugriff ist möglich, wenn nicht mehr explizit innerhalb eines Userthreads auf ein Datenbankergebnis gewartet wird. Das kann mit nicht blockierenden und asynchronen Ein- und Ausgabeoperationen (asynchronous and non-blocking IO) erreicht werden. Der asynchrone Ansatz ermöglicht das Freigeben eines Userthreads, während IO-Operationen ausgeführt werden. Anstatt also, wie beim synchronen Ansatz üblich, aktiv auf ein Ergebnis zu warten, kann ein Userthread während einer asynchronen IO-Operation andere Aufgaben erledigen. Die Zahl blockierender und leerlaufender Threads wird verringert und die Arbeit nur noch von wenigen, aber dafür hoch ausgelasteten, Threads verrichtet.

Ein asynchrones Datenbank-API nutzt Callback-Methoden, um auf die Ergebnisse einer Datenbankinteraktion zu reagieren, anstatt auf sie zu warten. Frei nach dem Motto „Wir rufen Sie zurück, wenn wir fertig sind“, werden übergebene Callback-Methoden erst dann ausgeführt, wenn das Ergebnis einer jeweiligen IOOperation zur Verfügung steht. Auch das neue Datenbankzugriffs-API ADBA wird es ermöglichen, mithilfe von Callback-Methoden asynchron auf Ergebnisse einer Datenbankoperation zu reagieren.

Das ist ADBA

Das Asynchronous Java Database Access API standardisiert den asynchronen und nicht blockierenden Zugriff auf relationale Datenbanken in Java SE. Es wird von der gleichen Expert Group, die auch für JDBC zuständig ist, spezifiziert. Unter anderem wurden für ADBA folgende Ziele formuliert:

  • Das API ist asynchron: Im Gegensatz zum aktuellen JDBC-API sollen bei der Verwendung von ADBA Userthreads möglichst nicht blockiert werden. Das hat unmittelbare Auswirkungen auf die Gestaltung des API – schließlich müssen für das asynchrone Handling Callback-Mechanismen ein fester Bestandteil sein.
  • Das API ist Java-SE-kompatibel: Das API darf ausschließlich Java-SE-Bordmittel verwenden und nicht auf Bibliotheken von Drittanbietern setzen. Zusätzlich ist das API komplett unabhängig von den existierenden java.sql- und javax.sql-Paketen, die Teil von JDBC sind.
  • Das API ist minimal: ADBA spezifiziert ein standardisiertes API und verzichtet möglichst auf konkrete Implementierungen. ADBA soll außerdem ein eindeutiges API sein, bei dem es stets nur genau einen Weg gibt, etwas umzusetzen. Bei der Spezifikation wird weiterhin versucht, auf zu starke Abstraktion zu verzichten, um so den Fokus auf Datendurchsatz und Performanz zu legen. Deshalb werden z.B. Abstraktionen wie JDBC Escapes oder Parameter Markers nicht Teil dieses API. Jegliches an ADBA übergebenes SQL ist also stets herstellerspezifisch.
  • Das API ist immutable und fluent: Viele der Datenstrukturen und Methoden sind für den einmaligen Gebrauch definiert. So können beispielsweise Konfigurationsmethoden nur einmal auf einer bestimmten Instanz aufgerufen und bereits verwendete Instanzen nicht erneut genutzt werden. Zudem setzt ADBA auf ein Fluent-API.

Die JDBC Expert Group (EG) hat bereits einen konkreten Vorschlag für ADBA veröffentlicht. Allerdings ist die Wahrscheinlichkeit hoch, dass sich das bereits spezifizierte API bis zum Release noch stark verändern wird. Der aktuelle Code von ADBA ist jederzeit auf der Oracle-Homepage einsehbar.

Nach aktuellem Stand soll ADBA auf dem Completion Stage-Interface basieren, das seit Java SE 8 im JDK zu finden ist und mit CompletableFuture sogar eine konkrete Implementierung besitzt. Mit dem CompletionStage-Interface lässt sich eine Pipeline von Operationen erstellen, deren einzelne Schritte (Stages) jeweils asynchron ausgeführt werden können. Jede Stage kann dabei vom Ergebnis eines oder mehrerer vorheriger Schritte abhängen. Die CompletableFuture-Implementierung erlaubt auch, push-basiert, also ohne zu blockieren, auf asynchrone Ergebnisse zu reagieren.

Das Codebeispiel in Abbildung 1 zeigt, wie ein asynchrones Select-Statement mit ADBA erstellt werden kann. Mithilfe eines Fluent-API wird dazu eine Abfrage erzeugt und es werden Callback-Methoden registriert. Ein Userthread kümmert sich zunächst nur um die Erstellung der Abfrage, wird also im Prinzip nach dem submit-Aufruf direkt wieder freigegeben. Eine konkrete Implementierung von ADBA führt dann die eigentliche Datenbankoperation aus. Steht ein Ergebnis zur Verfügung, wird das jeweilige CompleteableFuture beendet, und das Ergebnis kann in einem Userthread verwendet werden. Erwähnenswert ist in diesem Beispiel noch das herstellerspezifische SQL. ADBA liefert keinerlei Abstraktion für die SQL-Abfragen, um Userthreads nicht unnötig zu belasten.

Neben asynchronen Select-Abfragen gibt es die Möglichkeit, andere Datenbankoperationen wie asynchrone Insert-Abfragen auszuführen. Auch Transaktionen sind möglich. Zudem lassen sich verschiedene Operationen sequenziell oder parallel miteinander verbinden.

Ist JDBC bald out

Das synchrone JDBC-API ist und bleibt laut der EG auch in Zukunft der De-facto-Standard. ADBA soll vor allem bei Applikationen, die mit einer hohen Anzahl an konkurrierenden Datenbankzugriffen umgehen müssen, zum Einsatz kommen (High Throughput Applications). Weil JDBC und ADBA zwei unabhängige APIs sind, ist es sogar denkbar, beide Ansätze innerhalb einer Anwendung parallel zu betreiben.

Non-blocking APIs haben das Potenzial, den Durchsatz zu erhöhen. Sie können aber auch dazu führen, dass das Threading-Problem lediglich verschoben wird. So kann beispielsweise auch ein unpassend konfigurierter Connection Pool zu einer hohen Anzahl an Datenbankthreads und damit zu Ressourcenverschwendung führen. Es ist zudem nicht sichergestellt, dass konkrete Treiberimplementierungen von ADBA unter der Haube auch wirklich vollkommen non-blocking agieren, also Non-Blocking IO bis auf die Betriebssystemebene betreiben. Die Spezifikation schreibt schließlich keine Implementierungsdetails vor.

ADBA kann zwar dazu beitragen, durchsatzstarke und ressourcensparende datenbankgetriebene Anwendungen in Java zu entwickeln. Es gibt aber trotzdem auch unabhängig vom asynchronen Ansatz viele weitere Stellschrauben, die für die Entwicklung von High Throughput Applications wichtig sind (etwa durchdachtes Connection Pooling, Prepared statements, Caching etc.).

Fazit

Der asynchrone Zugriff auf relationale Datenbanken ist in Java keineswegs eine neue Idee – Ansätze für asynchrone Datenbank-APIs existieren schon lange (ADBCJ). Das Ziel von ADBA ist die Standardisierung eines asynchronen Datenbank-API in Java SE. ADBA ist dabei kein Ersatz für JDBC, sondern eine Alternative dazu.

Die JDBC Expert Group hat vor Kurzem auf GitHub das Projekt AoJ (ADBA over JDBC) veröffentlicht. Diese Implementierung von ADBA ist aber nur ein Aufsatz auf das synchrone JDBC-API, agiert also immer noch blockierend. AoJ soll dazu dienen, der Community die frühzeitige Verwendung des API zu ermöglichen und schnell Feedback zu erhalten. Unabhängig davon hat Oracle bereits eine Proof-of-Concept-Implementierung von ADBA für die Oracle-Datenbank entwickelt, die durch die Verwendung von Java NIO Selectors in der Tat vollkommen non-blocking umgesetzt wurde.

Es gibt Diskussionen, ob das API nicht besser, oder zumindest zusätzlich, auf der Reactive-Streams-Klasse java.util.concurrent.Flow basieren sollte. Allerdings sieht es bisher nicht so aus, als ob die EG konkrete Bestrebungen in diese Richtung hat.

Spannend bleibt die Frage, wie sich ADBA in den Java-Enterprise-Standard integrieren lässt. Aktuell ist hier noch vieles darauf ausgelegt, dass ein Request von einem Thread abgearbeitet wird. Zu diesem Thema hat Arne Limburg bereits einige Gedankengänge festgehalten.

Ein Releasezeitpunkt für ADBA steht nicht fest. Es ist aber nicht unwahrscheinlich, dass die Spezifikation bereits Teil von Java SE 11 und damit der nächsten LTSVersion wird. In diesem Sinne: Stay tuned!

[marketing_widget_area 17|

Geschrieben von
Sven Kölpin
Sven Kölpin
Sven Kölpin ist Enterprise Developer bei der open knowledge GmbH in Oldenburg. Sein Schwerpunkt liegt auf der Entwicklung webbasierter Enterprise-Lösungen mittels Java EE.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: