canic_core/model/memory/sharding/
mod.rs1mod registry;
2
3pub use registry::*;
4
5use crate::{
6 cdk::{
7 structures::{BTreeMap, DefaultMemoryImpl, Memory, memory::VirtualMemory},
8 types::Principal,
9 },
10 eager_static, ic_memory,
11 ids::CanisterRole,
12 memory::impl_storable_bounded,
13 model::memory::id::sharding::{SHARDING_ASSIGNMENT_ID, SHARDING_REGISTRY_ID},
14 types::{BoundedString64, BoundedString128},
15};
16use candid::CandidType;
17use serde::{Deserialize, Serialize};
18use std::cell::RefCell;
19
20eager_static! {
25 static SHARDING_CORE: RefCell<ShardingCore<VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
26 ShardingCore::new(
27 BTreeMap::init(ic_memory!(ShardingRegistry, SHARDING_REGISTRY_ID)),
28 BTreeMap::init(ic_memory!(ShardingRegistry, SHARDING_ASSIGNMENT_ID)),
29 )
30 );
31}
32
33#[derive(CandidType, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
39pub struct ShardKey {
40 pub pool: BoundedString64,
41 pub tenant: BoundedString128,
42}
43
44impl ShardKey {
45 pub const STORABLE_MAX_SIZE: u32 = 192;
46
47 pub(crate) fn try_new(pool: &str, tenant: &str) -> Result<Self, String> {
48 Ok(Self {
49 pool: pool.try_into()?,
50 tenant: tenant.try_into()?,
51 })
52 }
53}
54
55impl_storable_bounded!(ShardKey, ShardKey::STORABLE_MAX_SIZE, false);
56
57#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
63pub struct ShardEntry {
64 #[serde(default = "ShardEntry::slot_default")]
66 pub slot: u32,
67 pub capacity: u32,
68 pub count: u32,
69 pub pool: BoundedString64,
70 pub canister_role: CanisterRole,
71 pub created_at: u64,
72}
73
74impl ShardEntry {
75 pub const STORABLE_MAX_SIZE: u32 = 240;
76 pub const UNASSIGNED_SLOT: u32 = u32::MAX;
77
78 pub(crate) fn try_new(
79 pool: &str,
80 slot: u32,
81 role: CanisterRole,
82 capacity: u32,
83 created_at: u64,
84 ) -> Result<Self, String> {
85 let pool = BoundedString64::try_new(pool).map_err(|err| format!("pool name: {err}"))?;
86
87 Ok(Self {
88 slot,
89 canister_role: role,
90 capacity,
91 count: 0,
92 pool,
93 created_at,
94 })
95 }
96
97 #[must_use]
99 pub const fn has_capacity(&self) -> bool {
100 self.count < self.capacity
101 }
102
103 #[must_use]
105 pub const fn load_bps(&self) -> Option<u64> {
106 if self.capacity == 0 {
107 None
108 } else {
109 Some((self.count as u64).saturating_mul(10_000) / self.capacity as u64)
110 }
111 }
112
113 const fn slot_default() -> u32 {
114 Self::UNASSIGNED_SLOT
115 }
116
117 #[must_use]
118 pub const fn has_assigned_slot(&self) -> bool {
119 self.slot != Self::UNASSIGNED_SLOT
120 }
121}
122
123impl_storable_bounded!(ShardEntry, ShardEntry::STORABLE_MAX_SIZE, false);
124
125pub struct ShardingCore<M: Memory> {
131 registry: BTreeMap<Principal, ShardEntry, M>,
132 assignments: BTreeMap<ShardKey, Principal, M>,
133}
134
135impl<M: Memory> ShardingCore<M> {
136 pub const fn new(
137 registry: BTreeMap<Principal, ShardEntry, M>,
138 assignments: BTreeMap<ShardKey, Principal, M>,
139 ) -> Self {
140 Self {
141 registry,
142 assignments,
143 }
144 }
145
146 pub(crate) fn insert_entry(&mut self, pid: Principal, entry: ShardEntry) {
151 self.registry.insert(pid, entry);
152 }
153
154 pub(crate) fn get_entry(&self, pid: &Principal) -> Option<ShardEntry> {
155 self.registry.get(pid)
156 }
157
158 pub(crate) fn all_entries(&self) -> Vec<(Principal, ShardEntry)> {
159 self.registry
160 .iter()
161 .map(|e| (*e.key(), e.value()))
162 .collect()
163 }
164
165 pub(crate) fn insert_assignment(&mut self, key: ShardKey, shard: Principal) {
170 self.assignments.insert(key, shard);
171 }
172
173 pub(crate) fn remove_assignment(&mut self, key: &ShardKey) -> Option<Principal> {
174 self.assignments.remove(key)
175 }
176
177 pub(crate) fn get_assignment(&self, key: &ShardKey) -> Option<Principal> {
178 self.assignments.get(key)
179 }
180
181 pub(crate) fn all_assignments(&self) -> Vec<(ShardKey, Principal)> {
182 self.assignments
183 .iter()
184 .map(|e| (e.key().clone(), e.value()))
185 .collect()
186 }
187}