有很多解决方案可以序列化一个小字典:json.loads
/ json.dumps
,pickle
,shelve
,ujson
,甚至可以使用{{1 }}
但是当处理可能100 GB的数据时,再也不可能使用这样的模块,这些模块在关闭/序列化时可能会重写整个数据。
sqlite
实际上不是一个选项,因为它使用客户端/服务器方案。
问题:哪些密钥:值存储,无服务器,能够使用100多GB的数据,在Python中经常使用?
我正在寻找使用标准“Pythonic”redis
语法的解决方案:
d[key] = value
注意:BsdDB(BerkeleyDB)似乎已被弃用。似乎有一个LevelDB for Python,但它似乎并不为人所知 - 我haven't found是一个可以在Windows上使用的版本。哪些是最常见的?
答案 0 :(得分:9)
您可以使用sqlitedict为SQLite数据库提供键值接口。
SQLite limits page表示理论最大值为140 TB,具体取决于page_size
和max_page_count
。但是,Python 3.5.2-2ubuntu0~16.04.4(sqlite3
2.6.0)的默认值为page_size=1024
和max_page_count=1073741823
。这提供了大约1100 GB的最大数据库大小,符合您的要求。
您可以使用以下软件包:
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()
关于内存使用情况。 SQLite不需要您的数据集适合RAM。默认情况下,它会缓存最多cache_size
个页面,这几乎不是2MiB(与上面相同的Python)。这是您可以用来检查数据的脚本。跑步前:
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()
像./sqlitedct.py & psrecord --plot=plot.png --interval=0.1 $!
一样运行它。在我的情况下,它生成此图表:
和数据库文件:
$ du -h my_db.sqlite
84M my_db.sqlite
答案 1 :(得分:4)
我会考虑HDF5。它有几个优点:
/foo/bar
)寻址。它也有一些缺点:
h5py
)。您可以将HDF5视为存储值(标量或N维)的一种方式 数组)在单个文件内的层次结构内(或实际上是多个这样的文件)。将值存储在单个磁盘文件中的最大问题是,您将压倒一些文件系统;您可以将HDF5视为文件中的文件系统,当您将一百万个值放在一个“目录”中时,该文件系统不会丢失。
答案 2 :(得分:3)
标准库中的shelve模块可以做到这一点:
import shelve
with shelve.open('myfile.db') as d:
d['hello'] = 17 # Auto serializes any Python object with pickle
d[str(183)] = [12, 14, 24] # Keys, however, must be strings
d.sync() # Explicitly write to disc (automatically performed on close)
这使用python dbm模块从磁盘保存和加载数据,而不加载整个对象。
dbm示例:
import dbm, json
with dbm.open('myfile2.db', 'c') as d:
d['hello'] = str(17)
d[str(183)] = json.dumps([12, 14, 24])
d.sync()
但是,搁置时有两个注意事项:
pickle
进行序列化。这意味着数据与Python耦合在一起,可能还有用于保存数据的python版本。如果需要解决的话,可以直接使用dbm
模块(相同的接口,但是只能将字符串用作键/值)。因此,以下第三方选项copied from here将是不错的选择:
答案 3 :(得分:2)
我知道这是一个老问题,但是很久以前我写了这样的东西:
https://github.com/dagnelies/pysos
它像普通的python dict
一样工作,但具有的优势是,它在Windows上比shelve
效率高得多,并且它也是跨平台的,这与shelve
数据存储不同基于操作系统。
要安装:
pip install pysos
用法:
import pysos
db = pysos.Dict('somefile')
db['hello'] = 'persistence!'
编辑:性能
仅给出一个大致数字,这是一个迷你基准测试(在Windows笔记本电脑上):
import pysos
t = time.time()
import time
N = 100 * 1000
db = pysos.Dict("test.db")
for i in range(N):
db["key_" + str(i)] = {"some": "object_" + str(i)}
db.close()
print('PYSOS time:', time.time() - t)
# => PYSOS time: 3.424309253692627
生成的文件约为3.5 Mb。 ...因此,大致来说,您每秒可以插入1 mb的数据。
编辑:工作原理
每次设置值时都会写入,但仅键/值对会写入。因此,添加/更新/删除项目的成本始终是相同的,尽管仅添加是“更好的”,因为大量更新/删除会导致文件中的数据碎片(浪费的垃圾字节)。内存中保留的是映射(密钥->文件中的位置),因此您只需要确保有足够的RAM来存储所有这些密钥。强烈建议使用SSD。 100 MB轻松又快速。像最初发布的100 GB会很大,但是可行。即使是原始的100 GB读/写也要花费一些时间。
答案 4 :(得分:2)
LMDB(闪电内存映射数据库)是一种非常快速的键值存储,它具有Python bindings,可以轻松处理巨大的数据库文件。
还有lmdbm包装器,它提供Pythonic d[key] = value
语法。
默认情况下,它仅支持字节值,但可以轻松扩展以将序列化器(json,msgpack,pickle)用于其他类型的值。
import json
from lmdbm import Lmdb
class JsonLmdb(Lmdb):
def _pre_key(self, value):
return value.encode("utf-8")
def _post_key(self, value):
return value.decode("utf-8")
def _pre_value(self, value):
return json.dumps(value).encode("utf-8")
def _post_value(self, value):
return json.loads(value.decode("utf-8"))
with JsonLmdb.open("test.db", "c") as db:
db["key"] = {"some": "object"}
obj = db["key"]
print(obj["some"]) # prints "object"
一些基准。 lmdbm和sqlitedict使用了分批插入的插件(每个插件1000个)。对于非分批插入,写入性能会受到很大影响,因为默认情况下每个插入都会打开一个新事务。 dbm指的是stdlib dbm.dumb。在Win 7,Python 3.8,SSD上进行了测试。
以秒为单位的连续写入
| items | lmdbm | pysos |sqlitedict| dbm |
|------:|------:|------:|---------:|--------:|
| 10| 0.0000| 0.0000| 0.01600| 0.01600|
| 100| 0.0000| 0.0000| 0.01600| 0.09300|
| 1000| 0.0320| 0.0460| 0.21900| 0.84200|
| 10000| 0.1560| 2.6210| 2.09100| 8.42400|
| 100000| 1.5130| 4.9140| 20.71700| 86.86200|
|1000000|18.1430|48.0950| 208.88600|878.16000|
几秒钟内随机读取
| items | lmdbm | pysos |sqlitedict| dbm |
|------:|------:|------:|---------:|-------:|
| 10| 0.0000| 0.000| 0.0000| 0.0000|
| 100| 0.0000| 0.000| 0.0630| 0.0150|
| 1000| 0.0150| 0.016| 0.4990| 0.1720|
| 10000| 0.1720| 0.250| 4.2430| 1.7470|
| 100000| 1.7470| 3.588| 49.3120| 18.4240|
|1000000|17.8150| 38.454| 516.3170|196.8730|
有关基准脚本,请参见https://github.com/Dobatymo/lmdb-python-dbm/blob/master/benchmark.py
答案 5 :(得分:1)
首先,不推荐使用bsddb(或其新名称Oracle BerkeleyDB)。
根据经验,LevelDB / RocksDB / bsddb比wiredtiger慢,这就是我推荐有线网格的原因。
wiredtiger是mongodb的存储引擎,因此它在生产中得到了很好的测试。在AjguDB项目之外的Python中很少或根本没有使用wiretiger;我使用wiredtiger(通过AjguDB)来存储和查询大约80GB的wikidata和概念。
这是一个允许模仿python2 shelve模块的示例类。基本上, 它是一个wiretiger后端字典,其中键只能是字符串:
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()
来自@saaj的改编测试程序回答:
#!/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()
使用以下命令行:
python test-wtdict.py & psrecord --plot=plot.png --interval=0.1 $!
我生成了以下图表:
$ du -h wt
60M wt
当预写日志激活时:
$ du -h wt
260M wt
这没有性能调整和压缩。
Wiredtiger直到最近还没有已知的限制,文档已更新为以下内容:
WiredTiger支持PB级表,最大4GB记录,最多64位记录。
答案 6 :(得分:0)
另一个值得一看的解决方案是DiskCache's Index (API docs)。它是原子、线程和进程安全的,并且具有事务(请参阅功能比较 here)。