Skip to main content

mf_persistence/subscriber/
mod.rs

1//! 基于事件总线的持久化订阅者:在 TrApply 事件中持久化事务并按策略写入快照
2use std::{collections::HashSet, fmt, sync::Arc};
3
4use async_trait::async_trait;
5use dashmap::DashMap;
6use mf_core::{
7    event::{Event, EventHandler},
8    ForgeResult,
9};
10use mf_state::transaction::{get_tr_id, Transaction};
11use serde_json::json;
12
13use crate::api::{CommitMode, EventStore, PersistOptions, PersistedEvent, Snapshot};
14use crate::ser::{
15    checksum32, compress_if_needed, frame_invert_steps, frame_steps,
16    SnapshotData, TypeWrapper,
17};
18
19#[derive(Default)]
20struct SnapshotCounters {
21    last_snapshot_ms: i64,
22    last_snapshot_upto_lsn: i64,
23    events_since: u32,
24    bytes_since: u64,
25}
26
27#[derive(Copy, Clone)]
28enum PersistAction {
29    Apply,
30    Undo,
31    Redo,
32}
33
34pub struct SnapshotSubscriber<E: EventStore + 'static> {
35    store: Arc<E>,
36    options: PersistOptions,
37    commit_mode: CommitMode,
38    default_doc_id: String,
39    // 进程内已持久化的事务ID集合,避免重复写入(进程重启后靠数据库幂等键)
40    persisted: DashMap<u64, ()>,
41    // 每个文档的快照计数器
42    snap_counters: DashMap<String, SnapshotCounters>,
43    // 待写快照的 upto_lsn
44    pending_snapshot_lsn: DashMap<String, i64>,
45}
46
47impl<E: EventStore + 'static> fmt::Debug for SnapshotSubscriber<E> {
48    fn fmt(
49        &self,
50        f: &mut fmt::Formatter<'_>,
51    ) -> fmt::Result {
52        f.write_str("SnapshotSubscriber")
53    }
54}
55
56impl<E: EventStore + 'static> Clone for SnapshotSubscriber<E> {
57    fn clone(&self) -> Self {
58        Self {
59            store: self.store.clone(),
60            options: self.options.clone(),
61            commit_mode: self.commit_mode,
62            default_doc_id: self.default_doc_id.clone(),
63            persisted: DashMap::new(),
64            snap_counters: DashMap::new(),
65            pending_snapshot_lsn: DashMap::new(),
66        }
67    }
68}
69
70impl<E: EventStore + 'static> SnapshotSubscriber<E> {
71    pub fn new(
72        store: Arc<E>,
73        options: PersistOptions,
74        default_doc_id: impl Into<String>,
75    ) -> Self {
76        Self {
77            commit_mode: options.commit_mode,
78            store,
79            options,
80            default_doc_id: default_doc_id.into(),
81            persisted: DashMap::new(),
82            snap_counters: DashMap::new(),
83            pending_snapshot_lsn: DashMap::new(),
84        }
85    }
86
87    async fn persist_one(
88        &self,
89        tr_id: u64,
90        doc_id: &str,
91        frames: Vec<TypeWrapper>,
92        meta: serde_json::Value,
93    ) -> ForgeResult<Option<(i64, usize)>> {
94        if frames.is_empty() {
95            return Ok(None);
96        }
97        let framed = serde_json::to_vec(&frames).map_err(|e| {
98            mf_core::error::error_utils::middleware_error(format!(
99                "步骤编码失败: {e}"
100            ))
101        })?;
102        let payload = compress_if_needed(&framed, self.options.compression)
103            .map_err(|e| {
104                mf_core::error::error_utils::middleware_error(e.to_string())
105            })?;
106        let checksum = checksum32(&payload);
107
108        let ev = PersistedEvent {
109            lsn: 0,
110            tr_id,
111            doc_id: doc_id.to_string(),
112            ts: chrono::Utc::now().timestamp_millis(),
113            actor: None,
114            idempotency_key: format!("tr:{tr_id}"),
115            payload,
116            meta,
117            checksum,
118        };
119
120        match self.commit_mode {
121            CommitMode::MemoryOnly => Ok(None),
122            _ => {
123                let bytes = ev.payload.len();
124                self.store
125                    .append(ev)
126                    .await
127                    .map(|lsn| Some((lsn, bytes)))
128                    .map_err(|e| {
129                        mf_core::error::error_utils::middleware_error(
130                            e.to_string(),
131                        )
132                    })
133            },
134        }
135    }
136
137    fn resolve_doc_id(
138        &self,
139        transaction: &Transaction,
140        state: &mf_state::State,
141    ) -> String {
142        let doc_id = transaction
143            .get_meta::<String>("doc_id")
144            .unwrap_or_else(|| state.doc().root_id().to_string());
145        if doc_id.is_empty() {
146            self.default_doc_id.clone()
147        } else {
148            doc_id
149        }
150    }
151
152    fn should_snapshot(
153        &self,
154        doc_id: &str,
155    ) -> bool {
156        if let Some(counters) = self.snap_counters.get(doc_id) {
157            let now = chrono::Utc::now().timestamp_millis();
158            let time_ok = self.options.snapshot_every_ms > 0
159                && now - counters.last_snapshot_ms
160                    >= self.options.snapshot_every_ms as i64;
161            let count_ok = self.options.snapshot_every_n_events > 0
162                && counters.events_since
163                    >= self.options.snapshot_every_n_events;
164            let bytes_ok = self.options.snapshot_every_bytes > 0
165                && counters.bytes_since >= self.options.snapshot_every_bytes;
166            time_ok || count_ok || bytes_ok
167        } else {
168            false
169        }
170    }
171
172    async fn write_snapshot(
173        &self,
174        doc_id: &str,
175        upto_lsn: i64,
176        state: &mf_state::State,
177    ) -> ForgeResult<()> {
178        let mut ser = state.serialize().await.map_err(|e| {
179            mf_core::error::error_utils::middleware_error(format!(
180                "状态序列化失败: {e}"
181            ))
182        })?;
183        let snap = SnapshotData {
184            node_pool: std::mem::take(&mut ser.node_pool),
185            state_fields: std::mem::take(&mut ser.state_fields),
186        };
187        let blob = serde_json::to_vec(&snap).map_err(|e| {
188            mf_core::error::error_utils::middleware_error(format!(
189                "快照编码失败: {e}"
190            ))
191        })?;
192        let blob = compress_if_needed(&blob, self.options.compression)
193            .map_err(|e| {
194                mf_core::error::error_utils::middleware_error(format!(
195                    "快照压缩失败: {e}"
196                ))
197            })?;
198
199        let snap = Snapshot {
200            doc_id: doc_id.to_string(),
201            upto_lsn,
202            created_at: chrono::Utc::now().timestamp_millis(),
203            state_blob: blob,
204            version: 1,
205        };
206
207        self.store.write_snapshot(snap).await.map_err(|e| {
208            mf_core::error::error_utils::middleware_error(e.to_string())
209        })?;
210
211        // 重置计数器
212        let mut entry =
213            self.snap_counters.entry(doc_id.to_string()).or_default();
214        entry.last_snapshot_ms = chrono::Utc::now().timestamp_millis();
215        entry.last_snapshot_upto_lsn = upto_lsn;
216        entry.events_since = 0;
217        entry.bytes_since = 0;
218        Ok(())
219    }
220
221    async fn process_transactions(
222        &self,
223        state: &Arc<mf_state::State>,
224        transactions: &[Arc<Transaction>],
225        action: PersistAction,
226    ) -> ForgeResult<()> {
227        if transactions.is_empty() {
228            return Ok(());
229        }
230        let mut touched_docs: HashSet<String> = HashSet::new();
231
232        for tr in transactions.iter() {
233            let doc_id = self.resolve_doc_id(tr, state);
234            let (persist_id, frames, meta) = match action {
235                PersistAction::Apply => (tr.id, frame_steps(tr), json!({})),
236                PersistAction::Undo => (
237                    get_tr_id(),
238                    frame_invert_steps(tr),
239                    json!({
240                        "action": "undo",
241                        "source_tr_id": tr.id,
242                    }),
243                ),
244                PersistAction::Redo => (
245                    get_tr_id(),
246                    frame_steps(tr),
247                    json!({
248                        "action": "redo",
249                        "source_tr_id": tr.id,
250                    }),
251                ),
252            };
253
254            if frames.is_empty() {
255                continue;
256            }
257
258            if matches!(action, PersistAction::Apply)
259                && self.persisted.contains_key(&persist_id)
260            {
261                continue;
262            }
263
264            match self.persist_one(persist_id, &doc_id, frames, meta).await {
265                Ok(Some((lsn, bytes))) => {
266                    self.persisted.insert(persist_id, ());
267                    touched_docs.insert(doc_id.clone());
268                    let mut entry =
269                        self.snap_counters.entry(doc_id.clone()).or_default();
270                    entry.events_since += 1;
271                    entry.bytes_since += bytes as u64;
272                    let cur = self
273                        .pending_snapshot_lsn
274                        .get(&doc_id)
275                        .map(|v| *v.value())
276                        .unwrap_or(-1);
277                    if lsn > cur {
278                        self.pending_snapshot_lsn
279                            .insert(doc_id.clone(), lsn);
280                    }
281                },
282                Ok(None) => {},
283                Err(e) => return Err(e),
284            }
285        }
286
287        self.flush_snapshots(touched_docs, state).await
288    }
289
290    async fn flush_snapshots(
291        &self,
292        touched_docs: HashSet<String>,
293        state: &Arc<mf_state::State>,
294    ) -> ForgeResult<()> {
295        for doc_id in touched_docs.into_iter() {
296            if let Some(upto) =
297                self.pending_snapshot_lsn.get(&doc_id).map(|v| *v.value())
298            {
299                if upto >= 0 {
300                    let has_snapshot = self
301                        .store
302                        .latest_snapshot(&doc_id)
303                        .await
304                        .map(|s| s.is_some())
305                        .unwrap_or(false);
306                    if !has_snapshot || self.should_snapshot(&doc_id) {
307                        self.write_snapshot(&doc_id, upto, state).await?;
308                        self.pending_snapshot_lsn.remove(&doc_id);
309                    }
310                }
311            }
312        }
313        Ok(())
314    }
315}
316
317#[async_trait]
318impl<E: EventStore + 'static> EventHandler<Event> for SnapshotSubscriber<E> {
319    async fn handle(
320        &self,
321        event: &Event,
322    ) -> ForgeResult<()> {
323        match event {
324            Event::Create(state) => {
325                let doc_id = state.doc().root_id().to_string();
326                let has_snapshot = self
327                    .store
328                    .latest_snapshot(&doc_id)
329                    .await
330                    .map(|s| s.is_some())
331                    .unwrap_or(false);
332                if !has_snapshot {
333                    self.write_snapshot(&doc_id, 0, state).await?;
334                }
335            },
336            Event::TrApply { new_state, transactions, .. } => {
337                self.process_transactions(new_state, transactions, PersistAction::Apply).await?;
338            },
339            Event::Undo { new_state, transactions, .. } => {
340                self.process_transactions(new_state, transactions, PersistAction::Undo).await?;
341            },
342            Event::Redo { new_state, transactions, .. } => {
343                self.process_transactions(new_state, transactions, PersistAction::Redo).await?;
344            },
345            _ => {},
346        }
347        Ok(())
348    }
349}