use std::fmt;
use std::io::{Error, ErrorKind, Result};
use log::warn;
use serde::{Deserialize, Serialize};
use crate::meta_format::BlockPtrFormat;
use crate::staging::config::StagingConfig;
use crate::data_cache::config::HyperFileDataCacheConfig;
use crate::node_cache::config::HyperFileNodeCacheConfig;
#[cfg(feature = "wal")]
use crate::wal::config::HyperFileWalConfig;
use crate::*;
const MIN_ROOT_SIZE: usize = 56;
const MIN_META_BLOCK_SIZE: usize = 4096;
const MIN_DATA_BLOCK_SIZE: usize = 4096;
const MAX_BLOCK_SHIFT: u32 = 30;
pub(crate) const DEFAULT_ROOT_SIZE: usize = MIN_ROOT_SIZE;
const DEFAULT_META_BLOCK_SIZE: usize = MIN_META_BLOCK_SIZE;
const DEFAULT_DATA_BLOCK_SIZE: usize = MIN_DATA_BLOCK_SIZE;
const DEFAULT_BLOCK_PTR_FORMAT: BlockPtrFormat = BlockPtrFormat::PartedSegment;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct HyperFileMetaConfig {
pub root_size: usize,
pub meta_block_size: usize,
pub data_block_size: usize,
pub block_ptr_format: BlockPtrFormat,
}
impl fmt::Display for HyperFileMetaConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "root size: {}, meta block size: {}, data block size: {}, block ptr format: {:?}",
self.root_size, self.meta_block_size, self.data_block_size, self.block_ptr_format)
}
}
impl HyperFileMetaConfig {
pub fn new(root_size: usize, meta_block_size: usize, data_block_size: usize, block_ptr_format: BlockPtrFormat) -> Self {
let root_size = if root_size != DEFAULT_ROOT_SIZE {
warn!("only support fixed root size == {} at this moment", DEFAULT_ROOT_SIZE);
DEFAULT_ROOT_SIZE
} else {
root_size
};
let root_size = std::cmp::max(root_size, DEFAULT_ROOT_SIZE);
let meta_block_size = std::cmp::max(meta_block_size, DEFAULT_META_BLOCK_SIZE);
let data_block_size = std::cmp::max(data_block_size, DEFAULT_DATA_BLOCK_SIZE);
let meta_block_size = 1 << meta_block_size.checked_ilog2().unwrap();
let data_block_size = 1 << data_block_size.checked_ilog2().unwrap();
Self {
root_size: root_size,
meta_block_size: meta_block_size,
data_block_size: data_block_size,
block_ptr_format: block_ptr_format,
}
}
pub fn as_u32(&self) -> u32 {
let root_multiple: u32 = (self.root_size / 8) as u32;
assert!(self.root_size % 8 == 0);
let meta_block_shift: u32 = self.meta_block_size.checked_ilog2().expect("invalid meta block size");
let data_block_shift: u32 = self.data_block_size.checked_ilog2().expect("invalid meta block size");
let block_ptr_format: u32 = self.block_ptr_format as u8 as u32;
root_multiple << 24 | meta_block_shift << 16 | data_block_shift << 8 | block_ptr_format
}
pub fn try_from_u32(data: u32) -> Result<Self> {
let block_ptr_format = BlockPtrFormat::try_from_u8((data & 0xFF) as u8)?;
if block_ptr_format == BlockPtrFormat::Nop {
return Err(Error::new(ErrorKind::InvalidData,
"block ptr format is Nop, which no container is written with"));
}
let data_shift = (data >> 8) & 0xFF;
let meta_shift = (data >> 16) & 0xFF;
for (what, shift, min) in [
("data block size", data_shift, MIN_DATA_BLOCK_SIZE),
("meta block size", meta_shift, MIN_META_BLOCK_SIZE),
] {
let min_shift = min.ilog2();
if shift < min_shift || shift > MAX_BLOCK_SHIFT {
return Err(Error::new(ErrorKind::InvalidData, format!(
"{} shift {} is outside {}..={}", what, shift, min_shift, MAX_BLOCK_SHIFT)));
}
}
let data_block_size = 1usize << data_shift;
let meta_block_size = 1usize << meta_shift;
let root_size: usize = (((data >> 24) & 0xFF) * 8) as usize;
if root_size < MIN_ROOT_SIZE {
return Err(Error::new(ErrorKind::InvalidData, format!(
"root size {} is below the minimum {}", root_size, MIN_ROOT_SIZE)));
}
Ok(Self {
root_size,
meta_block_size,
data_block_size,
block_ptr_format,
})
}
pub fn from_u32(data: u32) -> Self {
Self::try_from_u32(data).expect("meta config")
}
}
impl Default for HyperFileMetaConfig {
fn default() -> Self {
Self {
root_size: DEFAULT_ROOT_SIZE,
meta_block_size: DEFAULT_META_BLOCK_SIZE,
data_block_size: DEFAULT_DATA_BLOCK_SIZE,
block_ptr_format: DEFAULT_BLOCK_PTR_FORMAT,
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct HyperFileRuntimeConfig {
pub forward_origin_concurrency: usize,
pub forward_origin_threshold: usize,
pub forward_origin_chunk_size: usize,
pub segment_buffer_size: usize,
pub segment_mpu_chunk_size: usize,
#[serde(default = "default_segment_mpu_max_concurrency")]
pub segment_mpu_max_concurrency: usize,
#[serde(default = "default_segment_max_object_bytes")]
pub segment_max_object_bytes: usize,
pub data_cache_blocks: usize,
pub data_cache_dirty_max_bytes_threshold: usize,
pub data_cache_dirty_max_blocks_threshold: usize,
pub data_cache_dirty_max_flush_interval: u64,
pub node_cache_blocks: usize,
#[serde(default = "default_read_get_max_bytes")]
pub read_get_max_bytes: usize,
#[serde(default = "default_parted_segment_enabled")]
pub parted_segment_enabled: bool,
#[serde(default = "default_data_cache_dirty_min_bytes_to_part")]
pub data_cache_dirty_min_bytes_to_part: usize,
#[serde(default = "default_read_max_concurrency")]
pub read_max_concurrency: usize,
#[serde(default)]
pub flush_conflict_policy: FlushConflictPolicy,
}
fn default_read_get_max_bytes() -> usize { DEFAULT_READ_GET_MAX_BYTES }
fn default_segment_mpu_max_concurrency() -> usize { DEFAULT_SEGMENT_MPU_MAX_CONCURRENCY }
fn default_segment_max_object_bytes() -> usize { DEFAULT_SEGMENT_MAX_OBJECT_BYTES }
fn default_parted_segment_enabled() -> bool { true }
fn default_data_cache_dirty_min_bytes_to_part() -> usize { DEFAULT_MAX_DIRTY_DATA_BYTES_THRESHOLD }
fn default_read_max_concurrency() -> usize { DEFAULT_READ_MAX_CONCURRENCY }
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum FlushConflictPolicy {
RetryLastWriterWins,
FailFast,
}
impl Default for FlushConflictPolicy {
fn default() -> Self {
Self::RetryLastWriterWins
}
}
impl Default for HyperFileRuntimeConfig {
fn default() -> Self {
Self {
forward_origin_concurrency: DEFAULT_FORWARD_ORIGIN_CONCURRENCY,
forward_origin_threshold: DEFAULT_FORWARD_ORIGIN_THRESHOLD,
forward_origin_chunk_size: DEFAULT_FORWARD_ORIGIN_CHUNK_SIZE,
segment_buffer_size: DEFAULT_SEGMENT_BUFFER_SIZE,
segment_mpu_chunk_size: DEFAULT_SEGMENT_MPU_CHUNK_SIZE,
segment_mpu_max_concurrency: DEFAULT_SEGMENT_MPU_MAX_CONCURRENCY,
segment_max_object_bytes: DEFAULT_SEGMENT_MAX_OBJECT_BYTES,
data_cache_blocks: DEFAULT_DATA_CACHE_BLOCKS,
data_cache_dirty_max_bytes_threshold: DEFAULT_MAX_DIRTY_DATA_BYTES_THRESHOLD,
data_cache_dirty_max_blocks_threshold: DEFAULT_MAX_DIRTY_DATA_BLOCKS_THRESHOLD,
data_cache_dirty_max_flush_interval: DEFAULT_MAX_DIRTY_DATA_FLUSH_INTERVAL,
node_cache_blocks: DEFAULT_NODE_CACHE_BLOCKS,
flush_conflict_policy: FlushConflictPolicy::default(),
read_get_max_bytes: DEFAULT_READ_GET_MAX_BYTES,
parted_segment_enabled: true,
data_cache_dirty_min_bytes_to_part: DEFAULT_MAX_DIRTY_DATA_BYTES_THRESHOLD,
read_max_concurrency: DEFAULT_READ_MAX_CONCURRENCY,
}
}
}
impl HyperFileRuntimeConfig {
pub fn default_large() -> Self {
Self {
forward_origin_concurrency: DEFAULT_FORWARD_ORIGIN_CONCURRENCY,
forward_origin_threshold: DEFAULT_FORWARD_ORIGIN_THRESHOLD,
forward_origin_chunk_size: DEFAULT_FORWARD_ORIGIN_CHUNK_SIZE,
segment_buffer_size: DEFAULT_LARGE_SEGMENT_BUFFER_SIZE,
segment_mpu_chunk_size: DEFAULT_SEGMENT_MPU_CHUNK_SIZE,
segment_mpu_max_concurrency: DEFAULT_SEGMENT_MPU_MAX_CONCURRENCY,
segment_max_object_bytes: DEFAULT_SEGMENT_MAX_OBJECT_BYTES,
data_cache_blocks: DEFAULT_LARGE_DATA_CACHE_BLOCKS,
data_cache_dirty_max_bytes_threshold: DEFAULT_LARGE_MAX_DIRTY_DATA_BYTES_THRESHOLD,
data_cache_dirty_max_blocks_threshold: DEFAULT_LARGE_MAX_DIRTY_DATA_BLOCKS_THRESHOLD,
data_cache_dirty_max_flush_interval: DEFAULT_MAX_DIRTY_DATA_FLUSH_INTERVAL,
node_cache_blocks: DEFAULT_MAX_NODE_CACHE_BLOCKS,
flush_conflict_policy: FlushConflictPolicy::default(),
read_get_max_bytes: DEFAULT_READ_GET_MAX_BYTES,
parted_segment_enabled: true,
data_cache_dirty_min_bytes_to_part: DEFAULT_MAX_DIRTY_DATA_BYTES_THRESHOLD,
read_max_concurrency: DEFAULT_READ_MAX_CONCURRENCY,
}
}
pub fn default_middle() -> Self {
Self {
forward_origin_concurrency: DEFAULT_FORWARD_ORIGIN_CONCURRENCY,
forward_origin_threshold: DEFAULT_FORWARD_ORIGIN_THRESHOLD,
forward_origin_chunk_size: DEFAULT_FORWARD_ORIGIN_CHUNK_SIZE,
segment_buffer_size: DEFAULT_MIDDLE_SEGMENT_BUFFER_SIZE,
segment_mpu_chunk_size: DEFAULT_SEGMENT_MPU_CHUNK_SIZE,
segment_mpu_max_concurrency: DEFAULT_SEGMENT_MPU_MAX_CONCURRENCY,
segment_max_object_bytes: DEFAULT_SEGMENT_MAX_OBJECT_BYTES,
data_cache_blocks: DEFAULT_MIDDLE_DATA_CACHE_BLOCKS,
data_cache_dirty_max_bytes_threshold: DEFAULT_MIDDLE_MAX_DIRTY_DATA_BYTES_THRESHOLD,
data_cache_dirty_max_blocks_threshold: DEFAULT_MIDDLE_MAX_DIRTY_DATA_BLOCKS_THRESHOLD,
data_cache_dirty_max_flush_interval: DEFAULT_MAX_DIRTY_DATA_FLUSH_INTERVAL,
node_cache_blocks: DEFAULT_MAX_NODE_CACHE_BLOCKS,
flush_conflict_policy: FlushConflictPolicy::default(),
read_get_max_bytes: DEFAULT_READ_GET_MAX_BYTES,
parted_segment_enabled: true,
data_cache_dirty_min_bytes_to_part: DEFAULT_MAX_DIRTY_DATA_BYTES_THRESHOLD,
read_max_concurrency: DEFAULT_READ_MAX_CONCURRENCY,
}
}
}
#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize)]
pub struct HyperFileConfig {
pub meta: HyperFileMetaConfig,
pub staging: StagingConfig,
#[serde(default)]
pub runtime: HyperFileRuntimeConfig,
#[cfg(feature = "wal")]
pub wal: HyperFileWalConfig,
pub data_cache: HyperFileDataCacheConfig,
pub node_cache: HyperFileNodeCacheConfig,
}
impl HyperFileConfig {
pub fn from_json_string(s: &str) -> Result<Self> {
serde_json::from_str(s)
.map_err(|e| {
let err_msg = format!("{}", e);
Error::new(ErrorKind::InvalidInput, err_msg)
})
}
pub fn to_json_string(&self, pretty: bool) -> String {
if pretty {
return serde_json::to_string_pretty(self).unwrap();
}
serde_json::to_string(self).unwrap()
}
}
pub struct HyperFileConfigBuilder {
pub(crate) config: HyperFileConfig,
}
impl HyperFileConfigBuilder {
pub fn new() -> Self {
let config = HyperFileConfig::default();
Self {
config
}
}
pub fn from(config: &HyperFileConfig) -> Self {
Self {
config: config.to_owned(),
}
}
pub fn with_meta_config(mut self, meta: &HyperFileMetaConfig) -> Self {
self.config.meta = meta.to_owned();
self
}
pub fn with_staging_config(mut self, staging: &StagingConfig) -> Self {
self.config.staging = staging.to_owned();
self
}
pub fn with_runtime_config(mut self, runtime: &HyperFileRuntimeConfig) -> Self {
self.config.runtime = runtime.to_owned();
self
}
#[cfg(feature = "wal")]
pub fn with_wal_config(mut self, wal: &HyperFileWalConfig) -> Self {
self.config.wal = wal.to_owned();
self
}
pub fn with_data_cache_config(mut self, cache: &HyperFileDataCacheConfig) -> Self {
self.config.data_cache = cache.to_owned();
self
}
pub fn with_node_cache_config(mut self, cache: &HyperFileNodeCacheConfig) -> Self {
self.config.node_cache = cache.to_owned();
self
}
pub fn build(&self) -> HyperFileConfig {
self.config.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn runtime_config_without_the_part_bound_reads_with_the_default() {
for (name, cfg) in [
("default", HyperFileRuntimeConfig::default()),
("large", HyperFileRuntimeConfig::default_large()),
("middle", HyperFileRuntimeConfig::default_middle()),
] {
assert_eq!(cfg.segment_mpu_max_concurrency, DEFAULT_SEGMENT_MPU_MAX_CONCURRENCY, "{}", name);
assert_eq!(cfg.segment_max_object_bytes, DEFAULT_SEGMENT_MAX_OBJECT_BYTES, "{}", name);
let mut v = serde_json::to_value(&cfg).unwrap();
assert!(v.as_object_mut().unwrap().remove("segment_mpu_max_concurrency").is_some(), "{}", name);
assert!(v.as_object_mut().unwrap().remove("segment_max_object_bytes").is_some(), "{}", name);
let back: HyperFileRuntimeConfig = serde_json::from_value(v).expect(name);
assert_eq!(back, cfg, "{}", name);
}
}
#[test]
fn meta_config_default_round_trip() {
let cfg = HyperFileMetaConfig::default();
let encoded = cfg.as_u32();
let decoded = HyperFileMetaConfig::from_u32(encoded);
assert_eq!(cfg, decoded);
}
#[test]
fn meta_config_custom_round_trip() {
let cfg = HyperFileMetaConfig::new(56, 8192, 65536, BlockPtrFormat::MicroGroup);
let encoded = cfg.as_u32();
let decoded = HyperFileMetaConfig::from_u32(encoded);
assert_eq!(decoded.root_size, 56);
assert_eq!(decoded.meta_block_size, 8192);
assert_eq!(decoded.data_block_size, 65536);
assert_eq!(decoded.block_ptr_format, BlockPtrFormat::MicroGroup);
}
#[test]
fn meta_config_enforces_min_sizes() {
let cfg = HyperFileMetaConfig::new(56, 1, 1, BlockPtrFormat::Flat);
assert!(cfg.meta_block_size >= MIN_META_BLOCK_SIZE);
assert!(cfg.data_block_size >= MIN_DATA_BLOCK_SIZE);
}
#[test]
fn meta_config_power_of_two_alignment() {
let cfg = HyperFileMetaConfig::new(56, 5000, 5000, BlockPtrFormat::Flat);
assert_eq!(cfg.meta_block_size, 4096);
assert_eq!(cfg.data_block_size, 4096);
}
#[test]
fn meta_config_json_round_trip() {
let cfg = HyperFileConfig::default();
let json = cfg.to_json_string(false);
let decoded = HyperFileConfig::from_json_string(&json).unwrap();
assert_eq!(cfg, decoded);
}
#[test]
fn meta_config_json_pretty() {
let cfg = HyperFileConfig::default();
let json = cfg.to_json_string(true);
assert!(json.contains('\n'));
let decoded = HyperFileConfig::from_json_string(&json).unwrap();
assert_eq!(cfg, decoded);
}
#[test]
fn meta_config_from_invalid_json() {
let result = HyperFileConfig::from_json_string("not json");
assert!(result.is_err());
}
#[test]
fn meta_config_rejects_an_unpopulated_field() {
let e = HyperFileMetaConfig::try_from_u32(0).unwrap_err();
assert_eq!(e.kind(), ErrorKind::InvalidData);
assert!(format!("{}", e).contains("Nop"), "{}", e);
}
#[test]
fn meta_config_rejects_each_field_out_of_range() {
let good = HyperFileMetaConfig::default();
let ok = good.as_u32();
assert_eq!(HyperFileMetaConfig::try_from_u32(ok).unwrap(), good,
"a config this crate encoded must decode back");
assert!(HyperFileMetaConfig::try_from_u32((ok & !0xFF) | 0x07).is_err());
let below = (ok & !0x0000FF00) | (11 << 8);
assert!(HyperFileMetaConfig::try_from_u32(below).is_err(), "shift 11 accepted");
let absurd = (ok & !0x0000FF00) | (200 << 8);
assert!(HyperFileMetaConfig::try_from_u32(absurd).is_err(), "shift 200 accepted");
assert!(HyperFileMetaConfig::try_from_u32((ok & !0x00FF0000) | (11 << 16)).is_err());
assert!(HyperFileMetaConfig::try_from_u32((ok & !0x00FF0000) | (200 << 16)).is_err());
assert!(HyperFileMetaConfig::try_from_u32(ok & !0xFF000000).is_err());
}
#[test]
fn meta_config_accepts_the_sizes_in_use() {
for data_block_size in [4096usize, 65536, 524288] {
let c = HyperFileMetaConfig::new(
DEFAULT_ROOT_SIZE, DEFAULT_META_BLOCK_SIZE, data_block_size,
DEFAULT_BLOCK_PTR_FORMAT);
let back = HyperFileMetaConfig::try_from_u32(c.as_u32())
.expect("a config in use must decode");
assert_eq!(back.data_block_size, data_block_size);
assert_eq!(back, c);
}
}
}