CDS-Views sind sowohl im SAP S/4HANA-Umfeld als auch im SAP BW/4HANA Data Warehouse ein mächtiges Werkzeug zur Datenmodellierung. Insbesondere im S/4HANA-System sind CDS-Views unerlässlich und formen im Zusammenspiel das leicht zu erweiternde Virtuelle Datenmodell (VDM), das die Grundlage des modernen S/4HANA-Reportings darstellt.
Die Erweiterbarkeit des VDM birgt allerdings auch Tücken: Werden Views nicht stringent nach soliden Entwicklungsrichtlinien entworfen, läuft man Gefahr, dass der Endanwender im Reporting über eine schlechte Performance klagt. Das liegt daran, dass dem Entwickler bei der Modellierung von CDS-Views viele Freiheiten eingeräumt werden, die jedoch Gift für den Optimizer sind. Denn die CDS DDL (Data Definition Language) ist deklarativ – der Entwickler beschreibt, „was“ er haben möchte, überlässt das „wie“ allerdings dem Optimizer und der Datenbank. Diese Bequemlichkeit ist für den Entwickler grundsätzlich ein Segen, wird aber schnell zum Fluch, wenn der Optimizer aufgrund schlechter Modellierungsentscheidungen unbeabsichtigt an seiner Arbeit gehindert wird und so Performance-Probleme entstehen.
Glücklicherweise lassen sich die meisten Performance-Probleme auf eine Reihe typischer, wiederkehrender Muster – sogenannte Anti-Patterns – zurückführen. Entwickler, die diese Muster kennen und wissen, wie sie aufgelöst werden müssen, können die Performance der betroffenen CDS-View beträchtlich steigern. In diesem Blogbeitrag werden 5 typische Anti-Patterns und der jeweilige Lösungsansatz vorgestellt.
Leider gibt es kein offizielles Tool der SAP, mit dessen Hilfe Anti-Patterns und eigens definierte Regelbrüche der projektinternen Entwicklungsrichtlinien regelbasiert gebündelt aufgespürt werden können. Um diesen Anforderungen gerecht zu werden, haben wir ein movisco-Tool entwickelt, das in unseren Projekten die Analyse von CDS-Views erleichtert und effizienter macht.
Es gibt zwei Wege, um in CDS-Views Daten aus mehreren Entitäten zu verbinden: den klassischen Join und die Assoziation. Der entscheidende Unterschied liegt im Ausführungszeitpunkt: ein JOIN im SELECT … FROM wird standardmäßig immer ausgeführt, sobald die View gelesen wird – unabhängig davon, ob die Felder der verbundenen View in der Abfrage benötigt werden. Die Assoziation fungiert hingegen als „Join auf Vorrat“. Die Assoziation wird nur dann zum Join, wenn ein Feld in der Abfrage benötigt wird („Lazy Evaluation“).
Insbesondere in wiederverwendbaren Interface-Views, die typischerweise mehrere Entitäten zusammenführen und viele Felder für die darauf aufsetzenden Views bereitstellen, können Joins schnell zum Flaschenhals werden.
Tipp: Alternativ dazu kann man auch im JOIN den Zusatz „TO ONE“ vergeben, wenn diese Kardinalität fachlich zutrifft. Der Unterschied zwischen einem LEFT OUTER JOIN TO ONE und einer Assoziation liegt technisch darin, zu welchem Zeitpunkt ungenutzte Felder wegoptimiert werden. Bei der Assoziation erfolgt dies komplett deterministisch, der Optimizer muss sich also nicht darum kümmern. Beim JOIN muss der Optimizer im Suchraum einen optimalen Plan finden, was jedoch je nach Komplexität nicht garantiert ist.
Die Möglichkeit zur Wiederverwendung von Views ist eine der großen Stärken von CDS-Views und zugleich eine typische Quelle für schleichende Performance-Probleme. Auch wenn das Modell auf jeder Ebene modular und sauber aussieht, kann die Stapelung von zu vielen Views für den Optimizer zum Verhängnis werden.
Jede Ebene bringt eigene Joins, Berechnungen und Projektionen mit. Der Optimizer muss diesen Baum flach ziehen, um einen geeigneten Ausführungsplan zu erstellen – und je mehr Tabellen und Operatoren zusammenkommen, desto größer wird die Wahrscheinlichkeit, dass der Optimizer auf den Holzweg geführt wird und einen suboptimalen Plan nimmt.
Tipp: Ab wann die View-Schachtelung zum Problem wird, kann nicht pauschal benannt werden. Als grober Richtwert kann aber gesagt werden, dass 100 zugrundliegende Datenbanktabellen in einer View niemals überschritten werden sollen. In einem konkreten Projekt sollte diese Grenze jedoch individuell geprüft und gegebenenfalls weiter eingeschränkt werden.
Berechnete Felder sind ein mächtiges Werkzeug bei der Modellierung von CDS-Views, da auf diesem Weg komplexe fachliche Logik virtuell berechnet werden kann, ohne von einer Persistenzschicht abhängig zu sein. Hier sollte man jedoch aufpassen, dass man dem Optimizer keine Steine in den Weg legt. Das Problem: Berechnungen sind für den Optimizer grundsätzlich opak. Eine Berechnung an der falschen Stelle schmälert den Handlungsspielraum des Optimizers und verhindert beispielsweise den Filter-Pushdown. Das tritt insbesondere an zwei Stellen auf: Bei der Filterung mittels WHERE und in der ON-Bedingung von JOINs.
Sobald ein WHERE auf einem berechneten Feld aufsetzt (auch CAST zählt dazu), muss die Berechnung zwangsläufig für die gesamte Datenmenge ausgeführt werden, bevor der Filter angewendet werden kann. Aus einer gut gemeinten Einschränkung, die im Idealfall entlastend wirken soll, wird so ein Full-Table-Durchlauf. Besser ist es, nur die Originalfelder der zugrundeliegenden Tabellen für die Filterung zu verwenden, da so der Filter früher angewendet werden kann.
Im ON-Teil eines JOINs sind Berechnungen noch fataler: Ein Join ist genau dann am effizientesten, wenn die Gleichheit zweier Spalten geprüft wird. Sobald eine Berechnung ins Spiel kommt, leidet sowohl die Effizienz des Joins, als auch die Filter-Propagierung, die bei JOINs über den ON-Teil erfolgt. Problematisch sind neben „echten“ Berechnungen zudem auch die Nicht-Gleichheitsoperatoren >, < und LIKE. Es sollte in diesen Fällen dringend geprüft werden, ob man Einschränkungen dieser Art wirklich im JOIN vornehmen muss oder gegebenenfalls auslagern kann.
Tipp: Bei sehr komplexen Berechnungen, die zwangsläufig auf großen Datenmengen laufen, sollte evaluiert werden, ob die Logik nicht eher ein Kandidat für die Persistierung ist, um wiederholte Laufzeitberechnungen zu vermeiden.
Ein verbreiteter, gut gemeinter Reflex bei der Modellierung ist es, leere Felder „aufzuhübschen": Statt einen NULL-Wert stehen zu lassen, ersetzt man ihn über ein CASE mit konstantem ELSE (etwa ELSE '' oder ELSE 0) durch einen festen Standardwert. Fachlich sieht das sauberer aus – für den Optimizer wird es jedoch zum Bremsklotz.
Der Hintergrund: Bei einem LEFT OUTER JOIN liefert die rechte Seite NULL, wenn kein Partnersatz existiert. Genau diese NULL-Semantik nutzt der Optimizer, um überflüssige Joins zu eliminieren und die Reihenfolge von Join und Berechnung frei zu wählen. Kapselt man ein Feld aus dem Outer Join aber in ein CASE, das dank konstantem ELSE nie NULL zurückgibt, ist die Berechnung nicht mehr „NULL-erhaltend". Der Optimizer darf die Ausführungsreihenfolge von Join und Berechnung dann nicht mehr vertauschen – und verliert die Möglichkeit, den Join wegzuoptimieren.
Besonders teuer wird das in wiederverwendbaren Views, die selbst als rechte Seite eines Joins in darüberliegenden Views dienen: Ein einziges konstantes ELSE kann dort den zugehörigen Join für alle Konsumenten festnageln – auch für jene, die das Feld nie abfragen.
Tipp: NULL-Werte überall zulassen, wo es fachlich vertretbar ist. Ist ein Standardwert unverzichtbar, gehört er in eine höhere View-Ebene oberhalb des Joins.
Eine Aggregation, also GROUP BY in Verbindung mit SUM, COUNT, MAX oder MIN, bereits weit unten im Modell einzubauen, kann dem Optimizer in vielen Fällen Probleme bereiten.
Intuitiv fühlt es sich richtig an, da auf diese Weise bereits früh wichtige Transformationen der Daten erfolgen können. Für den HANA-Optimizer ist ein GROUP BY aber grundsätzlich eine Barriere im Ausführungsplan. Filter, die im Datenmodell „oberhalb“ der Aggregation angesiedelt sind, können nur dann durch diese Barriere geschoben werden, wenn der Filter sich auf einen Gruppierungsschlüssel bezieht.
Kritischer ist der Fall, wenn durch einen (INNER) JOIN eine Einschränkung eingeführt wird. Angenommen wir haben ein Modellierungsszenario mit Sales- und Kundendaten, und es werden auf Kundenebene alle Verkäufe aggregiert. Wenn jetzt durch einen Join nachträglich eine Einschränkung bspw. nach Region der Kunden erfolgt, muss standardmäßig zuerst die Aggregation & Berechnung erfolgen und erst im Anschluss die Einschränkung der Kundenanzahl, was jedoch alles andere als effizient ist. In Fällen wie diesem kann der Optimizer im Idealfall durch eine Semi-Join-Reduktion bzw. Group-By-Pushdown jedoch die Ausführung so verändern, dass statt des skizzierten naiven Plans ein deutlich effizienterer Plan gewählt wird, da das Merkmal „Kunde“ Join- und Gruppierungsschlüssel ist. Es ist jedoch nicht garantiert, dass der Optimizer diesen idealen Plan ableiten kann. Daher ist es sinnvoll, durch geschickte Modellierung diese Unsicherheit zu vermeiden.
Tipp: Filter, die sich auf die Kennzahlen einer Aggregation beziehen, sind deutlich problematischer und erfordern prinzipiell immer, dass die Aggregation zuerst stattfindet. Hier sollte dringend evaluiert werden, ob der Filter nötig ist und möglicherweise dem Optimizer Lösungsräume versperrt.
Die vorgestellten Muster zu kennen ist die eine Sache – sie in einem gewachsenen Datenmodell mit hunderten Views zuverlässig aufzuspüren, die Andere. Von Hand ist das kaum zu leisten, weshalb sich der Blick lohnt, welche Werkzeuge dabei unterstützen.
SAP liefert durchaus Bordmittel, die jeweils einen Ausschnitt abdecken. Der Dependency Analyzer in den ABAP Development Tools visualisiert die View-Hierarchie und liefert über die Complexity Metrics die Zahl der zugrunde liegenden Tabellen – ideal, um eine zu komplexe Verschachtelung (Anti-Pattern Nr. 2) zu erkennen. Das ABAP Test Cockpit (ATC) prüft CDS-Views statisch gegen vordefinierte Regeln, darunter einige Performance-Checks. Und PlanViz sowie der SQL Monitor zeigen anhand echter Laufzeitdaten, wie ein Statement tatsächlich ausgeführt wird, und decken so etwa einen fehlgeschlagenen Pushdown auf.
So wertvoll diese Werkzeuge sind – sie haben blinde Flecken. Der Dependency Analyzer betrachtet nur die Komplexität, nicht die übrigen Muster. PlanViz und der SQL Monitor arbeiten reaktiv: Sie benötigen repräsentative Laufzeitdaten und eine manuelle Analyse View für View, greifen also erst, wenn das Problem bereits spürbar ist. Das ATC ließe sich zwar um eigene Checks erweitern, doch diese teils view-übergreifenden Muster als CDS-Checks abzubilden und zu pflegen, bliebe aufwendige Eigenentwicklung. Und keines der Bordmittel spürt die hier beschriebenen Anti-Patterns gebündelt und statisch auf.
Genau diese Lücke schließt unser movisco-Tool. Es analysiert CDS-Views rein statisch – ohne Laufzeitdaten – über den gewünschten Modell-Scope hinweg, erkennt die genannten Anti-Patterns automatisch und gebündelt und prüft dabei auch eigens definierte Regeln. So werden Performance-Risiken bereits während der Entwicklung sichtbar, statt erst im Reporting aufzufallen – und das automatisiert in einem Durchlauf, statt mühsam Stück für Stück.
Sie möchten wissen, wo diese Muster in Ihren CDS-Views schlummern? Sprechen Sie uns gerne an! Wir nehmen Ihr Datenmodell mit Unterstützung unseres CDS-Tools für Sie unter die Lupe und zeigen Ihnen die kritischen Stellen samt konkreter Handlungsempfehlungen auf.
Wir freuen uns über Ihre direkte Kontaktaufnahme!