@@ -4,7 +4,7 @@ use common::{Record, Storage, StorageRead};
44use futures:: stream:: { self , StreamExt } ;
55use roaring:: RoaringBitmap ;
66
7- use std:: collections:: { HashMap , HashSet } ;
7+ use std:: collections:: HashMap ;
88
99use crate :: index:: { InvertedIndex , SeriesSpec } ;
1010use crate :: model:: { Sample , SeriesFingerprint , SeriesId , TimeBucket } ;
@@ -255,114 +255,73 @@ pub(crate) trait OpenTsdbStorageReadExt: StorageRead {
255255 Ok ( max_series_id)
256256 }
257257
258- /// Load samples for a batch of series using a narrowed sequential scan .
258+ /// Load samples for a batch of series using multi_get .
259259 ///
260- /// Scans from the minimum to maximum series_id key, filtering to only
261- /// the requested IDs via HashSet lookup. This is faster than N individual
262- /// `get()` calls because sequential scan has much lower per-record overhead
263- /// (~0.35ms vs ~6ms for point reads due to LSM tree traversal).
264- ///
265- /// Stops early once all requested series have been found.
266- #[ tracing:: instrument( level = "info" , skip( self , bucket, series_ids) , fields( bucket_start = bucket. start, wanted = series_ids. len( ) , scanned, range_size) ) ]
260+ /// Uses the storage layer's multi_get which internally performs a
261+ /// sequential scan from min to max key. This is much faster than
262+ /// individual get() calls because it amortizes SST index reads and
263+ /// data block reads across all requested keys.
264+ #[ tracing:: instrument( level = "info" , skip( self , bucket, series_ids) , fields( bucket_start = bucket. start, wanted = series_ids. len( ) , found) ) ]
267265 async fn get_time_series_batch (
268266 & self ,
269267 bucket : & TimeBucket ,
270268 series_ids : & [ SeriesId ] ,
271269 ) -> Result < HashMap < SeriesId , Vec < Sample > > > {
272- let wanted: HashSet < SeriesId > = series_ids. iter ( ) . copied ( ) . collect ( ) ;
273- let min_id = * series_ids. iter ( ) . min ( ) . unwrap ( ) ;
274- let max_id = * series_ids. iter ( ) . max ( ) . unwrap ( ) ;
275- let range_size = ( max_id - min_id + 1 ) as u64 ;
276- tracing:: Span :: current ( ) . record ( "range_size" , range_size) ;
277-
278- // Narrow scan from min to max+1 series_id key
279- let start_key = TimeSeriesKey {
280- time_bucket : bucket. start ,
281- bucket_size : bucket. size ,
282- series_id : min_id,
283- }
284- . encode ( ) ;
285- let end_key = TimeSeriesKey {
286- time_bucket : bucket. start ,
287- bucket_size : bucket. size ,
288- series_id : max_id. wrapping_add ( 1 ) ,
289- }
290- . encode ( ) ;
291- let range = common:: BytesRange :: new (
292- std:: ops:: Bound :: Included ( start_key) ,
293- std:: ops:: Bound :: Excluded ( end_key) ,
294- ) ;
295-
296- let mut iter = self . scan_iter ( range) . await ?;
297- let mut result = HashMap :: with_capacity ( series_ids. len ( ) ) ;
298- let mut scanned = 0u64 ;
299- let mut found = 0usize ;
300- while let Some ( record) = iter. next ( ) . await ? {
301- scanned += 1 ;
302- let key = TimeSeriesKey :: decode ( record. key . as_ref ( ) ) ?;
303- if wanted. contains ( & key. series_id ) {
304- let samples: Vec < Sample > = match TimeSeriesIterator :: new ( record. value . as_ref ( ) ) {
305- Some ( iter) => iter. filter_map ( |r| r. ok ( ) ) . collect ( ) ,
306- None => Vec :: new ( ) ,
307- } ;
308- result. insert ( key. series_id , samples) ;
309- found += 1 ;
310- if found == wanted. len ( ) {
311- break ; // All requested series found
270+ let keys: Vec < bytes:: Bytes > = series_ids
271+ . iter ( )
272+ . map ( |& series_id| {
273+ TimeSeriesKey {
274+ time_bucket : bucket. start ,
275+ bucket_size : bucket. size ,
276+ series_id,
312277 }
313- }
278+ . encode ( )
279+ } )
280+ . collect ( ) ;
281+
282+ let records = self . multi_get ( & keys) . await ?;
283+ let mut result = HashMap :: with_capacity ( records. len ( ) ) ;
284+
285+ for ( key_bytes, record) in records {
286+ let key = TimeSeriesKey :: decode ( key_bytes. as_ref ( ) ) ?;
287+ let samples: Vec < Sample > = match TimeSeriesIterator :: new ( record. value . as_ref ( ) ) {
288+ Some ( iter) => iter. filter_map ( |r| r. ok ( ) ) . collect ( ) ,
289+ None => Vec :: new ( ) ,
290+ } ;
291+ result. insert ( key. series_id , samples) ;
314292 }
315- tracing:: Span :: current ( ) . record ( "scanned " , scanned ) ;
293+ tracing:: Span :: current ( ) . record ( "found " , result . len ( ) ) ;
316294 Ok ( result)
317295 }
318296
319- /// Load forward index entries for a batch of series using a narrowed scan .
320- #[ tracing:: instrument( level = "info" , skip( self , bucket, series_ids) , fields( bucket_start = bucket. start, wanted = series_ids. len( ) , scanned , range_size ) ) ]
297+ /// Load forward index entries for a batch of series using multi_get .
298+ #[ tracing:: instrument( level = "info" , skip( self , bucket, series_ids) , fields( bucket_start = bucket. start, wanted = series_ids. len( ) , found ) ) ]
321299 async fn get_forward_index_batch (
322300 & self ,
323301 bucket : & TimeBucket ,
324302 series_ids : & [ SeriesId ] ,
325303 ) -> Result < ForwardIndex > {
326- let wanted: HashSet < SeriesId > = series_ids. iter ( ) . copied ( ) . collect ( ) ;
327- let min_id = * series_ids. iter ( ) . min ( ) . unwrap ( ) ;
328- let max_id = * series_ids. iter ( ) . max ( ) . unwrap ( ) ;
329- let range_size = ( max_id - min_id + 1 ) as u64 ;
330- tracing:: Span :: current ( ) . record ( "range_size" , range_size) ;
331-
332- let start_key = ForwardIndexKey {
333- time_bucket : bucket. start ,
334- bucket_size : bucket. size ,
335- series_id : min_id,
336- }
337- . encode ( ) ;
338- let end_key = ForwardIndexKey {
339- time_bucket : bucket. start ,
340- bucket_size : bucket. size ,
341- series_id : max_id. wrapping_add ( 1 ) ,
342- }
343- . encode ( ) ;
344- let range = common:: BytesRange :: new (
345- std:: ops:: Bound :: Included ( start_key) ,
346- std:: ops:: Bound :: Excluded ( end_key) ,
347- ) ;
304+ let keys: Vec < bytes:: Bytes > = series_ids
305+ . iter ( )
306+ . map ( |& series_id| {
307+ ForwardIndexKey {
308+ time_bucket : bucket. start ,
309+ bucket_size : bucket. size ,
310+ series_id,
311+ }
312+ . encode ( )
313+ } )
314+ . collect ( ) ;
348315
349- let mut iter = self . scan_iter ( range ) . await ?;
316+ let records = self . multi_get ( & keys ) . await ?;
350317 let result = ForwardIndex :: default ( ) ;
351- let mut scanned = 0u64 ;
352- let mut found = 0usize ;
353- while let Some ( record) = iter. next ( ) . await ? {
354- scanned += 1 ;
355- let key = ForwardIndexKey :: decode ( record. key . as_ref ( ) ) ?;
356- if wanted. contains ( & key. series_id ) {
357- let value = ForwardIndexValue :: decode ( record. value . as_ref ( ) ) ?;
358- result. series . insert ( key. series_id , value. into ( ) ) ;
359- found += 1 ;
360- if found == wanted. len ( ) {
361- break ;
362- }
363- }
318+
319+ for ( key_bytes, record) in records {
320+ let key = ForwardIndexKey :: decode ( key_bytes. as_ref ( ) ) ?;
321+ let value = ForwardIndexValue :: decode ( record. value . as_ref ( ) ) ?;
322+ result. series . insert ( key. series_id , value. into ( ) ) ;
364323 }
365- tracing:: Span :: current ( ) . record ( "scanned " , scanned ) ;
324+ tracing:: Span :: current ( ) . record ( "found " , result . series . len ( ) ) ;
366325 Ok ( result)
367326 }
368327
0 commit comments