@@ -35,7 +35,7 @@ use tracing::error;
3535
3636use restate_rocksdb:: { Priority , StorageTaskKind } ;
3737use restate_storage_api:: StorageError ;
38- use restate_storage_api:: vqueue_table:: filters:: ScanEntryIdFilter ;
38+ use restate_storage_api:: vqueue_table:: filters:: { ScanEntryIdFilter , ScanMetaFilter } ;
3939use restate_storage_api:: vqueue_table:: metadata:: { VQueueMeta , VQueueMetaRef } ;
4040use restate_storage_api:: vqueue_table:: {
4141 EntryKey , EntryMetadata , EntryStatusHeader , EntryValue , ReadVQueueTable , ScanVQueueTable ,
@@ -398,31 +398,155 @@ impl ReadVQueueTable for PartitionStoreTransaction<'_> {
398398}
399399
400400impl 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
0 commit comments