This is a test message to test the length of the message box.
Login
BTP Schnittstellen Performance
Erstellt von Software-Heroes

BTP - Schnittstellen Performance

117

Welche Schnittstellentechnologie hat aktuell die beste Performance beim Zugriff auf On-Premise Daten in ABAP? Hier gehen wir mehr in die Details.

Werbung


In diesem Artikel schauen wir uns die Performance von verschiedenen Technologien und Protokollen an und vergleichen die Laufzeit in verschiedenen Szenarien miteinander. Am Ende schauen wir auf das Ergebnis und leiten daraus weitere Schlussfolgerungen ab.

 

Einleitung

Hast du dich schon einmal gefragt, welche Technologie für den Zugriff auf Daten in einem On-Premise System die besten und schnellsten sind? Aktuell gibt es im SAP Bereich die verschiedenen Möglichkeiten Daten zu verteilen oder auch bereitzustellen. Da wären zum Beispiel der klassische RFC, SOAP für modernere Schnittstellen, der neue Standard REST und OData oder für die Verteilung von Belegen und Stammdaten IDoc. Heute schauen wir uns den Zugriff auf On-Premise Daten und die damit verbundene Laufzeit an.

 

On-Premise

Bevor wir mit dem Zugriff beginnen können, müssen wir unser On-Premise System erst noch vorbereiten. Dazu legen wir eine Tabelle an und stellen die Daten nach Außen zur Verfügung.

 

Tabelle

Die Tabelle befüllen wir mit unterschiedlichen Daten und Datentypen und verwenden dabei als Schlüssel eine UUID. Daneben haben wir zufällig generierte Inhalte und ein String Feld mit zufälligen Zeichenketten. Insgesamt sollen die Daten eine breite Vielfalt an Daten abdecken.

@EndUserText.label : 'Performance Test'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zbs_dmo_perf {
  key client       : abap.clnt not null;
  key identifier   : sysuuid_x16 not null;
  item_description : abap.char(40);
  description      : abap.char(150);
  @Semantics.amount.currencyCode : 'zbs_dmo_perf.currency'
  amount           : abap.curr(15,2);
  currency         : abap.cuky;
  blob             : abap.string(0);
  ndate            : abap.dats;
  ntime            : abap.tims;
  utc              : abap.utclong;
}

 

Initialisierung

Die Datenerzeugung übernimmt eine ausführbare Klasse für uns. Dabei wollen wir zufällige Daten erzeugen, bei denen die Datensätze recht unterschiedlich sind. Damit wollen wir eine Komprimierung bzw. Verdichtung beim Datentransfer vermeiden, wenn du viele gleiche Datensätze existieren.

CLASS zcl_bs_demo_fill_performance DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

  PRIVATE SECTION.
    DATA randomizer_amount   TYPE REF TO zcl_bs_demo_random.
    DATA randomizer_currency TYPE REF TO zcl_bs_demo_random.
    DATA randomizer_text     TYPE REF TO zcl_bs_demo_random.
    DATA randomizer_blob     TYPE REF TO zcl_bs_demo_random.
    DATA randomizer_date     TYPE REF TO zcl_bs_demo_random.
    DATA randomizer_time     TYPE REF TO zcl_bs_demo_random.
    DATA all_letters         TYPE string.

    METHODS initialize_class_attributes.

    METHODS get_random_currency
      RETURNING VALUE(result) TYPE waers.

    METHODS get_description
      RETURNING VALUE(result) TYPE zbs_dmo_perf-description.

    METHODS get_blob
      RETURNING VALUE(result) TYPE zbs_dmo_perf-blob.

    METHODS get_letter
      RETURNING VALUE(result) TYPE string.

    METHODS get_date
      RETURNING VALUE(result) TYPE d.

    METHODS get_time
      RETURNING VALUE(result) TYPE t.

ENDCLASS.


