Der OP schrieb doch etwas von Filtern, so dass nicht unbedingt jeder Datensazt in der Datenbank landen muss...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.
Datenbank für 9GB Text
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
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?
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?
@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: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.
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;
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.
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.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
Alternativvorschlag: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
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()
Natürlich hast du nur ein Beispiel gegeben. Ohne die tatsächlich verwendeten Daten weiß man natürlich nicht, ob sowas hilfreich ist.
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.
@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
@jrech: klingt cool, werde ich noch testen.
Leider hat mein gestriger Versuch mit multiprocessing.pool() keinen Erfolg gebracht.
Danke,
rman
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).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.
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).
Hier noch eine Variante mit einem regulären Ausdruck:rman hat geschrieben:@snafu: danke für den Tipp, habe es gerade implementiert. Hat die Einlesezeit um 15% verkürzt.
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(',')
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...
@jerch: Naja, aber dann ist man doch schon direkt bei C-Programmierung. Wozu dann überhaupt Python. 
