Il existe de nombreuses solutions pour sérialiser un petit dictionnaire: json.loads
/json.dumps
, pickle
, shelve
, ujson
, ou même en utilisant sqlite
.
Mais lorsqu'il s'agit éventuellement de 100 Go de données, il n'est plus possible d'utiliser de tels modules qui pourraient éventuellement réécrire l'ensemble des données lors de la fermeture/sérialisation.
redis
n'est pas vraiment une option car il utilise un schéma client/serveur.
Question: Quelle clé: magasin de valeurs, sans serveur, capable de travailler avec plus de 100 Go de données, sont fréquemment utilisés en Python?
Je recherche une solution avec un standard "Pythonic" d[key] = value
syntaxe:
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
Remarque: BsdDB (BerkeleyDB) semble obsolète. Il semble y avoir un LevelDB pour Python , mais il ne semble pas bien connu - et je je n'ai pas trouvé une version prête à l'emploi sur Windows. Lesquels seraient les plus courants?
Vous pouvez utiliser sqlitedict qui fournit une interface de valeur-clé à la base de données SQLite.
SQLite page limites indique que le maximum théorique est de 140 TB selon page_size
et max_page_count
. Cependant, les valeurs par défaut pour Python 3.5.2-2ubuntu0 ~ 16.04.4 (sqlite3
2.6.0), sont page_size=1024
et max_page_count=1073741823
. Cela donne ~ 1100 Go de taille de base de données maximale qui correspond à vos besoins.
Vous pouvez utiliser le package comme:
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()
À propos de l'utilisation de la mémoire. SQLite n'a pas besoin de votre jeu de données pour tenir dans la RAM. Par défaut, il met en cache jusqu'à cache_size
pages, qui fait à peine 2 Mo (le même Python comme ci-dessus). Voici le script que vous pouvez utiliser pour le vérifier avec vos données. Avant de lancer:
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()
Exécutez-le comme ./sqlitedct.py & psrecord --plot=plot.png --interval=0.1 $!
. Dans mon cas, il produit ce graphique:
Et fichier de base de données:
$ du -h my_db.sqlite
84M my_db.sqlite
Je considérerais HDF5 pour cela. Il présente plusieurs avantages:
/foo/bar
).Il présente également certains inconvénients:
h5py
).Vous pouvez considérer HDF5 comme un moyen de stocker des valeurs (scalaires ou tableaux à N dimensions) dans une hiérarchie à l'intérieur d'un seul fichier (ou même de plusieurs de ces fichiers). Le plus gros problème avec simplement le stockage de vos valeurs dans un seul fichier disque serait que vous submergez certains systèmes de fichiers; vous pouvez considérer HDF5 comme un système de fichiers dans un fichier qui ne tombera pas lorsque vous mettez un million de valeurs dans un "répertoire".
Tout d'abord, bsddb (ou sous son nouveau nom Oracle BerkeleyDB) n'est pas obsolète.
Par expérience, LevelDB/RocksDB/bsddb sont plus lents que wiredtiger , c'est pourquoi je recommande wiredtiger.
wiredtiger est le moteur de stockage de mongodb, il est donc bien testé en production. Il y a peu ou pas d'utilisation de wiredtiger dans Python en dehors de mon projet AjguDB; j'utilise wiredtiger (via AjguDB) pour stocker et interroger les wikidata et le concept d'environ 80 Go.
Voici un exemple de classe qui permet d'imiter le module python2 shelve . Fondamentalement, c'est un dictionnaire backend wiredtiger où les clés ne peuvent être que des chaînes:
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()
Voici le programme de test adapté de @saaj answer:
#!/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()
En utilisant la ligne de commande suivante:
python test-wtdict.py & psrecord --plot=plot.png --interval=0.1 $!
J'ai généré le diagramme suivant:
$ du -h wt
60M wt
Lorsque le journal d'écriture anticipée est actif:
$ du -h wt
260M wt
Ceci sans réglage ni compression des performances.
Wiredtiger n'a pas de limite connue jusqu'à récemment, la documentation a été mise à jour comme suit:
WiredTiger prend en charge les tables de pétaoctets, enregistre jusqu'à 4 Go et enregistre des nombres jusqu'à 64 bits.