linkrs_storage/edge/mutable_csr/
persistence.rs1use super::super::{ColdStamps, EdgeId, HotNbr, INVALID_EDGE_ID};
2use super::live_set::LiveKeySet;
3use super::overflow::OverflowStorage;
4use super::serialization::{
5 decode_overflow_chunk, decode_topology_i64_column, decode_topology_u32_column,
6 decode_topology_u64_column, encode_overflow_chunk, encode_topology_i64_column,
7 encode_topology_u32_column, encode_topology_u64_column, TopologyColumnEncoding,
8 MUTABLE_CSR_FORMAT_VERSION,
9};
10use super::write::EdgePosition;
11use super::MutableCsr;
12use crate::persistence::{read_u32_le, read_u64_le};
13use linkrs_core::{StorageError, StorageResult};
14
15#[derive(Debug, Default)]
21pub struct CsrDumpScratch {
22 endpoints: Vec<u32>,
23 ranks: Vec<i64>,
24 edge_ids: Vec<u64>,
25 deletes: Vec<u64>,
26}
27
28impl CsrDumpScratch {
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub(crate) fn endpoints(&self) -> &[u32] {
34 &self.endpoints
35 }
36
37 pub(crate) fn ranks(&self) -> &[i64] {
38 &self.ranks
39 }
40
41 pub(crate) fn edge_ids(&self) -> &[u64] {
42 &self.edge_ids
43 }
44
45 pub(crate) fn deletes(&self) -> &[u64] {
46 &self.deletes
47 }
48
49 pub(crate) fn fill_from_split(&mut self, hot: &[HotNbr], cold: &[ColdStamps]) {
50 debug_assert_eq!(hot.len(), cold.len());
51 self.endpoints.clear();
52 self.ranks.clear();
53 self.edge_ids.clear();
54 self.deletes.clear();
55 self.endpoints.reserve(hot.len());
56 self.ranks.reserve(hot.len());
57 self.edge_ids.reserve(hot.len());
58 self.deletes.reserve(hot.len());
59 for slot in hot {
60 self.endpoints.push(slot.endpoint);
61 self.ranks.push(slot.rank);
62 self.edge_ids.push(slot.edge_id.0);
63 }
64 for stamp in cold {
65 self.deletes.push(stamp.delete_ts);
66 }
67 }
68}
69
70impl MutableCsr {
71 pub fn dump(&self) -> Vec<u8> {
97 let mut result = Vec::new();
98 self.dump_into(&mut result);
99 result
100 }
101
102 pub fn dump_into(&self, out: &mut Vec<u8>) {
106 let mut scratch = CsrDumpScratch::new();
107 self.dump_into_with_scratch(out, &mut scratch);
108 }
109
110 pub fn dump_into_with_scratch(&self, out: &mut Vec<u8>, scratch: &mut CsrDumpScratch) {
117 let start = out.len();
118 out.extend_from_slice(&MUTABLE_CSR_FORMAT_VERSION.to_le_bytes());
119 out.extend_from_slice(&(self.rows.adj_offsets.len() as u64).to_le_bytes());
120 out.extend_from_slice(&self.edge_count.to_le_bytes());
121 out.extend_from_slice(&(self.hot_list.len() as u64).to_le_bytes());
122 out.extend_from_slice(&(self.overflow_chunk_edges as u64).to_le_bytes());
123 out.extend_from_slice(&self.tombstone_reuse_cutoff.to_le_bytes());
124
125 let (_, offsets_payload) = encode_topology_u32_column(&self.rows.adj_offsets);
126 out.extend_from_slice(&offsets_payload);
127 let (_, degrees_payload) = encode_topology_u32_column(&self.rows.degrees);
128 out.extend_from_slice(°rees_payload);
129 let (_, caps_payload) = encode_topology_u32_column(&self.rows.primary_capacities);
130 out.extend_from_slice(&caps_payload);
131
132 scratch.fill_from_split(&self.hot_list, &self.cold_list);
133 let (_, endpoints_payload) = encode_topology_u32_column(&scratch.endpoints);
134 out.extend_from_slice(&endpoints_payload);
135 let (_, ranks_payload) = encode_topology_i64_column(&scratch.ranks);
136 out.extend_from_slice(&ranks_payload);
137 let (_, edge_ids_payload) = encode_topology_u64_column(&scratch.edge_ids);
138 out.extend_from_slice(&edge_ids_payload);
139 let (_, delete_payload) = encode_topology_u64_column(&scratch.deletes);
140 out.extend_from_slice(&delete_payload);
141
142 for vid in 0..self.rows.adj_offsets.len() {
143 let chunks = self.overflow_chunks.get(vid as u32);
144 out.extend_from_slice(&(chunks.map_or(0, Vec::len) as u32).to_le_bytes());
145 if let Some(chunks) = chunks {
146 for chunk in chunks {
147 encode_overflow_chunk(chunk, out);
148 }
149 }
150 }
151 let crc = crc32fast::hash(&out[start..]);
152 out.extend_from_slice(&crc.to_le_bytes());
153 }
154
155 pub fn topology_encoding_report(&self) -> Vec<(String, TopologyColumnEncoding, usize, usize)> {
161 let endpoints: Vec<u32> = self.hot_list.iter().map(|hot| hot.endpoint).collect();
162 let edge_ids: Vec<u64> = self.hot_list.iter().map(|hot| hot.edge_id.0).collect();
163 let (endpoint_choice, _) = encode_topology_u32_column(&endpoints);
164 let (edge_id_choice, _) = encode_topology_u64_column(&edge_ids);
165 let (offsets_choice, _) = encode_topology_u32_column(&self.rows.adj_offsets);
166 let (degrees_choice, _) = encode_topology_u32_column(&self.rows.degrees);
167 let (caps_choice, _) = encode_topology_u32_column(&self.rows.primary_capacities);
168 vec![
169 (
170 "neighbor".to_string(),
171 endpoint_choice.encoding,
172 endpoint_choice.plain_bytes,
173 endpoint_choice.encoded_bytes,
174 ),
175 (
176 "edge_id".to_string(),
177 edge_id_choice.encoding,
178 edge_id_choice.plain_bytes,
179 edge_id_choice.encoded_bytes,
180 ),
181 (
182 "offsets".to_string(),
183 offsets_choice.encoding,
184 offsets_choice.plain_bytes,
185 offsets_choice.encoded_bytes,
186 ),
187 (
188 "lengths".to_string(),
189 degrees_choice.encoding,
190 degrees_choice.plain_bytes,
191 degrees_choice.encoded_bytes,
192 ),
193 (
194 "capacities".to_string(),
195 caps_choice.encoding,
196 caps_choice.plain_bytes,
197 caps_choice.encoded_bytes,
198 ),
199 ]
200 }
201
202 pub fn load(&mut self, data: &[u8]) -> StorageResult<()> {
208 if data.len() < 40 {
209 return Err(StorageError::deserialize_error(
210 "CSR data too short for header",
211 ));
212 }
213 let (body, trailer) = data.split_at(data.len() - 4);
214 let mut stored_bytes = [0u8; 4];
215 stored_bytes.copy_from_slice(trailer);
216 let stored = u32::from_le_bytes(stored_bytes);
217 let computed = crc32fast::hash(body);
218 if stored != computed {
219 return Err(StorageError::deserialize_error(format!(
220 "CSR dump CRC mismatch: stored={:#x} computed={:#x}",
221 stored, computed
222 )));
223 }
224 let data = body;
225
226 self.reset_baseline_counters();
227
228 let mut offset = 0usize;
229
230 let format_version = read_u32_le(data, &mut offset)?;
231 if format_version != MUTABLE_CSR_FORMAT_VERSION {
232 return Err(StorageError::deserialize_error(format!(
233 "Unsupported mutable CSR format version: {format_version}"
234 )));
235 }
236 let vertex_capacity = read_u64_le(data, &mut offset)? as usize;
237 let edge_count = read_u64_le(data, &mut offset)?;
238 let primary_len = read_u64_le(data, &mut offset)? as usize;
239 let overflow_chunk_edges = read_u64_le(data, &mut offset)? as usize;
240 if overflow_chunk_edges == 0 {
241 return Err(StorageError::deserialize_error(
242 "Mutable CSR overflow chunk size must be greater than zero",
243 ));
244 }
245 let tombstone_reuse_cutoff = read_u64_le(data, &mut offset)?;
246
247 let (adj_offsets, degrees, primary_capacities) = (
248 decode_topology_u32_column(data, &mut offset)?,
249 decode_topology_u32_column(data, &mut offset)?,
250 decode_topology_u32_column(data, &mut offset)?,
251 );
252 if adj_offsets.len() != vertex_capacity
253 || degrees.len() != vertex_capacity
254 || primary_capacities.len() != vertex_capacity
255 {
256 return Err(StorageError::deserialize_error(
257 "Mutable CSR header column length mismatch",
258 ));
259 }
260 let (endpoints, ranks, edge_ids, delete_stamps) = (
261 decode_topology_u32_column(data, &mut offset)?,
262 decode_topology_i64_column(data, &mut offset)?,
263 decode_topology_u64_column(data, &mut offset)?,
264 decode_topology_u64_column(data, &mut offset)?,
265 );
266 if endpoints.len() != primary_len
267 || ranks.len() != primary_len
268 || edge_ids.len() != primary_len
269 || delete_stamps.len() != primary_len
270 {
271 return Err(StorageError::deserialize_error(
272 "Mutable CSR neighbor column length mismatch",
273 ));
274 }
275 let mut hot_list = Vec::with_capacity(primary_len);
276 let mut cold_list = Vec::with_capacity(primary_len);
277 for index in 0..primary_len {
278 hot_list.push(HotNbr {
279 endpoint: endpoints[index],
280 rank: ranks[index],
281 edge_id: EdgeId(edge_ids[index]),
282 });
283 cold_list.push(ColdStamps {
284 delete_ts: delete_stamps[index],
285 });
286 }
287
288 for vid in 0..vertex_capacity {
293 let offset = adj_offsets[vid] as usize;
294 let degree = degrees[vid] as usize;
295 let capacity = primary_capacities[vid] as usize;
296 if degree > capacity
297 || offset.saturating_add(degree) > primary_len
298 || offset.saturating_add(capacity) > primary_len
299 {
300 return Err(StorageError::deserialize_error(format!(
301 "Mutable CSR row {} out of range: offset={} degree={} capacity={} primary_len={}",
302 vid, offset, degree, capacity, primary_len
303 )));
304 }
305 }
306
307 let mut overflow_chunks = OverflowStorage::new();
308 let mut overflow_capacity = 0usize;
309 let mut live_keys: Vec<Vec<((u32, i64), EdgePosition)>> = vec![Vec::new(); vertex_capacity];
310 let mut recomputed: u64 = 0;
311 for (hot, cold) in hot_list.iter().zip(cold_list.iter()) {
312 if hot.edge_id != INVALID_EDGE_ID && cold.is_live() {
313 recomputed += 1;
314 }
315 }
316 for (vid, keys) in live_keys.iter_mut().enumerate() {
317 let row_offset = adj_offsets[vid] as usize;
318 let degree = degrees[vid] as usize;
319 for i in 0..degree {
320 match (hot_list.get(row_offset + i), cold_list.get(row_offset + i)) {
321 (Some(hot), Some(cold)) if hot.edge_id != INVALID_EDGE_ID && cold.is_live() => {
322 keys.push((
323 (hot.endpoint, hot.rank),
324 EdgePosition::Primary { slot: i as u32 },
325 ));
326 }
327 _ => {}
328 }
329 }
330 }
331 for (vid, row_keys) in live_keys.iter_mut().enumerate() {
332 let chunk_count = read_u32_le(data, &mut offset)? as usize;
333 let mut chunks = Vec::with_capacity(chunk_count);
334 for chunk_idx in 0..chunk_count {
335 let chunk = decode_overflow_chunk(data, &mut offset)?;
336 if chunk.len() > overflow_chunk_edges {
337 return Err(StorageError::deserialize_error(
338 "Mutable CSR overflow chunk exceeds configured chunk size",
339 ));
340 }
341 for (slot_idx, (hot, cold)) in
342 chunk.hot_slice().iter().zip(chunk.cold_slice()).enumerate()
343 {
344 if hot.edge_id != INVALID_EDGE_ID && cold.is_live() {
345 recomputed += 1;
346 row_keys.push((
347 (hot.endpoint, hot.rank),
348 EdgePosition::Overflow {
349 chunk: chunk_idx as u32,
350 slot: slot_idx as u32,
351 },
352 ));
353 }
354 }
355 overflow_capacity = overflow_capacity.saturating_add(chunk.capacity().max(1));
356 chunks.push(chunk);
357 }
358 if !chunks.is_empty() {
359 overflow_chunks.insert(vid as u32, chunks);
360 }
361 }
362
363 if recomputed != edge_count {
364 return Err(StorageError::deserialize_error(format!(
365 "Mutable CSR edge count mismatch: stored={}, recomputed={}",
366 edge_count, recomputed
367 )));
368 }
369
370 self.total_edge_capacity = hot_list.len().saturating_add(overflow_capacity);
371 self.rows.adj_offsets = adj_offsets;
372 self.rows.degrees = degrees;
373 self.rows.primary_capacities = primary_capacities;
374 self.overflow_chunks = overflow_chunks;
375 self.overflow_chunk_edges = overflow_chunk_edges;
376 self.hot_list = hot_list;
377 self.cold_list = cold_list;
378 self.edge_count = edge_count;
379 self.tombstone_reuse_cutoff = tombstone_reuse_cutoff;
384 self.reuse_hint = vec![super::core::REUSE_HINT_UNKNOWN; vertex_capacity];
385 self.live_counts = vec![0; vertex_capacity];
386 self.tombstone_counts = vec![0; vertex_capacity];
387 self.primary_sorted = vec![false; vertex_capacity];
391 self.live_sets.clear();
392 self.live_sets.ensure_capacity(vertex_capacity);
393 for (vid, keys) in live_keys.into_iter().enumerate() {
394 if keys.len() > super::live_set::LIVE_SET_WIDTH_BOUND {
396 self.live_sets
397 .insert(vid as u32, LiveKeySet::from_positions(keys));
398 }
399 }
400 for vid in 0..vertex_capacity {
402 let degree = self.rows.degrees[vid] as usize;
403 let offset = self.rows.adj_offsets[vid] as usize;
404 let mut live = 0u32;
405 let mut tombstones = 0u32;
406 for i in 0..degree {
407 if let Some(cold) = self.cold_list.get(offset + i) {
408 if cold.is_live() {
409 live += 1;
410 } else {
411 tombstones += 1;
412 }
413 }
414 }
415 if let Some(chunks) = self.overflow_chunks.get(vid as u32) {
416 for chunk in chunks {
417 for cold in chunk.cold_slice() {
418 if cold.is_live() {
419 live += 1;
420 } else {
421 tombstones += 1;
422 }
423 }
424 }
425 }
426 self.live_counts[vid] = live;
427 self.tombstone_counts[vid] = tombstones;
428 }
429 if offset != data.len() {
430 return Err(StorageError::deserialize_error(
431 "unexpected trailing data in mutable CSR payload",
432 ));
433 }
434
435 Ok(())
436 }
437}