Datenbank für 9GB Text

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Sirius3 hat geschrieben: Warum die Daten in der Datenbank plötzlich nur noch 9GB sind, versteh ich nicht, weil Datenbanken sind nicht für ihre Speichersparsamkeit bekannt.
Der OP schrieb doch etwas von Filtern, so dass nicht unbedingt jeder Datensazt in der Datenbank landen muss...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
snafu
User
Beiträge: 6831
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wie wäre es mit einem kompletten Überführen der CSV-Daten in die DB per CSV-Import und anschließendem Filtern mittels SQL-Statements? Selbst wenn am Ende 2/3 der Daten weggeworfen werden, könnte man ja trotzdem mal ausprobieren, ob das schneller ist als die Verarbeitung mit Python-Befehlen. Weil mindestens ein Anfassen jeder Textzeile aus der CSV-Datei dürfte ja sowieso nötig sein, wenn man das sauber lösen möchte. Warum dann nicht auch direkt Datensätze in der DB daraus erzeugen?

Natürlich hätte man hierbei eine riesige zusätzliche Datenmenge als Zwischenergebnis. Ob das an die Grenzen des zur Verfügung stehenden Festplattenspeichers stößt, oder ob die Datenbank dann anderweitig überlastet wird, kann ich nicht beurteilen. Das ließe sich aber leicht herausfinden durch Ausprobieren (und der nötigen Geduld).

Die zusätzliche Angabe eines Filter-Kriteriums beim datenbankseitigen CSV-Import ist nicht möglich, oder?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@snafu:
75GB ist halt einfach mal viel, die zu bewegen kostet selbst auf schnellen SSDs spürbar Zeit. Wenn diese 75GB komplett in die DB sollen, werden je nach Tabellenlayout/Feldern da schnell auch mal 100GB+ draus. Ich würde hier versuchen, den Platten-IO so gering wie möglich zu halten, d.h. soviel wie möglich im Speicher zu entscheiden und so wenig wie möglich zu lesen/schreiben. Auf DB-Ebene ginge das u.a. mit einer stored procedure beim CSV-Import, kA ob sqlite sowas kann - falls nicht, wird der IO-Aufwand absurd - 75GB laden --> 100GB+ in Tabellen schreiben --> Dubletten/unerwünschte Daten per SQL filtern und löschen (verwirft 90% der vorher geschriebenen Daten wieder).

Falls die DB selbst zum Knackpunkt wird (scheint ja nicht der Fall zu sein), könnte man Sirius' Idee von weiter oben nochmal verfolgen. rman scheint ja primär an ASCII-Dateien der Daten interessiert zu sein und die DB-Logik nur für die Insert/Update-Entscheidungen zu nutzen. Evtl. kann man das auf Dateisystemebene schneller erledigen und den DB-Ansatz verwerfen.

Edit:
Mit sqlite kann man zumindest mit Triggern das gewünschte Verhalten erreichen:

Code: Alles auswählen

CREATE TABLE test
(
  id INTEGER PRIMARY KEY,
  uid char(10)
);

-- example insert before trigger
-- test condition is a simple NULL check
CREATE TRIGGER before_insert_test BEFORE INSERT ON test FOR EACH ROW
BEGIN
    SELECT CASE
    WHEN NEW.uid is NULL -- complicated test goes here
    THEN RAISE(IGNORE)
    END;
END;

-- couple of inserts (load data/import from file)
INSERT INTO test (uid) VALUES ('before');
INSERT INTO test (uid) VALUES (NULL);
INSERT INTO test (uid) VALUES ('after');

-- show result
SELECT * FROM test;
Für die Updates würde es analog funktionieren. Die Daten müssten wahrscheinlich temporär in eine andere Tabelle geschrieben werden, um für die Testauswertung nicht den Index zu verlieren/erneuern zu müssen.
Benutzeravatar
snafu
User
Beiträge: 6831
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Bliebe nur die Unsicherheit, ob das auch mit einem direkten CSV-Import seitens der Datenbank funktioniert. Ansonsten müsste man das Lesen und Splitten der CSV-Daten ja immer noch in einer Python-Schleife machen. Lediglich das Filtern würde eingespart werden.
Benutzeravatar
sparrow
User
Beiträge: 4504
Registriert: Freitag 17. April 2009, 10:28

Vielleicht ist es sinnvoll, wenn der Threadsteller seine komplette Problemstellung offenbart. Wenn es bessere Möglichkeiten der Lösung statt eines relationalen DBMS gibt, stecken wir in einer Sackgasse ;)
Benutzeravatar
snafu
User
Beiträge: 6831
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

sparrow hat geschrieben:Vielleicht ist es sinnvoll, wenn der Threadsteller seine komplette Problemstellung offenbart. Wenn es bessere Möglichkeiten der Lösung statt eines relationalen DBMS gibt, stecken wir in einer Sackgasse ;)
Das auf jeden Fall. Es wurde ja schon angemerkt, dass als Alternative ein Diff der CSV-Rohdaten genommen werden könnte, um dann die Datenbank-Updates nur noch mit den aus dem Diff ermittelten Unterschieden machen zu müssen, falls ich das richtig verstanden habe.
Benutzeravatar
snafu
User
Beiträge: 6831
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

rman hat geschrieben:

