linkrs_storage/edge/node_group/
append_log.rs1use linkrs_core::types::{EdgeId, Timestamp, VertexId};
10use linkrs_core::{StorageError, StorageResult};
11
12use super::super::{MutableCsrTrait, Nbr};
13use super::{CsrShardSet, TableShardManifest};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub(crate) struct AppendInsert {
18 pub local: u32,
19 pub nbr: Nbr,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub(crate) struct AppendDelete {
25 pub local: u32,
26 pub edge_id: EdgeId,
27 pub delete_ts: Timestamp,
28}
29
30#[derive(Debug, Clone, Default)]
38pub(crate) struct ShardAppendLog {
39 pub inserts: Vec<AppendInsert>,
40 pub deletes: Vec<AppendDelete>,
41}
42
43impl ShardAppendLog {
44 pub fn is_empty(&self) -> bool {
45 self.inserts.is_empty() && self.deletes.is_empty()
46 }
47
48 pub fn clear(&mut self) {
49 self.inserts.clear();
50 self.deletes.clear();
51 }
52
53 pub fn op_count(&self) -> usize {
54 self.inserts.len() + self.deletes.len()
55 }
56}
57
58pub(crate) fn encode_append_ops(
62 manifest: &TableShardManifest,
63 inserts: &[AppendInsert],
64 deletes: &[AppendDelete],
65) -> Vec<u8> {
66 let mut out = Vec::new();
67 out.extend_from_slice(&manifest.group_bits.to_le_bytes());
68 out.extend_from_slice(&(inserts.len() as u64).to_le_bytes());
69 for insert in inserts {
70 out.extend_from_slice(&insert.local.to_le_bytes());
71 out.extend_from_slice(&insert.nbr.endpoint.to_le_bytes());
72 out.extend_from_slice(&insert.nbr.rank.to_le_bytes());
73 out.extend_from_slice(&insert.nbr.edge_id.0.to_le_bytes());
74 out.extend_from_slice(&insert.nbr.delete_ts.to_le_bytes());
75 }
76 out.extend_from_slice(&(deletes.len() as u64).to_le_bytes());
77 for delete in deletes {
78 out.extend_from_slice(&delete.local.to_le_bytes());
79 out.extend_from_slice(&delete.edge_id.0.to_le_bytes());
80 out.extend_from_slice(&delete.delete_ts.to_le_bytes());
81 }
82 out
83}
84
85pub(crate) fn decode_append_ops(
88 data: &[u8],
89 manifest: &TableShardManifest,
90) -> StorageResult<(Vec<AppendInsert>, Vec<AppendDelete>)> {
91 let mut cursor = 0usize;
92 let take = |data: &[u8], cursor: &mut usize, len: usize| -> StorageResult<Vec<u8>> {
93 if data.len() - *cursor < len {
94 return Err(StorageError::deserialize_error(
95 "append log payload too short",
96 ));
97 }
98 let slice = data[*cursor..*cursor + len].to_vec();
99 *cursor += len;
100 Ok(slice)
101 };
102 let carried_bits = u32::from_le_bytes(
103 take(data, &mut cursor, 4)?
104 .try_into()
105 .map_err(|_| StorageError::deserialize_error("append log width too short"))?,
106 );
107 if carried_bits != manifest.group_bits {
108 return Err(StorageError::deserialize_error(format!(
109 "append log width mismatch: log carries {}, table holds {}",
110 carried_bits, manifest.group_bits
111 )));
112 }
113 let insert_count = u64::from_le_bytes(
114 take(data, &mut cursor, 8)?
115 .try_into()
116 .map_err(|_| StorageError::deserialize_error("append log insert count too short"))?,
117 ) as usize;
118 let mut inserts = Vec::with_capacity(insert_count);
119 for _ in 0..insert_count {
120 let local = u32::from_le_bytes(
121 take(data, &mut cursor, 4)?
122 .try_into()
123 .map_err(|_| StorageError::deserialize_error("append log insert row too short"))?,
124 );
125 let endpoint = u32::from_le_bytes(
126 take(data, &mut cursor, 4)?
127 .try_into()
128 .map_err(|_| StorageError::deserialize_error("append log endpoint too short"))?,
129 );
130 let rank = u64::from_le_bytes(
131 take(data, &mut cursor, 8)?
132 .try_into()
133 .map_err(|_| StorageError::deserialize_error("append log rank too short"))?,
134 ) as i64;
135 let edge_id = EdgeId(u64::from_le_bytes(
136 take(data, &mut cursor, 8)?
137 .try_into()
138 .map_err(|_| StorageError::deserialize_error("append log edge id too short"))?,
139 ));
140 let delete_ts =
141 u64::from_le_bytes(take(data, &mut cursor, 8)?.try_into().map_err(|_| {
142 StorageError::deserialize_error("append log delete stamp too short")
143 })?);
144 let nbr = Nbr::with_timestamps(endpoint, rank, edge_id, delete_ts);
145 inserts.push(AppendInsert { local, nbr });
146 }
147 let delete_count = u64::from_le_bytes(
148 take(data, &mut cursor, 8)?
149 .try_into()
150 .map_err(|_| StorageError::deserialize_error("append log delete count too short"))?,
151 ) as usize;
152 let mut deletes = Vec::with_capacity(delete_count);
153 for _ in 0..delete_count {
154 let local = u32::from_le_bytes(
155 take(data, &mut cursor, 4)?
156 .try_into()
157 .map_err(|_| StorageError::deserialize_error("append log delete row too short"))?,
158 );
159 let edge_id = EdgeId(u64::from_le_bytes(
160 take(data, &mut cursor, 8)?.try_into().map_err(|_| {
161 StorageError::deserialize_error("append log delete edge id too short")
162 })?,
163 ));
164 let delete_ts =
165 u64::from_le_bytes(take(data, &mut cursor, 8)?.try_into().map_err(|_| {
166 StorageError::deserialize_error("append log delete stamp too short")
167 })?);
168 deletes.push(AppendDelete {
169 local,
170 edge_id,
171 delete_ts,
172 });
173 }
174 if cursor != data.len() {
175 return Err(StorageError::deserialize_error(
176 "unexpected trailing data in append log".to_string(),
177 ));
178 }
179 Ok((inserts, deletes))
180}
181
182impl CsrShardSet {
183 pub(crate) fn record_append_insert(&mut self, gid: usize, local: u32, nbr: Nbr) {
185 if let Some(shard) = self.shards.get_mut(&gid) {
186 shard.append.inserts.push(AppendInsert { local, nbr });
187 }
188 }
189
190 pub(crate) fn record_append_delete(
192 &mut self,
193 gid: usize,
194 local: u32,
195 edge_id: EdgeId,
196 delete_ts: Timestamp,
197 ) {
198 if let Some(shard) = self.shards.get_mut(&gid) {
199 shard.append.deletes.push(AppendDelete {
200 local,
201 edge_id,
202 delete_ts,
203 });
204 }
205 }
206
207 pub fn group_has_append_log(&self, gid: usize) -> bool {
209 self.shards
210 .get(&gid)
211 .is_some_and(|shard| !shard.append.is_empty())
212 }
213
214 pub fn group_append_op_count(&self, gid: usize) -> usize {
216 self.shards
217 .get(&gid)
218 .map_or(0, |shard| shard.append.op_count())
219 }
220
221 pub(crate) fn group_append_ops(&self, gid: usize) -> (Vec<AppendInsert>, Vec<AppendDelete>) {
223 self.shards
224 .get(&gid)
225 .map(|shard| (shard.append.inserts.clone(), shard.append.deletes.clone()))
226 .unwrap_or_default()
227 }
228
229 pub fn clear_group_append_log(&mut self, gid: usize) {
232 if let Some(shard) = self.shards.get_mut(&gid) {
233 shard.append.clear();
234 }
235 }
236
237 pub fn clear_all_append_logs(&mut self) {
240 for shard in self.shards.values_mut() {
241 shard.append.clear();
242 }
243 }
244
245 pub fn encode_group_append_log(&self, gid: usize, manifest: &TableShardManifest) -> Vec<u8> {
251 match self.shards.get(&gid) {
252 Some(shard) => {
253 encode_append_ops(manifest, &shard.append.inserts, &shard.append.deletes)
254 }
255 None => encode_append_ops(manifest, &[], &[]),
256 }
257 }
258
259 pub fn replay_group_append_log(
262 &mut self,
263 gid: usize,
264 data: &[u8],
265 manifest: &TableShardManifest,
266 ) -> StorageResult<()> {
267 let (inserts, deletes) = decode_append_ops(data, manifest)?;
268 let shard = self.shards.get_mut(&gid).ok_or_else(|| {
269 StorageError::deserialize_error(format!("group {} out of range on append replay", gid))
270 })?;
271 if matches!(shard.variant, super::super::CsrVariant::Frozen(_)) {
275 return Err(StorageError::deserialize_error(format!(
276 "append sidecar for frozen group {}",
277 gid
278 )));
279 }
280 for insert in inserts {
281 shard
282 .variant
283 .insert_edge(
284 insert.local,
285 VertexId::edge_endpoint_key(insert.nbr.endpoint, insert.nbr.rank),
286 insert.nbr.edge_id,
287 Timestamp::MAX,
288 )
289 .map_err(|e| {
290 StorageError::deserialize_error(format!(
291 "append log insert replay failed in group {}: {}",
292 gid, e
293 ))
294 })?;
295 }
296 for delete in deletes {
297 shard
298 .variant
299 .delete_edge(delete.local, delete.edge_id, delete.delete_ts)
300 .map_err(|e| {
301 StorageError::deserialize_error(format!(
302 "append log delete replay failed in group {}: {}",
303 gid, e
304 ))
305 })?;
306 }
307 Ok(())
308 }
309}