CLASS zcl_bs_demo_fill_performance IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    DATA new_database_entries TYPE STANDARD TABLE OF zbs_dmo_perf WITH EMPTY KEY.

    initialize_class_attributes( ).

    DELETE FROM zbs_dmo_perf.
    COMMIT WORK.

    DO 50 TIMES.
      DO 500 TIMES.
        DATA(new_entry) = VALUE zbs_dmo_perf( identifier  = xco_cp=>uuid( )->value
                                              description = get_description( )
                                              amount      = randomizer_amount->rand( )
                                              currency    = get_random_currency( )
                                              blob        = get_blob( )
                                              ndate       = get_date( )
                                              ntime       = get_time( )
                                              utc         = utclong_current( ) ).
        new_entry-item_description = |Item from { new_entry-ndate DATE = USER } in currency { new_entry-currency }|.

        INSERT new_entry INTO TABLE new_database_entries.
      ENDDO.

      INSERT zbs_dmo_perf FROM TABLE @new_database_entries.
      COMMIT WORK.

      CLEAR new_database_entries.
    ENDDO.

    out->write( |New Entries created in ZBS_DMO_PERF| ).
  ENDMETHOD.


  METHOD initialize_class_attributes.
    all_letters = to_lower( sy-abcde ) && to_upper( sy-abcde ).
    randomizer_amount = NEW zcl_bs_demo_random( id_min = 2
                                                id_max = 450 ).
    randomizer_currency = NEW zcl_bs_demo_random( id_min = 1
                                                  id_max = 4 ).
    randomizer_text = NEW zcl_bs_demo_random( id_min = 1
                                              id_max = 52 ).
    randomizer_blob = NEW zcl_bs_demo_random( id_min = 500
                                              id_max = 2000 ).
    randomizer_date = NEW zcl_bs_demo_random( id_min = 0
                                              id_max = 730 ).
    randomizer_time = NEW zcl_bs_demo_random( id_min = 0
                                              id_max = 360 ).
  ENDMETHOD.


  METHOD get_random_currency.
    CASE randomizer_currency->rand( ).
      WHEN 1.
        result = 'EUR'.
      WHEN 2.
        result = 'USD'.
      WHEN 3.
        result = 'RUB'.
      WHEN 4.
        result = 'CHF'.
    ENDCASE.
  ENDMETHOD.


  METHOD get_description.
    DO 150 TIMES.
      result &&= get_letter( ).
    ENDDO.
  ENDMETHOD.


  METHOD get_blob.
    DO randomizer_blob->rand( ) TIMES.
      result &&= get_letter( ).
    ENDDO.
  ENDMETHOD.


  METHOD get_letter.
    result = substring( val = all_letters
                        off = CONV i( randomizer_text->rand( ) - 1 )
                        len = 1 ).
  ENDMETHOD.


  METHOD get_date.
    RETURN sy-datum - randomizer_date->rand( ).
  ENDMETHOD.


  METHOD get_time.
    RETURN sy-uzeit - randomizer_time->rand( ) * 60.
  ENDMETHOD.
ENDCLASS.

 

Die Klasse erzeugt 25000 Datensätze, jeweils in Blöcken von 500 Einheiten, die an die Datenbank übergeben werden. Damit wollen wir verhindern, dass wir in einem Schritt eine sehr große Anzahl von Einträgen übergeben. Nach Ausführung der Klasse finden wir in der Tabelle unsere Daten.

 

RFC Funktionsbaustein

Der erste Typ Schnittstelle ist ein klassischer RFC Funktionsbaustein. An den Baustein übergeben wir eine Liste von Feldern, die wir Lesen wollen und einen TOP/SKIP, damit wir Teile der Datenmenge lesen können. Bevor du allerdings die generische Liste einfach an das SELECT Statement übergibst, solltest du den Inhalt prüfen. Dazu verwenden wir eine Standardklasse, die die Überprüfung für uns durchführt.

FUNCTION z_bs_demo_get_performance_data
  IMPORTING
    VALUE(selection_fields) TYPE string_table
    VALUE(top) TYPE i
    VALUE(skip) TYPE i
  EXPORTING
    VALUE(performance_data) TYPE zbs_t_demo_performance.



  DATA(fields_for_select) = ``.
  LOOP AT selection_fields REFERENCE INTO DATA(field_name).
    DATA(validated_name) = cl_abap_dyn_prg=>check_column_name( val    = field_name->*
                                                               strict = abap_true ).

    IF fields_for_select <> ``.
      fields_for_select &&= `, `.
    ENDIF.
    fields_for_select &&= validated_name.
  ENDLOOP.

  IF fields_for_select IS INITIAL.
    fields_for_select = `*`.
  ENDIF.

  SELECT FROM zbs_dmo_perf
    FIELDS (fields_for_select)
    ORDER BY identifier
    INTO CORRESPONDING FIELDS OF TABLE @performance_data
    UP TO @top ROWS
    OFFSET @skip.