Code: Alles auswählen

with open(fileName,'rb') as csvfile:
    for line in csvfile:
        row = line.strip().split(',')    # hier geht meiste Zeit drauf
        if not row[5].startswith(uid_range):  # test ob uid in erlaubter range
            continue
        dataList.append(row)
        totallines += 1
        if not totallines%100:
            cursor.executemany('INSERT INTO tableName VALUES (%s)')%dataList
Alternativvorschlag:

Code: Alles auswählen

def get_rows_in_range(csvfile, uid_range):
    for line in csvfile:
        if line.split(",", 5)[-1].startswith(uid_range):
            yield line.strip()
Soll heißen: Das Splitten kannst du abkürzen, indem du als zweiten Parameter angibst, wieviele Teilstücke maximal ausgeliefert werden sollen. Die Methode splittet dann bei Erreichen dieser Maximalangabe einfach nicht mehr weiter und gibt somit das letzte Stück mit dem kompletten restlichen Text zurück. Da du ja eh mit ``.startswith()`` testet, kannst du dann einfach das letzte Stück benutzen und dann, falls nötig, nochmal einen kompletten Split machen.

Natürlich hast du nur ein Beispiel gegeben. Ohne die tatsächlich verwendeten Daten weiß man natürlich nicht, ob sowas hilfreich ist.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Wenn die Einträge in den Dateien formatstabil sind, könnte man eine Kombination aus Hash-Tabellen mit Dateisystemablage nutzen. Unklar ist halt, ob die auftretenden Kollisionen ein Problem darstellen würden. Denkbar ist hier vieles, ohne nähere Angaben über die Daten oder einen Bsp-datensatz bleibt das Spekulatius.
rman
User
Beiträge: 8
Registriert: Freitag 30. Januar 2015, 16:17

@snafu: danke für den Tipp, habe es gerade implementiert. Hat die Einlesezeit um 15% verkürzt.
@jrech: klingt cool, werde ich noch testen.

Leider hat mein gestriger Versuch mit multiprocessing.pool() keinen Erfolg gebracht.

Danke,
rman
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

snafu hat geschrieben:Bliebe nur die Unsicherheit, ob das auch mit einem direkten CSV-Import seitens der Datenbank funktioniert. Ansonsten müsste man das Lesen und Splitten der CSV-Daten ja immer noch in einer Python-Schleife machen. Lediglich das Filtern würde eingespart werden.
In der Tat geht das nicht mehr mit der .import Funktion von sqlite3 (konnte ich gestern nicht testen). Die Entwickler von sqlite geben auch einen Grund an, warum komplexere Funktionen fehlen - sqlite ist als single access drop in für Applikationen gedacht, komplexere Aufgaben liegen in der Zuständigkeit der Applikationslogik (Trigger sind da schon ein Zugeständnis). Die üblichen verdächtigen RDBMSe bringen diese halt mit, um parallele Zugriffe standardisiert zu ermöglichen (MySQL z.B. aktiviert insert-Trigger für csv-Importe).
Im vorliegenden Fall kommt man also mit sqlite nicht ums Parsen der CSV-Daten herum. Abhilfe würde ein anderes RDMBS schaffen, mit sqlite müsste man die Geschäftslogik verschlanken/beschleunigen (z.B. kleiner CSV-Importer in C geschrieben).
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

jerch hat geschrieben:z.B. kleiner CSV-Importer in C geschrieben
So wie das Modul aus der Standardbibliothek?
Das Leben ist wie ein Tennisball.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@EyDu:
Nicht ganz, eben ohne Pythoninterpreter als "Kleber" zwischen csv und sqlite.
Benutzeravatar
snafu
User
Beiträge: 6831
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

rman hat geschrieben:@snafu: danke für den Tipp, habe es gerade implementiert. Hat die Einlesezeit um 15% verkürzt.
Hier noch eine Variante mit einem regulären Ausdruck:

Code: Alles auswählen

import re

def get_rows_in_range(csvfile, uid_range):
    uid_range = str(uid_range)
    match_pattern = re.compile('(.*?,){5}').match
    for line in csvfile:
        uid = match_pattern(line).group(1)
        if uid.startswith(uid_range):
            yield line.strip().split(',')
Ich habe aber schon oft die Erfahrung gemacht, das die Nutzung von Python-Builtins bei String-Operationen schneller ist als die Regex-Engine. Müsstest du mal ausprobieren, ob das die ``.split(',', 5)``-Variante noch schlagen kann.

Die Zahl in der geschweiften Klammer beim Pattern ist (zumindest in diesem Kontext) die Angabe der Spalte (Zählung beginnt bei 1). Das spätere ``.group(1)`` hat nichts damit zu tun, sondern das bezieht sich auf den regulären Ausdruck. Zu beachten ist, dass ich das Komma als Spaltentrenner mit ins Ergebnis genommen habe. Da ja eh mit ``.startswith()`` gearbeitet wird, wollte ich den regulären Ausdruck möglichst einfach halten und habe deshalb keine Sonderbehandlung fürs fünfte Komma mit eingebaut.

Ich bin gespannt, was deine Messungen ergeben...
BlackJack

@jerch: Naja, aber dann ist man doch schon direkt bei C-Programmierung. Wozu dann überhaupt Python. :-)
Antworten