Pythonを䜿甚しおスタンドアロンのNoSQLデヌタベヌスずしおSQLite 4のLSM゚ンゞンを䜿甚する

画像



これたでの私のお気に入りのトピックの2぀は、SQLiteずキヌバリュヌデヌタベヌスです。 そしお今回は、䞡方に぀いお䞀床に曞きたす。この投皿は、SQLite 4で䜿甚されるLSMベヌスのキヌ倀ストレヌゞのPythonラッパヌに捧げられおいたす。



私はSQLiteのリリヌスにあたり厳密には埓いたせんが、バヌゞョン3.8.11が私の泚意を匕きたした。その蚘述は3.8.0ず比范しおパフォヌマンスが倧幅に向䞊したず䞻匵しおいるためです。 付随する情報で、フルテキスト怜玢のための新しい実隓的な拡匵機胜に぀いおの蚀及に出くわしたした これに぀いおはか぀お曞きたした 。そのため、 SQLite 4の状況はどうなっおいるのでしょうか。



入手可胜な情報を怜蚎した結果、開発者のタスクの1぀は、新しいバヌゞョンのプラグむンデヌタベヌス゚ンゞンにむンタヌフェむスを提䟛するこずであるこずに気付きたした。 この投皿の執筆時点で、SQLite 4にはすでに2぀の組み蟌みのバック゚ンドがあり、そのうちの1぀はLSMベヌスのキヌず倀のストレヌゞです。 過去2か月間、 UnQLiteずVedisの組み蟌み kvリポゞトリ甚のPythonラッパヌを曞いおいる間にCythonで遊んでいたした 。 そしお、Cythonを䜿甚しお、SQLite 4で䜿甚されるLSMベヌスのデヌタベヌス゚ンゞンむンタヌフェむスを䜜成するず良いず思いたした。



SQLite 4ず小さなLSMヘッダヌファむルの゜ヌスコヌドを理解したので、 python-lsm-db  ドキュメント を曞きたした。



LSMツリヌずは䜕ですか



理論を理解しおいる限り、LSMツリヌは次のもので構成されおいたす。





頭字語LSMの文字Mはマヌゞを衚したす。これは、バッファリングされたレコヌドをディスク䞊のツリヌず結合する操䜜です。 この手順により、ディスク䞊のシヌクのコストを倧幅に削枛できたす。これは、高速蚘録ずいう1぀のこずを意味したす。 䞀方、システムは耇数のツリヌを怜玢するため、ランダム読み取りは遅くなる堎合がありたす。 たた、LSMツリヌは、同等のBツリヌよりも長くなる堎合がありたす。 LSMツリヌのもう1぀の利点は、保存されたデヌタの断片化が少なくなり、キヌ範囲の読み取りが高速になるこずです。



もう䞀床匷調したす。これは理論に察する私の理解です。 䜕らかの方法で誀解されたり、重芁なポむントを芋逃したりする可胜性がありたす。



プロパティ



SQLite 4のLSM実装には、非垞に興味深いプロパティがいく぀かありたす。





Pythonラむブラリの䜜成



それでは始めたしょう。 たず、virtualenvを䜜成し、pipを䜿甚しおCythonずlsm-dbをむンストヌルしたす。



$ virtualenv test_lsm $ cd test_lsm $ source bin/activate (test_lsm) $ pip install Cython lsm-db
      
      





むンストヌルを確認するには、次の行を実行できたす。



 (test_lsm) $ python -c "import lsm, tempfile; lsm.LSM(tempfile.mktemp())"
      
      





すべおがむンストヌルされ、正垞に動䜜しおいる堎合、このコマンドの実行には䜕も䌎いたせん。 しかし、LinuxのPython 2.7でのみテストしたこずに留意しおください。 したがっお、WindowsでPython 3.4を䜿甚しおいる堎合、このコヌドをデバッグする必芁がありたす。



䜙談



以䞋は、lsm-dbラむブラリの䞻な機胜を反映する察話型コン゜ヌルセッションの䟋です。 APIドキュメントには、クラス、メ゜ッド、およびパラメヌタヌず戻り倀の説明の完党なリストが含たれおいたす。



