1

Es gibt viele Lösungen ein kleines Wörterbuch serialisiert: json.loads/json.dumps, pickle, shelve, ujson oder sogar durch sqlite verwenden.Key: Wertspeicher in Python für möglicherweise 100 GB Daten, ohne Client/Server-

Aber wenn es sich um möglicherweise 100 GB Daten handelt, ist es nicht mehr möglich, solche Module zu verwenden, die möglicherweise die gesamten Daten beim Schließen/Serialisieren neu schreiben würden.

redis ist nicht wirklich eine Option, weil es ein Client/Server-Schema verwendet.

Frage: Welcher Schlüssel: Wertspeicher, serverless, in der Lage, mit mehr als 100 GB Daten zu arbeiten, werden häufig in Python verwendet?

Ich bin auf der Suche nach einer Lösung mit einem Standard "Pythonic" d[key] = value Syntax:

import mydb 
d = mydb.mydb('myfile.db') 
d['hello'] = 17   # able to use string or int or float as key 
d[183] = [12, 14, 24] # able to store lists as values (will probably internally jsonify it?) 
d.flush()    # easy to flush on disk 

Hinweis: BsdDB (BerkeleyDB) scheint veraltet zu sein. Es scheint eine LevelDB for Python zu sein, aber es scheint nicht gut bekannt - und ich haven't found eine Version, die bereit ist, unter Windows zu verwenden. Welche wären die häufigsten?

+3

SQLite sollte groß arbeiten. Hattest du Probleme damit? Es ist das DBMS, das klein ist, aber die DB selbst kann groß sein. Siehe https://stackoverflow.com/questions/14451624/will-sqlite-performance-degrade-if-the-database-size-is-greater-than-2-gigabytes – Himanshu

+0

@Himanshu Es ist die Tatsache, die Verwendung mit SQLite ist nicht so einfach wie 'db [key] = value' oder' db.put ('key', 'value') ', verwendet aber stattdessen SQL ... Und ich möchte INSERT in TABLE oder SELECT ... für ... vermeiden nur ein einfacher Schlüssel: value 'db [key] = value' set/get. – Basj

+0

Können Sie die Daten mehr beschreiben? 100 GB von was? Wie groß ist der kleinste/mittlere/größte Wert? Wie viele Schlüssel/Wert-Paare bilden die 100 GB? –

Antwort

4

können Sie sqlitedict verwenden, die SQLite-Datenbank Schlüssel-Wert-Schnittstelle zur Verfügung stellt.

SQLite limits page sagt, dass das theoretische Maximum 140 TB ist, abhängig von page_size und max_page_count. Die Standardwerte für Python 3.5.2-2ubuntu0 ~ 16.04.4 (sqlite3 2.6.0) sind jedoch page_size=1024 und max_page_count=1073741823. Dies ergibt ~ 1100 GB maximale Datenbankgröße, die Ihren Anforderungen entspricht.

können Sie das Paket wie verwenden:

from sqlitedict import SqliteDict 

mydict = SqliteDict('./my_db.sqlite', autocommit=True) 
mydict['some_key'] = any_picklable_object 
print(mydict['some_key']) 
for key, value in mydict.items(): 
    print(key, value) 
print(len(mydict)) 
mydict.close() 

aktualisieren

Über Speichernutzung. SQLite benötigt nicht, dass Ihre Datenmenge in den Arbeitsspeicher passt.Standardmäßig speichert es bis zu cache_size Seiten, die kaum 2MiB (das gleiche Python wie oben) ist. Hier ist das Skript, mit dem Sie es mit Ihren Daten überprüfen können. Vor dem Lauf:

pip install lipsum psutil matplotlib psrecord sqlitedict 

sqlitedct.py

#!/usr/bin/env python3 

import os 
import random 
from contextlib import closing 

import lipsum 
from sqlitedict import SqliteDict 

def main(): 
    with closing(SqliteDict('./my_db.sqlite', autocommit=True)) as d: 
     for _ in range(100000): 
      v = lipsum.generate_paragraphs(2)[0:random.randint(200, 1000)] 
      d[os.urandom(10)] = v 

