Einstieg in Continuous Integration bei der Entwicklung von Data-Warehouse-Systemen

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
Maintain a Single Source Repository

 
  • Der gesamte Code sollte unter Versionskontrolle stehen.

  • Es gibt ein zentrales Repository mit dem „Bauplan“, um den letzten Stand des gesamten Projektes auf einer neuen Umgebung aufzuspielen.

 

SP 2
Automate the Build

 
  • Es sollte per Knopfdruck möglich sein, einen Release-Kandidaten zu erzeugen, der potentiell ausgerollt werden kann.

  • Alle Komponenten sollten dabei eingeschlossen werden, die für die Lauffähigkeit des Systems notwendig sind.

 

SP 3
Make your Build Self-Testing

 
  • Es sollten Tests geschrieben werden, die während der Erstellung des Builds automatisch ausgeführt werden und im Fehlerfall das Build zum Fehlschlagen bringen.

 

SP 4
Everyone Commits to the Mainline Every Day

 
  • Mindestens einmal am Tag wird der Entwicklungsstand auf das zentrale Repository zurückgeschrieben.

 

SP 5
Every Commit should Build the Mainline on an Integration Machine

 
  • Nach jedem Festschreiben sollte der Entwicklungsstand automatisch auf einer
    (Integrations-)Umgebung ausgerollt werden.

 

SP 6
Fix Broken Builds Immediately

 
  • Das kontinuierliche und frühzeitige Erkennen von Fehlern kann nur gewährleistet werden, wenn nach fehlgeschlagenen Builds die Fehler frühzeitig erkannt und behoben werden.

 

SP 7
Keep the Build Fast

 
  • Ebenfalls wichtig ist die Leichtgewichtigkeit des Buildprozesses.

  • Fehler führen zu Problemen, wenn sie durch lange Buildprozesse erst spät erkannt werden.

 

SP 8
Test in a Clone of the Production Environment

 
  • Vor dem Release sollte die Entwicklung auf einer möglichst produktionsnahen Umgebung getestet werden.

 

SP 9
Make it Easy for Anyone to Get the Latest Executable

 
  • Alle Beteiligten können auf die zuletzt erfolgreich gebauten Artefakte zugreifen.

 

SP 10
Everyone can See What’s Happening

 
  • Es werden Monitore und Dashboards bereitgestellt, mit denen alle Beteiligten schnell einen Eindruck vom aktuellen Stand des Builds bekommen.

  • Entwickler werden automatisch darüber informiert, wenn ein Build durch ihre Integration fehlgeschlagen ist.

 

SP 11
Automate Deployment

 
  • Mithilfe von Skripten sollte es möglich sein, einen beliebigen ReleaseKandidaten auf eine beliebige Umgebung automatisiert zu deployen.

  • Neben der verbesserten Geschwindigkeit werden dadurch außerdem Komplexität und Fehleranfälligkeit reduziert.

  • Das automatische Deployment ist außerdem Grundvoraussetzung für automatisierte Tests.

 

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.

Views: 394
clear

dominik-schuster

Kontakt

Dominik Schuster
Consultant
DE + 49 (89) 122 281 110
CH + 41 (44) 585 39 80