Skip to content

Commit b07cc3d

Browse files
committed
x86 build + other fixes
1 parent b185b38 commit b07cc3d

8 files changed

Lines changed: 232 additions & 127 deletions

File tree

src/cli.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ pub struct Options {
317317
#[arg(
318318
long = "hot-tier-download-chunk-size",
319319
env = "P_HOT_TIER_DOWNLOAD_CHUNK_SIZE",
320+
value_parser = clap::value_parser!(u64).range(5242880..),
320321
default_value = "8388608",
321322
help = "Chunk size in bytes for parallel hot tier downloads (default 8 MiB)"
322323
)]
@@ -325,10 +326,11 @@ pub struct Options {
325326
#[arg(
326327
long = "hot-tier-download-concurrency",
327328
env = "P_HOT_TIER_DOWNLOAD_CONCURRENCY",
329+
value_parser = clap::value_parser!(u64).range(1..),
328330
default_value = "16",
329331
help = "Number of concurrent range requests per hot tier download"
330332
)]
331-
pub hot_tier_download_concurrency: usize,
333+
pub hot_tier_download_concurrency: u64,
332334

333335
#[arg(
334336
long = "hot-tier-files-per-stream-concurrency",
@@ -341,14 +343,16 @@ pub struct Options {
341343
#[arg(
342344
long = "hot-tier-latest-minutes",
343345
env = "P_HOT_TIER_LATEST_MINUTES",
346+
value_parser = clap::value_parser!(u64).range(1..),
344347
default_value = "10",
345348
help = "Files whose timestamp is within the last N minutes are 'latest'; rest are 'historic'."
346349
)]
347-
pub hot_tier_latest_minutes: i64,
350+
pub hot_tier_latest_minutes: u64,
348351

349352
#[arg(
350353
long = "hot-tier-historic-sync-minutes",
351354
env = "P_HOT_TIER_HISTORIC_SYNC_MINUTES",
355+
value_parser = clap::value_parser!(u32).range(1..),
352356
default_value = "5",
353357
help = "Interval (minutes) at which the historic hot-tier sync runs."
354358
)]

src/hottier.rs

Lines changed: 118 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,19 @@ impl HotTierManager {
116116
if let Some(state) = self.state_cache.read().await.get(&key).cloned() {
117117
return Ok(state);
118118
}
119-
let mut cache = self.state_cache.write().await;
120-
if let Some(state) = cache.get(&key).cloned() {
121-
return Ok(state);
122-
}
119+
// key not present, reconcile
123120
let sht = self.reconcile_stream(stream, tenant_id).await?;
124121
let state = Arc::new(StreamSyncState {
125122
sht: AsyncMutex::new(sht),
126123
});
127-
cache.insert(key, state.clone());
124+
125+
let mut cache = self.state_cache.write().await;
126+
if cache.insert(key, state.clone()).is_some() {
127+
tracing::warn!(
128+
"Key- {:?} was absent during read lock but already exists after reconcile!",
129+
(tenant_id, stream),
130+
);
131+
};
128132
Ok(state)
129133
}
130134