if __name__ == '__main__': 
    main() 

Run es wie ./sqlitedct.py & psrecord --plot=plot.png --interval=0.1 $!. In meinem Fall erzeugt er dieses Diagramm: chart

und Datenbank-Datei:

$ du -h my_db.sqlite 
84M my_db.sqlite 
+0

Sehr schöne Benchmark, danke @saaj! Neugierig sein: Was macht 'mit Schließen (...) als ...: Tu es? – Basj

+0

Über die Y-Achse des CPU-Graphen zwischen 0 und 200%, durchschnittlich 150%, ist die y-Achse korrekt? – Basj

+0

@Basj 1) ['contextlib.closing'] (https://docs.python.org/3/library/contextlib.html#contextlib.closing). 2) Ich denke es ist, da 'sqlite3' einen eigenen Thread erstellt, der GIL freigibt, wenn er in' _sqlite3' binär arbeitet. Es wird also über 100%. – saaj

3

würde ich HDF5 dafür in Betracht ziehen. Es hat mehrere Vorteile:

  • Verwendbar von vielen Programmiersprachen.
  • Verwendbar von Python über das ausgezeichnete h5py Paket.
  • Kampf getestet, einschließlich mit großen Datensätzen.
  • Unterstützt String-Werte variabler Länge.
  • Werte sind adressierbar durch einen Dateisystem-ähnlichen "Pfad" (/foo/bar).
  • Werte können Arrays sein (und sind normalerweise), müssen aber nicht sein.
  • Optionale integrierte Komprimierung.
  • Optionales "Chunking" zum schrittweisen Schreiben von Chunks.
  • Es ist nicht erforderlich, den gesamten Datensatz gleichzeitig in den Speicher zu laden.

es hat einige Nachteile zu:

  • Extrem flexibel, bis zu dem Punkt macht es schwer, einen einzigen Ansatz zu definieren.
  • Kompliziertes Format, das nicht ohne die offizielle HDF5 C-Bibliothek verwendet werden kann (aber es gibt viele Wrapper, z. B. h5py).
  • Barocke C/C++ API (die Python ist nicht so).
  • Wenig Unterstützung für gleichzeitige Autoren (oder Writer + Leser). Schreibvorgänge müssen möglicherweise mit einer groben Granularität gesperrt werden.

Sie von HDF5 als eine Möglichkeit denken können Werte (Skalare oder N-dimensionalen Arrays) innerhalb einer Hierarchie innerhalb einer einzigen Datei (oder auch mehrere solcher Dateien) zu speichern.Das größte Problem beim bloßen Speichern Ihrer Werte in einer einzelnen Datei wäre, dass Sie einige Dateisysteme überlasten würden. Sie können sich HDF5 als Dateisystem innerhalb einer Datei vorstellen, die nicht herunterfällt, wenn Sie eine Million Werte in ein "Verzeichnis" schreiben.

+1

HDF5 ist keine Datenbank, es ist Serialisierungsformat. Es ist erforderlich, das ganze im Speicher zu laden. – amirouche

+0

Danke. Kannst du 3 oder 4 Zeilen Code einfügen, die zeigen, wie man es benutzt, wie in der (bearbeiteten) Frage? d. h. 'import ...', dann Erzeugung von DB, dann 'd [Schlüssel] = Wert ', dann lege es auf Platte. – Basj

+0

@amirouche: Offensichtlich ist HDF5 keine Datenbank. Die Frage hat nicht nach einer Datenbank gefragt. HDF5 erfordert nicht, dass alles in den Speicher geladen wird - Sie können Slices, "Hyperslabs", einzelne Arrays oder Attribute in eine hierarchische Datei laden usw. Es muss absolut nicht mehr geladen werden, als Sie in den Speicher laden möchten. Trotzdem liegen die Daten von OP in der Größenordnung von 100 GB, und 100 GB Hauptspeicher sind heute leicht auf Standard-Servern und sogar auf einigen Desktops zu finden. –

0

Erstens ist bsddb (oder unter seinem neuen Namen Oracle BerkeleyDB) nicht veraltet.

Aus Erfahrung LevelDB/RocksDB/bsddb sind langsamer als wiredtiger, deshalb empfehle ich wiredtiger.

Wiredtiger ist die Speicher-Engine für Mongodb und ist daher in der Produktion gut getestet. Es gibt wenig oder gar keinen Gebrauch von wiredtiger in Python außerhalb meines AjguDB-Projekts; Ich benutze wiredtiger (über AjguDB) um Wikidata und das Konzept zu speichern und abzufragen.

Hier ist eine Beispielklasse, die das Modul python2 shelve imitieren kann. Grundsätzlich es ist ein wiredtiger Backend-Wörterbuch, in dem Schlüssel nur Strings sein:

import json 

from wiredtiger import wiredtiger_open 


WT_NOT_FOUND = -31803 


class WTDict: 
    """Create a wiredtiger backed dictionary""" 

    def __init__(self, path, config='create'): 
     self._cnx = wiredtiger_open(path, config) 
     self._session = self._cnx.open_session() 
     # define key value table 
     self._session.create('table:keyvalue', 'key_format=S,value_format=S') 
     self._keyvalue = self._session.open_cursor('table:keyvalue') 

    def __enter__(self): 
     return self 

    def close(self): 
     self._cnx.close() 

    def __exit__(self, *args, **kwargs): 
     self.close() 

    def _loads(self, value): 
     return json.loads(value) 

    def _dumps(self, value): 
     return json.dumps(value) 

    def __getitem__(self, key): 
     self._session.begin_transaction() 
     self._keyvalue.set_key(key) 
     if self._keyvalue.search() == WT_NOT_FOUND: 
      raise KeyError() 
     out = self._loads(self._keyvalue.get_value()) 
     self._session.commit_transaction() 
     return out 

    def __setitem__(self, key, value): 
     self._session.begin_transaction() 
     self._keyvalue.set_key(key) 
     self._keyvalue.set_value(self._dumps(value)) 
     self._keyvalue.insert() 
     self._session.commit_transaction() 

Hier wird das angepasste Testprogramm aus @saaj Antwort:

#!/usr/bin/env python3 

import os 
import random 

import lipsum 
from wtdict import WTDict 


def main(): 
    with WTDict('wt') as wt: 
     for _ in range(100000): 
      v = lipsum.generate_paragraphs(2)[0:random.randint(200, 1000)] 
      wt[os.urandom(10)] = v 

if __name__ == '__main__': 
    main() 

Mit der folgenden Befehlszeile:

python test-wtdict.py & psrecord --plot=plot.png --interval=0.1 $! 

Ich habe das folgende Diagramm erstellt:

wt performance without wal

$ du -h wt 
60M wt 

Wenn Write-Ahead-Protokoll aktiv ist:

wt performance with wal

$ du -h wt 
260M wt 

Dies ist ohne Performance tunning und Kompression.

Wiredtiger hat keine bekannte Grenze bis vor kurzem wurde die Dokumentation der folgenden aktualisiert:

WiredTiger unterstützt Petabyte Tabellen, zeichnet bis zu 4 GB und Rekordzahlen von bis zu 64 Bit.

http://source.wiredtiger.com/1.6.4/architecture.html

+0

Danke. Kannst du ein Beispiel für Code mit 'wiredtiger' geben? Ist es möglich, es mit einer einfachen API wie 'import wiredtiger' zu verwenden?' Wt = wiredtiger.wiredtiger ('myfile.db') '' wt ['hallo'] = 17'' wt [183] ​​= [12, 14, 24] '' wt.flush() 'dh die Hauptanforderungen sind: 1)' wt [key] = value' Syntax 2) in der Lage, string oder int oder float als Schlüssel zu verwenden 3) in der Lage, Listen als Werte zu speichern 4) einfach auf Festplatte splittern – Basj

+0

Ist es für Python 2 oder Python 3? – amirouche

+0

Ich benutze Python 2 @amirouche. – Basj