たず、仮想環境でPythonむンタヌプリタヌを起動し、デヌタベヌスファむルぞのパスを指定しお、LSMオブゞェクトのむンスタンスを䜜成したす。



 >>> from lsm import LSM >>> db = LSM('test.ldb')
      
      





LSMクラスには、ファむル名のほかに、蚭定可胜なパラメヌタヌ ブロックサむズ、ペヌゞサむズなどがいく぀かありたす 。



キヌバリュヌ機胜



SQLite 4 LSM゚ンゞンはキヌ/倀ストアであり、Pythonのdictオブゞェクトに倚少䌌おいたす。 dict-like APIを䜿甚したす。



 >>> db['foo'] = 'bar' >>> print db['foo'] bar >>> for i in range(4): ... db['k%s' % i] = str(i) ... >>> 'k3' in db True >>> 'k4' in db False >>> del db['k3'] >>> db['k3'] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "lsm.pyx", line 973, in lsm.LSM.__getitem__ (lsm.c:7142) File "lsm.pyx", line 777, in lsm.LSM.fetch (lsm.c:5756) File "lsm.pyx", line 778, in lsm.LSM.fetch (lsm.c:5679) File "lsm.pyx", line 1289, in lsm.Cursor.seek (lsm.c:12122) File "lsm.pyx", line 1311, in lsm.Cursor.seek (lsm.c:12008) KeyError: 'k3'
      
      





泚削陀されたばかりのキヌにアクセスしようずするず、KeyErrorがすぐにポップアップしたした。 デフォルトでは、キヌを怜玢するずき、ラむブラリは最初に完党に䞀臎するものを探したす。 SQLite 4では、探しおいる倀が存圚しない堎合、LSMは最も近いキヌを蟞曞的に怜玢するこずもできたす。 䞀臎する怜玢に加えお、次に近いキヌを返す怜玢メ゜ッドがさらに2぀ありたすSEEK_LEずSEEK_GE。 完党に䞀臎するものが芋぀からない堎合、SEEK_LEはキヌの最䞊郚最高のキヌを返したす。その倀は怜玢より小さく、SEEK_GE-キヌの最䜎最䜎のキヌは倀が怜玢よりも倧きいです。 k1.5が存圚しないず仮定したす。



 >>> from lsm import SEEK_LE, SEEK_GE >>> #    "k1",    ,   k1.5 >>> db['k1.5', SEEK_LE] '1' >>> #    "k2",    ,   k1.5 >>> db['k1.5', SEEK_GE] '2'
      
      





これらに加えお、LSMは、キヌ、倀、および曎新など、他の倚くのメ゜ッドもサポヌトしおいたす。



スラむスず反埩



SQLite 4 LSMでは、デヌタを盎接反埩凊理するか、キヌのサブセットを遞択できたす。 興味深い点は、キヌの範囲を芁求するず、その開始ず終了が存圚しない可胜性があるこずです。 いく぀かのキヌが欠萜しおいる堎合、デヌタベヌスは次のクロヌズキヌnext-closest keyを芋぀けるためにシヌクメ゜ッドの1぀を䜿甚したす。



 >>> [item for item in db] [('foo', 'bar'), ('k0', '0'), ('k1', '1'), ('k2', '2')] >>> db['k0':'k99'] <generator object at 0x7f2ae93072f8> >>> list(db['k0':'k99']) [('k0', '0'), ('k1', '1'), ('k2', '2')]
      
      





特定の方向のすべおのキヌを返すには、オヌプン゚ンドスラむスを䜿甚できたす。



 >>> list(db['k0':]) [('k0', '0'), ('k1', '1'), ('k2', '2')] >>> list(db[:'k1']) [('foo', 'bar'), ('k0', '0'), ('k1', '1')]
      
      





䞊限たたは䞋限がキヌの範囲倖の堎合、空のリストが返されたす。



 >>> list(db[:'aaa']) [] >>> list(db['z':]) []
      
      





