use super::ShardedVertexTable;
pub(super) const MAX_SHARDS: usize = 256;
pub const DEFAULT_SEGMENT_SLOTS_BITS: u32 = 14;
const GLOBAL_ID_BITS: u32 = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShardLayout {
pub num_shards: usize,
pub segment_slots_bits: u32,
pub total_segments: u32,
}
impl ShardLayout {
pub fn for_new_table(num_shards: usize) -> Self {
let segment_slots_bits = DEFAULT_SEGMENT_SLOTS_BITS;
Self {
num_shards: num_shards.clamp(1, MAX_SHARDS).next_power_of_two(),
segment_slots_bits,
total_segments: 1u32 << (GLOBAL_ID_BITS - segment_slots_bits),
}
}
pub fn for_new_table_with_estimate(
parallelism_shards: usize,
estimated_rows: Option<u64>,
) -> Self {
Self::for_new_table(shards_for_estimate(estimated_rows, parallelism_shards))
}
pub fn segment_slots(&self) -> u32 {
1 << self.segment_slots_bits
}
pub fn segment_slots_mask(&self) -> u32 {
self.segment_slots() - 1
}
pub fn is_consistent(&self) -> bool {
self.segment_slots_bits >= 1
&& self.segment_slots_bits < GLOBAL_ID_BITS
&& self.total_segments == 1u32 << (GLOBAL_ID_BITS - self.segment_slots_bits)
&& self.num_shards.is_power_of_two()
&& self.num_shards <= MAX_SHARDS
}
}
pub const SINGLE_SHARD_MAX_ESTIMATE: u64 = 100_000;
pub fn shards_for_estimate(estimated_rows: Option<u64>, parallelism_shards: usize) -> usize {
match estimated_rows {
None => 1,
Some(n) if n <= SINGLE_SHARD_MAX_ESTIMATE => 1,
Some(_) => parallelism_shards.clamp(1, MAX_SHARDS).next_power_of_two(),
}
}
pub(super) fn encode_id(shard: usize, local_id: u32, layout: ShardLayout) -> u32 {
try_encode_id(shard, local_id, layout).expect("shard id encoding out of range")
}
pub(super) fn try_encode_id(
shard: usize,
local_id: u32,
layout: ShardLayout,
) -> Result<u32, String> {
if !layout.is_consistent() {
return Err("shard layout is inconsistent".to_string());
}
if shard >= layout.num_shards {
return Err(format!(
"shard {} out of range for {} shards",
shard, layout.num_shards
));
}
let segment = shard as u32 + (local_id >> layout.segment_slots_bits) * layout.num_shards as u32;
if segment >= layout.total_segments {
return Err(format!(
"local_id {} in shard {} exceeds the segment address space",
local_id, shard
));
}
Ok((segment << layout.segment_slots_bits) | (local_id & layout.segment_slots_mask()))
}
pub(super) fn decode_id(global_id: u32, layout: ShardLayout) -> (usize, u32) {
try_decode_id(global_id, layout).expect("global id decoding out of range")
}
pub(super) fn try_decode_id(global_id: u32, layout: ShardLayout) -> Result<(usize, u32), String> {
if !layout.is_consistent() {
return Err("shard layout is inconsistent".to_string());
}
let segment = global_id >> layout.segment_slots_bits;
if segment >= layout.total_segments {
return Err(format!(
"global id {} segment {} out of range",
global_id, segment
));
}
let shard = (segment % layout.num_shards as u32) as usize;
let local_id = (segment / layout.num_shards as u32) << layout.segment_slots_bits
| (global_id & layout.segment_slots_mask());
Ok((shard, local_id))
}
pub(super) const ROUTER_VERSION: u8 = 1;
pub(crate) fn finalize(mut hash: u64) -> u64 {
hash = hash.wrapping_add(0x9e3779b97f4a7c15);
hash = (hash ^ (hash >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
hash = (hash ^ (hash >> 27)).wrapping_mul(0x94d049bb133111eb);
hash ^ (hash >> 31)
}
pub(crate) fn fxhash(s: &str) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in s.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
finalize(hash)
}
pub(crate) fn fxhash_i64(n: i64) -> u64 {
finalize(n as u64)
}
impl ShardedVertexTable {
pub(super) fn shard_index_by_str(&self, external_id: &str) -> usize {
let hash = fxhash(external_id);
(hash as usize) & (self.layout.num_shards - 1)
}
pub(super) fn shard_index_by_i64(&self, external_id: i64) -> usize {
let hash = fxhash_i64(external_id);
(hash as usize) & (self.layout.num_shards - 1)
}
pub(super) fn record_allocation(&self, idx: usize, local_id: u32) -> u32 {
self.try_record_allocation(idx, local_id)
.expect("shard id encoding out of range")
}
pub(super) fn decode_id(&self, global_id: u32) -> (usize, u32) {
decode_id(global_id, self.layout)
}
pub(super) fn encode_id(&self, shard: usize, local_id: u32) -> u32 {
encode_id(shard, local_id, self.layout)
}
pub(super) fn try_record_allocation(&self, idx: usize, local_id: u32) -> Result<u32, String> {
try_encode_id(idx, local_id, self.layout)
}
pub(super) fn try_decode_global_id(&self, global_id: u32) -> Result<(usize, u32), String> {
try_decode_id(global_id, self.layout)
}
pub fn num_shards(&self) -> usize {
self.layout.num_shards
}
pub fn layout(&self) -> ShardLayout {
self.layout
}
pub fn generation(&self) -> u64 {
self.generation
}
}