File: index.rst

package info (click to toggle)
python-rocksdb 0.8.0~rc3-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 480 kB
  • sloc: python: 798; cpp: 408; makefile: 158
file content (403 lines) | stat: -rw-r--r-- 11,271 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
Basic Usage of python-rocksdb
*****************************

Open
====

The most basic open call is ::

    import rocksdb

    db = rocksdb.DB("test.db", rocksdb.Options(create_if_missing=True))

A more production ready open can look like this ::

    import rocksdb

    opts = rocksdb.Options()
    opts.create_if_missing = True
    opts.max_open_files = 300000
    opts.write_buffer_size = 67108864
    opts.max_write_buffer_number = 3
    opts.target_file_size_base = 67108864

    opts.table_factory = rocksdb.BlockBasedTableFactory(
        filter_policy=rocksdb.BloomFilterPolicy(10),
        block_cache=rocksdb.LRUCache(2 * (1024 ** 3)),
        block_cache_compressed=rocksdb.LRUCache(500 * (1024 ** 2)))

    db = rocksdb.DB("test.db", opts)

It assings a cache of 2.5G, uses a bloom filter for faster lookups and keeps
more data (64 MB) in memory before writting a .sst file.

About Bytes And Unicode
=======================

RocksDB stores all data as uninterpreted *byte strings*.
pyrocksdb behaves the same and uses nearly everywhere byte strings too.
In python2 this is the ``str`` type. In python3 the ``bytes`` type. 
Since the default string type for string literals differs between python 2 and 3,
it is strongly recommended to use an explicit ``b`` prefix for all byte string
literals in both python2 and python3 code.
For example ``b'this is a byte string'``. This avoids ambiguity and ensures
that your code keeps working as intended if you switch between python2 and python3.

The only place where you can pass unicode objects are filesytem paths like

* Directory name of the database itself :py:meth:`rocksdb.DB.__init__`

* :py:attr:`rocksdb.Options.wal_dir`

* :py:attr:`rocksdb.Options.db_log_dir`

To encode this path name, `sys.getfilesystemencoding()` encoding is used.

Access
======

Store, Get, Delete is straight forward ::

    # Store
    db.put(b"key", b"value")

    # Get
    db.get(b"key")

    # Delete
    db.delete(b"key")

It is also possible to gather modifications and
apply them in a single operation ::

    batch = rocksdb.WriteBatch()
    batch.put(b"key", b"v1")
    batch.delete(b"key")
    batch.put(b"key", b"v2")
    batch.put(b"key", b"v3")

    db.write(batch)

Fetch of multiple values at once ::

    db.put(b"key1", b"v1")
    db.put(b"key2", b"v2")

    ret = db.multi_get([b"key1", b"key2", b"key3"])

    # prints b"v1"
    print ret[b"key1"]

    # prints None
    print ret[b"key3"]

Iteration
=========

Iterators behave slightly different than expected. Per default they are not
valid. So you have to call one of its seek methods first ::

    db.put(b"key1", b"v1")
    db.put(b"key2", b"v2")
    db.put(b"key3", b"v3")

    it = db.iterkeys()
    it.seek_to_first()

    # prints [b'key1', b'key2', b'key3']
    print list(it)

    it.seek_to_last()
    # prints [b'key3']
    print list(it)

    it.seek(b'key2')
    # prints [b'key2', b'key3']
    print list(it)

There are also methods to iterate over values/items ::

    it = db.itervalues()
    it.seek_to_first()

    # prints [b'v1', b'v2', b'v3']
    print list(it)

    it = db.iteritems()
    it.seek_to_first()

    # prints [(b'key1', b'v1'), (b'key2, b'v2'), (b'key3', b'v3')]
    print list(it)

Reversed iteration ::

    it = db.iteritems()
    it.seek_to_last()

    # prints [(b'key3', b'v3'), (b'key2', b'v2'), (b'key1', b'v1')]
    print list(reversed(it))

