slatedb 0.4.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -0
- data/ext/slatedb/Cargo.toml +8 -8
- data/ext/slatedb/src/admin.rs +21 -0
- data/ext/slatedb/src/database.rs +136 -41
- data/ext/slatedb/src/transaction.rs +21 -8
- data/ext/slatedb/src/write_batch.rs +2 -2
- data/lib/slatedb/admin.rb +28 -0
- data/lib/slatedb/database.rb +70 -2
- data/lib/slatedb/reader.rb +13 -8
- data/lib/slatedb/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c46e1c81aa2f9f98834847d89ebe70a3caa18dd7307ec864d2d18fb448b8c366
|
|
4
|
+
data.tar.gz: 1e24f5bb9dc914c3f9f5fe7025d481b0eb93160ffd5834f78d039c333d66e434
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b5fce35dda024e1011917c41685c88eadf9537558bcc643c06e4db988fc99dd3819f4d0b5f5be1ff5f660a33053b3c4b6fc579e1723523edc940cef67682d39
|
|
7
|
+
data.tar.gz: e0025127be66530aa82e64bcc98141b20cf5e5fd0a66290f32bcd88b16b4b7143a144238f85b0f209c7f7fc56477d85cfa3556f5b1808940f31d55086bf7e99d
|
data/README.md
CHANGED
|
@@ -64,6 +64,34 @@ SlateDb::Database.open("/tmp/mydb") do |db|
|
|
|
64
64
|
end # automatically closed
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
### Compaction Controls
|
|
68
|
+
|
|
69
|
+
SlateDB 0.17 adds controls for how long compaction inputs remain protected from
|
|
70
|
+
garbage collection and when to consolidate sorted runs:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
SlateDb::Database.open("mydb",
|
|
74
|
+
url: "s3://mybucket/path",
|
|
75
|
+
compactor_checkpoint_lifetime: 30 * 60_000,
|
|
76
|
+
sorted_run_consolidation_threshold: 8) do |db|
|
|
77
|
+
db.put("key", "value")
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`compactor_checkpoint_lifetime` is in milliseconds and defaults to 15 minutes.
|
|
82
|
+
Reads, scans, snapshots, and transactions using old compaction inputs must finish
|
|
83
|
+
within that time; after it expires, garbage collection can remove those inputs.
|
|
84
|
+
|
|
85
|
+
`sorted_run_consolidation_threshold` starts fallback consolidation when the
|
|
86
|
+
projected sorted-run count exceeds the threshold. It is not a hard limit.
|
|
87
|
+
Zero disables it (the default); use at least two to enable it. SlateDB warns and
|
|
88
|
+
disables the trigger for one. Low thresholds can increase storage reads and writes.
|
|
89
|
+
|
|
90
|
+
### Key Size
|
|
91
|
+
|
|
92
|
+
Keys can now exceed the previous 65,535-byte limit, up to SlateDB's new
|
|
93
|
+
4,294,967,295-byte limit. Very large keys still require enough memory to process.
|
|
94
|
+
|
|
67
95
|
### Persistent Storage
|
|
68
96
|
|
|
69
97
|
For persistent storage, provide an object store URL:
|
|
@@ -501,6 +529,14 @@ SlateDb::Reader.open("/tmp/mydb", url: "s3://bucket/path") do |reader|
|
|
|
501
529
|
reader.scan("prefix:").each do |k, v|
|
|
502
530
|
puts "#{k}: #{v}"
|
|
503
531
|
end
|
|
532
|
+
|
|
533
|
+
# Range and prefix scans accept :asc/:ascending or :desc/:descending.
|
|
534
|
+
reader.scan("user:100", "user:200", order: :desc).each do |k, v|
|
|
535
|
+
puts "#{k}: #{v}"
|
|
536
|
+
end
|
|
537
|
+
reader.scan_prefix("user:", from: "100", to: "200", order: :desc).each do |k, v|
|
|
538
|
+
puts "#{k}: #{v}"
|
|
539
|
+
end
|
|
504
540
|
end
|
|
505
541
|
|
|
506
542
|
# Open at a specific checkpoint (pins the reader to that checkpoint's state)
|
|
@@ -561,8 +597,19 @@ admin.run_gc(min_age: 3600_000) # Set min age for all directorie
|
|
|
561
597
|
admin.run_gc(manifest_min_age: 86400_000) # Custom age for manifest (1 day)
|
|
562
598
|
admin.run_gc(wal_min_age: 60_000) # Custom age for WAL (1 minute)
|
|
563
599
|
admin.run_gc(compacted_min_age: 60_000) # Custom age for compacted (1 minute)
|
|
600
|
+
|
|
601
|
+
# Delete a database (SlateDB >= 0.16.0)
|
|
602
|
+
would_delete = admin.delete_db # Dry run: returns the object paths
|
|
603
|
+
# that *would* be deleted, deleting nothing
|
|
604
|
+
deleted = admin.delete_db(confirm: true) # Actually delete; returns the removed paths
|
|
564
605
|
```
|
|
565
606
|
|
|
607
|
+
`delete_db` is idempotent, and a confirmed delete of a directory that is not a
|
|
608
|
+
SlateDB database (no manifest and no in-progress deletion marker) is refused, so
|
|
609
|
+
a mistyped path cannot wipe an unrelated directory. For a cloned database, any
|
|
610
|
+
checkpoints it pinned in parent databases are stripped before its own objects
|
|
611
|
+
are removed.
|
|
612
|
+
|
|
566
613
|
### Flushing
|
|
567
614
|
|
|
568
615
|
Ensure all writes are persisted:
|
|
@@ -572,6 +619,26 @@ db.put("key", "value")
|
|
|
572
619
|
db.flush
|
|
573
620
|
```
|
|
574
621
|
|
|
622
|
+
By default, `flush` flushes the write-ahead log (WAL) if enabled, otherwise the
|
|
623
|
+
memtable. You can choose explicitly with `flush_type:` (SlateDB >= 0.16.0):
|
|
624
|
+
|
|
625
|
+
```ruby
|
|
626
|
+
db.flush(flush_type: :wal) # Flush the write-ahead log
|
|
627
|
+
db.flush(flush_type: :memtable) # Freeze and flush memtable entries to object storage
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Closing
|
|
631
|
+
|
|
632
|
+
Closing flushes the active memtable first by default, so durable writes are not
|
|
633
|
+
lost. You can control the final flush when closing (SlateDB >= 0.16.0):
|
|
634
|
+
|
|
635
|
+
```ruby
|
|
636
|
+
db.close # Flush the memtable, then close (default)
|
|
637
|
+
db.close(flush_type: :wal) # Flush the WAL before closing
|
|
638
|
+
db.close(flush: false) # Close without a final flush; writes that are not
|
|
639
|
+
# yet durable may be lost
|
|
640
|
+
```
|
|
641
|
+
|
|
575
642
|
## Thread Safety
|
|
576
643
|
|
|
577
644
|
**SlateDB is fully thread-safe and optimized for concurrent access.**
|
data/ext/slatedb/Cargo.toml
CHANGED
|
@@ -11,13 +11,13 @@ name = "slatedb"
|
|
|
11
11
|
crate-type = ["cdylib"]
|
|
12
12
|
|
|
13
13
|
[dependencies]
|
|
14
|
-
slatedb = "0.
|
|
15
|
-
magnus = { version = "0.
|
|
16
|
-
rb-sys = { version = "0.9.
|
|
17
|
-
tokio = { version = "1.
|
|
18
|
-
bytes = "1.
|
|
19
|
-
serde_json = "1.0.
|
|
14
|
+
slatedb = "0.17.0"
|
|
15
|
+
magnus = { version = "0.9.3", features = ["rb-sys"] }
|
|
16
|
+
rb-sys = { version = "0.9.130", features = ["stable-api-compiled-fallback"] }
|
|
17
|
+
tokio = { version = "1.54.1", features = ["rt-multi-thread", "sync"] }
|
|
18
|
+
bytes = "1.12.1"
|
|
19
|
+
serde_json = "1.0.154"
|
|
20
20
|
url = "2.5.8"
|
|
21
21
|
once_cell = "1.21.4"
|
|
22
|
-
log = "0.4.
|
|
23
|
-
uuid = "1.
|
|
22
|
+
log = "0.4.34"
|
|
23
|
+
uuid = "1.29.0"
|
data/ext/slatedb/src/admin.rs
CHANGED
|
@@ -265,6 +265,26 @@ impl Admin {
|
|
|
265
265
|
|
|
266
266
|
Ok(())
|
|
267
267
|
}
|
|
268
|
+
|
|
269
|
+
/// Delete a database, stripping any checkpoints it pinned in parent
|
|
270
|
+
/// databases (for clones) before removing its own objects (SlateDB >= 0.16.0).
|
|
271
|
+
///
|
|
272
|
+
/// # Arguments
|
|
273
|
+
/// * `confirm` - When `false` (the default), performs a dry run and returns
|
|
274
|
+
/// the list of object paths that *would* be deleted without touching
|
|
275
|
+
/// anything. When `true`, actually deletes and returns the object paths
|
|
276
|
+
/// that were removed.
|
|
277
|
+
///
|
|
278
|
+
/// A `confirm` delete of a directory with neither a manifest nor a leftover
|
|
279
|
+
/// `.deleting` marker is refused, so an incorrect path cannot wipe an
|
|
280
|
+
/// unrelated directory. The operation is idempotent.
|
|
281
|
+
///
|
|
282
|
+
/// # Returns
|
|
283
|
+
/// An array of object paths (deleted, or that would be deleted for a dry run).
|
|
284
|
+
pub fn delete_db(&self, confirm: bool) -> Result<Vec<String>, Error> {
|
|
285
|
+
let deleted = block_on_result(async { self.inner.delete_db(confirm).await })?;
|
|
286
|
+
Ok(deleted)
|
|
287
|
+
}
|
|
268
288
|
}
|
|
269
289
|
|
|
270
290
|
/// Define the Admin class on the SlateDb module.
|
|
@@ -282,6 +302,7 @@ pub fn define_admin_class(ruby: &Ruby, module: &magnus::RModule) -> Result<(), E
|
|
|
282
302
|
class.define_method("_refresh_checkpoint", method!(Admin::refresh_checkpoint, 2))?;
|
|
283
303
|
class.define_method("_delete_checkpoint", method!(Admin::delete_checkpoint, 1))?;
|
|
284
304
|
class.define_method("_run_gc", method!(Admin::run_gc, 1))?;
|
|
305
|
+
class.define_method("_delete_db", method!(Admin::delete_db, 1))?;
|
|
285
306
|
|
|
286
307
|
Ok(())
|
|
287
308
|
}
|
data/ext/slatedb/src/database.rs
CHANGED
|
@@ -4,7 +4,8 @@ use std::sync::{Arc, Mutex};
|
|
|
4
4
|
use magnus::prelude::*;
|
|
5
5
|
use magnus::{function, method, Error, RHash, Ruby};
|
|
6
6
|
use slatedb::config::{
|
|
7
|
-
|
|
7
|
+
CloseOptions, DurabilityLevel, FlushOptions, FlushType, MergeOptions, PutOptions, ReadOptions,
|
|
8
|
+
ScanOptions, Settings, Ttl, WriteOptions,
|
|
8
9
|
};
|
|
9
10
|
use slatedb::object_store::memory::InMemory;
|
|
10
11
|
use slatedb::{Db, IsolationLevel, IterationOrder, KeyValue};
|
|
@@ -79,13 +80,32 @@ impl Database {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
fn write_options_from_kwargs(kwargs: &RHash) -> Result<WriteOptions, Error> {
|
|
82
|
-
let await_durable = get_optional::<bool>(kwargs, "await_durable")?.unwrap_or(true);
|
|
83
83
|
let seqnum = get_optional::<u64>(kwargs, "seqnum")?.unwrap_or(0);
|
|
84
84
|
|
|
85
|
-
Ok(WriteOptions {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
Ok(WriteOptions { seqnum })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Parse the `await_durable` keyword argument (defaults to `true`).
|
|
89
|
+
///
|
|
90
|
+
/// As of SlateDB 0.16.0, `await_durable` is no longer a `WriteOptions`
|
|
91
|
+
/// field. Write methods return immediately after updating the in-memory
|
|
92
|
+
/// WAL/MemTable and hand back a `WriteHandle`; blocking until the write is
|
|
93
|
+
/// durable in object storage is done by awaiting that handle. This helper
|
|
94
|
+
/// preserves the previous ergonomic `await_durable:` behavior for callers.
|
|
95
|
+
fn await_durable_from_kwargs(kwargs: &RHash) -> Result<bool, Error> {
|
|
96
|
+
Ok(get_optional::<bool>(kwargs, "await_durable")?.unwrap_or(true))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Parse a `flush_type` name into a SlateDB `FlushType` (SlateDB >= 0.16.0).
|
|
100
|
+
fn parse_flush_type(name: &str) -> Result<FlushType, Error> {
|
|
101
|
+
match name {
|
|
102
|
+
"wal" => Ok(FlushType::Wal),
|
|
103
|
+
"memtable" | "mem_table" => Ok(FlushType::MemTable),
|
|
104
|
+
other => Err(invalid_argument_error(&format!(
|
|
105
|
+
"invalid flush_type: {} (expected 'wal' or 'memtable')",
|
|
106
|
+
other
|
|
107
|
+
))),
|
|
108
|
+
}
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
/// Open a database at the given path.
|
|
@@ -100,6 +120,23 @@ impl Database {
|
|
|
100
120
|
pub fn open(path: String, url: Option<String>, kwargs: RHash) -> Result<Self, Error> {
|
|
101
121
|
// Try string-based merge operator first, then proc-based
|
|
102
122
|
let merge_operator = parse_merge_operator(&kwargs)?.or(parse_merge_operator_proc(&kwargs)?);
|
|
123
|
+
let checkpoint_lifetime = get_optional::<u64>(&kwargs, "compactor_checkpoint_lifetime")?
|
|
124
|
+
.map(std::time::Duration::from_millis);
|
|
125
|
+
let consolidation_threshold =
|
|
126
|
+
get_optional::<usize>(&kwargs, "sorted_run_consolidation_threshold")?;
|
|
127
|
+
|
|
128
|
+
let mut settings = Settings::default();
|
|
129
|
+
if let Some(compactor) = settings.compactor_options.as_mut() {
|
|
130
|
+
if let Some(lifetime) = checkpoint_lifetime {
|
|
131
|
+
compactor.checkpoint_lifetime = lifetime;
|
|
132
|
+
}
|
|
133
|
+
if let Some(threshold) = consolidation_threshold {
|
|
134
|
+
compactor.scheduler_options.insert(
|
|
135
|
+
"sorted_run_consolidation_threshold".to_string(),
|
|
136
|
+
threshold.to_string(),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
103
140
|
|
|
104
141
|
let db = block_on_result(async {
|
|
105
142
|
let object_store: Arc<dyn slatedb::object_store::ObjectStore> =
|
|
@@ -109,7 +146,7 @@ impl Database {
|
|
|
109
146
|
Arc::new(InMemory::new())
|
|
110
147
|
};
|
|
111
148
|
|
|
112
|
-
let mut builder = Db::builder(path, object_store);
|
|
149
|
+
let mut builder = Db::builder(path, object_store).with_settings(settings);
|
|
113
150
|
if let Some(merge_operator) = merge_operator {
|
|
114
151
|
builder = builder.with_merge_operator(merge_operator);
|
|
115
152
|
}
|
|
@@ -249,16 +286,14 @@ impl Database {
|
|
|
249
286
|
}
|
|
250
287
|
|
|
251
288
|
let put_opts = PutOptions { ttl: Ttl::Default };
|
|
252
|
-
|
|
253
|
-
let write_opts = WriteOptions {
|
|
254
|
-
await_durable: true,
|
|
255
|
-
seqnum: 0,
|
|
256
|
-
};
|
|
289
|
+
let write_opts = WriteOptions::default();
|
|
257
290
|
|
|
258
291
|
block_on_result(async {
|
|
259
|
-
self
|
|
292
|
+
let handle = self
|
|
293
|
+
.inner
|
|
260
294
|
.put_with_options(key.as_bytes(), value.as_bytes(), &put_opts, &write_opts)
|
|
261
|
-
.await
|
|
295
|
+
.await?;
|
|
296
|
+
handle.await_durable().await
|
|
262
297
|
})?;
|
|
263
298
|
self.increment_metric("db.put.count");
|
|
264
299
|
|
|
@@ -280,18 +315,23 @@ impl Database {
|
|
|
280
315
|
let ttl = get_optional::<u64>(&kwargs, "ttl")?;
|
|
281
316
|
let put_opts = PutOptions {
|
|
282
317
|
ttl: match ttl {
|
|
283
|
-
Some(ms) => Ttl::
|
|
318
|
+
Some(ms) => Ttl::ExpireAfterMillis(ms),
|
|
284
319
|
None => Ttl::Default,
|
|
285
320
|
},
|
|
286
321
|
};
|
|
287
322
|
|
|
288
|
-
// Parse await_durable
|
|
289
323
|
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
324
|
+
let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
|
|
290
325
|
|
|
291
326
|
block_on_result(async {
|
|
292
|
-
self
|
|
327
|
+
let handle = self
|
|
328
|
+
.inner
|
|
293
329
|
.put_with_options(key.as_bytes(), value.as_bytes(), &put_opts, &write_opts)
|
|
294
|
-
.await
|
|
330
|
+
.await?;
|
|
331
|
+
if await_durable {
|
|
332
|
+
handle.await_durable().await?;
|
|
333
|
+
}
|
|
334
|
+
Ok(())
|
|
295
335
|
})?;
|
|
296
336
|
self.increment_metric("db.put_with_options.count");
|
|
297
337
|
|
|
@@ -307,15 +347,14 @@ impl Database {
|
|
|
307
347
|
return Err(invalid_argument_error("key cannot be empty"));
|
|
308
348
|
}
|
|
309
349
|
|
|
310
|
-
let write_opts = WriteOptions
|
|
311
|
-
await_durable: true,
|
|
312
|
-
seqnum: 0,
|
|
313
|
-
};
|
|
350
|
+
let write_opts = WriteOptions::default();
|
|
314
351
|
|
|
315
352
|
block_on_result(async {
|
|
316
|
-
self
|
|
353
|
+
let handle = self
|
|
354
|
+
.inner
|
|
317
355
|
.delete_with_options(key.as_bytes(), &write_opts)
|
|
318
|
-
.await
|
|
356
|
+
.await?;
|
|
357
|
+
handle.await_durable().await
|
|
319
358
|
})?;
|
|
320
359
|
self.increment_metric("db.delete.count");
|
|
321
360
|
|
|
@@ -333,11 +372,17 @@ impl Database {
|
|
|
333
372
|
}
|
|
334
373
|
|
|
335
374
|
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
375
|
+
let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
|
|
336
376
|
|
|
337
377
|
block_on_result(async {
|
|
338
|
-
self
|
|
378
|
+
let handle = self
|
|
379
|
+
.inner
|
|
339
380
|
.delete_with_options(key.as_bytes(), &write_opts)
|
|
340
|
-
.await
|
|
381
|
+
.await?;
|
|
382
|
+
if await_durable {
|
|
383
|
+
handle.await_durable().await?;
|
|
384
|
+
}
|
|
385
|
+
Ok(())
|
|
341
386
|
})?;
|
|
342
387
|
self.increment_metric("db.delete_with_options.count");
|
|
343
388
|
|
|
@@ -550,7 +595,10 @@ impl Database {
|
|
|
550
595
|
/// * `batch` - The WriteBatch to write
|
|
551
596
|
pub fn write(&self, batch: &WriteBatch) -> Result<(), Error> {
|
|
552
597
|
let batch_inner = batch.take()?;
|
|
553
|
-
block_on_result(async {
|
|
598
|
+
block_on_result(async {
|
|
599
|
+
let handle = self.inner.write(batch_inner).await?;
|
|
600
|
+
handle.await_durable().await
|
|
601
|
+
})?;
|
|
554
602
|
Ok(())
|
|
555
603
|
}
|
|
556
604
|
|
|
@@ -561,13 +609,19 @@ impl Database {
|
|
|
561
609
|
/// * `kwargs` - Keyword arguments (await_durable, seqnum)
|
|
562
610
|
pub fn write_with_options(&self, batch: &WriteBatch, kwargs: RHash) -> Result<(), Error> {
|
|
563
611
|
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
612
|
+
let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
|
|
564
613
|
|
|
565
614
|
let batch_inner = batch.take()?;
|
|
566
615
|
|
|
567
616
|
block_on_result(async {
|
|
568
|
-
self
|
|
617
|
+
let handle = self
|
|
618
|
+
.inner
|
|
569
619
|
.write_with_options(batch_inner, &write_opts)
|
|
570
|
-
.await
|
|
620
|
+
.await?;
|
|
621
|
+
if await_durable {
|
|
622
|
+
handle.await_durable().await?;
|
|
623
|
+
}
|
|
624
|
+
Ok(())
|
|
571
625
|
})?;
|
|
572
626
|
|
|
573
627
|
Ok(())
|
|
@@ -584,16 +638,14 @@ impl Database {
|
|
|
584
638
|
}
|
|
585
639
|
|
|
586
640
|
let merge_opts = MergeOptions { ttl: Ttl::Default };
|
|
587
|
-
|
|
588
|
-
let write_opts = WriteOptions {
|
|
589
|
-
await_durable: true,
|
|
590
|
-
seqnum: 0,
|
|
591
|
-
};
|
|
641
|
+
let write_opts = WriteOptions::default();
|
|
592
642
|
|
|
593
643
|
block_on_result(async {
|
|
594
|
-
self
|
|
644
|
+
let handle = self
|
|
645
|
+
.inner
|
|
595
646
|
.merge_with_options(key.as_bytes(), value.as_bytes(), &merge_opts, &write_opts)
|
|
596
|
-
.await
|
|
647
|
+
.await?;
|
|
648
|
+
handle.await_durable().await
|
|
597
649
|
})?;
|
|
598
650
|
|
|
599
651
|
Ok(())
|
|
@@ -618,17 +670,23 @@ impl Database {
|
|
|
618
670
|
let ttl = get_optional::<u64>(&kwargs, "ttl")?;
|
|
619
671
|
let merge_opts = MergeOptions {
|
|
620
672
|
ttl: match ttl {
|
|
621
|
-
Some(ms) => Ttl::
|
|
673
|
+
Some(ms) => Ttl::ExpireAfterMillis(ms),
|
|
622
674
|
None => Ttl::Default,
|
|
623
675
|
},
|
|
624
676
|
};
|
|
625
677
|
|
|
626
678
|
let write_opts = Self::write_options_from_kwargs(&kwargs)?;
|
|
679
|
+
let await_durable = Self::await_durable_from_kwargs(&kwargs)?;
|
|
627
680
|
|
|
628
681
|
block_on_result(async {
|
|
629
|
-
self
|
|
682
|
+
let handle = self
|
|
683
|
+
.inner
|
|
630
684
|
.merge_with_options(key.as_bytes(), value.as_bytes(), &merge_opts, &write_opts)
|
|
631
|
-
.await
|
|
685
|
+
.await?;
|
|
686
|
+
if await_durable {
|
|
687
|
+
handle.await_durable().await?;
|
|
688
|
+
}
|
|
689
|
+
Ok(())
|
|
632
690
|
})?;
|
|
633
691
|
|
|
634
692
|
Ok(())
|
|
@@ -708,6 +766,19 @@ impl Database {
|
|
|
708
766
|
Ok(())
|
|
709
767
|
}
|
|
710
768
|
|
|
769
|
+
/// Flush the database, choosing the flush type (SlateDB >= 0.16.0).
|
|
770
|
+
///
|
|
771
|
+
/// # Arguments
|
|
772
|
+
/// * `flush_type` - "wal" to flush the WAL, or "memtable" to freeze and
|
|
773
|
+
/// flush memtable entries to object storage.
|
|
774
|
+
pub fn flush_with_options(&self, flush_type: String) -> Result<(), Error> {
|
|
775
|
+
let options = FlushOptions {
|
|
776
|
+
flush_type: Self::parse_flush_type(&flush_type)?,
|
|
777
|
+
};
|
|
778
|
+
block_on_result(async { self.inner.flush_with_options(options).await })?;
|
|
779
|
+
Ok(())
|
|
780
|
+
}
|
|
781
|
+
|
|
711
782
|
/// Return the database metrics registry.
|
|
712
783
|
pub fn metrics(&self) -> Result<Metrics, Error> {
|
|
713
784
|
Ok(Metrics::new(self.metrics.clone()))
|
|
@@ -718,6 +789,22 @@ impl Database {
|
|
|
718
789
|
block_on_result(async { self.inner.close().await })?;
|
|
719
790
|
Ok(())
|
|
720
791
|
}
|
|
792
|
+
|
|
793
|
+
/// Close the database, controlling the final flush (SlateDB >= 0.16.0).
|
|
794
|
+
///
|
|
795
|
+
/// # Arguments
|
|
796
|
+
/// * `flush_type` - `Some("wal"|"memtable")` to flush before closing, or
|
|
797
|
+
/// `None` to close without a final flush (writes that are not yet durable
|
|
798
|
+
/// may be lost).
|
|
799
|
+
pub fn close_with_options(&self, flush_type: Option<String>) -> Result<(), Error> {
|
|
800
|
+
let flush_type = match flush_type {
|
|
801
|
+
Some(name) => Some(Self::parse_flush_type(&name)?),
|
|
802
|
+
None => None,
|
|
803
|
+
};
|
|
804
|
+
let options = CloseOptions { flush_type };
|
|
805
|
+
block_on_result(async { self.inner.close_with_options(options).await })?;
|
|
806
|
+
Ok(())
|
|
807
|
+
}
|
|
721
808
|
}
|
|
722
809
|
|
|
723
810
|
/// Define the Database class on the SlateDb module.
|
|
@@ -772,9 +859,17 @@ pub fn define_database_class(ruby: &Ruby, module: &magnus::RModule) -> Result<()
|
|
|
772
859
|
"_create_checkpoint",
|
|
773
860
|
method!(Database::create_checkpoint, 1),
|
|
774
861
|
)?;
|
|
775
|
-
class.define_method("
|
|
862
|
+
class.define_method("_flush", method!(Database::flush, 0))?;
|
|
863
|
+
class.define_method(
|
|
864
|
+
"_flush_with_options",
|
|
865
|
+
method!(Database::flush_with_options, 1),
|
|
866
|
+
)?;
|
|
776
867
|
class.define_method("_metrics", method!(Database::metrics, 0))?;
|
|
777
|
-
class.define_method("
|
|
868
|
+
class.define_method("_close", method!(Database::close, 0))?;
|
|
869
|
+
class.define_method(
|
|
870
|
+
"_close_with_options",
|
|
871
|
+
method!(Database::close_with_options, 1),
|
|
872
|
+
)?;
|
|
778
873
|
|
|
779
874
|
Ok(())
|
|
780
875
|
}
|
|
@@ -109,7 +109,7 @@ impl Transaction {
|
|
|
109
109
|
let ttl = get_optional::<u64>(&kwargs, "ttl")?;
|
|
110
110
|
let put_opts = PutOptions {
|
|
111
111
|
ttl: match ttl {
|
|
112
|
-
Some(ms) => Ttl::
|
|
112
|
+
Some(ms) => Ttl::ExpireAfterMillis(ms),
|
|
113
113
|
None => Ttl::Default,
|
|
114
114
|
},
|
|
115
115
|
};
|
|
@@ -172,7 +172,7 @@ impl Transaction {
|
|
|
172
172
|
let ttl = get_optional::<u64>(&kwargs, "ttl")?;
|
|
173
173
|
let merge_opts = MergeOptions {
|
|
174
174
|
ttl: match ttl {
|
|
175
|
-
Some(ms) => Ttl::
|
|
175
|
+
Some(ms) => Ttl::ExpireAfterMillis(ms),
|
|
176
176
|
None => Ttl::Default,
|
|
177
177
|
},
|
|
178
178
|
};
|
|
@@ -395,7 +395,16 @@ impl Transaction {
|
|
|
395
395
|
.take()
|
|
396
396
|
.ok_or_else(|| closed_error("transaction is closed"))?;
|
|
397
397
|
|
|
398
|
-
block_on_result(async {
|
|
398
|
+
block_on_result(async {
|
|
399
|
+
// As of SlateDB 0.16.0, commit returns as soon as the write is in
|
|
400
|
+
// memory and hands back an optional WriteHandle (None for an empty
|
|
401
|
+
// or read-only transaction). Await durability to preserve the
|
|
402
|
+
// previous blocking-commit behavior.
|
|
403
|
+
if let Some(handle) = txn.commit().await? {
|
|
404
|
+
handle.await_durable().await?;
|
|
405
|
+
}
|
|
406
|
+
Ok(())
|
|
407
|
+
})?;
|
|
399
408
|
Ok(())
|
|
400
409
|
}
|
|
401
410
|
|
|
@@ -403,10 +412,7 @@ impl Transaction {
|
|
|
403
412
|
pub fn commit_with_options(&self, kwargs: RHash) -> Result<(), Error> {
|
|
404
413
|
let await_durable = get_optional::<bool>(&kwargs, "await_durable")?.unwrap_or(true);
|
|
405
414
|
let seqnum = get_optional::<u64>(&kwargs, "seqnum")?.unwrap_or(0);
|
|
406
|
-
let write_opts = WriteOptions {
|
|
407
|
-
await_durable,
|
|
408
|
-
seqnum,
|
|
409
|
-
};
|
|
415
|
+
let write_opts = WriteOptions { seqnum };
|
|
410
416
|
|
|
411
417
|
let txn = self
|
|
412
418
|
.inner
|
|
@@ -414,7 +420,14 @@ impl Transaction {
|
|
|
414
420
|
.take()
|
|
415
421
|
.ok_or_else(|| closed_error("transaction is closed"))?;
|
|
416
422
|
|
|
417
|
-
block_on_result(async {
|
|
423
|
+
block_on_result(async {
|
|
424
|
+
if let Some(handle) = txn.commit_with_options(&write_opts).await? {
|
|
425
|
+
if await_durable {
|
|
426
|
+
handle.await_durable().await?;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
Ok(())
|
|
430
|
+
})?;
|
|
418
431
|
Ok(())
|
|
419
432
|
}
|
|
420
433
|
|
|
@@ -49,7 +49,7 @@ impl WriteBatch {
|
|
|
49
49
|
let ttl = get_optional::<u64>(&kwargs, "ttl")?;
|
|
50
50
|
let put_opts = PutOptions {
|
|
51
51
|
ttl: match ttl {
|
|
52
|
-
Some(ms) => Ttl::
|
|
52
|
+
Some(ms) => Ttl::ExpireAfterMillis(ms),
|
|
53
53
|
None => Ttl::Default,
|
|
54
54
|
},
|
|
55
55
|
};
|
|
@@ -102,7 +102,7 @@ impl WriteBatch {
|
|
|
102
102
|
let ttl = get_optional::<u64>(&kwargs, "ttl")?;
|
|
103
103
|
let merge_opts = MergeOptions {
|
|
104
104
|
ttl: match ttl {
|
|
105
|
-
Some(ms) => Ttl::
|
|
105
|
+
Some(ms) => Ttl::ExpireAfterMillis(ms),
|
|
106
106
|
None => Ttl::Default,
|
|
107
107
|
},
|
|
108
108
|
};
|
data/lib/slatedb/admin.rb
CHANGED
|
@@ -118,5 +118,33 @@ module SlateDb
|
|
|
118
118
|
opts[:min_age] = min_age if min_age
|
|
119
119
|
_run_gc(opts)
|
|
120
120
|
end
|
|
121
|
+
|
|
122
|
+
# Delete a database, removing its objects from the object store
|
|
123
|
+
# (SlateDB >= 0.16.0).
|
|
124
|
+
#
|
|
125
|
+
# For a cloned database, any checkpoints it pinned in parent databases are
|
|
126
|
+
# stripped before its own objects are removed.
|
|
127
|
+
#
|
|
128
|
+
# By default this performs a dry run: it returns the object paths that
|
|
129
|
+
# *would* be deleted without removing anything. Pass +confirm: true+ to
|
|
130
|
+
# actually delete. A confirmed delete of a directory that is not a SlateDB
|
|
131
|
+
# database (no manifest and no in-progress deletion marker) is refused, so a
|
|
132
|
+
# mistyped path cannot wipe an unrelated directory. The operation is
|
|
133
|
+
# idempotent.
|
|
134
|
+
#
|
|
135
|
+
# @param confirm [Boolean] When false (the default), performs a dry run and
|
|
136
|
+
# returns the paths that would be deleted. When true, deletes and returns
|
|
137
|
+
# the paths that were removed.
|
|
138
|
+
# @return [Array<String>] Object paths deleted (or that would be deleted)
|
|
139
|
+
#
|
|
140
|
+
# @example Preview what would be deleted
|
|
141
|
+
# admin.delete_db.each { |path| puts path }
|
|
142
|
+
#
|
|
143
|
+
# @example Actually delete the database
|
|
144
|
+
# deleted = admin.delete_db(confirm: true)
|
|
145
|
+
#
|
|
146
|
+
def delete_db(confirm: false)
|
|
147
|
+
_delete_db(confirm)
|
|
148
|
+
end
|
|
121
149
|
end
|
|
122
150
|
end
|
data/lib/slatedb/database.rb
CHANGED
|
@@ -12,6 +12,12 @@ module SlateDb
|
|
|
12
12
|
# @param merge_operator [Symbol, String, Proc, nil] Optional merge operator.
|
|
13
13
|
# Can be a symbol/string ("string_concat" or "concat") or a Proc/lambda
|
|
14
14
|
# that takes (key, existing_value, new_value) and returns the merged value.
|
|
15
|
+
# @param compactor_checkpoint_lifetime [Integer, nil] How long compaction inputs
|
|
16
|
+
# are protected from garbage collection, in milliseconds (default: 15 minutes).
|
|
17
|
+
# Reads using those inputs must finish within this time.
|
|
18
|
+
# @param sorted_run_consolidation_threshold [Integer, nil] Sorted-run count above
|
|
19
|
+
# which fallback consolidation starts. Zero disables it (default); use at least
|
|
20
|
+
# two to enable it. One is disabled by SlateDB with a warning. This is not a hard limit.
|
|
15
21
|
# @yield [db] If a block is given, yields the database and ensures it's closed
|
|
16
22
|
# @return [Database] The opened database (or block result if block given)
|
|
17
23
|
#
|
|
@@ -38,8 +44,12 @@ module SlateDb
|
|
|
38
44
|
# db.merge("counter", "3")
|
|
39
45
|
# db.get("counter") # => "8"
|
|
40
46
|
#
|
|
41
|
-
def open(path, url: nil, merge_operator: nil
|
|
42
|
-
|
|
47
|
+
def open(path, url: nil, merge_operator: nil,
|
|
48
|
+
compactor_checkpoint_lifetime: nil, sorted_run_consolidation_threshold: nil)
|
|
49
|
+
opts = {
|
|
50
|
+
compactor_checkpoint_lifetime: compactor_checkpoint_lifetime,
|
|
51
|
+
sorted_run_consolidation_threshold: sorted_run_consolidation_threshold
|
|
52
|
+
}.compact
|
|
43
53
|
|
|
44
54
|
case merge_operator
|
|
45
55
|
when Symbol, String
|
|
@@ -506,5 +516,63 @@ module SlateDb
|
|
|
506
516
|
def metrics
|
|
507
517
|
_metrics
|
|
508
518
|
end
|
|
519
|
+
|
|
520
|
+
# Flush in-memory writes to object storage.
|
|
521
|
+
#
|
|
522
|
+
# With no arguments this flushes pending writes durably, matching the
|
|
523
|
+
# previous behavior. Pass +flush_type+ to control what is flushed
|
|
524
|
+
# (SlateDB >= 0.16.0).
|
|
525
|
+
#
|
|
526
|
+
# @param flush_type [Symbol, String, nil] Which data to flush: :wal to flush
|
|
527
|
+
# the write-ahead log, or :memtable to freeze and flush memtable entries
|
|
528
|
+
# to object storage. When nil (the default), SlateDB flushes the WAL if
|
|
529
|
+
# enabled, otherwise the memtable.
|
|
530
|
+
# @return [void]
|
|
531
|
+
#
|
|
532
|
+
# @example Flush pending writes
|
|
533
|
+
# db.flush
|
|
534
|
+
#
|
|
535
|
+
# @example Explicitly flush the WAL
|
|
536
|
+
# db.flush(flush_type: :wal)
|
|
537
|
+
#
|
|
538
|
+
def flush(flush_type: nil)
|
|
539
|
+
if flush_type.nil?
|
|
540
|
+
_flush
|
|
541
|
+
else
|
|
542
|
+
_flush_with_options(flush_type.to_s)
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Close the database.
|
|
547
|
+
#
|
|
548
|
+
# By default the active memtable is flushed before closing (matching the
|
|
549
|
+
# previous behavior). Pass +flush: false+ to close without a final flush,
|
|
550
|
+
# in which case writes that are not yet durable may be lost
|
|
551
|
+
# (SlateDB >= 0.16.0).
|
|
552
|
+
#
|
|
553
|
+
# @param flush [Boolean] Whether to flush before closing (default: true)
|
|
554
|
+
# @param flush_type [Symbol, String, nil] When flushing, which data to
|
|
555
|
+
# flush: :wal or :memtable. Defaults to :memtable. Ignored when
|
|
556
|
+
# +flush: false+.
|
|
557
|
+
# @return [void]
|
|
558
|
+
#
|
|
559
|
+
# @example Close (flushing the memtable first)
|
|
560
|
+
# db.close
|
|
561
|
+
#
|
|
562
|
+
# @example Close without flushing
|
|
563
|
+
# db.close(flush: false)
|
|
564
|
+
#
|
|
565
|
+
# @example Flush the WAL before closing
|
|
566
|
+
# db.close(flush_type: :wal)
|
|
567
|
+
#
|
|
568
|
+
def close(flush: true, flush_type: nil)
|
|
569
|
+
if !flush
|
|
570
|
+
_close_with_options(nil)
|
|
571
|
+
elsif flush_type.nil?
|
|
572
|
+
_close
|
|
573
|
+
else
|
|
574
|
+
_close_with_options(flush_type.to_s)
|
|
575
|
+
end
|
|
576
|
+
end
|
|
509
577
|
end
|
|
510
578
|
end
|
data/lib/slatedb/reader.rb
CHANGED
|
@@ -106,16 +106,19 @@ module SlateDb
|
|
|
106
106
|
#
|
|
107
107
|
# @param start_key [String] The start key (inclusive)
|
|
108
108
|
# @param end_key [String, nil] The end key (exclusive)
|
|
109
|
+
# @param order [Symbol, String, nil] Iteration order (:asc/:ascending or :desc/:descending)
|
|
109
110
|
# @return [Iterator] An iterator over key-value pairs
|
|
110
111
|
#
|
|
111
112
|
def scan(start_key, end_key = nil, durability_filter: nil, dirty: nil,
|
|
112
|
-
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, &)
|
|
113
|
-
opts = {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, order: nil, &)
|
|
114
|
+
opts = {
|
|
115
|
+
durability_filter: durability_filter&.to_s,
|
|
116
|
+
dirty: dirty,
|
|
117
|
+
read_ahead_bytes: read_ahead_bytes,
|
|
118
|
+
cache_blocks: cache_blocks,
|
|
119
|
+
max_fetch_tasks: max_fetch_tasks,
|
|
120
|
+
order: order&.to_s
|
|
121
|
+
}.compact
|
|
119
122
|
|
|
120
123
|
iter = if opts.empty?
|
|
121
124
|
_scan(start_key, end_key)
|
|
@@ -138,6 +141,7 @@ module SlateDb
|
|
|
138
141
|
# @param read_ahead_bytes [Integer, nil] Number of bytes to read ahead
|
|
139
142
|
# @param cache_blocks [Boolean, nil] Whether to cache blocks
|
|
140
143
|
# @param max_fetch_tasks [Integer, nil] Maximum number of fetch tasks
|
|
144
|
+
# @param order [Symbol, String, nil] Iteration order (:asc/:ascending or :desc/:descending)
|
|
141
145
|
# @param from [String, nil] Inclusive lower bound suffix, appended to the
|
|
142
146
|
# prefix, to start scanning from (e.g. prefix "user:" with from "100"
|
|
143
147
|
# starts at "user:100"). Defaults to the start of the prefix.
|
|
@@ -146,7 +150,7 @@ module SlateDb
|
|
|
146
150
|
# @return [Iterator] An iterator over key-value pairs
|
|
147
151
|
#
|
|
148
152
|
def scan_prefix(prefix, durability_filter: nil, dirty: nil,
|
|
149
|
-
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil,
|
|
153
|
+
read_ahead_bytes: nil, cache_blocks: nil, max_fetch_tasks: nil, order: nil,
|
|
150
154
|
from: nil, to: nil, &)
|
|
151
155
|
opts = {
|
|
152
156
|
durability_filter: durability_filter&.to_s,
|
|
@@ -154,6 +158,7 @@ module SlateDb
|
|
|
154
158
|
read_ahead_bytes: read_ahead_bytes,
|
|
155
159
|
cache_blocks: cache_blocks,
|
|
156
160
|
max_fetch_tasks: max_fetch_tasks,
|
|
161
|
+
order: order&.to_s,
|
|
157
162
|
subrange_from: from,
|
|
158
163
|
subrange_to: to
|
|
159
164
|
}.compact
|
data/lib/slatedb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: slatedb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SlateDB Contributors
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.9.
|
|
18
|
+
version: 0.9.130
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.9.
|
|
25
|
+
version: 0.9.130
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rake
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '1.
|
|
74
|
+
version: '1.92'
|
|
75
75
|
type: :development
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '1.
|
|
81
|
+
version: '1.92'
|
|
82
82
|
description: A cloud-native embedded key-value store built on object storage
|
|
83
83
|
email:
|
|
84
84
|
- slatedb@example.com
|
|
@@ -137,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
138
|
version: '0'
|
|
139
139
|
requirements: []
|
|
140
|
-
rubygems_version: 4.0.
|
|
140
|
+
rubygems_version: 4.0.20
|
|
141
141
|
specification_version: 4
|
|
142
142
|
summary: Ruby bindings for SlateDB
|
|
143
143
|
test_files: []
|