ENDFUNCTION.

 

Hinweis: Neben der Prüfung der übergebenen Felder, sollte im Falle eines RFC Funktionsbausteins auch eine sinnvolle Berechtigungsprüfung erfolgen, um die Daten zu schützen (Best Practice).

 

OData Service

Im nächsten Schritt wollen wir einen OData Service zur Verfügung stellen. Dabei schauen wir uns das Protokoll Version 2 und Version 4 an. Dazu legen wir auf unserer Tabelle einen Basis View an und normalisieren die Felder.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Base View Performance Data'
define view entity ZBS_B_DMOPerformance
  as select from zbs_dmo_perf
{
  key identifier       as Identifier,
      item_description as ItemDescription,
      description      as RandomDescription,
      @Semantics.amount.currencyCode: 'Currency'
      amount           as Amount,
      currency         as Currency,
      blob             as BlobObject,
      ndate            as NewDate,
      ntime            as NewTime,
      utc              as UTCTimestamp
}

 

Dazu generieren wir einen Root View und binden diesen dann in eine Service Definition ein. Da wir hier kein Verhalten benötigen, sparen wir uns die Verhaltensdefinition und -implementierung.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Root Entity Performance'
define root view entity ZBS_R_DMOPerformance
  as select from ZBS_B_DMOPerformance
{
  key Identifier,
      ItemDescription,
      RandomDescription,
      Amount,
      Currency,
      BlobObject,
      NewDate,
      NewTime,
      UTCTimestamp
}

 

Darüber generieren wir uns dann zwei Service Bindings und geben eine OData API nach außen für die Protokolle OData v2 und OData v4.

 

ABAP Environment

Im nächsten Schritt müssen wir die Schnittstellen im ABAP Environment einbinden und entsprechende Objekte (Consumption Model) für die Konsumierung anlegen. In allen Beispielen verwenden wir den Destination Service in der BTP, um auf die Daten zuzugreifen. Die aktuelle Best Practice ist im Moment aber die Anlage eines Communication Arrangement und eines Communication Systems.

 

Funktionsbaustein

Über die ACO_PROXY ziehen wir uns die Definition des Funktionsbausteins, um die Metadaten dann einzuspielen. Wie du einen RFC Funktionsbaustein einbindest und aufrufst, erfährst du in diesem Artikel. Im Anschluss legen wir ein Consumption Model über die Metadaten ein und bauen den Aufruf auf.

DATA(destination) = cl_rfc_destination_provider=>create_by_cloud_destination( destinations-rfc ).

DATA(consumption_model) = NEW zcl_bs_demo_intf_func( destination = destination ).

consumption_model->z_bs_demo_get_performance_data( EXPORTING selection_fields = request_data-fields
                                                             skip             = request_data-skip
                                                             top              = request_data-top
                                                   IMPORTING performance_data = result-data_rfc ).

 

OData

Über den Service speichern wir uns für beide Protokolle die Metadaten mit dem Zusatz "$metadata". Wie du einen OData Service im ABAP Environment einbinden und konsumieren kannst, erfährst du in diesem Artikel. Im Anschluss implementieren wir den Aufruf für die beiden Arten von Aufrufen. Diese unterscheiden sich bei der Instanziierung des Objekts, sind aber sonst recht identisch.

DATA(destination) = cl_http_destination_provider=>create_by_cloud_destination( destinations-http ).
DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( destination ).

DATA(client_proxy) = /iwbep/cl_cp_factory_remote=>create_v2_remote_proxy(
    is_proxy_model_key       = VALUE #( repository_id       = 'DEFAULT'
                                        proxy_model_id      = 'ZBS_DEMO_INTF_O2'
                                        proxy_model_version = '0001' )
    io_http_client           = http_client
    iv_relative_service_root = '/sap/opu/odata/sap/ZBS_API_PERFROMANCE_O2/' ).

DATA(request) = client_proxy->create_resource_for_entity_set( 'PERFROMANCE' )->create_request_for_read( ).
request->set_top( request_data-top )->set_skip( request_data-skip ).
request->set_select_properties( request_data-fields ).

DATA(response) = request->execute( ).
response->get_business_data( IMPORTING et_business_data = result-data_o2 ).

 

