Skip to content

Commit b14d86b

Browse files
authored
Upgrade to slatedb 0.14 (opendata-oss#500)
Upgrades to slatedb 0.14. LogDb makes use of new subrange argument in `scan_prefix` to specify the sequence range of a query.
1 parent 82f03fa commit b14d86b

17 files changed

Lines changed: 502 additions & 99 deletions

File tree

Cargo.lock

Lines changed: 333 additions & 57 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ serde_json = "1.0"
4949
serde_with = { version = "3", features = ["base64"] }
5050
serde_yaml = "0.9"
5151
foyer = "0.22"
52-
slatedb = { version = "0.13.0", features = ["compaction_filters"] }
53-
slatedb-common = "0.13.0"
52+
slatedb = { version = "0.14.0", features = ["compaction_filters"] }
53+
slatedb-common = "0.14.0"
5454
thiserror = "2.0"
5555
tokio = { version = "1.0", features = ["full", "test-util"] }
5656
tokio-util = "0.7"

buffer/src/consumer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use std::sync::Arc;
22
use std::time::{Instant, SystemTime, UNIX_EPOCH};
33

44
use bytes::Bytes;
5-
use slatedb::object_store::ObjectStore;
65
use slatedb::object_store::path::Path;
6+
use slatedb::object_store::{ObjectStore, ObjectStoreExt};
77
use tokio_util::sync::CancellationToken;
88

99
use crate::config::ConsumerConfig;

buffer/src/gc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl GarbageCollector {
9393
let mut failed: u64 = 0;
9494
if !to_delete.is_empty() {
9595
tracing::debug!(count = to_delete.len(), "GC deleting orphaned batch files");
96-
let locations = stream::iter(to_delete.iter().cloned().map(Ok));
96+
let locations = stream::iter(to_delete.into_iter().map(Ok));
9797
let mut results = self.object_store.delete_stream(locations.boxed());
9898
while let Some(result) = results.next().await {
9999
match result {
@@ -169,8 +169,8 @@ mod tests {
169169
use crate::queue::QueueProducer;
170170
use bytes::Bytes;
171171
use common::ObjectStoreConfig;
172-
use slatedb::object_store::PutPayload;
173172
use slatedb::object_store::memory::InMemory;
173+
use slatedb::object_store::{ObjectStoreExt, PutPayload};
174174
use std::time::Duration;
175175

176176
const TEST_MANIFEST_PATH: &str = "test/manifest";

buffer/src/producer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::time::{Duration, Instant, SystemTime};
44
use bytes::Bytes;
55
use common::clock::Clock;
66
use slatedb::object_store::path::Path;
7-
use slatedb::object_store::{ObjectStore, PutPayload};
7+
use slatedb::object_store::{ObjectStore, ObjectStoreExt, PutPayload};
88
use tokio::sync::mpsc;
99
use tokio_util::sync::CancellationToken;
1010

@@ -421,8 +421,8 @@ mod tests {
421421
use bytes::Bytes;
422422
use common::ObjectStoreConfig;
423423
use common::clock::{MockClock, SystemClock};
424-
use slatedb::object_store::ObjectStore;
425424
use slatedb::object_store::memory::InMemory;
425+
use slatedb::object_store::{ObjectStore, ObjectStoreExt};
426426
use std::time::UNIX_EPOCH;
427427

428428
async fn read_manifest_entries(store: &Arc<dyn ObjectStore>, path: &str) -> Vec<QueueEntry> {

buffer/src/queue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
44
use bytes::{BufMut, Bytes, BytesMut};
55
use slatedb::object_store::path::Path;
66
use slatedb::object_store::{
7-
Error as ObjectStoreError, ObjectStore, PutMode, PutPayload, UpdateVersion,
7+
Error as ObjectStoreError, ObjectStore, ObjectStoreExt, PutMode, PutPayload, UpdateVersion,
88
};
99

1010
use crate::error::{Error, Result};

common/src/bytes.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,43 @@ impl BytesRange {
9595
end: Unbounded,
9696
}
9797
}
98+
99+
/// Builds the absolute key range for a prefix scan narrowed to `subrange`.
100+
///
101+
/// `subrange` bounds are key *suffixes* interpreted relative to `prefix`: a
102+
/// bound `s` selects the full key `prefix ++ s`. An unbounded subrange end
103+
/// falls back to the prefix's natural upper bound (its lexicographic
104+
/// successor), so `from_prefix_and_subrange(p, &BytesRange::unbounded())`
105+
/// equals [`Self::prefix`].
106+
///
107+
/// This mirrors SlateDB's own `scan_prefix` subrange semantics so the
108+
/// in-memory fallback in [`StorageRead::scan_prefix_iter`] agrees with the
109+
/// SlateDB path byte-for-byte.
110+
///
111+
/// [`StorageRead::scan_prefix_iter`]: crate::storage::StorageRead::scan_prefix_iter
112+
pub fn from_prefix_and_subrange(prefix: &[u8], subrange: &BytesRange) -> Self {
113+
let concat = |suffix: &Bytes| -> Bytes {
114+
let mut key = BytesMut::with_capacity(prefix.len() + suffix.len());
115+
key.extend_from_slice(prefix);
116+
key.extend_from_slice(suffix);
117+
key.freeze()
118+
};
119+
let start = match &subrange.start {
120+
Included(s) => Included(concat(s)),
121+
Excluded(s) => Excluded(concat(s)),
122+
Unbounded if prefix.is_empty() => Unbounded,
123+
Unbounded => Included(Bytes::copy_from_slice(prefix)),
124+
};
125+
let end = match &subrange.end {
126+
Included(e) => Included(concat(e)),
127+
Excluded(e) => Excluded(concat(e)),
128+
Unbounded => match lex_increment(prefix) {
129+
Some(successor) => Excluded(successor),
130+
None => Unbounded,
131+
},
132+
};
133+
Self { start, end }
134+
}
98135
}
99136

100137
impl RangeBounds<Bytes> for BytesRange {
@@ -106,6 +143,17 @@ impl RangeBounds<Bytes> for BytesRange {
106143
}
107144
}
108145

146+
/// SlateDB 0.14 scans are bounded by its own [`slatedb::ByteRangeBounds`] trait
147+
/// rather than `std::ops::RangeBounds`, so we map our bounds onto byte slices.
148+
impl slatedb::ByteRangeBounds for BytesRange {
149+
fn start_bound(&self) -> Bound<&[u8]> {
150+
self.start.as_ref().map(Bytes::as_ref)
151+
}
152+
fn end_bound(&self) -> Bound<&[u8]> {
153+
self.end.as_ref().map(Bytes::as_ref)
154+
}
155+
}
156+
109157
#[cfg(test)]
110158
mod tests {
111159
use proptest::prelude::*;

common/src/storage/factory.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub use slatedb::db_cache::foyer_hybrid::FoyerHybridCache;
1717
pub use slatedb::db_cache::{CachedEntry, CachedKey, SplitCache};
1818
use slatedb::object_store::{self, ObjectStore};
1919
pub use slatedb::{CompactorBuilder, DbBuilder};
20-
use slatedb::{DbReader, FilterPolicy, SstReader};
20+
use slatedb::{DbReader, FilterPolicy, PrefixExtractor, SstReader};
2121
use tracing::info;
2222
use uuid::Uuid;
2323

@@ -161,6 +161,9 @@ impl StorageBuilder {
161161
if let Some(policies) = self.semantics.filter_policies {
162162
db_builder = db_builder.with_filter_policies(policies);
163163
}
164+
if let Some(extractor) = self.semantics.segment_extractor {
165+
db_builder = db_builder.with_segment_extractor(extractor);
166+
}
164167
let db = db_builder.build().await.map_err(|e| {
165168
StorageError::Storage(format!("Failed to create SlateDB: {}", e))
166169
})?;
@@ -250,6 +253,7 @@ impl StorageReaderRuntime {
250253
pub struct StorageSemantics {
251254
pub(crate) merge_operator: Option<Arc<dyn MergeOperator>>,
252255
pub(crate) filter_policies: Option<Vec<Arc<dyn FilterPolicy>>>,
256+
pub(crate) segment_extractor: Option<Arc<dyn PrefixExtractor>>,
253257
}
254258

255259
impl StorageSemantics {
@@ -275,6 +279,17 @@ impl StorageSemantics {
275279
self.filter_policies = Some(policies);
276280
self
277281
}
282+
283+
/// Sets the segment extractor (SlateDB RFC-0024) for writers and readers.
284+
///
285+
/// SlateDB 0.14 persists the extractor's name in the manifest and refuses
286+
/// to open a writer or `DbReader` whose configured extractor doesn't match.
287+
/// Routing this through semantics keeps the writer, compactor, and
288+
/// standalone reader paths aligned on a single extractor.
289+
pub fn with_segment_extractor(mut self, extractor: Arc<dyn PrefixExtractor>) -> Self {
290+
self.segment_extractor = Some(extractor);
291+
self
292+
}
278293
}
279294

280295
pub fn new_slatedb_compactor_builder(
@@ -403,6 +418,9 @@ pub async fn create_storage_read(
403418
if let Some(policies) = semantics.filter_policies {
404419
builder = builder.with_filter_policies(policies);
405420
}
421+
if let Some(extractor) = semantics.segment_extractor {
422+
builder = builder.with_segment_extractor(extractor);
423+
}
406424
if let Some(cache) = cache.clone() {
407425
builder = builder.with_db_cache(cache);
408426
}

common/src/storage/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,14 @@ pub trait StorageRead: Any + Send + Sync {
243243
range: BytesRange,
244244
) -> StorageResult<Box<dyn StorageIterator + Send + 'static>>;
245245

246-
/// Returns an iterator over records whose key starts with `prefix`.
246+
/// Returns an iterator over records whose key starts with `prefix`,
247+
/// restricted to `subrange`.
248+
///
249+
/// `subrange` bounds are key *suffixes* interpreted relative to `prefix`: a
250+
/// bound `s` selects the full key `prefix ++ s`. Pass
251+
/// [`BytesRange::unbounded`] to scan the prefix's entire keyspace. Callers
252+
/// with a known sub-key ordering (e.g. the log's trailing sequence bytes)
253+
/// use this to bound the scan natively instead of filtering client-side.
247254
///
248255
/// Backends that support prefix-aware SST filters (e.g. SlateDB with a
249256
/// configured `PrefixExtractor` or custom `FilterPolicy`) consult those
@@ -258,14 +265,17 @@ pub trait StorageRead: Any + Send + Sync {
258265
/// [`slatedb::FilterContext`].
259266
///
260267
/// The default implementation delegates to [`Self::scan_iter`] with the
261-
/// range derived from the prefix, which preserves correctness for any
262-
/// backend; backends that can do better should override this.
268+
/// absolute range derived from `prefix` and `subrange`, which preserves
269+
/// correctness for any backend; backends that can do better should override
270+
/// this.
263271
async fn scan_prefix_iter(
264272
&self,
265273
prefix: Bytes,
274+
subrange: BytesRange,
266275
_filter_context: Option<FilterContext>,
267276
) -> StorageResult<Box<dyn StorageIterator + Send + 'static>> {
268-
self.scan_iter(BytesRange::prefix(prefix)).await
277+
self.scan_iter(BytesRange::from_prefix_and_subrange(&prefix, &subrange))
278+
.await
269279
}
270280

271281
/// Collects all records in the range into a Vec.

common/src/storage/slate.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,13 @@ impl StorageRead for SlateDbStorage {
242242
async fn scan_prefix_iter(
243243
&self,
244244
prefix: Bytes,
245+
subrange: BytesRange,
245246
filter_context: Option<FilterContext>,
246247
) -> StorageResult<Box<dyn StorageIterator + Send + 'static>> {
247248
let options = default_scan_options().with_filter_context(filter_context);
248249
let iter = self
249250
.db
250-
.scan_prefix_with_options(prefix, &options)
251+
.scan_prefix_with_options(prefix, subrange, &options)
251252
.await
252253
.map_err(StorageError::from_storage)?;
253254
Ok(Box::new(SlateDbIterator { iter }))
@@ -326,12 +327,13 @@ impl StorageRead for SlateDbStorageSnapshot {
326327
async fn scan_prefix_iter(
327328
&self,
328329
prefix: Bytes,
330+
subrange: BytesRange,
329331
filter_context: Option<FilterContext>,
330332
) -> StorageResult<Box<dyn StorageIterator + Send + 'static>> {
331333
let options = default_scan_options().with_filter_context(filter_context);
332334
let iter = self
333335
.snapshot
334-
.scan_prefix_with_options(prefix, &options)
336+
.scan_prefix_with_options(prefix, subrange, &options)
335337
.await
336338
.map_err(StorageError::from_storage)?;
337339
Ok(Box::new(SlateDbIterator { iter }))
@@ -549,12 +551,13 @@ impl StorageRead for SlateDbStorageReader {
549551
async fn scan_prefix_iter(
550552
&self,
551553
prefix: Bytes,
554+
subrange: BytesRange,
552555
filter_context: Option<FilterContext>,
553556
) -> StorageResult<Box<dyn StorageIterator + Send + 'static>> {
554557
let options = default_scan_options().with_filter_context(filter_context);
555558
let iter = self
556559
.reader
557-
.scan_prefix_with_options(prefix, &options)
560+
.scan_prefix_with_options(prefix, subrange, &options)
558561
.await
559562
.map_err(StorageError::from_storage)?;
560563
Ok(Box::new(SlateDbIterator { iter }))

0 commit comments

Comments
 (0)