@@ -221,31 +221,41 @@ impl HotTierManager {
221221 stream : & str ,
222222 tenant_id : & Option < String > ,
223223 ) -> Result < ( ) , HotTierError > {
224- let mut entries = fs:: read_dir ( date_dir) . await ?;
225- while let Some ( entry) = entries. next_entry ( ) . await ? {
226- let p = entry. path ( ) ;
227- let Some ( name_os) = p. file_name ( ) else {
228- continue ;
229- } ;
230- let name = name_os. to_string_lossy ( ) ;
231- if name. ends_with ( ".partial" ) {
232- let _ = fs:: remove_file ( & p) . await ;
233- * partials_removed += 1 ;
234- info ! (
235- stream = %stream,
236- tenant = ?tenant_id,
237- path = %p. display( ) ,
238- "reconcile: deleted partial orphan"
239- ) ;
240- continue ;
241- }
242- if name. ends_with ( ".manifest.json" ) {
243- continue ;
244- }
245- if !p. is_file ( ) {
246- continue ;
224+ let mut stack: Vec < PathBuf > = vec ! [ date_dir. clone( ) ] ;
225+ while let Some ( dir) = stack. pop ( ) {
226+ let mut entries = fs:: read_dir ( & dir) . await ?;
227+ while let Some ( entry) = entries. next_entry ( ) . await ? {
228+ let p = entry. path ( ) ;
229+ let ft = entry. file_type ( ) . await ?;
230+ if ft. is_dir ( ) {
231+ stack. push ( p) ;
232+ continue ;
233+ }
234+ let Some ( name_os) = p. file_name ( ) else {
235+ continue ;
236+ } ;
237+ let name = name_os. to_string_lossy ( ) ;
238+ if name. ends_with ( ".partial" ) {
239+ let _ = fs:: remove_file ( & p) . await ;
240+ * partials_removed += 1 ;
241+ info ! (
242+ stream = %stream,
243+ tenant = ?tenant_id,
244+ path = %p. display( ) ,
245+ "reconcile: deleted partial orphan"
246+ ) ;
247+ continue ;
248+ }
249+ if name. ends_with ( ".manifest.json" ) {
250+ continue ;
251+ }
252+ if !ft. is_file ( ) {
253+ continue ;
254+ }
255+ if let Ok ( rel) = p. strip_prefix ( date_dir) {
256+ on_disk. insert ( rel. to_string_lossy ( ) . into_owned ( ) ) ;
257+ }
247258 }
248- on_disk. insert ( name. into_owned ( ) ) ;
249259 }
250260 Ok ( ( ) )
251261 }
@@ -275,8 +285,8 @@ impl HotTierManager {
275285 Err ( _) => false ,
276286 } ;
277287 if ok {
278- if let Some ( name ) = local. file_name ( ) . and_then ( |s| s . to_str ( ) ) {
279- keep_names. insert ( name . to_owned ( ) ) ;
288+ if let Ok ( rel ) = local. strip_prefix ( date_dir ) {
289+ keep_names. insert ( rel . to_string_lossy ( ) . into_owned ( ) ) ;
280290 }
281291 * total_used += f. file_size ;
282292 kept. push ( f) ;
@@ -493,7 +503,7 @@ impl HotTierManager {
493503 ' a : ' static ,
494504 {
495505 let latest_min = PARSEABLE . options . hot_tier_latest_minutes ;
496- let historic_min = PARSEABLE . options . hot_tier_historic_sync_minutes . max ( 1 ) ;
506+ let historic_min = PARSEABLE . options . hot_tier_historic_sync_minutes ;
497507 info ! (
498508 latest_minutes = latest_min,
499509 historic_sync_minutes = historic_min,
@@ -516,6 +526,21 @@ impl HotTierManager {
516526 for stream in PARSEABLE . streams . list ( & tenant_id) {
517527 if this. check_stream_hot_tier_exists ( & stream, & tenant_id) {
518528 this. spawn_stream_tasks ( stream, tenant_id. clone ( ) ) . await ;
529+ } else {
530+ // check for potential orphan directory on disk
531+ let path = if let Some ( tenant_id) = tenant_id. as_ref ( ) {
532+ self . hot_tier_path . join ( tenant_id) . join ( stream)
533+ } else {
534+ self . hot_tier_path . join ( stream)
535+ } ;
536+ if path. exists ( ) {
537+ // delete this entire folder as stream meta says no hottier for stream
538+ if let Err ( e) = fs:: remove_dir_all ( & path) . await {
539+ tracing:: error!(
540+ "Unable to remove orphaned hottier dir- `{path:?}` with error- {e}"
541+ ) ;
542+ } ;
543+ }
519544 }
520545 }
521546 }
@@ -538,9 +563,8 @@ impl HotTierManager {
538563 }
539564
540565 let latest_interval = Duration :: from_secs ( 60 ) ;
541- let historic_interval = Duration :: from_secs (
542- PARSEABLE . options . hot_tier_historic_sync_minutes . max ( 1 ) as u64 * 60 ,
543- ) ;
566+ let historic_interval =
567+ Duration :: from_secs ( PARSEABLE . options . hot_tier_historic_sync_minutes as u64 * 60 ) ;
544568
545569 info ! ( stream = %stream, tenant = ?tenant_id, "spawning per-stream hot tier tasks" ) ;
546570
@@ -658,8 +682,9 @@ impl HotTierManager {
658682 return Ok ( ( ) ) ;
659683 }
660684
661- let latest_minutes = PARSEABLE . options . hot_tier_latest_minutes . max ( 0 ) ;
662- let cutoff = chrono:: Utc :: now ( ) . naive_utc ( ) - chrono:: Duration :: minutes ( latest_minutes) ;
685+ let latest_minutes = PARSEABLE . options . hot_tier_latest_minutes ;
686+ let cutoff = chrono:: Utc :: now ( ) . naive_utc ( )
687+ - chrono:: Duration :: minutes ( latest_minutes. try_into ( ) . unwrap ( ) ) ;
663688
664689 // Build flat work list: (date, file, local_path) ordered latest-date-first,
665690 // and within a date by descending file_path (matches old `pop()` order).
@@ -717,10 +742,7 @@ impl HotTierManager {
717742 ) ;
718743
719744 let state = self . get_or_load_state ( stream, tenant_id) . await ?;
720- let concurrency = PARSEABLE
721- . options
722- . hot_tier_files_per_stream_concurrency
723- . max ( 4 ) ;
745+ let concurrency = PARSEABLE . options . hot_tier_files_per_stream_concurrency ;
724746
725747 // Reservation failure (out of disk + nothing to evict) is sticky:
726748 // once one file can't be placed, no subsequent file will fit either.
@@ -1150,9 +1172,23 @@ impl HotTierManager {
11501172 let mut delete_successful = false ;
11511173 let mut freed_total: u64 = 0 ;
11521174 let dates = self . fetch_hot_tier_dates ( stream, tenant_id) . await ?;
1175+ if dates. is_empty ( ) {
1176+ info ! (
1177+ stream = %stream,
1178+ tenant = ?tenant_id,
1179+ "eviction: no date dirs found, nothing to evict"
1180+ ) ;
1181+ }
11531182 ' loop_dates: for date in dates {
11541183 let path = self . get_stream_path_for_date ( stream, & date, tenant_id) ;
11551184 if !path. exists ( ) {
1185+ info ! (
1186+ stream = %stream,
1187+ tenant = ?tenant_id,
1188+ date = %date,
1189+ path = %path. display( ) ,
1190+ "eviction: date path missing, skipping"
1191+ ) ;
11561192 continue ;
11571193 }
11581194
@@ -1164,11 +1200,27 @@ impl HotTierManager {
11641200 . to_string_lossy ( )
11651201 . ends_with ( ".manifest.json" )
11661202 } ) ;
1203+ if manifest_files. is_empty ( ) {
1204+ info ! (
1205+ stream = %stream,
1206+ tenant = ?tenant_id,
1207+ date = %date,
1208+ path = %path. display( ) ,
1209+ "eviction: no .manifest.json files in date dir"
1210+ ) ;
1211+ continue ;
1212+ }
11671213 for manifest_file in manifest_files {
11681214 let file = fs:: read ( manifest_file. path ( ) ) . await ?;
11691215 let mut manifest: Manifest = serde_json:: from_slice ( & file) ?;
11701216
11711217 if manifest. files . is_empty ( ) {
1218+ info ! (
1219+ stream = %stream,
1220+ tenant = ?tenant_id,
1221+ manifest = %manifest_file. path( ) . display( ) ,
1222+ "eviction: manifest has zero file entries"
1223+ ) ;
11721224 continue ;
11731225 }
11741226
@@ -1182,7 +1234,18 @@ impl HotTierManager {
11821234 let first_file_path = self . hot_tier_path . join ( & first_file. file_path ) ;
11831235 let minute_to_delete = first_file_path. parent ( ) . unwrap ( ) ;
11841236
1185- if minute_to_delete. exists ( ) {
1237+ if !minute_to_delete. exists ( ) {
1238+ info ! (
1239+ stream = %stream,
1240+ tenant = ?tenant_id,
1241+ manifest = %manifest_file. path( ) . display( ) ,
1242+ first_file = %first_file. file_path,
1243+ minute = %minute_to_delete. display( ) ,
1244+ "eviction: minute dir referenced by manifest does not exist on disk"
1245+ ) ;
1246+ continue ;
1247+ }
1248+ {
11861249 if let ( Some ( download_date_time) , Some ( delete_date_time) ) = (
11871250 extract_datetime ( download_file_path. to_str ( ) . unwrap ( ) ) ,
11881251 extract_datetime ( first_file_path. to_str ( ) . unwrap ( ) ) ,
@@ -1244,7 +1307,7 @@ impl HotTierManager {
12441307 new_available = stream_hot_tier. available_size,
12451308 "evicted"
12461309 ) ;
1247- if stream_hot_tier. available_size <= parquet_file_size {
1310+ if stream_hot_tier. available_size < parquet_file_size {
12481311 continue ;
12491312 } else {
12501313 break ' loop_dates;
0 commit comments