Immer neue Datenquellen und Anwendungsgebiete sorgen auch weiterhin für den stetigen Ausbau von Datenhaltungssystemen, wie DWH, Data Lake oder Analytics Platform. Mit den erweiterten Anforderungen müssen auch die Datenbewirtschaftungsprozesse Schritt halten. Nicht selten wachsen kleine BI-Anwendungen zu großen Initiativen, an denen mehrere Entwicklerteams beteiligt sind. In vielen Branchen müssen Anpassungen schneller vorgenommen werden als jemals zuvor, was die Lage zusätzlich verschärft. Den Teams wird dadurch eine kurze Reaktionszeit sowie eine hohe Flexibilität abverlangt, die jedoch nicht zuletzt von der Infrastruktur getragen werden muss.
Mit Continuous Integration den steigenden Anforderungen in der Softwareentwicklung gerecht werden
Um die gestiegenen Anforderungen an die Größe und Anzahl der Entwicklungsteams und die damit einhergehenden parallelen Entwicklungsprozesse in einem System bewältigen zu können, haben sich in der klassischen Softwareentwicklung schon lange Prinzipien wie die Continuous Integration (CI) durchgesetzt. Ziel von CI ist es, sicherzustellen, dass Code und weitere Entwicklungsobjekte regelmäßig integriert und mithilfe automatischer Jobs getestet werden. Dabei werden die Änderungen in ein zentrales Repository integriert, das unter Versionskontrolle steht. Infolge des Festschreibens der Änderungen werden automatische Builds gestartet, die diverse Tests und Konsistenzprüfungen beinhalten. Schlägt einer der Tests fehl, wird der Entwickler automatisch informiert.
Für die Handhabe von Datenbanksystemen als Backend von Applikationen können weitere Schritte in den Buildprozess eingebaut werden, wie das Updaten und Testen der Datenbank.[1] Da bei dem Aufbau und der Weiterentwicklung von Data-Warehouse-Systemen (DWS) ein Schwerpunkt auf der Entwicklung von Datenbanksystemen liegt, sollen hier die Besonderheiten von CI bei der Datenbankentwicklung im Vergleich zur Applikationsentwicklung beschrieben werden. Dafür wird zuerst die grundlegende Funktionsweise von CI beschrieben, und es werden allgemeine Schlüsselpraktiken vorgestellt. Basierend auf den Schlüsselpraktiken werden im Anschluss die wesentlichen Unterschiede dargestellt und Lösungsvorschläge unterbreitet.
Continuous Integration im Überblick
Continuous Integration verpflichtet nicht, ein bestimmtes Toolset zu verwenden oder bestimmte Werkzeuge einzusetzen. Im Rahmen von CI werden lediglich Praktiken vorgeschlagen, mit denen viele Probleme beseitigt werden können, die im Zusammenhang mit der parallelen Entwicklung auf demselben System stehen. Zum Einstieg in die Thematik bietet sich am besten der Workflow an, der bei der Entwicklung mit einer CI-Umgebung durchlaufen wird:
Abbildung 1 – Workflow für Entwickler im CI-Umfeld
Schlüsselpraktiken für effektive Continuous Integration
Um eine solche Vorgehensweise bei der Entwicklung mitsamt den verschiedenen Prüfinstanzen zu ermöglichen, müssen gewisse Vorbedingungen erfüllt sein. Bereits 2006 beschrieb Martin Fowler eine Reihe von Schlüsselpraktiken (SP), die für eine effektive Continuous Integration befolgt werden sollten:[2]
SP 1 |
|
SP 2 |
|
SP 3 |
|
SP 4 |
|
SP 5 |
|
SP 6 |
|
SP 7 |
|
SP 8 |
|
SP 9 |
|
SP 10 |
|
SP 11 |
|
Tabelle 1 – Eigene Darstellung der CI-Schlüsselpraktiken nach Martin Fowler 2006
Für DWS haben die vorgestellten SP unterschiedliche Konsequenzen, wie es bei der Erstellung von Software der Fall ist. Viele Prinzipien können aber nahezu 1:1 übernommen werden. Bei anderen SP müssen besondere Umstände beachtet oder Kompromisse gefunden werden. Im Folgenden wird auf die wichtigsten Herausforderungen näher eingegangen:
Herausforderungen beim Anwenden von CI bei der Datenbankentwicklung
Full-Deployment vs. Delta-Deployment
Während beim Releasen einer Softwareapplikation der gesamte Code überschrieben werden kann (Full-Deployment), muss bei der Arbeit mit Datenbanksystemen beachtet werden, dass bei dem erneuten Deployen von Objekten die darin persistierten Daten in der Regel verloren gehen. Auch in der klassischen Softwareentwicklung werden Datenbanksysteme genutzt, doch zumeist lediglich als Backend einer Software- oder Webapplikation. Das Datenbanksystem wird dabei für kleinere Transaktionen optimiert, und Änderungen im Datenmodell kommen selten vor und sind meist rein additiv. Weiterhin arbeitet für gewöhnlich eine geringere Anzahl an Entwicklern auf dem Datenbanksystem. Die meisten Änderungen können mit einfachen Skripten oder DB-Diffs erstellt werden, und nur in Sonderfällen muss ein besonderes Augenmerk auf die Daten gelegt werden. In DWS hingegen entfällt ein Großteil der Arbeit auf die Datenbank, und strukturelle Änderungen, Datenmigrationen und -archivierungen oder Updates gehören zur Tagesordnung. Die Änderungen können daher nicht via DB-Diff vereinfacht erstellt und deployt werden. Für ein Release muss ein spezifisches Transitions-/Upgradeskript bereitgestellt werden, das neben den strukturellen Änderungen (DDL) auch den Datenänderungen (DML) Rechnung trägt. Dabei ist es essentiell, dass keine Daten verloren gehen oder verfälscht werden. Zusätzlich stellt die große Menge an Daten (im Vergleich zu transaktionalen Systemen) eine Herausforderung dar. Zur Reduktion der dadurch erhöhten Komplexität haben sich verschiedene Tools, wie Liquibase oder Flyway, etabliert. Eine Übersicht über mögliche Tools gibt es hier.
Ausschließlich Upgradeskripte zu erstellen, kann jedoch zu Problemen führen. Immer die gesamten Strukturänderungen und Konfigurationsdateien aller Upgradeskripte sequenziell auszuführen, um den aktuellen Entwicklungsstand auf einer leeren Umgebung aufzuspielen, führt schnell zu Engpässen, wodurch gegen SP 7 verstoßen wird. Rein aus dem Code ist weiterhin schwer ersichtlich, welche Objekte in welcher Form im aktuellen Entwicklungsstand vorliegen, da sie in verschiedenen Upgradeskripten verteilt liegen. Dies sind nur ein paar Gründe dafür, neben den Upgradeskripten einen Snapshot des aktuellen Entwicklungsstandes zu pflegen bzw. wenn möglich zu generieren. Aber Vorsicht: Werden zwei Codeartefakte gepflegt, muss sichergestellt werden (z. B. mithilfe von automatischen DB-Diffs), dass diese nicht auseinanderdriften.
Abhängigkeiten innerhalb des Builds
Innerhalb der Datenbank existieren Abhängigkeiten zwischen Objekten, die sich nicht immer vereinfacht auflösen lassen. So kann eine View auf eine Funktion zugreifen, aber auch umgekehrt. Besonders wenn aus den zuvor angesprochenen Gründen mit Snapshots gearbeitet wird und der letzte Entwicklungsstand auf einer leeren Umgebung ausgerollt werden soll, kann es zu Herausforderungen kommen. In der klassischen Softwareentwicklung wäre das Standardvorgehen an dieser Stelle, das System und seine Loader zu analysieren, was sich im Ergebnis ähnlich zu dem Deployment von Objektkategorien verhält. In DWS reicht es wegen des oben beschriebenen Sachverhalts jedoch unter Umständen nicht aus, einfach aufeinander aufbauende Objektkategorien zu deployen (erst alle Tabellen, dann alle Views usw.). Die wohl einfachste, aber weniger elegante Lösung wäre es, mit den Abhängigkeiten zu leben und diese mit Coding-Standards und Konventionen weitestgehend in den Griff zu bekommen. Eine elegantere, aber unter Umständen aufwändige Lösung wäre es, die Abhängigkeiten vor dem Deployment zu analysieren und einen optimalen Ablaufpfad zu erzeugen.
Heterogene Produktionsumgebung
Mithilfe von CI können viele Probleme frühzeitig erkannt und behoben werden, indem die einzelnen Schritte bis in die Produktivumgebung automatisiert und getestet werden. In der Softwareentwicklung spiegelt die CI-Umgebung für gewöhnlich die Produktion ziemlich genau wider. Aus folgenden Gründen ist dies bei der Datenbankentwicklung aber nur eingeschränkt praktikabel:
- Die notwendige Speicherkapazität für eine volle Kopie der Produktion kann gigantisch sein.
- Das Klonen (oder Einspielen des Backups) der Produktion kann viel Zeit kosten.
- Aufgrund der DSGVO können die Daten zumeist nicht 1:1 für Testzwecke genutzt werden.
- Aus Kostengründen können Hardware und Lizenzen nicht immer mehrfach bereitgestellt werden.
Der Drahtseilakt liegt an dieser Stelle darin, trotz der beschriebenen Einschränkungen eine möglichst realitätsnahe Testumgebung (vgl. SP 8) und gleichzeitig ein schnelles Build zu gewährleisten (vgl. SP 7). Besonders bei umfangreichen Produktionsumgebungen in DWS kann den beschriebenen Herausforderungen nur mit Kompromissen begegnet werden. So kann eine speziell für produktionsnahe Tests ausgelegte Umgebung in regelmäßigen Abständen mit einem älteren, anonymisierten Datenstand bespielt und von allen Entwicklern gemeinschaftlich genutzt werden (Pre-Production). Entwickler müssen sich dabei jedoch sehr genau abstimmen, sodass die Ressourcen optimal genutzt und Konflikte vermieden werden können. Durch die längere Dauer bis zur Aktualisierung der Umgebung sollten die Entwickler die Umgebung „sauber“ halten und gewissenhaft mit den Daten umgehen.
Automatisierte Tests
Im Gegensatz zur Anwendungsentwicklung, bei der einzelne Units ziemlich isoliert getestet und Schnittstellen vereinfacht simuliert werden können, gibt es bei DWS verschiedene Einschränkungen. Da für aussagekräftige Tests tatsächlich Daten mithilfe von Bewirtschaftungsprozessen in der Datenbank kopiert werden, lassen sich einzelne Tests nicht ohne weiteres parallelisieren. Sonst könnte ein beliebiger Test durch den parallelen Aufruf die Quelldaten eines anderen Tests überschreiben/manipulieren, bevor dieser abgeschlossen war. Neben Parallelität bei den Tests innerhalb eines Builds können aus denselben Gründen auch nicht mehrere Builds mit Tests gleichzeitig gestartet werden, wenn sie gegen dieselbe Umgebung deployen. Des Weiteren bedeutet die sequenzielle Ausführung einzelner Tests auch eine verlängerte Laufzeit. Die Laufzeit kann zwar dadurch beschleunigt werden, dass Ablaufskripte für die optimale Reihenfolge bei der Testausführung aus den Abhängigkeiten generiert werden (zuerst Test1, dann Test2 und Test3 parallel, dann Test4 usw.). Jedoch kann das Build dabei nur zu einem gewissen Maß beschleunigt werden, und weiterhin ist eine vollständige Abdeckung nur bedingt möglich, da nicht alle Abhängigkeiten ohne weiteres automatisiert herauszulesen sind. Aus diesen Gründen können alle Tests nicht ohne weiteres bei jedem Build ausgeführt werden. Eine aufwändigere Lösungsmöglichkeit wäre es nun, nur diejenigen Tests auszuführen, zu denen sich Objekte auch tatsächlich geändert haben. Alleine schon durch dynamisches SQL wäre jedoch eine vollständige Abdeckung dabei nur schwer zu realisieren, sodass auch hier wieder eine Annäherung durch das Eingehen von Kompromissen angezielt wird. Für die automatisierten Tests könnten beispielsweise sogenannte „Nightly Builds“ konfiguriert werden, die zu festgesetzten Zeiten laufen. Weiterhin könnten die Tests auch separiert werden, sodass beispielsweise leichtgewichtige Unittests bei jedem Build mitlaufen und umfangreichere und zeitintensive Tests in „Nightly Builds“ ausgelagert werden.
Fazit & Ausblick
Mithilfe von CI kann die Qualität von (Software-)Inkrementen vor der Auslieferung auf die Produktionsumgebung erheblich gesteigert werden. Neben der Möglichkeit, die lokale Entwicklungsumgebung jederzeit neu zu erzeugen und Konflikte frühzeitig zu erkennen, hat die CI-Umgebung weiterhin einen pädagogischen Mehrwert: Der Entwickler wird durch die kurzen Feedbackschleifen auch automatisch dazu erzogen, sauberer und gewissenhafter zu arbeiten, sodass das automatische Build seltener „rot“ wird.
Die meisten SP können aus der klassischen Softwareentwicklung übernommen werden, und von ihren Vorteilen kann direkt profitiert werden. Andere SP werden hingegen durch natürliche Unterschiede zwischen der Software- und DWS-Entwicklung beeinflusst und können nicht 1:1 übernommen werden. Als wesentlicher Unterschied kann dabei benannt werden, dass die meisten Fehler in DWS erst zum Zeitpunkt des Aufspielens auf eine Testumgebung und des Startens von Datenbewirtschaftungsprozessen gefunden werden können („run-time“), während in der klassischen Softwareentwicklung der Code bereits zur „compile-time“ umfangreicher getestet werden kann.
Mithilfe verschiedener Kompromisse zwischen Testabdeckung, Nutzen, Laufzeit und Kosten kann mit den spezifischen Eigenschaften der Datenbank umgegangen und von den Vorteilen einer CI-Landschaft profitiert werden.
Quellen
[1] Vgl. Glover, Andrew et al.: Continuous Integration: Improving Software Quality and Reducing Risk, Https://www.safaribooksonline.com/library/view/continuous-integration-improving/9780321336385/#toc, 2007.
[2] Vgl. Fowler, Martin: Continuous Integration. https://www.martinfowler.com/articles/continuousIntegration.html, 2016.