@@ -221,31 +225,41 @@ impl HotTierManager {
221225
stream: &str,
222226
tenant_id: &Option<String>,
223227
) -> 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;
228+
let mut stack: Vec<PathBuf> = vec![date_dir.clone()];
229+
while let Some(dir) = stack.pop() {
230+
let mut entries = fs::read_dir(&dir).await?;
231+
while let Some(entry) = entries.next_entry().await? {
232+
let p = entry.path();
233+
let ft = entry.file_type().await?;
234+
if ft.is_dir() {
235+
stack.push(p);
236+
continue;
237+
}
238+
let Some(name_os) = p.file_name() else {
239+
continue;
240+
};
241+
let name = name_os.to_string_lossy();
242+
if name.ends_with(".partial") {
243+
let _ = fs::remove_file(&p).await;
244+
*partials_removed += 1;
245+
info!(
246+
stream = %stream,
247+
tenant = ?tenant_id,
248+
path = %p.display(),
249+
"reconcile: deleted partial orphan"
250+
);
251+
continue;
252+
}
253+
if name.ends_with(".manifest.json") {
254+
continue;
255+
}
256+
if !ft.is_file() {
257+
continue;
258+
}
259+
if let Ok(rel) = p.strip_prefix(date_dir) {
260+
on_disk.insert(rel.to_string_lossy().into_owned());
261+
}
247262
}
248-
on_disk.insert(name.into_owned());
249263
}
250264
Ok(())
251265
}
@@ -275,8 +289,8 @@ impl HotTierManager {
275289
Err(_) => false,
276290
};
277291
if ok {
278-
if let Some(name) = local.file_name().and_then(|s| s.to_str()) {
279-
keep_names.insert(name.to_owned());
292+
if let Ok(rel) = local.strip_prefix(date_dir) {
293+
keep_names.insert(rel.to_string_lossy().into_owned());
280294
}
281295
*total_used += f.file_size;
282296
kept.push(f);
@@ -493,7 +507,7 @@ impl HotTierManager {
493507
'a: 'static,
494508
{
495509
let latest_min = PARSEABLE.options.hot_tier_latest_minutes;
496-
let historic_min = PARSEABLE.options.hot_tier_historic_sync_minutes.max(1);
510+
let historic_min = PARSEABLE.options.hot_tier_historic_sync_minutes;
497511
info!(
498512
latest_minutes = latest_min,
499513
historic_sync_minutes = historic_min,
@@ -516,6 +530,21 @@ impl HotTierManager {
516530
for stream in PARSEABLE.streams.list(&tenant_id) {
517531
if this.check_stream_hot_tier_exists(&stream, &tenant_id) {
518532
this.spawn_stream_tasks(stream, tenant_id.clone()).await;
533+
} else {
534+
// check for potential orphan directory on disk
535+
let path = if let Some(tenant_id) = tenant_id.as_ref() {
536+
self.hot_tier_path.join(tenant_id).join(stream)
537+
} else {
538+
self.hot_tier_path.join(stream)
539+
};
540+
if path.exists() {
541+
// delete this entire folder as stream meta says no hottier for stream
542+
if let Err(e) = fs::remove_dir_all(&path).await {
543+
tracing::error!(
544+
"Unable to remove orphaned hottier dir- `{path:?}` with error- {e}"
545+
);
546+
};
547+
}
519548
}
520549
}
521550
}
@@ -538,9 +567,8 @@ impl HotTierManager {
538567
}
539568

540569
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-
);
570+
let historic_interval =
571+
Duration::from_secs(PARSEABLE.options.hot_tier_historic_sync_minutes as u64 * 60);
544572

545573
info!(stream = %stream, tenant = ?tenant_id, "spawning per-stream hot tier tasks");
546574

@@ -588,6 +616,12 @@ impl HotTierManager {
588616
if let Some(t) = self.tasks.write().await.remove(&key) {
589617
t.latest.abort();
590618
t.historic.abort();
619+
620+
// run these tasks till completion or till JoinError
621+
// post this, we delete the folders so its better if these tasks complete
622+
// and then deletion happens
623+
let _ = t.latest.await;
624+
let _ = t.historic.await;
591625
info!(stream = %stream, tenant = ?tenant_id, "aborted per-stream hot tier tasks");
592626
}
593627
}
@@ -658,8 +692,9 @@ impl HotTierManager {
658692
return Ok(());
659693
}
660694

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);
695+
let latest_minutes = PARSEABLE.options.hot_tier_latest_minutes;
696+
let cutoff = chrono::Utc::now().naive_utc()
697+
- chrono::Duration::minutes(latest_minutes.try_into().unwrap());
663698

664699
// Build flat work list: (date, file, local_path) ordered latest-date-first,
665700
// and within a date by descending file_path (matches old `pop()` order).
@@ -717,10 +752,7 @@ impl HotTierManager {
717752
);
718753

719754
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);
755+
let concurrency = PARSEABLE.options.hot_tier_files_per_stream_concurrency;
724756

725757
// Reservation failure (out of disk + nothing to evict) is sticky:
726758
// once one file can't be placed, no subsequent file will fit either.
@@ -896,7 +928,7 @@ impl HotTierManager {
896928
let download_result = PARSEABLE
897929
.storage
898930
.get_object_store()
899-
.buffered_write(&parquet_file_path, tenant_id, parquet_path.clone())
931+
.parallel_chunked_download(&parquet_file_path, tenant_id, parquet_path.clone())
900932
.await;
901933
let dl_elapsed = dl_start.elapsed();
902934

@@ -1150,9 +1182,23 @@ impl HotTierManager {
11501182
let mut delete_successful = false;
11511183
let mut freed_total: u64 = 0;
11521184
let dates = self.fetch_hot_tier_dates(stream, tenant_id).await?;
1185+
if dates.is_empty() {
1186+
info!(
1187+
stream = %stream,
1188+
tenant = ?tenant_id,
1189+
"eviction: no date dirs found, nothing to evict"
1190+
);
1191+
}
11531192
'loop_dates: for date in dates {
11541193
let path = self.get_stream_path_for_date(stream, &date, tenant_id);
11551194
if !path.exists() {
1195+
info!(
1196+
stream = %stream,
1197+
tenant = ?tenant_id,
1198+
date = %date,
1199+
path = %path.display(),
1200+
"eviction: date path missing, skipping"
1201+
);
11561202
continue;
11571203
}
11581204

@@ -1164,11 +1210,27 @@ impl HotTierManager {
11641210
.to_string_lossy()
11651211
.ends_with(".manifest.json")
11661212
});
1213+
if manifest_files.is_empty() {
1214+
info!(
1215+
stream = %stream,
1216+
tenant = ?tenant_id,
1217+
date = %date,
1218+
path = %path.display(),
1219+
"eviction: no .manifest.json files in date dir"
1220+
);
1221+
continue;
1222+
}
11671223
for manifest_file in manifest_files {
11681224
let file = fs::read(manifest_file.path()).await?;
11691225
let mut manifest: Manifest = serde_json::from_slice(&file)?;
11701226

11711227
if manifest.files.is_empty() {
1228+
info!(
1229+
stream = %stream,
1230+
tenant = ?tenant_id,
1231+
manifest = %manifest_file.path().display(),
1232+
"eviction: manifest has zero file entries"
1233+
);
11721234
continue;
11731235
}
11741236

@@ -1182,7 +1244,18 @@ impl HotTierManager {
11821244
let first_file_path = self.hot_tier_path.join(&first_file.file_path);
11831245
let minute_to_delete = first_file_path.parent().unwrap();
11841246

1185-
if minute_to_delete.exists() {
1247+
if !minute_to_delete.exists() {
1248+
info!(
1249+
stream = %stream,
1250+
tenant = ?tenant_id,
1251+
manifest = %manifest_file.path().display(),
1252+
first_file = %first_file.file_path,
1253+
minute = %minute_to_delete.display(),
1254+
"eviction: minute dir referenced by manifest does not exist on disk"
1255+
);
1256+
continue;
1257+
}
1258+
{
11861259
if let (Some(download_date_time), Some(delete_date_time)) = (
11871260
extract_datetime(download_file_path.to_str().unwrap()),
11881261
extract_datetime(first_file_path.to_str().unwrap()),
@@ -1244,7 +1317,7 @@ impl HotTierManager {
12441317
new_available = stream_hot_tier.available_size,
12451318
"evicted"
12461319
);
1247-
if stream_hot_tier.available_size <= parquet_file_size {
1320+
if stream_hot_tier.available_size < parquet_file_size {
12481321
continue;
12491322
} else {
12501323
break 'loop_dates;
@@ -1263,7 +1336,7 @@ impl HotTierManager {
12631336
Ok(delete_successful)
12641337
}
12651338

1266-
///check if the disk is available to download the parquet file
1339+
/// check if the disk is available to download the parquet file
12671340
/// check if the disk usage is above the threshold
12681341
pub async fn is_disk_available(&self, size_to_download: u64) -> Result<bool, HotTierError> {
12691342
if let Some(DiskUtil {

src/storage/azure_blob.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ impl BlobStore {
224224
.await
225225
{
226226
Ok(()) => {
227-
tokio::fs::rename(&partial, &write_path).await?;
227+
if let Err(e) = tokio::fs::rename(&partial, &write_path).await {
228+
let _ = tokio::fs::remove_file(&partial).await;
229+
return Err(e.into());
230+
}
228231
Ok(())
229232
}
230233
Err(e) => {
@@ -255,18 +258,15 @@ impl BlobStore {
255258
file.set_len(total).await?;
256259
let std_file = Arc::new(file.into_std().await);
257260

258-
let chunk = PARSEABLE
259-
.options
260-
.hot_tier_download_chunk_size
261-
.max(8 * 1024 * 1024);
262-
let concurrency = PARSEABLE.options.hot_tier_download_concurrency.max(6);
261+
let chunk = PARSEABLE.options.hot_tier_download_chunk_size;
262+
let concurrency = PARSEABLE.options.hot_tier_download_concurrency;
263263
let ranges: Vec<Range<u64>> = (0..total)
264264
.step_by(chunk as usize)
265265
.map(|s| s..(s + chunk).min(total))
266266
.collect();
267267
let chunk_count = ranges.len() as u64;
268268
let client = self.client.clone();
269-
let semaphore = Arc::new(tokio::sync::Semaphore::new(concurrency));
269+
let semaphore = Arc::new(tokio::sync::Semaphore::new(concurrency as usize));
270270

271271
futures::stream::iter(ranges)
272272
.map(|r| {
@@ -281,15 +281,14 @@ impl BlobStore {
281281
let bytes = client.get_range(&src, r.clone()).await?;
282282
let offset = r.start;
283283
tokio::task::spawn_blocking(move || -> std::io::Result<()> {
284-
use std::os::unix::fs::FileExt;
285-
std_file.write_all_at(&bytes, offset)
284+
crate::storage::write_all_at(&std_file, &bytes, offset)
286285
})
287286
.await
288287
.map_err(|e| ObjectStorageError::Custom(format!("join: {e}")))??;
289288
Ok::<_, ObjectStorageError>(())
290289
}
291290
})
292-
.buffer_unordered(concurrency)
291+
.buffer_unordered(concurrency as usize)
293292
.try_collect::<Vec<_>>()
294293
.await?;
295294

@@ -572,7 +571,7 @@ impl BlobStore {
572571

573572
#[async_trait]
574573
impl ObjectStorage for BlobStore {
575-
async fn buffered_write(
574+
async fn parallel_chunked_download(
576575
&self,
577576
path: &RelativePath,
578577
tenant_id: &Option<String>,

0 commit comments

Comments
 (0)