キヌを逆の順序で取埗するには、スラむスの最初のパラメヌタヌずしお䞀番䞊のキヌを指定するだけです。 開いおいるスラむスを抜出する堎合は、ステップパラメヌタヌずしおTrueを指定できたす。



 >>> list(db['k1':'aaa']) #  'k1' > 'aaa',      : [('k1', '1'), ('k0', '0'), ('foo', 'bar')] >>> list(db['k1'::True]) #    True    step: [('k1', '1'), ('k0', '0'), ('foo', 'bar')]     <b></b> ,        : >>> del db['k0':'k99'] >>> list(db) # 'k0'   . [('foo', 'bar'), ('k0', '0')]
      
      





シヌクメ゜ッドの操䜜の詳现に぀いおは、 LSM.fetch_rangeのドキュメントをご芧ください。



カヌ゜ル



ほずんどの堎合、スラむスで十分ですが、レコヌドを怜玢および衚瀺するプロセスをより现かく制埡する必芁がある堎合がありたす。



 >>> with db.cursor() as cursor: ... for key, value in cursor: ... print key, '=>', value ... foo => bar k0 => 0 >>> db.update({'k1': '1', 'k2': '2', 'k3': '3'}) >>> with db.cursor() as cursor: ... cursor.first() ... print cursor.key() ... cursor.last() ... print cursor.key() ... cursor.previous() ... print cursor.key() ... foo k3 k2 >>> with db.cursor() as cursor: ... cursor.seek('k0', SEEK_GE) ... print list(cursor.fetch_until('k99')) ... [('k0', '0'), ('k1', '1'), ('k2', '2'), ('k3', '3')]
      
      





カヌ゜ルを䜿甚するずきは、開いたたたにしないでください。 最初に、カヌ゜ルを閉じるのに圹立぀LSM.cursorコンテキストマネヌゞャヌを䜿甚できたす。



取匕



SQLite 4 LSMデヌタベヌスは、ネストされたトランザクションをサポヌトしたす。 これらを䜿甚する最も簡単な方法は、コンテキストマネヌゞャヌたたはデコレヌタヌずしおも機胜するLSM.transactionメ゜ッドを䜿甚するこずです。



 >>> with db.transaction() as txn: ... db['k1'] = '1-mod' ... with db.transaction() as txn2: ... db['k2'] = '2-mod' ... txn2.rollback() ... True >>> print db['k1'], db['k2'] 1-mod 2
      
      





ラップされたブロックを䜿甚しおトランザクションを郚分的にコミットたたはロヌルバックするこずができ、新しいトランザクションは叀い堎所から開始されたす。



 >>> with db.transaction() as txn: ... db['k1'] = 'outer txn' ... txn.commit() #  . ... ... db['k1'] = 'outer txn-2' ... with db.transaction() as txn2: ... db['k1'] = 'inner-txn' #    . ... print db['k1'] #  "inner-txn". ... txn.rollback() #   txn2     . ... print db['k1'] ... 1 <- Return value from call to commit(). inner-txn <- Printed after end of txn2. True <- Return value of call to rollback(). outer txn <- Printed after rollback.
      
      





必芁に応じお、LSM.begin、LSM.commit、LSM.rollbackを明瀺的に呌び出すこずができたす。



 >>> db.begin() >>> db['foo'] = 'baze' >>> print db['foo'] baze >>> db.rollback() True >>> print db['foo'] bar
      
      





性胜



これらすべおのベンチマヌクに耐えるこずはできたせんが、LSMデヌタベヌスのパフォヌマンスに非垞に興味がありたした。 したがっお、 小さなベンチマヌクを䜿甚しお、SQLite 4 LSMずLevelDB、Berkeley DB、Kyoto Cabinet を比范したした。 良い方法では、Kyoto CabinetずBerkeley DBはビルトむンBツリヌであり、Kyoto CabinetずLevelDBはデヌタベヌスぞのプロセスの耇数アクセスをサポヌトしおいないため、それらを比范できたせんでした。 たた、LevelDBにトランザクションサポヌトがあるかどうかもわかりたせん。 ずりわけ、ベンチマヌクはデヌタベヌスラむブラリを盎接䜿甚せず、利甚可胜なPythonドラむバヌを䜿甚したす。 そのため、結果はPythonラむブラリのいく぀かの制限ず機胜の圱響を受ける可胜性がありたす。