SeekForPrev (Take the example from `https://github.com/facebook/rocksdb/wiki/SeekForPrev`)::

    db.put(b'a1', b'a1_value')
    db.put(b'a3', b'a3_value')
    db.put(b'b1', b'b1_value')
    db.put(b'b2', b'b2_value')
    db.put(b'c2', b'c2_value')
    db.put(b'c4', b'c4_value')

    it = db.iteritems()
    it.seek(b'a1')
    assertEqual(it.get(), (b'a1', b'a1_value'))
    it.seek(b'a3')
    assertEqual(it.get(), (b'a3', b'a3_value'))
    it.seek_for_prev(b'c4')
    assertEqual(it.get(), (b'c4', b'c4_value'))
    it.seek_for_prev(b'c3')
    assertEqual(it.get(), (b'c2', b'c2_value'))


Snapshots
=========

Snapshots are nice to get a consistent view on the database ::

    self.db.put(b"a", b"1")
    self.db.put(b"b", b"2")

    snapshot = self.db.snapshot()
    self.db.put(b"a", b"2")
    self.db.delete(b"b")

    it = self.db.iteritems()
    it.seek_to_first()

    # prints {b'a': b'2'}
    print dict(it)

    it = self.db.iteritems(snapshot=snapshot)
    it.seek_to_first()

    # prints {b'a': b'1', b'b': b'2'}
    print dict(it)


MergeOperator
=============

Merge operators are useful for efficient read-modify-write operations.
For more details see `Merge Operator <https://github.com/facebook/rocksdb/wiki/Merge-Operator>`_

A python merge operator must either implement the
:py:class:`rocksdb.interfaces.AssociativeMergeOperator` or
:py:class:`rocksdb.interfaces.MergeOperator` interface.

The following example python merge operator implements a counter ::

    class AssocCounter(rocksdb.interfaces.AssociativeMergeOperator):
        def merge(self, key, existing_value, value):
            if existing_value:
                s = int(existing_value) + int(value)
                return (True, str(s).encode('ascii'))
            return (True, value)

        def name(self):
            return b'AssocCounter'


    opts = rocksdb.Options()
    opts.create_if_missing = True
    opts.merge_operator = AssocCounter()
    db = rocksdb.DB('test.db', opts)

    db.merge(b"a", b"1")
    db.merge(b"a", b"1")

    # prints b'2'
    print db.get(b"a")

We provide a set of default operators ``rocksdb.merge_operators.UintAddOperator`` and ``rocksdb.merge_operators.StringAppendOperator``::

    from rocksdb.merge_operators import UintAddOperator, StringAppendOperator
    opts = rocksdb.Options()
    opts.create_if_missing = True
    # you should also play with StringAppendOperator
    opts.merge_operator = UintAddOperator()
    db = rocksdb.DB('/tmp/test', opts)
    self.db.put(b'a', struct.pack('Q', 5566))
    for x in range(1000):
        self.db.merge(b"a", struct.pack('Q', x))
    self.assertEqual(5566 + sum(range(1000)), struct.unpack('Q', self.db.get(b'a'))[0])



PrefixExtractor
===============

According to `Prefix API <https://github.com/facebook/rocksdb/wiki/Proposal-for-prefix-API>`_
a prefix_extractor can reduce IO for scans within a prefix range.
A python prefix extractor must implement the :py:class:`rocksdb.interfaces.SliceTransform` interface.

The following example presents a prefix extractor of a static size.
So always the first 5 bytes are used as the prefix ::

    class StaticPrefix(rocksdb.interfaces.SliceTransform):
        def name(self):
            return b'static'

        def transform(self, src):
            return (0, 5)

        def in_domain(self, src):
            return len(src) >= 5

        def in_range(self, dst):
            return len(dst) == 5

    opts = rocksdb.Options()
    opts.create_if_missing=True
    opts.prefix_extractor = StaticPrefix()

    db = rocksdb.DB('test.db', opts)

    db.put(b'00001.x', b'x')
    db.put(b'00001.y', b'y')
    db.put(b'00001.z', b'z')

    db.put(b'00002.x', b'x')
    db.put(b'00002.y', b'y')
    db.put(b'00002.z', b'z')

    db.put(b'00003.x', b'x')
    db.put(b'00003.y', b'y')
    db.put(b'00003.z', b'z')

    prefix = b'00002'

    it = db.iteritems()
    it.seek(prefix)

    # prints {b'00002.z': b'z', b'00002.y': b'y', b'00002.x': b'x'}
    print dict(itertools.takewhile(lambda item: item[0].startswith(prefix), it))


