ursula_stream/
snapshot.rs1use serde::Deserialize;
2use serde::Serialize;
3use ursula_shard::BucketStreamId;
4
5use crate::integrity::StreamIntegritySnapshot;
6use crate::model::BucketQuotaSnapshot;
7use crate::model::BucketUsageSnapshot;
8use crate::model::ColdChunkRef;
9use crate::model::ColdGcEntry;
10use crate::model::HotPayloadSegment;
11use crate::model::ObjectPayloadRef;
12use crate::model::ProducerSnapshot;
13use crate::model::StreamAttrs;
14use crate::model::StreamMessageRecord;
15use crate::model::StreamMetadata;
16use crate::model::StreamVisibleSnapshot;
17use crate::record_index::StreamRecordIndex;
18
19#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
20pub struct StreamSnapshot {
21 pub buckets: Vec<String>,
22 #[serde(default)]
24 pub erased_buckets: Vec<String>,
25 pub streams: Vec<StreamSnapshotEntry>,
26 #[serde(default)]
27 pub pending_cold_gc: Vec<ColdGcEntry>,
28 #[serde(default)]
29 pub next_cold_gc_seq: u64,
30 #[serde(default)]
33 pub shared_cold_object_owners: Vec<SharedColdObjectOwnersSnapshot>,
34 #[serde(default)]
37 pub bucket_usage: Vec<BucketUsageSnapshot>,
38 #[serde(default)]
40 pub bucket_quotas: Vec<BucketQuotaSnapshot>,
41}
42
43#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub struct SharedColdObjectOwnersSnapshot {
45 pub s3_path: String,
46 pub bucket_ids: Vec<String>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct StreamSnapshotEntry {
51 pub metadata: StreamMetadata,
52 #[serde(default)]
53 pub attrs: Option<StreamAttrs>,
54 pub hot_start_offset: u64,
55 pub payload: Vec<u8>,
56 pub hot_segments: Vec<HotPayloadSegment>,
57 #[serde(default)]
58 pub cold_frontier_offset: u64,
59 #[serde(default)]
60 pub cold_index_generation: u64,
61 pub cold_chunks: Vec<ColdChunkRef>,
62 pub external_segments: Vec<ObjectPayloadRef>,
63 pub message_records: Vec<StreamMessageRecord>,
64 #[serde(default)]
65 pub record_index: Option<StreamRecordIndex>,
66 pub integrity: StreamIntegritySnapshot,
67 #[serde(default)]
70 pub retained_offset: Option<u64>,
71 pub visible_snapshot: Option<StreamVisibleSnapshot>,
72 pub producer_states: Vec<ProducerSnapshot>,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
76pub enum StreamSnapshotError {
77 #[error("snapshot contains duplicate bucket '{0}'")]
78 DuplicateBucket(String),
79 #[error("snapshot contains duplicate erased bucket '{0}'")]
80 DuplicateErasedBucket(String),
81 #[error("snapshot bucket '{0}' is both active and erased")]
82 ActiveBucketErased(String),
83 #[error("snapshot contains duplicate stream '{0}'")]
84 DuplicateStream(BucketStreamId),
85 #[error("snapshot stream '{stream_id}' contains duplicate producer '{producer_id}'")]
86 DuplicateProducer {
87 stream_id: BucketStreamId,
88 producer_id: String,
89 },
90 #[error("snapshot stream '{0}' references a missing bucket")]
91 MissingBucket(BucketStreamId),
92 #[error(
93 "snapshot stream '{stream_id}' tail offset {tail_offset} does not match payload length {payload_len}"
94 )]
95 PayloadLengthMismatch {
96 stream_id: BucketStreamId,
97 tail_offset: u64,
98 payload_len: usize,
99 },
100 #[error("snapshot stream '{stream_id}' has inconsistent message boundaries")]
101 MessageBoundaryMismatch { stream_id: BucketStreamId },
102 #[error("snapshot stream '{stream_id}' has inconsistent record boundaries")]
103 RecordBoundaryMismatch { stream_id: BucketStreamId },
104 #[error("snapshot stream '{stream_id}' has inconsistent integrity setsums")]
105 IntegrityMismatch { stream_id: BucketStreamId },
106 #[error(
107 "snapshot stream '{stream_id}' visible snapshot offset {snapshot_offset} is beyond tail offset {tail_offset}"
108 )]
109 SnapshotOffsetOutOfRange {
110 stream_id: BucketStreamId,
111 snapshot_offset: u64,
112 tail_offset: u64,
113 },
114}