Skip to content

Commit 4525dcf

Browse files
AhmedSolimantillrohrmann
authored andcommitted
[VQueues] Optimize sys_vqueue_meta point lookups
Use the generic exact ID selection machinery for sys_vqueue_meta so id equality and IN predicates route directly to the owning Restate partitions. Add a targeted vqueue-meta read path backed by bounded RocksDB multi-get batches, while keeping range scans for scope and non-point predicates. Vqueue-meta ID range scans now use total-order iteration when the bounds span multiple partition keys. Add DataFusion coverage for vqueue-meta point queries and NOT IN handling so negated lists remain normal filters instead of lookup inputs.
1 parent c5ff7b7 commit 4525dcf

7 files changed

Lines changed: 440 additions & 38 deletions

File tree

crates/partition-store/src/vqueue_table/mod.rs

Lines changed: 145 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use tracing::error;
3535

3636
use restate_rocksdb::{Priority, StorageTaskKind};
3737
use restate_storage_api::StorageError;
38-
use restate_storage_api::vqueue_table::filters::ScanEntryIdFilter;
38+
use restate_storage_api::vqueue_table::filters::{ScanEntryIdFilter, ScanMetaFilter};
3939
use restate_storage_api::vqueue_table::metadata::{VQueueMeta, VQueueMetaRef};
4040
use restate_storage_api::vqueue_table::{
4141
EntryKey, EntryMetadata, EntryStatusHeader, EntryValue, ReadVQueueTable, ScanVQueueTable,
@@ -398,31 +398,155 @@ impl ReadVQueueTable for PartitionStoreTransaction<'_> {
398398
}
399399

400400
impl ScanVQueueMetaTable for PartitionStore {
401-
fn for_each_vqueue_meta<
401+
fn for_each_vqueue_meta<F>(
402+
&self,
403+
filter: ScanMetaFilter,
404+
mut f: F,
405+
) -> Result<impl Future<Output = Result<()>> + Send>
406+
where
402407
F: for<'a> FnMut((&'a VQueueId, &'a VQueueMetaRef<'a>)) -> std::ops::ControlFlow<()>
403408
+ Send
404409
+ Sync
405410
+ 'static,
406-
>(
407-
&self,
408-
range: KeyRange,
409-
mut f: F,
410-
) -> Result<impl Future<Output = Result<()>> + Send> {
411-
self.iterator_for_each(
412-
"df-vqueue-meta",
413-
Priority::Low,
414-
TableScan::ScanPartitionKeyRange::<MetaKey>(range),
415-
move |(mut key, value)| {
416-
let meta_key = break_on_err(MetaKey::deserialize_from(&mut key))?;
417-
let meta = break_on_err(
418-
VQueueMetaRef::decode_borrowed(value).map_err(StorageError::BilrostDecode),
419-
)?;
411+
{
412+
// Fast path: each vqueue id maps to exactly one fixed-length key, so an
413+
// exact set is served via batched multi-get calls instead of scanning
414+
// every metadata row in the partition-key range.
415+
if let ScanMetaFilter::MetaIdSet(ids) = filter {
416+
return Ok(multi_get_vqueue_meta(self, ids, f).boxed());
417+
}
420418

421-
let (vqueue_id,) = meta_key.split();
422-
f((&vqueue_id, &meta)).map_break(Ok)
423-
},
424-
)
425-
.map_err(|_| StorageError::OperationalError)
419+
let scan = match filter {
420+
ScanMetaFilter::PartitionKey(range) => {
421+
TableScan::ScanPartitionKeyRange::<MetaKey>(range)
422+
}
423+
ScanMetaFilter::MetaIdRange(range) => {
424+
let start = MetaKey::from(&range.start);
425+
let end = MetaKey::from(&range.last);
426+
TableScan::RangeInclusive(start, end)
427+
}
428+
ScanMetaFilter::MetaIdSet(_) => unreachable!("handled above"),
429+
};
430+
431+
let scan_fut = self
432+
.iterator_for_each(
433+
"df-vqueue-meta",
434+
Priority::Low,
435+
scan,
436+
move |(mut key, value)| {
437+
let meta_key = break_on_err(MetaKey::deserialize_from(&mut key))?;
438+
let meta = break_on_err(
439+
VQueueMetaRef::decode_borrowed(value).map_err(StorageError::BilrostDecode),
440+
)?;
441+
442+
let (vqueue_id,) = meta_key.split();
443+
f((&vqueue_id, &meta)).map_break(Ok)
444+
},
445+
)
446+
.map_err(|_| StorageError::OperationalError)?;
447+
448+
Ok(scan_fut.boxed())
449+
}
450+
}
451+
452+
/// Maximum number of vqueue-metadata keys passed to one RocksDB multi-get call.
453+
const VQUEUE_META_MULTI_GET_BATCH_SIZE: usize = 500;
454+
455+
/// Serves a vqueue-metadata lookup for a known set of ids via batched
456+
/// `batched_multi_get` calls, dispatched on the storage background thread-pool.
457+
///
458+
/// `ids` is already sorted in on-disk key order (`VQueueId`'s `Ord` matches its
459+
/// key byte encoding, which is prefixed by the partition key), which is what
460+
/// `batched_multi_get_cf_opt`'s `sorted_input=true` requires.
461+
///
462+
/// `MetaKey`s are fixed-length, so each batch is packed back-to-back into a
463+
/// single buffer and handed to the multi-get as `chunks_exact` slices.
464+
fn multi_get_vqueue_meta<F>(
465+
store: &PartitionStore,
466+
ids: BTreeSet<VQueueId>,
467+
mut f: F,
468+
) -> impl Future<Output = Result<()>> + Send
469+
where
470+
F: for<'a> FnMut((&'a VQueueId, &'a VQueueMetaRef<'a>)) -> std::ops::ControlFlow<()>
471+
+ Send
472+
+ Sync
473+
+ 'static,
474+
{
475+
const KEY_LEN: usize = MetaKey::serialized_length_fixed();
476+
477+
let rocksdb = store.partition_db().rocksdb().clone();
478+
let cf_name: restate_rocksdb::CfName = store.partition_db().partition().cf_name().into();
479+
480+
async move {
481+
rocksdb
482+
.run_background_read_op(
483+
"df-vqueue-meta",
484+
StorageTaskKind::MultiGet,
485+
Priority::Low,
486+
move |raw_db| -> Result<()> {
487+
let Some(cf) = raw_db.cf_handle(cf_name.as_str()) else {
488+
return Err(StorageError::Generic(anyhow::anyhow!(
489+
"column family {cf_name} not found for vqueue meta multi-get"
490+
)));
491+
};
492+
493+
let batch_capacity = ids.len().min(VQUEUE_META_MULTI_GET_BATCH_SIZE);
494+
let mut key_buf = BytesMut::with_capacity(batch_capacity * KEY_LEN);
495+
let mut batch_ids = Vec::with_capacity(batch_capacity);
496+
497+
let mut readopts = ReadOptions::default();
498+
// future proofing to make use of parallel L0 reads and async-io
499+
// if/when we build rocksdb with COROUTINES=1 and IO-URING support.
500+
// by default, this will not do anything.
501+
readopts.set_async_io(true);
502+
readopts.set_optimize_multiget_for_io(true);
503+
504+
let mut ids = ids.into_iter();
505+
loop {
506+
key_buf.clear();
507+
batch_ids.clear();
508+
509+
for id in ids.by_ref().take(VQUEUE_META_MULTI_GET_BATCH_SIZE) {
510+
EncodeTableKey::serialize_to(&MetaKey::from(&id), &mut key_buf);
511+
batch_ids.push(id);
512+
}
513+
514+
if batch_ids.is_empty() {
515+
break;
516+
}
517+
518+
let results = raw_db.batched_multi_get_cf_opt(
519+
&cf,
520+
key_buf.chunks_exact(KEY_LEN),
521+
true,
522+
&readopts,
523+
);
524+
525+
for (id, result) in batch_ids.iter().zip(results) {
526+
let Some(value) =
527+
result.map_err(|e| StorageError::Generic(e.into()))?
528+
else {
529+
continue;
530+
};
531+
532+
let meta = VQueueMetaRef::decode_borrowed(value.as_ref())
533+
.map_err(StorageError::BilrostDecode)?;
534+
535+
if f((id, &meta)).is_break() {
536+
return Ok(());
537+
}
538+
}
539+
540+
if batch_ids.len() < VQUEUE_META_MULTI_GET_BATCH_SIZE {
541+
break;
542+
}
543+
}
544+
545+
Ok(())
546+
},
547+
)
548+
.await
549+
.map_err(|_| StorageError::OperationalError)?
426550
}
427551
}
428552