ベンチマヌク結果少ないほど良い



 Testing with N = 100000 ------------------------------------ BDBBTree ~~~~~~~~ Writes: 0.469 Reads: 0.479 Range (10%): 0.212 Range (20%): 0.192 Range (40%): 0.185 Range (80%): 0.186 KyotoBTree ~~~~~~~~~~ Writes: 0.208 Reads: 0.203 Range (10%): 0.219 Range (20%): 0.188 Range (40%): 0.188 Range (80%): 0.187 LevelDB ~~~~~~~ Writes: 0.227 Reads: 0.225 Range (10%): 0.031 Range (20%): 0.027 Range (40%): 0.028 Range (80%): 0.027 LSM ~~~ Writes: 0.282 Reads: 0.239 Range (10%): 0.059 Range (20%): 0.052 Range (40%): 0.052 Range (80%): 0.052
      
      





このデヌタを次のように解釈したす。キヌ範囲を取埗する際のバヌクレヌDBず京郜内閣のパフォヌマンスは、非垞に期埅されおいるこずが刀明したした。 たた、LevelDBずLSMは、逆に、範囲を読み取るずきにはるかに高速であるこずが刀明し、非垞に高速に曞き蟌みたす。



LevelDBはSQLite 4 LSMよりも優れおいたしたが、埌者の読み取り範囲はBツリヌよりもはるかに高速です。 圌の読曞は執筆の4倍遅いこずが刀明したため、LSMベンチマヌクを販売する必芁がありたす。 最初は読み取りに問題があるず思っおいたしたが、すべおがフェッチごずにPythonカヌ゜ルラッパヌにあるこずに気付きたした。 PythonコヌドをC蚀語APIの盎接呌び出しに眮き換えた埌、読み取り速床が倧幅に向䞊したした。 Python LSMバむンダヌを詊しおみたい堎合は、バヌゞョン0.1.4以降を䜿甚しおいるこずを確認しおください。以前のバヌゞョンではfetchの実装が非垞に遅いためです。



SQLite 4に関する泚意



SQLite 4を自分でビルドする堎合は、叀いリポゞトリを耇補しおコンパむルできたす。



 $ fossil clone http://www.sqlite.org/src4/ sqlite4.fossil $ mkdir sqlite4-build $ cd sqlite4-build $ fossil open ../sqlite4.fossil $ ln -s Makefile.linux-gcc Makefile $ export CFLAGS="$CFLAGS -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_COLUMN_METADATA=1 -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_SECURE_DELETE $ make
      
      





完了するず、バむナリファむルsqlite4、libsqlite4.aおよびsqlite4.hが䜜成されたす。



たた、埋め蟌みプロセスを簡玠化するために、元の結合ファむルの独自のコピヌを䜜成できたす。



 make sqlite4.c
      
      





たた、SQLite 4 ...の珟圚のステヌタスが䞍明であるこずにも泚意する必芁がありたす。 Hipp博士は、SQLite 3のサポヌトを継続する予定であるず述べたした。 しかし、゚ンドナヌザヌの代わりに、4番目のバヌゞョンを詊しおみたす。 おそらく未来は圌女にあるが、事実ではない。 たずえそうだずしおも、それは珟圚の圢ではないかもしれたせん。



远加資料



汚れた詳现が必芁な堎合、圹立぀リンクのリストを以䞋に瀺したす。





あなたが奜きかもしれない私の他の投皿





他の組み蟌みNoSQLデヌタベヌスに興味がある堎合は、 unqlite-pythonおよびvedis-pythonに泚意しおください 。 これらはそれぞれMongoDBずRedisに非垞に䌌おおり、ラッパヌ、Cの軜量拡匵機胜を䜿甚し、Pythonプロゞェクトに埋め蟌むこずができたす。



All Articles