Tools und Workflows zur Datenspeicherung in der Cloud, Teil 5 Metadaten-Index für einen Data Lake in AWS

Autor / Redakteur: Dipl. -Ing. Thomas Drilling / Stephan Augsten

Um Daten in einem S3-basierten Data Lake sinnvoll durchsuchen zu können, müssen diese im Moment des Hinzufügens indiziert werden. Mit serverlosen Lambda-Funktionen lässt sich so ein Vorgang automatisieren. Für den eigentlichen Index verwenden wir eine DynamoDB-Datenbank, weil diese hochverfügbar ist und Zugriffszeiten im unteren Millisekundenbereich ermöglicht.

Anbieter zum Thema

Über einen Stream erhält DynamoDB die Informationen über jede Änderung an den Datenelementen der Tabelle.
Über einen Stream erhält DynamoDB die Informationen über jede Änderung an den Datenelementen der Tabelle.
(Bild: Drilling / AWS Germany GmbH)

Für unseren Metadaten-Index benötigen wir zunächst eine DynamoDB-Tabelle.
Für unseren Metadaten-Index benötigen wir zunächst eine DynamoDB-Tabelle.
(Bild: Drilling / AWS Germany GmbH)

Zur Aufnahme unseres Index erstellen wir in der DynamoDB-Konsole eine neue DynamoDB-Tabelle mit dem Namen „Metadatenkatalog“, einem Primärschlüssel vom Typ Zeichenfolge (String) mit dem Namen „Erstellungsdatum“ und einem Sortierschlüssel vom Typ Zeichenfolge mit dem Namen „Objektschluessel“.

Anschließend klicken wir im „Übersicht“-Tab der neu erstellten Tabelle im Abschnitt „Stream Details“ auf „Stream verwalten“. Was hat es damit auf sich? Viele Anwendungen können zum Zeitpunkt einer Änderung von dieser Funktion „zum Erfassen der Änderungen an den in einer DynamoDB-Tabelle gespeicherten Elementen“ profitieren. Das Feature DynamoDB Streams erfasst eine zeitlich geordnete Abfolge von Änderungen auf Element-Ebene in jeder beliebigen DynamoDB-Tabelle und speichert diese Informationen bis zu 24 Stunden in einem Protokoll. Anwendungen können auf dieses Protokoll zugreifen und die Datenelemente vor und nach der Änderung nahezu in Echtzeit aufrufen.

Über einen Stream erhält DynamoDB die Informationen über jede Änderung an den Datenelementen der Tabelle.
Über einen Stream erhält DynamoDB die Informationen über jede Änderung an den Datenelementen der Tabelle.
(Bild: Drilling / AWS Germany GmbH)

Ein DynamoDB-Stream ist also ein strukturierter Informationsfluss zu/über Element-Änderungen in einer Amazon-DynamoDB-Tabelle. Aktiviert der Nutzer den Stream für eine bestehende Tabelle, werden von DynamoDB Informationen über jede Änderung an den Datenelementen in der Tabelle erfasst. Hierbei müssen wir uns für einen „Ansichtstyp“ entscheiden. Der Ansichtstyp steuert, welche Daten an den Stream gesendet werden. Wir konfigurieren ihn so, dass sowohl alte als auch neue Werte gesendet werden, wenn Daten aktualisiert werden.

Event-Logik mit Lambda

Sind die Vorbereitungen soweit abgeschlossen, könnte man beispielsweise zwei AWS-Lambda-Funktionen erstellen. Die Erste soll immer dann, wenn neue Datenobjekte im S3-Datenspeicher erstellt werden die zugehörigen Objektmetadaten in die eben erstelle DynamoDB-Tabelle schreiben. Sobald das erfolgt, könnte eine zweite Lambda-Funktion dafür sorgen, das Element im Amazon Elasticsearch Service zu veröffentlichen.

Zum Testen der Verarbeitungsketten mit Echtzeitdaten verwenden wir in unserem Beispiel ein vorbereitetes Twitterstorm-Dataset, dass wir in S3 bereitsellen. Ein kleines Python-Skript in einer EC2-Instanz erzeugt dann einen kontinuierlichen Datenstrom mit Test-Twitterposts. Die Business-Logik ist nicht in erster Linie Ziel dieses Beitrages. Dieser soll nur das Zusammenwirken der beteiligten AWS-Komponenten zeigen.