Backup And Restore
==================

Backup and Restore is done with a separate :py:class:`rocksdb.BackupEngine` object.

A backup can only be created on a living database object. ::

   import rocksdb

   db = rocksdb.DB("test.db", rocksdb.Options(create_if_missing=True))
   db.put(b'a', b'v1')
   db.put(b'b', b'v2')
   db.put(b'c', b'v3')

Backup is created like this.
You can choose any path for the backup destination except the db path itself.
If ``flush_before_backup`` is ``True`` the current memtable is flushed to disk
before backup. ::

    backup = rocksdb.BackupEngine("test.db/backups")
    backup.create_backup(db, flush_before_backup=True)

Restore is done like this.
The two arguments are the db_dir and wal_dir, which are mostly the same. ::

    backup = rocksdb.BackupEngine("test.db/backups")
    backup.restore_latest_backup("test.db", "test.db")


Change Memtable Or SST Implementations
======================================

As noted here :ref:`memtable_factories_label`, RocksDB offers different implementations for the memtable
representation. Per default :py:class:`rocksdb.SkipListMemtableFactory` is used,
but changing it to a different one is veary easy.

Here is an example for HashSkipList-MemtableFactory.
Keep in mind: To use the hashed based MemtableFactories you must set
:py:attr:`rocksdb.Options.prefix_extractor`.
In this example all keys have a static prefix of len 5. ::

    class StaticPrefix(rocksdb.interfaces.SliceTransform):
        def name(self):
            return b'static'

        def transform(self, src):
            return (0, 5)

        def in_domain(self, src):
            return len(src) >= 5

        def in_range(self, dst):
            return len(dst) == 5


    opts = rocksdb.Options()
    opts.prefix_extractor = StaticPrefix()
    opts.allow_concurrent_memtable_write = False
    opts.memtable_factory = rocksdb.HashSkipListMemtableFactory()
    opts.create_if_missing = True

    db = rocksdb.DB("test.db", opts)
    db.put(b'00001.x', b'x')
    db.put(b'00001.y', b'y')
    db.put(b'00002.x', b'x')

For initial bulk loads the Vector-MemtableFactory makes sense. ::

    opts = rocksdb.Options()
    opts.allow_concurrent_memtable_write = False
    opts.memtable_factory = rocksdb.VectorMemtableFactory()
    opts.create_if_missing = True

    db = rocksdb.DB("test.db", opts)

As noted here :ref:`table_factories_label`, it is also possible to change the
representation of the final data files.
Here is an example how to use a 'PlainTable'. ::

    opts = rocksdb.Options()
    opts.table_factory = rocksdb.PlainTableFactory()
    opts.create_if_missing = True

    db = rocksdb.DB("test.db", opts)

Change Compaction Style
=======================

RocksDB has a compaction algorithm called *universal*. This style typically
results in lower write amplification but higher space amplification than
Level Style Compaction. See here for more details,
https://github.com/facebook/rocksdb/wiki/Rocksdb-Architecture-Guide#multi-threaded-compactions

Here is an example to switch to *universal style compaction*. ::

    opts = rocksdb.Options()
    opts.compaction_style = "universal"
    opts.compaction_options_universal = {"min_merge_width": 3}

See here for more options on *universal style compaction*,
:py:attr:`rocksdb.Options.compaction_options_universal`

Iterate Over WriteBatch
=======================

In same cases you need to know, what operations happened on a WriteBatch.
The pyrocksdb WriteBatch supports the iterator protocol, see this example. ::

    batch = rocksdb.WriteBatch()
    batch.put(b"key1", b"v1")
    batch.delete(b'a')
    batch.merge(b'xxx', b'value')

    for op, key, value in batch:
        print op, key, value

    # prints the following three lines
    # Put key1 v1
    # Delete a
    # Merge xxx value