Plain HTTP

Als letztes Szenario implementieren wir einen Plain HTTP Aufruf, das heißt wir verwenden nicht das Consumption Model und auch nicht die Gateway Komponenten im System.

DATA(destination) = cl_http_destination_provider=>create_by_cloud_destination( destinations-http ).
DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( destination ).

DATA(fields_for_select) = ``.
LOOP AT request_data-fields REFERENCE INTO DATA(field_name).
  IF fields_for_select <> ``.
    fields_for_select &&= `,`.
  ENDIF.
  fields_for_select &&= field_name->*.
ENDLOOP.

DATA(uri) = `/sap/opu/odata4/sap/zbs_api_perfromance_o4/srvd_a2x/sap/zbs_demo_perfromance_data/0001/Perfromance?`.
uri &&= |$select={ fields_for_select }&$top={ request_data-top }&$skip={ request_data-skip }|.

DATA(request) = http_client->get_http_request( ).
request->set_uri_path( i_uri_path = uri ).

DATA(response) = http_client->execute( if_web_http_client=>get ).
DATA(status) = response->get_status( ).

IF status-code = 200.
  /ui2/cl_json=>deserialize( EXPORTING json = response->get_text( )
                             CHANGING  data = json_payload ).

  result-http_payload = json_payload-value.
ENDIF.

 

Die Logik ist dabei länger, da wir uns um alles selbst kümmern müssen. Zuerst müssen wir die URI zusammenbauen und die Feldliste, sowie TOP und SKIP, befüllen. Im Anschluss mappen wir das Ergebnis auf unsere interne Struktur. Dabei verwenden wir den OData v4, da dieser im Standard JSON als Ergebnis liefert.

 

Test

In diesem Kapitel schauen wir uns die verschiedenen Testszenarien an und was wir testen wollen.

 

Testfälle

Bei unserem Test wollen wir uns vier Zugriffsmethoden anschauen, per RFC mit Consumption Model, OData v2 und OData v4 mit Consumption Model, sowie per Plain HTTP, bei dem wir die Anfrage erstellen und das Mapping durchführen. Dazu testen wir verschiedene gängige Szenarien zur Datenbeschaffung:

  1. Alle Felder und Lesen von 100 Einträge
  2. Alle Felder und Lesen von 5000 Einträgen
  3. Ohne BLOB (RandomDescription, BlobObject) und Lesen von 5000 Einträgen
  4. Ohne BLOB (RandomDescription, BlobObject), viele Ausführungen und Lesen von einem 200er Block (gleiche Datensätze)
  5. Ohne BLOB (RandomDescription, BlobObject), viele Ausführungen und Lesen von 200er Blöcken
  6. Wertehilfe (Schlüssel, Beschreibung) und Lesen von 500 Einträgen
  7. Wertehilfe (Schlüssel, Beschreibung), viele Ausführungen und Lesen von einem 20er Block
  8. Wertehilfe (Schlüssel, Beschreibung), viele Ausführungen und Lesen von 20er Blöcken

 

Annahme

Mit welcher Annahme würden wir nun in den Test gehen? Grundsätzlich würden wir sagen, dass RFC immer noch das schnellere Protokoll ist, da weniger Mapping, Aufbereitung und Checks bei der Ausführung durchgeführt werden müssen. Zum anderen sollte Plain HTTP schneller als OData sein, da eine große Schicht ABAP wegfällt. Beim Thema Performance zwischen OData v2 und OData v4 wird es spannend. Grundsätzlich wird OData v4 als JSON übertragen, was die Payload kleiner macht.

 

Durchführung

Wenn du in das Repository schaust, verwenden wir einen HTTP Endpunkt um die Logik zu testen. Damit haben wir die Möglichkeit per Principal Propagation und per Basic Authentication zu testen. Je nachdem wie du den Destination Service verwenden willst oder welche Verbindung du prüfen möchtest.

Die Payload haben wir in verschiedene Teiltest unterteilt, da wir immer wieder feststellen mussten, dass einzelne Läufe plötzlich weit außerhalb der anderen Läufe lagen. Das Verhalten hatten wir uns nicht im Detail angeschaut, dazu ein Beispiel:

 

Ergebnisse