Stellen wir also nun eine AWS-Lambda-Funktion bereit, die ausgelöst wird, wenn Datenobjekte im Amazon S3-Bucket erstellt werden. Die Lambda-Funktion erfasst und veröffentlicht ein Element in der DynamoDB-Tabelle mit einer Reihe von Attributen, die sich auf die Metadaten beziehen. Wir erstellen die Lambda-Funktion in der Lambda-Konsole „from scratch“, also ohne Verwendung von Blueprints.

Im Beispiel verwenden wir als Runtime „Python 2.7“; die benötigte Rolle (hier „Lab1-LambdaExecutionRole“) haben wie zuvor erzeugt. Sie erlaubt der assoziierten Lambda-Funktion im Großen und Ganzen den Zugriff auf CloudWatch (Logs, Events, Alarme), S3, DynamoDB, Datapipeline, SNS, das Auflisten von Rollen in IAM.

Die von uns erstellte Lambda-Funktion will richtig konfiguriert sein.
Die von uns erstellte Lambda-Funktion will richtig konfiguriert sein.
(Bild: Drilling / AWS Germany GmbH)

Unsere in Python geschriebene Lambda-Funktion heißt im Beispiel “Lab1-S3ToDynamoDB.py. Der Code muss im Code-Editor eingefügt werden. Der Funktions-Name wird ohne die Endung „py“ eingetragen. Demnach ist in der Lambda-Console im Abschnitt „Function code“ bei „Runtime“ ebenfalls „Python 2.7“ auszuwählen und der „Handler“ hört auf dem Namen „lambda_function.lambda:handler“, wobei der Teil nach dem Punkt dem „Funktionsnamen“ unten entspricht.

Die Funktion führt im Wesentlichen folgende Schritte aus:

  • Ermitteln des Bucket-Namen und des Objekt-Keys für das erstelle Objekt, das die Lambda-Funktion ausgelöst hat.
  • Erstellen eines DynamoDB-Eintrages „über“ dieses S3-Objekt.
  • Eintragen des Items in die DynamoDB-Tabelle

Im Abschnitt „Grundlegende Einstellungen“ legen wir noch eine einfache „Beschreibung“ fest, z. B. „S3 to DynamoDB“. Beim Arbeitsspeicher und beim Timeout übernehmen wir der Einfachheit halber die Default-Einstellung. Fehlt nur noch der Trigger. Wir klicken zur Sicherheit aber zunächst noch rechts oben auf „Speichern“.

Als ersten Auslöser wählen wir das zu überwachende S3-Bucket.
Als ersten Auslöser wählen wir das zu überwachende S3-Bucket.
(Bild: Drilling / AWS Germany GmbH)

Im oberen Teil der Lambda-Console fügen wir nun den gewünschten Trigger, in diesem Fall „S3“ ein. Dessen Konfiguration erfolgt im Abschnitt „Auslöser konfigurieren“ gleich unterhalb des Navigationsbaums für „Auslöser hinzufügen“. Hier geben wir das zu überwachende S3-Bucket an und als Ereignistyp „Alles Objekterstellungsereignisse“. Danach klickt man unten rechts auf „Hinzufügen“, was gerne übersehen wird. Rechts der Trigger-Konfiguration sieht man übrigens die Ressourcen, auf die die Rolle Zugriff hat; diese entsprechen den oben erwähnten Richtlinien der unterliegenden Rolle

