rust_rocksdb/transactions/options.rs
1// Copyright 2021 Yiyuan Liu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use std::ptr::NonNull;
17use std::sync::Arc;
18
19use libc::c_int;
20
21use crate::ffi;
22
23pub struct TransactionOptions {
24 pub(crate) inner: *mut ffi::rocksdb_transaction_options_t,
25}
26
27unsafe impl Send for TransactionOptions {}
28unsafe impl Sync for TransactionOptions {}
29
30impl Default for TransactionOptions {
31 fn default() -> Self {
32 let txn_opts = unsafe { ffi::rocksdb_transaction_options_create() };
33 assert!(
34 !txn_opts.is_null(),
35 "Could not create RocksDB transaction options"
36 );
37 Self { inner: txn_opts }
38 }
39}
40
41impl TransactionOptions {
42 pub fn new() -> TransactionOptions {
43 TransactionOptions::default()
44 }
45
46 pub fn set_skip_prepare(&mut self, skip_prepare: bool) {
47 unsafe {
48 ffi::rocksdb_transaction_options_set_skip_prepare(self.inner, u8::from(skip_prepare));
49 }
50 }
51
52 /// Specifies use snapshot or not.
53 ///
54 /// Default: false.
55 ///
56 /// If a transaction has a snapshot set, the transaction will ensure that
57 /// any keys successfully written(or fetched via `get_for_update`) have not
58 /// been modified outside this transaction since the time the snapshot was
59 /// set.
60 /// If a snapshot has not been set, the transaction guarantees that keys have
61 /// not been modified since the time each key was first written (or fetched via
62 /// `get_for_update`).
63 ///
64 /// Using snapshot will provide stricter isolation guarantees at the
65 /// expense of potentially more transaction failures due to conflicts with
66 /// other writes.
67 ///
68 /// Calling `set_snapshot` will not affect the version of Data returned by `get`
69 /// methods.
70 pub fn set_snapshot(&mut self, snapshot: bool) {
71 unsafe {
72 ffi::rocksdb_transaction_options_set_set_snapshot(self.inner, u8::from(snapshot));
73 }
74 }
75
76 /// Specifies whether detect deadlock or not.
77 ///
78 /// Setting to true means that before acquiring locks, this transaction will
79 /// check if doing so will cause a deadlock. If so, it will return with
80 /// Status::Busy. The user should retry their transaction.
81 ///
82 /// Default: false.
83 pub fn set_deadlock_detect(&mut self, deadlock_detect: bool) {
84 unsafe {
85 ffi::rocksdb_transaction_options_set_deadlock_detect(
86 self.inner,
87 u8::from(deadlock_detect),
88 );
89 }
90 }
91
92 /// Specifies the wait timeout in milliseconds when a transaction attempts to lock a key.
93 ///
94 /// If 0, no waiting is done if a lock cannot instantly be acquired.
95 /// If negative, transaction lock timeout in `TransactionDBOptions` will be used.
96 ///
97 /// Default: -1.
98 pub fn set_lock_timeout(&mut self, lock_timeout: i64) {
99 unsafe {
100 ffi::rocksdb_transaction_options_set_lock_timeout(self.inner, lock_timeout);
101 }
102 }
103
104 /// Specifies expiration duration in milliseconds.
105 ///
106 /// If non-negative, transactions that last longer than this many milliseconds will fail to commit.
107 /// If not set, a forgotten transaction that is never committed, rolled back, or deleted
108 /// will never relinquish any locks it holds. This could prevent keys from being accessed by other writers.
109 ///
110 /// Default: -1.
111 pub fn set_expiration(&mut self, expiration: i64) {
112 unsafe {
113 ffi::rocksdb_transaction_options_set_expiration(self.inner, expiration);
114 }
115 }
116
117 /// Specifies the number of traversals to make during deadlock detection.
118 ///
119 /// Default: 50.
120 pub fn set_deadlock_detect_depth(&mut self, depth: i64) {
121 unsafe {
122 ffi::rocksdb_transaction_options_set_deadlock_detect_depth(self.inner, depth);
123 }
124 }
125
126 /// Specifies the maximum number of bytes used for the write batch. 0 means no limit.
127 ///
128 /// Default: 0.
129 pub fn set_max_write_batch_size(&mut self, size: usize) {
130 unsafe {
131 ffi::rocksdb_transaction_options_set_max_write_batch_size(self.inner, size);
132 }
133 }
134
135 /// The following three options enable optimizations for large transaction commit to
136 /// bypass memtable write.
137 /// - If any transaction's commit should bybass memtable write, set
138 /// commit_bypass_memtable to true.
139 /// - If only bypass memtable write for transactions with >= n operations, set
140 /// commit_bypass_memtable to false, large_txn_commit_optimize_threshold to n, and
141 /// large_txn_commit_optimize_byte_threshold to 0. Similarly for only optimize when a
142 /// transaction's write batch size is >= n.
143 /// - If bypass memtable write for transactions with >= n operations or >= x bytes, set
144 /// commit_bypass_memtable to false, large_txn_commit_optimize_threshold to n, and
145 /// large_txn_commit_optimize_byte_threshold to x.
146 ///
147 /// EXPERIMENTAL, SUBJECT TO CHANGE Only supports write-committed policy. If set to true,
148 /// the transaction will skip memtable write and ingest into the DB directly during
149 /// Commit(). This makes Commit() much faster for transactions with many operations.
150 /// Transaction neeeds to call Prepare() before Commit() for this option to take effect.
151 /// Transactions with Merge() or PutEntity() is not supported yet.
152 ///
153 /// Note that the transaction will be ingested as an immutable memtable for CFs it
154 /// updates, and the current memtable will be switched to a new one. So ingesting many
155 /// transactions in a short period of time may cause stall due to too many memtables. Note
156 /// that the ingestion relies on the transaction's underlying index,
157 /// (WriteBatchWithIndex), so updates that are added to the transaction without indexing
158 /// (i.e. added directly to the transaction underlying write batch through
159 /// Transaction::GetWriteBatch()->GetWriteBatch()) are not supported, and the optimization
160 /// will not apply in that case.
161 ///
162 /// NOTE: since WBWI keep track of the most recent update per key, a Put followed by a
163 /// SingleDelete will be written to DB as a SingleDelete. This can cause flush/compaction
164 /// to report `num_single_del_mismatch` due to consecutive SingleDeletes.
165 pub fn set_commit_bypass_memtable(&mut self, val: bool) {
166 unsafe {
167 ffi::rocksdb_transaction_options_set_commit_bypass_memtable(self.inner, u8::from(val));
168 }
169 }
170
171 /// Returns the value of the `commit_bypass_memtable` option.
172 pub fn get_commit_bypass_memtable(&self) -> bool {
173 unsafe { ffi::rocksdb_transaction_options_get_commit_bypass_memtable(self.inner) != 0 }
174 }
175
176 /// Setting to true means that before acquiring locks, this transaction will check if
177 /// doing so will cause a deadlock. If so, it will return with Status::Busy. The user
178 /// should retry their transaction.
179 pub fn get_deadlock_detect(&self) -> bool {
180 unsafe { ffi::rocksdb_transaction_options_get_deadlock_detect(self.inner) != 0 }
181 }
182
183 /// EXPERIMENTAL, SUBJECT TO CHANGE When the size of a transaction's write batch is at
184 /// least this threshold, we will enable optimizations for commiting a large transaction.
185 /// See comment for `commit_bypass_memtable` for more optimization detail.
186 ///
187 /// Default: 0 (disabled).
188 pub fn set_large_txn_commit_optimize_byte_threshold(&mut self, val: u64) {
189 unsafe {
190 ffi::rocksdb_transaction_options_set_large_txn_commit_optimize_byte_threshold(
191 self.inner, val,
192 );
193 }
194 }
195
196 /// Returns the value of the `large_txn_commit_optimize_byte_threshold` option.
197 pub fn get_large_txn_commit_optimize_byte_threshold(&self) -> u64 {
198 unsafe {
199 ffi::rocksdb_transaction_options_get_large_txn_commit_optimize_byte_threshold(
200 self.inner,
201 )
202 }
203 }
204
205 /// EXPERIMENTAL, SUBJECT TO CHANGE When the number of updates in a transaction is at
206 /// least this threshold, we will enable optimizations for commiting a large transaction.
207 /// See comment for `commit_bypass_memtable` for more optimization detail.
208 ///
209 /// Default: 0 (disabled).
210 pub fn set_large_txn_commit_optimize_threshold(&mut self, val: u32) {
211 unsafe {
212 ffi::rocksdb_transaction_options_set_large_txn_commit_optimize_threshold(
213 self.inner, val,
214 );
215 }
216 }
217
218 /// Returns the value of the `large_txn_commit_optimize_threshold` option.
219 pub fn get_large_txn_commit_optimize_threshold(&self) -> u32 {
220 unsafe {
221 ffi::rocksdb_transaction_options_get_large_txn_commit_optimize_threshold(self.inner)
222 }
223 }
224
225 /// The maximum number of bytes used for the write batch. 0 means no limit.
226 pub fn get_max_write_batch_size(&self) -> usize {
227 unsafe { ffi::rocksdb_transaction_options_get_max_write_batch_size(self.inner) }
228 }
229
230 /// Setting set_snapshot=true is the same as calling Transaction::SetSnapshot().
231 pub fn get_set_snapshot(&self) -> bool {
232 unsafe { ffi::rocksdb_transaction_options_get_set_snapshot(self.inner) != 0 }
233 }
234
235 /// If true, the TransactionDB implementation might skip concurrency control unless it is
236 /// overridden by TransactionOptions or TransactionDBWriteOptimizations. This can be used
237 /// in conjunction with DBOptions::unordered_write when the TransactionDB is used solely
238 /// for write ordering rather than concurrency control.
239 pub fn set_skip_concurrency_control(&mut self, val: bool) {
240 unsafe {
241 ffi::rocksdb_transaction_options_set_skip_concurrency_control(
242 self.inner,
243 u8::from(val),
244 );
245 }
246 }
247
248 /// Returns the value of the `skip_concurrency_control` option.
249 pub fn get_skip_concurrency_control(&self) -> bool {
250 unsafe { ffi::rocksdb_transaction_options_get_skip_concurrency_control(self.inner) != 0 }
251 }
252
253 /// In pessimistic transaction, if this is true, then you can skip Prepare before Commit,
254 /// otherwise, you must Prepare before Commit.
255 pub fn get_skip_prepare(&self) -> bool {
256 unsafe { ffi::rocksdb_transaction_options_get_skip_prepare(self.inner) != 0 }
257 }
258
259 /// If set, it states that the CommitTimeWriteBatch represents the latest state of the
260 /// application, has only one sub-batch, i.e., no duplicate keys, and meant to be used
261 /// later during recovery. It enables an optimization to postpone updating the memtable
262 /// with CommitTimeWriteBatch to only SwitchMemtable or recovery. This option does not
263 /// affect write-committed. Only write-prepared/write-unprepared transactions will be
264 /// affected.
265 pub fn set_use_only_the_last_commit_time_batch_for_recovery(&mut self, val: bool) {
266 unsafe {
267 ffi::rocksdb_transaction_options_set_use_only_the_last_commit_time_batch_for_recovery(
268 self.inner,
269 u8::from(val),
270 );
271 }
272 }
273
274 /// Returns the value of the `use_only_the_last_commit_time_batch_for_recovery` option.
275 pub fn get_use_only_the_last_commit_time_batch_for_recovery(&self) -> bool {
276 unsafe {
277 ffi::rocksdb_transaction_options_get_use_only_the_last_commit_time_batch_for_recovery(
278 self.inner,
279 ) != 0
280 }
281 }
282
283 /// DO NOT USE. This is only a temporary option dedicated for MyRocks that will soon be
284 /// removed. In normal use cases, meta info like column family's timestamp size is tracked
285 /// at the transaction layer, so it's not necessary and even detrimental to track such
286 /// info inside the internal WriteBatch because it may let anti-patterns like bypassing
287 /// Transaction write APIs and directly write to its internal `WriteBatch` retrieved like
288 /// this:
289 /// <https://github.com/facebook/mysql-5.6/blob/fb-mysql-8.0.32/storage/rocksdb/ha_rocksdb.cc#L4949-L4950>
290 /// Setting this option to true will keep aforementioned use case continue to work before
291 /// it's refactored out. When this flag is enabled, we also intentionally only track the
292 /// timestamp size in APIs that MyRocks currently are using, including Put, Merge, Delete
293 /// DeleteRange, SingleDelete.
294 pub fn set_write_batch_track_timestamp_size(&mut self, val: bool) {
295 unsafe {
296 ffi::rocksdb_transaction_options_set_write_batch_track_timestamp_size(
297 self.inner,
298 u8::from(val),
299 );
300 }
301 }
302
303 /// Returns the value of the `write_batch_track_timestamp_size` option.
304 pub fn get_write_batch_track_timestamp_size(&self) -> bool {
305 unsafe {
306 ffi::rocksdb_transaction_options_get_write_batch_track_timestamp_size(self.inner) != 0
307 }
308 }
309
310 /// Returns the current `deadlock_detect_depth` setting.
311 ///
312 /// See [`Self::set_deadlock_detect_depth`] for what this controls.
313 pub fn get_deadlock_detect_depth(&self) -> i64 {
314 unsafe { ffi::rocksdb_transaction_options_get_deadlock_detect_depth(self.inner) }
315 }
316
317 /// Returns the current `deadlock_timeout_us` setting.
318 ///
319 /// See [`Self::set_deadlock_timeout_us`] for what this controls.
320 pub fn get_deadlock_timeout_us(&self) -> i64 {
321 unsafe { ffi::rocksdb_transaction_options_get_deadlock_timeout_us(self.inner) }
322 }
323
324 /// Returns the current `expiration` setting.
325 ///
326 /// See [`Self::set_expiration`] for what this controls.
327 pub fn get_expiration(&self) -> i64 {
328 unsafe { ffi::rocksdb_transaction_options_get_expiration(self.inner) }
329 }
330
331 /// Returns the current `lock_timeout` setting.
332 ///
333 /// See [`Self::set_lock_timeout`] for what this controls.
334 pub fn get_lock_timeout(&self) -> i64 {
335 unsafe { ffi::rocksdb_transaction_options_get_lock_timeout(self.inner) }
336 }
337
338 /// Returns the current `write_batch_flush_threshold` setting.
339 ///
340 /// See [`Self::set_write_batch_flush_threshold`] for what this controls.
341 pub fn get_write_batch_flush_threshold(&self) -> i64 {
342 unsafe { ffi::rocksdb_transaction_options_get_write_batch_flush_threshold(self.inner) }
343 }
344
345 /// Timeout in microseconds before perform dead lock detection. If 0, deadlock detection
346 /// will be performed immediately.
347 ///
348 /// To optimize performance, this parameter could be tuned.
349 ///
350 /// When deadlock happens very frequently, deadlock timeout should be set to 0, so
351 /// deadlock will be detected immediately.
352 ///
353 /// When deadlock happen very rarely, this timeout could be turned to be slightly longer
354 /// than the typical transaction execution time, so that transaction will be waked up to
355 /// take the lock before this timeout, which will allow the transaction to save the CPU
356 /// time on deadlock detection.
357 ///
358 /// Deadlock timeout is always smaller than lock_timeout.
359 pub fn set_deadlock_timeout_us(&mut self, val: i64) {
360 unsafe {
361 ffi::rocksdb_transaction_options_set_deadlock_timeout_us(self.inner, val);
362 }
363 }
364
365 /// See TransactionDBOptions::default_write_batch_flush_threshold for description. If a
366 /// negative value is specified, then the default value from TransactionDBOptions is used.
367 pub fn set_write_batch_flush_threshold(&mut self, val: i64) {
368 unsafe {
369 ffi::rocksdb_transaction_options_set_write_batch_flush_threshold(self.inner, val);
370 }
371 }
372}
373
374impl Drop for TransactionOptions {
375 fn drop(&mut self) {
376 unsafe {
377 ffi::rocksdb_transaction_options_destroy(self.inner);
378 }
379 }
380}
381
382/// When a [`TransactionDB`] writes transaction data into the DB.
383///
384/// [`TransactionDB`] reads this once while opening, to choose which implementation to
385/// build, and keeps its own copy. Changing it on a [`TransactionDBOptions`] afterwards
386/// has no effect on a DB that is already open.
387///
388/// [`TransactionDB`]: crate::TransactionDB
389#[derive(Debug, Copy, Clone, PartialEq, Eq)]
390#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
391pub enum TxnDBWritePolicy {
392 /// Write data at commit time.
393 ///
394 /// Only committed data ever reaches the DB, so readers need no extra machinery to
395 /// tell committed from uncommitted. This is the default.
396 WriteCommitted = ffi::rocksdb_txndb_write_policy_write_committed as isize,
397
398 /// Write data after the prepare phase of two-phase commit.
399 ///
400 /// Data lands before the commit, so the DB has to track which of it is committed and
401 /// readers have to consult that. Experimental: RocksDB describes the non-committed
402 /// policies as less mature, less validated and less compatible with other features
403 /// than `WriteCommitted`.
404 WritePrepared = ffi::rocksdb_txndb_write_policy_write_prepared as isize,
405
406 /// Write data before the prepare phase of two-phase commit.
407 ///
408 /// A running transaction can flush its pending writes into the DB once its batch
409 /// crosses
410 /// [`TransactionDBOptions::set_default_write_batch_flush_threshold`], which keeps
411 /// memory bounded for very large transactions. Experimental, and the least mature of
412 /// the three.
413 WriteUnprepared = ffi::rocksdb_txndb_write_policy_write_unprepared as isize,
414}
415
416impl TxnDBWritePolicy {
417 /// Decodes a raw `rocksdb::TxnDBWritePolicy`.
418 ///
419 /// This covers every policy RocksDB defines today, so `None` only means a future
420 /// release added one.
421 pub(crate) fn try_from_raw(raw: c_int) -> Option<Self> {
422 match raw {
423 n if n == TxnDBWritePolicy::WriteCommitted as c_int => {
424 Some(TxnDBWritePolicy::WriteCommitted)
425 }
426 n if n == TxnDBWritePolicy::WritePrepared as c_int => {
427 Some(TxnDBWritePolicy::WritePrepared)
428 }
429 n if n == TxnDBWritePolicy::WriteUnprepared as c_int => {
430 Some(TxnDBWritePolicy::WriteUnprepared)
431 }
432 _ => None,
433 }
434 }
435}
436
437pub struct TransactionDBOptions {
438 pub(crate) inner: *mut ffi::rocksdb_transactiondb_options_t,
439}
440
441unsafe impl Send for TransactionDBOptions {}
442unsafe impl Sync for TransactionDBOptions {}
443
444impl Default for TransactionDBOptions {
445 fn default() -> Self {
446 let txn_db_opts = unsafe { ffi::rocksdb_transactiondb_options_create() };
447 assert!(
448 !txn_db_opts.is_null(),
449 "Could not create RocksDB transaction_db options"
450 );
451 Self { inner: txn_db_opts }
452 }
453}
454
455impl TransactionDBOptions {
456 pub fn new() -> TransactionDBOptions {
457 TransactionDBOptions::default()
458 }
459
460 /// Specifies the wait timeout in milliseconds when writing a key
461 /// outside a transaction (i.e. by calling `TransactionDB::put` directly).
462 ///
463 /// If 0, no waiting is done if a lock cannot instantly be acquired.
464 /// If negative, there is no timeout and will block indefinitely when acquiring
465 /// a lock.
466 ///
467 /// Not using a timeout can lead to deadlocks. Currently, there
468 /// is no deadlock-detection to recover from a deadlock. While DB writes
469 /// cannot deadlock with other DB writes, they can deadlock with a transaction.
470 /// A negative timeout should only be used if all transactions have a small
471 /// expiration set.
472 ///
473 /// Default: 1000(1s).
474 pub fn set_default_lock_timeout(&mut self, default_lock_timeout: i64) {
475 unsafe {
476 ffi::rocksdb_transactiondb_options_set_default_lock_timeout(
477 self.inner,
478 default_lock_timeout,
479 );
480 }
481 }
482
483 /// Specifies the default wait timeout in milliseconds when a transaction
484 /// attempts to lock a key if not specified in `TransactionOptions`.
485 ///
486 /// If 0, no waiting is done if a lock cannot instantly be acquired.
487 /// If negative, there is no timeout. Not using a timeout is not recommended
488 /// as it can lead to deadlocks. Currently, there is no deadlock-detection to
489 /// recover from a deadlock.
490 ///
491 /// Default: 1000(1s).
492 pub fn set_txn_lock_timeout(&mut self, txn_lock_timeout: i64) {
493 unsafe {
494 ffi::rocksdb_transactiondb_options_set_transaction_lock_timeout(
495 self.inner,
496 txn_lock_timeout,
497 );
498 }
499 }
500
501 /// Specifies the maximum number of keys that can be locked at the same time
502 /// per column family.
503 ///
504 /// If the number of locked keys is greater than `max_num_locks`, transaction
505 /// `writes` (or `get_for_update`) will return an error.
506 /// If this value is not positive, no limit will be enforced.
507 ///
508 /// Default: -1.
509 pub fn set_max_num_locks(&mut self, max_num_locks: i64) {
510 unsafe {
511 ffi::rocksdb_transactiondb_options_set_max_num_locks(self.inner, max_num_locks);
512 }
513 }
514
515 /// Specifies lock table stripes count.
516 ///
517 /// Increasing this value will increase the concurrency by dividing the lock
518 /// table (per column family) into more sub-tables, each with their own
519 /// separate mutex.
520 ///
521 /// Default: 16.
522 pub fn set_num_stripes(&mut self, num_stripes: usize) {
523 unsafe {
524 ffi::rocksdb_transactiondb_options_set_num_stripes(self.inner, num_stripes);
525 }
526 }
527
528 /// A flag to control for the whole DB whether user-defined timestamp based validation are
529 /// enabled when applicable. Only WriteCommittedTxn support user-defined timestamps so
530 /// this option only applies in this case.
531 pub fn set_enable_udt_validation(&mut self, val: bool) {
532 unsafe {
533 ffi::rocksdb_transactiondb_options_set_enable_udt_validation(self.inner, u8::from(val));
534 }
535 }
536
537 /// Returns the value of the `enable_udt_validation` option.
538 pub fn get_enable_udt_validation(&self) -> bool {
539 unsafe { ffi::rocksdb_transactiondb_options_get_enable_udt_validation(self.inner) != 0 }
540 }
541
542 /// Stores the number of latest deadlocks to track
543 pub fn set_max_num_deadlocks(&mut self, val: u32) {
544 unsafe {
545 ffi::rocksdb_transactiondb_options_set_max_num_deadlocks(self.inner, val);
546 }
547 }
548
549 /// Returns the value of the `max_num_deadlocks` option.
550 pub fn get_max_num_deadlocks(&self) -> u32 {
551 unsafe { ffi::rocksdb_transactiondb_options_get_max_num_deadlocks(self.inner) }
552 }
553
554 /// Increasing this value will increase the concurrency by dividing the lock table (per
555 /// column family) into more sub-tables, each with their own separate mutex.
556 pub fn get_num_stripes(&self) -> usize {
557 unsafe { ffi::rocksdb_transactiondb_options_get_num_stripes(self.inner) }
558 }
559
560 /// TODO(myabandeh): remove this option Note: this is a temporary option as a hot fix in
561 /// rollback of writeprepared txns in myrocks. MyRocks uses merge operands for autoinc
562 /// column id without however obtaining locks. This breaks the assumption behind the
563 /// rollback logic in myrocks. This hack of simply not rolling back merge operands works
564 /// for the special way that myrocks uses this operands.
565 pub fn set_rollback_merge_operands(&mut self, val: bool) {
566 unsafe {
567 ffi::rocksdb_transactiondb_options_set_rollback_merge_operands(
568 self.inner,
569 u8::from(val),
570 );
571 }
572 }
573
574 /// Returns the value of the `rollback_merge_operands` option.
575 pub fn get_rollback_merge_operands(&self) -> bool {
576 unsafe { ffi::rocksdb_transactiondb_options_get_rollback_merge_operands(self.inner) != 0 }
577 }
578
579 /// If true, the TransactionDB implementation might skip concurrency control unless it is
580 /// overridden by TransactionOptions or TransactionDBWriteOptimizations. This can be used
581 /// in conjunction with DBOptions::unordered_write when the TransactionDB is used solely
582 /// for write ordering rather than concurrency control.
583 pub fn set_skip_concurrency_control(&mut self, val: bool) {
584 unsafe {
585 ffi::rocksdb_transactiondb_options_set_skip_concurrency_control(
586 self.inner,
587 u8::from(val),
588 );
589 }
590 }
591
592 /// Returns the value of the `skip_concurrency_control` option.
593 pub fn get_skip_concurrency_control(&self) -> bool {
594 unsafe { ffi::rocksdb_transactiondb_options_get_skip_concurrency_control(self.inner) != 0 }
595 }
596
597 /// Deprecated, this option has no effect and may be removed in the future. Use
598 /// TransactionOptions::large_txn_commit_optimize_threshold instead.
599 ///
600 /// This option is only valid for write committed. If the number of updates in a
601 /// transaction is at least this threshold, then the transaction commit will skip
602 /// insertions into memtable as an optimization to reduce commit latency. See comment for
603 /// TransactionOptions::commit_bypass_memtable for more detail. Setting
604 /// TransactionOptions::commit_bypass_memtable to true takes precedence over this option.
605 pub fn set_txn_commit_bypass_memtable_threshold(&mut self, val: u32) {
606 unsafe {
607 ffi::rocksdb_transactiondb_options_set_txn_commit_bypass_memtable_threshold(
608 self.inner, val,
609 );
610 }
611 }
612
613 /// Returns the value of the `txn_commit_bypass_memtable_threshold` option.
614 pub fn get_txn_commit_bypass_memtable_threshold(&self) -> u32 {
615 unsafe {
616 ffi::rocksdb_transactiondb_options_get_txn_commit_bypass_memtable_threshold(self.inner)
617 }
618 }
619
620 /// EXPERIMENTAL
621 ///
622 /// Flag to enable/disable the per key point lock manager.
623 pub fn set_use_per_key_point_lock_mgr(&mut self, val: bool) {
624 unsafe {
625 ffi::rocksdb_transactiondb_options_set_use_per_key_point_lock_mgr(
626 self.inner,
627 u8::from(val),
628 );
629 }
630 }
631
632 /// Returns the value of the `use_per_key_point_lock_mgr` option.
633 pub fn get_use_per_key_point_lock_mgr(&self) -> bool {
634 unsafe {
635 ffi::rocksdb_transactiondb_options_get_use_per_key_point_lock_mgr(self.inner) != 0
636 }
637 }
638
639 /// Returns the current `default_lock_timeout` setting.
640 ///
641 /// See [`Self::set_default_lock_timeout`] for what this controls.
642 pub fn get_default_lock_timeout(&self) -> i64 {
643 unsafe { ffi::rocksdb_transactiondb_options_get_default_lock_timeout(self.inner) }
644 }
645
646 /// Returns the current `default_write_batch_flush_threshold` setting.
647 ///
648 /// See [`Self::set_default_write_batch_flush_threshold`] for what this controls.
649 pub fn get_default_write_batch_flush_threshold(&self) -> i64 {
650 unsafe {
651 ffi::rocksdb_transactiondb_options_get_default_write_batch_flush_threshold(self.inner)
652 }
653 }
654
655 /// Returns the current `max_num_locks` setting.
656 ///
657 /// See [`Self::set_max_num_locks`] for what this controls.
658 pub fn get_max_num_locks(&self) -> i64 {
659 unsafe { ffi::rocksdb_transactiondb_options_get_max_num_locks(self.inner) }
660 }
661
662 /// If positive, specifies the default wait timeout in milliseconds when a transaction
663 /// attempts to lock a key if not specified by TransactionOptions::lock_timeout.
664 ///
665 /// If 0, no waiting is done if a lock cannot instantly be acquired. If negative, there is
666 /// no timeout. Not using a timeout is not recommended as it can lead to deadlocks.
667 /// Currently, there is no deadlock-detection to recover from a deadlock.
668 pub fn get_transaction_lock_timeout(&self) -> i64 {
669 unsafe { ffi::rocksdb_transactiondb_options_get_transaction_lock_timeout(self.inner) }
670 }
671
672 /// This option is only valid for write unprepared. If a write batch exceeds this
673 /// threshold, then the transaction will implicitly flush the currently pending writes
674 /// into the database. A value of 0 or less means no limit.
675 pub fn set_default_write_batch_flush_threshold(&mut self, val: i64) {
676 unsafe {
677 ffi::rocksdb_transactiondb_options_set_default_write_batch_flush_threshold(
678 self.inner, val,
679 );
680 }
681 }
682
683 /// Sets when transaction data becomes visible in the DB.
684 ///
685 /// Only read while opening a [`TransactionDB`], so this has to be set before the
686 /// open call. See [`TxnDBWritePolicy`] for what each policy costs.
687 ///
688 /// Default: [`TxnDBWritePolicy::WriteCommitted`].
689 ///
690 /// [`TransactionDB`]: crate::TransactionDB
691 pub fn set_write_policy(&mut self, policy: TxnDBWritePolicy) {
692 unsafe {
693 ffi::rocksdb_transactiondb_options_set_write_policy(self.inner, policy as c_int);
694 }
695 }
696
697 /// The write policy set by [`Self::set_write_policy`].
698 ///
699 /// [`TxnDBWritePolicy`] covers every policy RocksDB defines today, so `None` only
700 /// shows up if a future release adds one.
701 pub fn get_write_policy(&self) -> Option<TxnDBWritePolicy> {
702 let raw = unsafe { ffi::rocksdb_transactiondb_options_get_write_policy(self.inner) };
703 TxnDBWritePolicy::try_from_raw(raw)
704 }
705}
706
707impl Drop for TransactionDBOptions {
708 fn drop(&mut self) {
709 unsafe {
710 ffi::rocksdb_transactiondb_options_destroy(self.inner);
711 }
712 }
713}
714
715pub struct OptimisticTransactionOptions {
716 pub(crate) inner: *mut ffi::rocksdb_optimistictransaction_options_t,
717}
718
719unsafe impl Send for OptimisticTransactionOptions {}
720unsafe impl Sync for OptimisticTransactionOptions {}
721
722impl Default for OptimisticTransactionOptions {
723 fn default() -> Self {
724 let txn_opts = unsafe { ffi::rocksdb_optimistictransaction_options_create() };
725 assert!(
726 !txn_opts.is_null(),
727 "Could not create RocksDB optimistic transaction options"
728 );
729 Self { inner: txn_opts }
730 }
731}
732
733impl OptimisticTransactionOptions {
734 pub fn new() -> OptimisticTransactionOptions {
735 OptimisticTransactionOptions::default()
736 }
737
738 /// Specifies use snapshot or not.
739 ///
740 /// Default: false.
741 ///
742 /// If a transaction has a snapshot set, the transaction will ensure that
743 /// any keys successfully written(or fetched via `get_for_update`) have not
744 /// been modified outside the transaction since the time the snapshot was
745 /// set.
746 /// If a snapshot has not been set, the transaction guarantees that keys have
747 /// not been modified since the time each key was first written (or fetched via
748 /// `get_for_update`).
749 ///
750 /// Using snapshot will provide stricter isolation guarantees at the
751 /// expense of potentially more transaction failures due to conflicts with
752 /// other writes.
753 ///
754 /// Calling `set_snapshot` will not affect the version of Data returned by `get`
755 /// methods.
756 pub fn set_snapshot(&mut self, snapshot: bool) {
757 unsafe {
758 ffi::rocksdb_optimistictransaction_options_set_set_snapshot(
759 self.inner,
760 u8::from(snapshot),
761 );
762 }
763 }
764
765 /// Setting set_snapshot=true is the same as calling Transaction::SetSnapshot().
766 pub fn get_set_snapshot(&self) -> bool {
767 unsafe { ffi::rocksdb_optimistictransaction_options_get_set_snapshot(self.inner) != 0 }
768 }
769}
770
771impl Drop for OptimisticTransactionOptions {
772 fn drop(&mut self) {
773 unsafe {
774 ffi::rocksdb_optimistictransaction_options_destroy(self.inner);
775 }
776 }
777}
778
779/// How an [`OptimisticTransactionDB`] checks a transaction for write conflicts at commit.
780///
781/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
782// The C API has no constants for these, unlike `TxnDBWritePolicy`. The discriminants are
783// spelled out in `rocksdb::OccValidationPolicy` in
784// include/rocksdb/utilities/optimistic_transaction_db.h and must stay in step with it.
785#[derive(Debug, Copy, Clone, PartialEq, Eq)]
786#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
787pub enum OccValidationPolicy {
788 /// Validate serially at commit, after the transaction has entered the write group.
789 ///
790 /// Validation is single threaded because the write group is. Simple, but it can
791 /// suffer from high mutex contention. See
792 /// <https://github.com/facebook/rocksdb/issues/4402>.
793 ValidateSerial = 0,
794
795 /// Validate in parallel before the transaction enters the write group.
796 ///
797 /// Each transaction takes locks for its write set in a well defined order, off the
798 /// write group, which cuts the mutex contention. This is the default. The locks come
799 /// from the pool sized by [`OptimisticTransactionDBOptions::set_occ_lock_buckets`]
800 /// or supplied by [`OptimisticTransactionDBOptions::set_shared_lock_buckets`].
801 ValidateParallel = 1,
802}
803
804impl OccValidationPolicy {
805 /// Decodes a raw `rocksdb::OccValidationPolicy`.
806 ///
807 /// This covers every policy RocksDB defines today, so `None` only means a future
808 /// release added one.
809 pub(crate) fn try_from_raw(raw: c_int) -> Option<Self> {
810 match raw {
811 n if n == OccValidationPolicy::ValidateSerial as c_int => {
812 Some(OccValidationPolicy::ValidateSerial)
813 }
814 n if n == OccValidationPolicy::ValidateParallel as c_int => {
815 Some(OccValidationPolicy::ValidateParallel)
816 }
817 _ => None,
818 }
819 }
820}
821
822struct OccLockBucketsWrapper {
823 inner: NonNull<ffi::rocksdb_optimistictransactiondb_lock_buckets_t>,
824}
825
826// The C handle is a `std::shared_ptr<OccLockBuckets>` and nothing else, so moving it
827// between threads moves a refcounted pointer. Every call this crate makes on it either
828// reads the pool's memory usage through a `const` method or copies the `shared_ptr` into
829// an options struct, and shared_ptr refcounting is atomic. The `Arc` in `OccLockBuckets`
830// is what guarantees the `_destroy` call is unshared.
831unsafe impl Send for OccLockBucketsWrapper {}
832unsafe impl Sync for OccLockBucketsWrapper {}
833
834impl Drop for OccLockBucketsWrapper {
835 fn drop(&mut self) {
836 unsafe {
837 ffi::rocksdb_optimistictransactiondb_lock_buckets_destroy(self.inner.as_ptr());
838 }
839 }
840}
841
842/// A pool of mutex locks used to validate optimistic transactions.
843///
844/// Only consulted when the validation policy is
845/// [`OccValidationPolicy::ValidateParallel`]. Hand it to
846/// [`OptimisticTransactionDBOptions::set_shared_lock_buckets`] to make several databases
847/// validate against one pool instead of each allocating its own.
848///
849/// Cloning is cheap and produces another handle to the same pool. The pool itself lives
850/// as long as any handle or any options value or database that was given one, so it is
851/// fine to drop every Rust handle right after the `set_shared_lock_buckets` call.
852#[derive(Clone)]
853pub struct OccLockBuckets(Arc<OccLockBucketsWrapper>);
854
855impl OccLockBuckets {
856 /// Allocates a pool of `bucket_count` locks.
857 ///
858 /// More buckets mean less contention between transactions validating at the same
859 /// time, and more memory. `cache_aligned` pads each lock out to a cache line, which
860 /// avoids false sharing between neighbouring buckets at the cost of more memory
861 /// again. RocksDB has historically used `false` here.
862 ///
863 /// # Panics
864 ///
865 /// Panics if RocksDB could not allocate the pool.
866 pub fn new(bucket_count: usize, cache_aligned: bool) -> Self {
867 let inner = NonNull::new(unsafe {
868 ffi::rocksdb_optimistictransactiondb_lock_buckets_create(
869 bucket_count,
870 u8::from(cache_aligned),
871 )
872 })
873 .expect("Could not create RocksDB OCC lock buckets");
874 Self(Arc::new(OccLockBucketsWrapper { inner }))
875 }
876
877 /// Estimates how much memory this pool occupies, in bytes.
878 pub fn approximate_memory_usage(&self) -> usize {
879 unsafe {
880 ffi::rocksdb_optimistictransactiondb_lock_buckets_approximate_memory_usage(
881 self.0.inner.as_ptr(),
882 )
883 }
884 }
885}
886
887/// Options for opening an [`OptimisticTransactionDB`].
888///
889/// These control conflict validation only. Everything else about the database still
890/// comes from [`Options`], which is passed alongside these to the `open` call.
891///
892/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
893/// [`Options`]: crate::Options
894pub struct OptimisticTransactionDBOptions {
895 pub(crate) inner: *mut ffi::rocksdb_optimistictransactiondb_options_t,
896}
897
898// The C handle wraps a plain `OptimisticTransactionDBOptions` struct: an enum, a
899// `uint32_t` and a `shared_ptr`. It owns no thread-local or thread-affine state, so it
900// can move between threads. Every mutating call here takes `&mut self`, so a shared `&`
901// only ever reaches the getters, which read those fields.
902unsafe impl Send for OptimisticTransactionDBOptions {}
903unsafe impl Sync for OptimisticTransactionDBOptions {}
904
905impl Default for OptimisticTransactionDBOptions {
906 fn default() -> Self {
907 let otxn_db_opts = unsafe { ffi::rocksdb_optimistictransactiondb_options_create() };
908 assert!(
909 !otxn_db_opts.is_null(),
910 "Could not create RocksDB optimistic transaction_db options"
911 );
912 Self {
913 inner: otxn_db_opts,
914 }
915 }
916}
917
918impl OptimisticTransactionDBOptions {
919 pub fn new() -> OptimisticTransactionDBOptions {
920 OptimisticTransactionDBOptions::default()
921 }
922
923 /// Specifies how transactions are checked for write conflicts at commit.
924 ///
925 /// Default: [`OccValidationPolicy::ValidateParallel`].
926 pub fn set_validate_policy(&mut self, policy: OccValidationPolicy) {
927 unsafe {
928 ffi::rocksdb_optimistictransactiondb_options_set_validate_policy(
929 self.inner,
930 policy as c_int,
931 );
932 }
933 }
934
935 /// The validation policy set by [`Self::set_validate_policy`].
936 ///
937 /// [`OccValidationPolicy`] covers every policy RocksDB defines today, so `None` only
938 /// shows up if a future release adds one.
939 pub fn get_validate_policy(&self) -> Option<OccValidationPolicy> {
940 let raw =
941 unsafe { ffi::rocksdb_optimistictransactiondb_options_get_validate_policy(self.inner) };
942 OccValidationPolicy::try_from_raw(raw)
943 }
944
945 /// Specifies how many striped mutex locks to allocate for validating transactions.
946 ///
947 /// Only used when the validation policy is
948 /// [`OccValidationPolicy::ValidateParallel`] and no pool was supplied through
949 /// [`Self::set_shared_lock_buckets`]. A larger count potentially reduces contention
950 /// but uses more memory.
951 ///
952 /// Default: 1048576 (1 << 20).
953 pub fn set_occ_lock_buckets(&mut self, num_buckets: u32) {
954 unsafe {
955 ffi::rocksdb_optimistictransactiondb_options_set_occ_lock_buckets(
956 self.inner,
957 num_buckets,
958 );
959 }
960 }
961
962 /// Returns the value of the `occ_lock_buckets` option.
963 pub fn get_occ_lock_buckets(&self) -> u32 {
964 unsafe { ffi::rocksdb_optimistictransactiondb_options_get_occ_lock_buckets(self.inner) }
965 }
966
967 /// Validates against `buckets` instead of a pool private to this database.
968 ///
969 /// Give the same [`OccLockBuckets`] to several databases and they share one set of
970 /// locks, which bounds the total memory spent on validation. Ignored unless the
971 /// validation policy is [`OccValidationPolicy::ValidateParallel`], and it overrides
972 /// [`Self::set_occ_lock_buckets`] when set.
973 ///
974 /// These options take their own reference to the pool, so `buckets` may be dropped
975 /// as soon as this returns.
976 pub fn set_shared_lock_buckets(&mut self, buckets: &OccLockBuckets) {
977 unsafe {
978 ffi::rocksdb_optimistictransactiondb_options_set_shared_lock_buckets(
979 self.inner,
980 buckets.0.inner.as_ptr(),
981 );
982 }
983 }
984}
985
986impl Drop for OptimisticTransactionDBOptions {
987 fn drop(&mut self) {
988 unsafe {
989 ffi::rocksdb_optimistictransactiondb_options_destroy(self.inner);
990 }
991 }
992}