web-dev-qa-db-ja.com

キー:値ストアPythonおそらく100 GBのデータ、クライアント/サーバーなし)

小さな辞書をシリアル化する多くの解決策があります:json.loads/json.dumpspickleshelveujson、またはsqlite

しかし、おそらく100 GBのデータを処理する場合、閉じる/シリアル化するときにデータ全体を書き換える可能性のあるモジュールを使用することはできなくなります。

redisはクライアント/サーバースキームを使用するため、実際にはオプションではありません。

質問:Pythonで頻繁に使用されるキー:値ストア、サーバーレス、100 GB以上のデータを処理できるものはどれですか?

標準の「Pythonic」d[key] = value構文のソリューションを探しています。

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 

注: BsdDB (BerkeleyDB)は推奨されないようです。 PythonのLevelDB があるようですが、それはよく知られていないようです-そして 見つかりません Windowsで使用する準備ができているバージョンです。最も一般的なものはどれですか?

9
Basj

SQLiteデータベースへのキーと値のインターフェイスを提供する sqlitedict を使用できます。

SQLite 制限ページ は、理論上の最大値が140 TB page_sizemax_page_countによって異なる]と述べています。ただし、Python 3.5.2-2ubuntu0〜16.04.4(sqlite3 2.6.0)はpage_size=1024max_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 $!のように実行します。私の場合、それはこのチャートを生成します: chart

そしてデータベースファイル:

$ du -h my_db.sqlite 
84M my_db.sqlite
14
saaj

HDF5 を検討します。これにはいくつかの利点があります。

  • 多くのプログラミング言語で使用できます。
  • Pythonから優れた h5py パッケージを介して使用できます。
  • 大規模なデータセットを含む、戦闘テスト済み。
  • 可変長文字列値をサポートします。
  • 値は、ファイルシステムのような「パス」(/foo/bar)でアドレス指定できます。
  • 値は配列(通常は配列)にすることができますが、そうである必要はありません。
  • オプションの組み込み圧縮。
  • チャンクを段階的に書き込むことができるオプションの「チャンキング」。
  • データセット全体を一度にメモリにロードする必要はありません。

また、いくつかの欠点もあります。

  • 非常に柔軟で、単一のアプローチを定義するのが困難になるほどで​​す。
  • 複雑なフォーマット。公式のHDF5 Cライブラリなしでは使用できません(ただし、ラッパーは多数あります(例:h5py)。).
  • バロックC/C++ API(Python 1つはそうではありません)。
  • 並行ライター(またはライター+リーダー)のサポートはほとんどありません。書き込みは、粗い粒度でロックする必要がある場合があります。

HDF5は、単一のファイル(または実際には複数のファイル)内の階層内に値(スカラーまたはN次元配列)を格納する方法と考えることができます。値を単一のディスクファイルに格納するだけの場合の最大の問題は、一部のファイルシステムを圧倒することです。 HDF5は、1つの「ディレクトリ」に100万個の値を入れても落ちないファイル内のファイルシステムと考えることができます。

4
John Zwinck

まず、bsddb(またはその新しい名前のOracle BerkeleyDB)は非推奨ではありません。

経験から、LevelDB/RocksDB/bsddbは wiredtiger よりも遅いため、wiredtigerをお勧めします。

ワイヤードタイガーはmongodbのストレージエンジンであるため、本番環境で十分にテストされています。 Python私のAjguDBプロジェクトの外では、wiredtigerをほとんど、またはまったく使用していません。私は(AjguDBを介して)wiredtigerを使用して、約80GBのウィキデータと概念を格納およびクエリしています。

以下は、python2 shelve モジュールを模倣できるクラスの例です。基本的に、それはキーが文字列のみであるwiredtigerバックエンド辞書です:

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 $!

次の図を生成しました。

wt performance without wal

$ du -h wt
60M wt

ログ先行書き込みがアクティブな場合:

wt performance with wal

$ du -h wt
260M    wt

これには、パフォーマンスの調整と圧縮がありません。

Wiredtigerには、最近まで既知の制限がありませんでした。ドキュメントは次のように更新されました。

WiredTigerは、ペタバイトテーブル、最大4GBのレコード、最大64ビットのレコード数をサポートしています。

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

2
amirouche