crates/storage-api/src/vqueue_table/filters.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::collections::BTreeSet;
1212
use std::range::RangeInclusive;
1313

1414
use restate_sharding::KeyRange;
15-
use restate_types::vqueues::VQueueEntryId;
15+
use restate_types::vqueues::{VQueueEntryId, VQueueId};
1616

1717
/// Filter vqueue entries by partition keys, entry ID range, or an exact set of entry IDs.
1818
#[derive(Debug, Clone)]
@@ -23,3 +23,18 @@ pub enum ScanEntryIdFilter {
2323
/// range scan. The set is sorted in on-disk key order.
2424
EntryIdSet(BTreeSet<VQueueEntryId>),
2525
}
26+
27+
/// Filter vqueue metadata rows by partition keys, vqueue ID range, or an exact
28+
/// set of vqueue IDs.
29+
///
30+
/// Each vqueue id maps to exactly one metadata row, so [`ScanMetaFilter::MetaIdSet`]
31+
/// is served via a batched multi-get instead of a partition-key-range scan.
32+
#[derive(Debug, Clone)]
33+
pub enum ScanMetaFilter {
34+
PartitionKey(KeyRange),
35+
MetaIdRange(RangeInclusive<VQueueId>),
36+
/// A known set of vqueue IDs served via batched multi-get calls. The set is
37+
/// sorted in on-disk key order (`VQueueId`'s `Ord` matches its key byte
38+
/// encoding, which is prefixed by the partition key).
39+
MetaIdSet(BTreeSet<VQueueId>),
40+
}

crates/storage-api/src/vqueue_table/tables.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use restate_sharding::{KeyRange, PartitionKey};
1212
use restate_types::vqueues::{Seq, VQueueId};
1313

14-
use super::filters::ScanEntryIdFilter;
14+
use super::filters::{ScanEntryIdFilter, ScanMetaFilter};
1515
use super::metadata::{VQueueMeta, VQueueMetaRef};
1616
use super::{
1717
EntryId, EntryKey, EntryMetadata, EntryStatusHeader, EntryValue, stats::EntryStatistics,
@@ -255,7 +255,7 @@ pub trait ScanVQueueMetaTable {
255255
+ 'static,
256256
>(
257257
&self,
258-
range: KeyRange,
258+
filter: ScanMetaFilter,
259259
f: F,
260260
) -> Result<impl Future<Output = Result<()>> + Send>;
261261
}

0 commit comments

Comments
 (0)