Nun benötigen wir noch eine zweite Lambda-Funktion (bei uns heißt sie „Lab-1-DynamodbToEs“, die für den entsprechenden Eintrag in einer bestehenden Eleastic-Search-Domain sorgt, sobald ein Eintrag in DynamoDB erstellt wurde. Diese setzt allerdings einen vorhandenen ElasticSearch-Cluster voraus, dessen Erstellen den Rahmen des Beitrages sprengen würde. Die Runtime ist erneut Python 2.7. Der Code hierfür ist zudem zu umfangreich für den Online-Editor in der grafischen Lambda-Console. Man kann ihn aber als ZIP-File hochladen. Als „Beschreibung“ verwenden wir „Lab 1 – DynamoDB to Elasticsearch“.

Als zweiter Auslöer dient die DynamoDB-Tabelle „Metadatenkatalog“.
Als zweiter Auslöer dient die DynamoDB-Tabelle „Metadatenkatalog“.
(Bild: Drilling / AWS Germany GmbH)

Als Trigger verwenden wir diesmal „DynamoDB“. Als Batch-Size nehmen wir „50“ Datensätze und als „Startposition“ „Neuste“. Um die Integration mit Lambda nicht ins Leere laufen zu lassen, haben wie den zugehörigen ES-Cluster zuvor aus einer AWS-Vorlage erstellt; auch der Code der zweiten Lambda-Funktion stammt aus einen Übungs-Fundus von AWS und soll hier nicht weiter thematisiert werden. Die Erweiterung demonstriert aber eindrucksvoll, wie man ergänzend zum DynamoDB-Index mit relativ einfachen Mitteln serverlos eine leistungsfähige Volltextsuche implementieren könnte. Wir behalten uns eine Erweiterung des Beispiels für einen künftigen Beitrag vor.

Testen der Funktionalität

Für einen Test der Funktionalität verwenden einen Twitter-Storm, der von einem Python-Script auf Basis von in einen S3-Bucket veröffentlichten Beispieldatensätzen generiert wird. Das Python-Script führen wir auf einer EC2-Instanz aus, um netzwerktechnisch Zugriff auf die beteiligten Ressourcen zu haben. Wir müssen unser Python-Skript dazu so konfigurieren, dass Daten in dem von uns bereitgestellten Kinesis-Firehose-Delivery-Stream veröffentlicht werden.

Die Testdatensätze bestehen aus Twitterstorm-Meldungen.
Die Testdatensätze bestehen aus Twitterstorm-Meldungen.
(Bild: Drilling / AWS Germany GmbH)

Dann starten wir unser Python-Script, das die Twitterstorm-Testdatensätze aus dem Testdatensatz-Bereitstellung-S3-Bucket auf unseren Delivery-Stream veröffentlicht:

python TwitterStorm.py start

Der Vorgang lässt sich mit ...

tail –f /tmp/TwitterStorm.log

... verfolgen, da unserer Skript so gestaltet ist, dass es dieser Log-Datei generiert.

Fassen wir noch einmal zusammen, was passiert:

  • das Skript auf der Amazon EC2 Instanz sendet permanent Daten zum Firehose Delivery Stream
  • Firehose sendet die Daten seinerseits zu S3
  • Amazon S3 triggert eine Lambda Funktion, die mit jedem neuen Upload eine Referenz in DynamoDB ablegt
  • DynamoDB triggert eine weitere Lambda-Funktion, die die Daten simultan in Elastis Search ablegt

In der Amazon-Kinesis-Konsole sehen wir den Datenfluss in Echtzeit.
In der Amazon-Kinesis-Konsole sehen wir den Datenfluss in Echtzeit.
(Bild: Drilling / AWS Germany GmbH)

Es dauert einige Minuten, bis die Daten durch das gesamte System fließen. Um die durch den Data Lake fließenden Daten zu verfolgen und um sicherzustellen, dass der Data Lake wie geplant funktioniert, wechseln wir in der Management Console zu „Kinesis“ und klicken in unserem Delivery-Stream „datastream“ auf „Monitoring“, um die Stream-Aktualisierungen zu beobachten. Die Daten sind auch in S3 sowie in der DynamoDB-Konsole sichtbar.

Das Durchsuchen des ES-Clusters funktioniert erst im realen Umfeld, nicht im Testszenario.
Das Durchsuchen des ES-Clusters funktioniert erst im realen Umfeld, nicht im Testszenario.
(Bild: Drilling / AWS Germany GmbH)

Für das Durchsuchen unseres ElasticSearch-Clusters bzw. der Elastic-Search-Domain müssten wir indes in einem der nächsten Teile noch eine API veröffentlichen und dann eine passendende Web-Anwendung zum Abrufen der Daten schreiben. Diese soll ebenfalls statisch auf S3 gehostet werden, um weiterhin ohne EC2-Server auszukommen. Der EC2-Maschine in diesem Beispiel diente ja nur als Basis für ein Python-Script, um die Testdaten zu generieren.

(ID:45685441)