diff --git a/Projektarbeit-3/Umsetzung.md b/Projektarbeit-3/Umsetzung.md index 74b591e..49b6faf 100644 --- a/Projektarbeit-3/Umsetzung.md +++ b/Projektarbeit-3/Umsetzung.md @@ -1,6 +1,8 @@ # Backend ## Datenbank +Die Entscheidung welche Datenbank benutzt werden sollte war relativ einfach, sie sollte vor allem leichtgewichtig sein, schnell und einfach zu bedienen. Aufgrund der hervorragenden Integration von SQLite in Python haben wir uns auch für SQLite entschieden. Sie bietet genug Datentypen, um alle unsere Anforderungen abzudecken. + Zunächst war es wichtig, zu erkennen, welche Tabellen nötig waren. Hier sind zunächst die Tabellen zu nennen, die die reine Datenhaltung der OpenApi Data abbilden. #### usage_stats ```sql @@ -51,7 +53,53 @@ CREATE TABLE IF NOT EXISTS read_files(file_path TEXT, etag TEXT PRIMARY KEY) Diese war notwendig um bei einem Abbruch der Initialisierung eine Wiederaufnahme des Prozesses zu ermöglichen. Es wird hierzu der Dateiname und der Dateihash (Checksumme) gespeichert. ## Datenaufbereitung (Praxis) -* Wie wurden die Parser geschrieben +#### Generelles Parserkonzept +Für jede der ca. 250 "usage-stats"-Dateien wird folgender Ablauf durchgeführt. +```mermaid +graph LR + DATA[Open Data] --> DOWNL[Herunterladen] + DOWNL --> PARSE[CSV parsen] + PARSE --> TRANS[Zeilen transformieren] + TRANS --> TYPING[Datentypen anpassen] + TYPING --> INSERT[in Datenbank schreiben] +``` + +Der Vorgang geschieht komplett im Arbeitsspeicher, was die maximale Lesegeschwindigkeit ermöglicht. Der Import dauert von 0 bis 100 % etwa 60 Minuten und verbraucht 6 Gigabyte Festplattenspeicher, sowie Netzwerktraffic. + +Der wichtigste Teil, der Importer der "usage-stats", ist hier Ausschnittsweise abgebildet. +```python +mapped = [] +for entry in entries: + try: + mapped.append(( + # Rental Id + int(entry[0]), + # Duration oder Duration_Seconds + int(entry[1] or "-1"), + # Bike Id + int(entry[2] or "-1"), + # End Date + int(datetime.strptime(entry[3][:16], "%d/%m/%Y %H:%M").timestamp()) if entry[3] else -1, + # EndStation Id + int(entry[4] or "-1"), + # EndStation Name + entry[5].strip(), + # Start Date + int(datetime.strptime(entry[6][:16], "%d/%m/%Y %H:%M").timestamp()) if entry[6] else -1, + # StartStation Id + int(entry[7]), + # StartStation Name + entry[8].strip() + )) + except ValueError as e: + LOG.error(f"Value Error {e} on line {entry}") + return + except KeyError as e: + LOG.error(f"Key Error {e} on line {entry}") + return +``` +Obiges zeigt die Transformation, sowie die Datentypanpassung eines Eintrags. Es fällt sofort die konvertierung der Datumsangaben auf, sowie die "-1" Defaultwerte der Zahlenspalten. Ebenso kann ein Value Error auftreten, wenn das Datum nicht geparsed werden kann. Sollte dies passieren, wird der komplette Datensatz ignoriert. Gleiches gilt für eine Datei mit weniger Spalten wie erwartet (KeyError). + ## Performance * 60 Mio Datensätze