Hier die zusammengefassten Testläufe. Für einen Durchschnitt wurden jeweils drei Läufe durchgeführt, sodass du auch die oben beschriebenen Abweichungen erkennen kannst. Scheinbar tritt das Phänomen bei vielen Aufrufen des HTTP Frameworks auf.

 

Testfälle 1 und 2:

 

Testfälle 3 und 4:

 

Testfälle 5 und 6:

 

Testfälle 7 und 8:

 

Auswertung

Welche Erkenntnisse nehmen wir aus dem Test und den Testergebnissen mit? Hier einmal eine kurze Zusammenfassung der Punkte aus dem Test:

  • RFC ist nach wie vor das einfachste und schnellste Protokoll, wenn es um das Lesen und Verschieben von Daten und Mengen geht.
  • Eine hohe Frequenz von HTTP Abfragen führt zu längeren Wartezeiten, hier am besten wenige Anfragen mit großen Datenmengen verwenden.
  • Plain HTTP Aufrufe sind beim Lesen und Parsen schneller als durch die OData Integration zu laufen.
  • Beim einmaligen Aufruf der Schnittstelle sind alle Technologien recht nah zusammen und können einfach genutzt werden.
  • Die Implementierung von HTTP und OData benötigt mehr Quellcode, um einfache Leseaktionen zu integrieren.

 

Zusammenfassung

In der Vergangenheit gab es für den Transfer von sehr großen Datenmengen die Dateiverarbeitung oder den direkten Aufruf von RFC Funktionsbausteinen, bei denen wir uns Teilmengen der Datensätze holen konnten. Die aktuelle Clean Core Strategie bewegt sich Richtung OData und SOAP Services. Die Empfehlung auf die klassischen Schnittstellen wie RFC oder IDoc zu vermeiden, sollte in Performance kritischen Situationen immer mit geprüft werden.

© SAP SE

 

Grundsätzlich ist RFC immer noch das schnellste Protokoll, wenn es um eine einfache Übertragung von Daten und Inhalten geht. Die Performance der OData Anbindung liegt vor allem in der Wrapper Schicht, die zwischen HTTP Framework und ABAP liegt und das Handling der Anfragen durchführt, aber auch zur Stabilität und Sicherheit beiträgt.

 

Vollständiges Beispiel

Die vollständigen Ressourcen findest du in verschiedenen GitHub Repositorys, einmal für den On-Premise und einmal für den ABAP Environment Teil. Die nicht gelisteten Hilfsklassen findest du in einem eigenen Repository, da wir sie immer wieder in unterschiedlichen Projekten verwenden.

 

Fazit

Mit diesem Test wollten wir dir das Thema Performance näherbringen und wie die aktuellen Technologien im Vergleich zum Klassiker aussehen. Dabei haben wir uns Events und SDA nicht angeschaut, um erst einmal bei den weit verbreiteten Techniken für direkten Zugriff zu bleiben.


Enthaltene Themen:
BTPABAP EnvironmentSchnittstellePerformance
Kommentare (0)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden Freitag neuen Content im Bereich ABAP und unregelmäßig in allen anderen Bereichen. Schaue bei unseren Tools und Apps vorbei, diese stellen wir kostenlos zur Verfügung.


RAP - Tree View (Löschverhalten)

Kategorie - ABAP

In diesem Artikel schauen wir uns das Verhalten beim Löschen von Knoten im Tree View mit RAP an. Dabei gibt es einige interessante Punkte zu beachten.

15.04.2025

BTP - User Anlage per API

Kategorie - ABAP

Wie kannst du eigentlich den Employee und den Business User per API im ABAP Environment anlegen? In diesem Artikel gehen wir auf die Details und die Implementierung ein.

11.04.2025

RAP - Tree View

Kategorie - ABAP

Du möchtest eine Hierarchie in RAP ganz einfach darstellen? Wie das in ABAP Cloud funktioniert, erfährst du in diesem Artikel.

08.04.2025

RAP - Classic Pattern

Kategorie - ABAP

In diesem Artikel schauen wir uns das Classic Pattern an und gehen auf die Anwendungsfälle der Implementierung in ABAP Cloud ein.

25.03.2025

BTP - Basic Authentication & Principal Propagation

Kategorie - ABAP

In diesem Artikel schauen wir uns die Anmeldemöglichkeiten vom ABAP Environment Richtung On-Premise an und wie sie Einfluss auf deine Entwicklung haben.

04.03.2025