Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_o3db_sync/src/dist/placement.rs

3.6 KiB, 47 runs

created by r1870400018:11388, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The placement service.
2//!
3//! [`Placement`] is the glue between [`OamConfig`] and [`PeerSet`]: it
4//! answers the three placement questions of distributed Ozone without
5//! involving transport or storage.
6//!
7//! The service holds a cached [`Threshold`] computed from the current
8//! `OamConfig`. Recomputation happens only on explicit configuration change
9//! (e.g. when the HyperLogLog estimate revises `N`). Every placement decision
10//! therefore costs one XOR-distance computation plus a bytewise comparison.
11//!
12//! # Read-through routing
13//!
14//! When the local peer is *not* a holder of a record it wants to read, it
15//! picks from the peers nearest the record hash regardless of the local
16//! threshold -- those are, under a well-mixed hash, the ones most likely to
17//! consider themselves holders even if the local view of `N` differs slightly
18//! from theirs. [`Placement::read_targets`] returns this list.
19//!
20//! [`OamConfig`]: crate::oam::config::OamConfig
21//! [`Threshold`]: crate::oam::threshold::Threshold
22//!
23//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
24//! Anthropic Claude
25
26use super::peer_set::PeerSet;
27use super::record::RecordId;
28
29use oxedyne_fe2o3_core::prelude::*;
30use crate::kademlia::id::NodeId;
31use crate::oam::{
32 config::OamConfig,
33 placement as oam,
34 threshold::Threshold,
35};
36
37
38/// The placement service consulted on every write and every non-local read.
39#[derive(Clone, Debug)]
40pub struct Placement {
41 local_peer_id: NodeId,
42 oam: OamConfig,
43 threshold: Threshold,
44}
45
46impl Placement {
47 /// Precomputes the 256-bit threshold.
48 pub fn new(local_peer_id: NodeId, oam: OamConfig) -> Self {
49 let threshold = oam.threshold();
50 Self { local_peer_id, oam, threshold }
51 }
52
53 pub fn local_peer_id(&self) -> &NodeId {
54 &self.local_peer_id
55 }
56
57 pub fn oam(&self) -> &OamConfig {
58 &self.oam
59 }
60
61 pub fn threshold(&self) -> &Threshold {
62 &self.threshold
63 }
64
65 /// Recomputes the cached threshold. Typically called after a HyperLogLog
66 /// estimate refresh revises `N`.
67 pub fn update_oam(&mut self, oam: OamConfig) {
68 self.threshold = oam.threshold();
69 self.oam = oam;
70 }
71
72 pub fn i_am_holder(&self, record: &RecordId) -> bool {
73 oam::is_holder(
74 &self.local_peer_id,
75 &record.as_node_id(),
76 &self.threshold,
77 )
78 }
79
80 /// The local peer is never included -- distributed Ozone handles the local
81 /// replica separately.
82 pub fn remote_holders<'a>(
83 &self,
84 record: &RecordId,
85 peers: &'a PeerSet,
86 )
87 -> Vec<&'a NodeId>
88 {
89 oam::holders(&record.as_node_id(), peers.as_slice(), &self.threshold)
90 }
91
92 /// Up to `count` peers nearest the record hash by XOR distance, regardless
93 /// of the cached threshold. Used when the local peer is not a holder and
94 /// must fetch the record from the network.
95 pub fn read_targets<'a>(
96 &self,
97 record: &RecordId,
98 peers: &'a PeerSet,
99 count: usize,
100 )
101 -> Vec<&'a NodeId>
102 {
103 oam::closest_holders(&record.as_node_id(), peers.as_slice(), count)
104 }
105
106 /// Convenience for a call site that needs the local answer and the remote
107 /// holders together.
108 pub fn decide<'a>(
109 &self,
110 record: &RecordId,
111 peers: &'a PeerSet,
112 )
113 -> PlacementDecision<'a>
114 {
115 let local = self.i_am_holder(record);
116 let remote = self.remote_holders(record, peers);
117 PlacementDecision {
118 local_is_holder: local,
119 remote_holders: remote,
120 }
121 }
122}
123
124
125/// The result of a placement decision for one record.
126#[derive(Clone, Debug)]
127pub struct PlacementDecision<'a> {
128 pub local_is_holder: bool,
129 pub remote_holders: Vec<&'a NodeId>,
130}
131
132impl<'a> PlacementDecision<'a> {
133 /// Local plus remote.
134 pub fn holder_count(&self) -> usize {
135 self.remote_holders.len() + usize::from(self.local_is_holder)
136 }
137}