Browse Source

feat: auto-configure max volume count when max=0

When -max=0, dynamically calculate max volume count based on free disk
space, existing volumes, and EC shard count — matching Go's
MaybeAdjustVolumeMax(). Recalculate on each heartbeat tick and when
volume_size_limit changes from master.
rust-volume-server
Chris Lu 1 day ago
parent
commit
830b42eca6
  1. 24
      seaweed-volume/src/server/heartbeat.rs
  2. 18
      seaweed-volume/src/storage/disk_location.rs
  3. 52
      seaweed-volume/src/storage/store.rs

24
seaweed-volume/src/server/heartbeat.rs

@ -236,11 +236,21 @@ async fn do_heartbeat(
match resp {
Ok(Some(hb_resp)) => {
if hb_resp.volume_size_limit > 0 {
let s = state.store.read().unwrap();
s.volume_size_limit.store(
hb_resp.volume_size_limit,
std::sync::atomic::Ordering::Relaxed,
);
let changed = {
let s = state.store.read().unwrap();
s.volume_size_limit.store(
hb_resp.volume_size_limit,
std::sync::atomic::Ordering::Relaxed,
);
s.maybe_adjust_volume_max()
};
if changed {
let adjusted_hb = collect_heartbeat(config, state);
last_volumes = adjusted_hb.volumes.iter().map(|v| (v.id, v.clone())).collect();
if tx.send(adjusted_hb).await.is_err() {
return Ok(None);
}
}
}
let metrics_changed = apply_metrics_push_settings(
state,
@ -263,6 +273,10 @@ async fn do_heartbeat(
}
_ = volume_tick.tick() => {
{
let s = state.store.read().unwrap();
s.maybe_adjust_volume_max();
}
let current_hb = collect_heartbeat(config, state);
last_volumes = current_hb.volumes.iter().map(|v| (v.id, v.clone())).collect();
if tx.send(current_hb).await.is_err() {

18
seaweed-volume/src/storage/disk_location.rs

@ -424,6 +424,24 @@ impl DiskLocation {
self.volumes.iter_mut()
}
/// Sum of unused space in writable volumes (volumeSizeLimit - actual size per volume).
/// Used by auto-max-volume-count to estimate how many more volumes can fit.
pub fn unused_space(&self, volume_size_limit: u64) -> u64 {
let mut unused: u64 = 0;
for vol in self.volumes.values() {
if vol.is_read_only() {
continue;
}
let dat_size = vol.dat_file_size().unwrap_or(0);
let idx_size = vol.idx_file_size();
let used = dat_size + idx_size;
if volume_size_limit > used {
unused += volume_size_limit - used;
}
}
unused
}
/// Check disk space against min_free_space and update is_disk_space_low.
pub fn check_disk_space(&self) {
let (total, free) = get_disk_stats(&self.directory);

52
seaweed-volume/src/storage/store.rs

@ -373,6 +373,58 @@ impl Store {
.sum()
}
/// Total EC shard count across all EC volumes.
pub fn ec_shard_count(&self) -> usize {
self.ec_volumes
.values()
.map(|ecv| ecv.shards.iter().filter(|s| s.is_some()).count())
.sum()
}
/// Recalculate max volume counts for locations with original_max_volume_count == 0.
/// Returns true if any max changed (caller should re-send heartbeat).
pub fn maybe_adjust_volume_max(&self) -> bool {
let volume_size_limit = self.volume_size_limit.load(Ordering::Relaxed);
if volume_size_limit == 0 {
return false;
}
let mut has_changes = false;
let mut new_max_total: i32 = 0;
let total_ec_shards = self.ec_shard_count();
for loc in &self.locations {
if loc.original_max_volume_count == 0 {
let current = loc.max_volume_count.load(Ordering::Relaxed);
let (_, free) = super::disk_location::get_disk_stats(&loc.directory);
let unused_space = loc.unused_space(volume_size_limit);
let unclaimed = (free as i64) - (unused_space as i64);
let vol_count = loc.volumes_len() as i32;
let ec_equivalent = ((total_ec_shards
+ crate::storage::erasure_coding::ec_shard::DATA_SHARDS_COUNT)
/ crate::storage::erasure_coding::ec_shard::DATA_SHARDS_COUNT)
as i32;
let mut max_count = vol_count + ec_equivalent;
if unclaimed > volume_size_limit as i64 {
max_count += (unclaimed as u64 / volume_size_limit) as i32 - 1;
}
loc.max_volume_count.store(max_count, Ordering::Relaxed);
new_max_total += max_count;
has_changes = has_changes || current != max_count;
} else {
new_max_total += loc.original_max_volume_count;
}
}
crate::metrics::MAX_VOLUMES.set(new_max_total as i64);
has_changes
}
/// Free volume slots across all locations.
pub fn free_volume_count(&self) -> i32 {
self.locations

Loading…
Cancel
Save