1- //! Startup cache warmer that pre-populates the block cache by scanning
2- //! recent time bucket key ranges through the normal storage read API .
1+ //! Startup cache warmer that pre-populates the block cache for recent time
2+ //! buckets .
33//!
4- //! This is a temporary workaround until SlateDB's CacheManager
5- //! (`set_warm_prefixes()` + `warm_current()`) is available. Delete this
6- //! module when that lands.
4+ //! It discovers the buckets overlapping the configured recent window and hands
5+ //! them to [`WarmStorage::warm`], which drives SlateDB's cache manager
6+ //! (`warm_sst`) over the SSTs backing those buckets — pulling their filters,
7+ //! index, and (optionally) sample data blocks into the block cache.
78
89use std:: time:: { Duration , Instant , SystemTime , UNIX_EPOCH } ;
910
10- use common:: BytesRange ;
11- use common:: storage:: StorageError ;
1211use tokio:: task:: JoinHandle ;
1312use tokio_util:: sync:: CancellationToken ;
1413
1514use crate :: promql:: config:: CacheWarmerConfig ;
16- use crate :: serde:: TimeBucketScoped ;
17- use crate :: serde:: key:: { ForwardIndexKey , InvertedIndexKey , SeriesDictionaryKey , TimeSeriesKey } ;
18- use crate :: storage:: StorageRead ;
19-
20- const LOG_INTERVAL : Duration = Duration :: from_secs ( 30 ) ;
15+ use crate :: storage:: WarmStorage ;
2116
2217pub ( crate ) struct CacheWarmerHandle {
2318 cancel : CancellationToken ,
@@ -35,7 +30,7 @@ impl CacheWarmerHandle {
3530
3631/// Spawns a one-off cache warming task. Returns a handle that must be
3732/// shut down before closing the database.
38- pub ( crate ) fn start < R : StorageRead + ' static > (
33+ pub ( crate ) fn start < R : WarmStorage + ' static > (
3934 storage : R ,
4035 config : CacheWarmerConfig ,
4136) -> CacheWarmerHandle {
@@ -46,7 +41,6 @@ pub(crate) fn start<R: StorageRead + 'static>(
4641 match warm ( & storage, & config, & cancel) . await {
4742 Ok ( stats) => tracing:: info!(
4843 buckets = stats. buckets,
49- records = stats. records,
5044 elapsed = ?stats. elapsed,
5145 "Cache warming complete"
5246 ) ,
@@ -59,11 +53,10 @@ pub(crate) fn start<R: StorageRead + 'static>(
5953
6054struct WarmStats {
6155 buckets : usize ,
62- records : u64 ,
6356 elapsed : Duration ,
6457}
6558
66- async fn warm < R : StorageRead > (
59+ async fn warm < R : WarmStorage > (
6760 storage : & R ,
6861 config : & CacheWarmerConfig ,
6962 cancel : & CancellationToken ,
@@ -80,58 +73,16 @@ async fn warm<R: StorageRead>(
8073 . await ?;
8174 let total = buckets. len ( ) ;
8275
83- let mut records: u64 = 0 ;
84- let mut last_log = Instant :: now ( ) ;
85-
86- for ( i, bucket) in buckets. iter ( ) . enumerate ( ) {
87- if cancel. is_cancelled ( ) {
88- tracing:: info!( "Cache warming cancelled" ) ;
89- break ;
90- }
91-
92- records += drain_scan ( storage, ForwardIndexKey :: bucket_range ( bucket) ) . await ?;
93- records += drain_scan ( storage, InvertedIndexKey :: bucket_range ( bucket) ) . await ?;
94- records += drain_scan ( storage, SeriesDictionaryKey :: bucket_range ( bucket) ) . await ?;
95-
96- if config. include_samples {
97- records += drain_scan ( storage, TimeSeriesKey :: bucket_range ( bucket) ) . await ?;
98- }
99-
100- if last_log. elapsed ( ) >= LOG_INTERVAL {
101- tracing:: info!(
102- bucket = i + 1 ,
103- total,
104- records,
105- elapsed = ?start. elapsed( ) ,
106- "Cache warming progress"
107- ) ;
108- last_log = Instant :: now ( ) ;
109- }
110- }
76+ storage
77+ . warm ( buckets, config. include_samples , cancel)
78+ . await ?;
11179
11280 Ok ( WarmStats {
11381 buckets : total,
114- records,
11582 elapsed : start. elapsed ( ) ,
11683 } )
11784}
11885
119- /// Scan a key range, consuming all records to populate the block cache.
120- /// Returns the number of records touched.
121- async fn drain_scan < R : StorageRead > ( storage : & R , range : BytesRange ) -> crate :: util:: Result < u64 > {
122- let mut iter = storage. scan ( range) . await ?;
123- let mut count = 0u64 ;
124- while iter
125- . next ( )
126- . await
127- . map_err ( StorageError :: from_storage) ?
128- . is_some ( )
129- {
130- count += 1 ;
131- }
132- Ok ( count)
133- }
134-
13586#[ cfg( test) ]
13687mod tests {
13788 use super :: * ;
@@ -173,9 +124,8 @@ mod tests {
173124 let cancel = CancellationToken :: new ( ) ;
174125 let stats = warm ( storage. as_ref ( ) , & config, & cancel) . await . unwrap ( ) ;
175126
176- // then
127+ // then — the recent bucket is discovered and warming succeeds
177128 assert ! ( stats. buckets > 0 ) ;
178- assert ! ( stats. records > 0 ) ;
179129 }
180130
181131 #[ tokio:: test]
@@ -193,11 +143,10 @@ mod tests {
193143
194144 // then
195145 assert_eq ! ( stats. buckets, 0 ) ;
196- assert_eq ! ( stats. records, 0 ) ;
197146 }
198147
199148 #[ tokio:: test]
200- async fn should_stop_on_cancellation ( ) {
149+ async fn should_complete_when_cancelled ( ) {
201150 // given
202151 let storage = create_storage ( ) . await ;
203152 let tsdb = Tsdb :: new ( storage. clone ( ) ) ;
@@ -224,9 +173,9 @@ mod tests {
224173 // when — cancel before starting
225174 let cancel = CancellationToken :: new ( ) ;
226175 cancel. cancel ( ) ;
227- let stats = warm ( storage. as_ref ( ) , & config, & cancel) . await . unwrap ( ) ;
228176
229- // then — should have found buckets but processed none
230- assert_eq ! ( stats. records, 0 ) ;
177+ // then — warming returns gracefully (the warm short-circuits on the
178+ // pre-cancelled token) rather than erroring or hanging
179+ warm ( storage. as_ref ( ) , & config, & cancel) . await . unwrap ( ) ;
231180 }
232181}
0 commit comments