kevy_embedded/
store_persist.rs1use std::io;
8use std::sync::RwLock;
9use std::time::Instant;
10
11use kevy_persist::RewriteStats;
12
13use crate::metric::KevyMetric;
14use crate::store::{Inner, Store, lock_write};
15
16impl Store {
17 pub fn fsync_aof(&self) -> io::Result<()> {
30 for shard in self.shards.iter() {
31 let mut g = lock_write(shard);
32 if let Some(aof) = &mut g.aof {
33 aof.sync_now()?;
34 }
35 }
36 Ok(())
37 }
38
39 pub fn rewrite_aof(&self) -> io::Result<Option<RewriteStats>> {
44 let mut agg: Option<RewriteStats> = None;
45 for shard in self.shards.iter() {
46 if let Some(stats) = self.rewrite_one_shard(shard)? {
47 let acc = agg.get_or_insert(RewriteStats { keys: 0, bytes: 0 });
48 acc.keys += stats.keys;
49 acc.bytes += stats.bytes;
50 }
51 }
52 Ok(agg)
53 }
54
55 fn rewrite_one_shard(&self, shard: &RwLock<Inner>) -> io::Result<Option<RewriteStats>> {
58 let start = Instant::now();
59 let (view, tmp, before_bytes) = {
62 let mut g = lock_write(shard);
63 let Inner { store, aof, .. } = &mut *g;
64 let Some(aof) = aof else { return Ok(None) };
65 if aof.is_rewriting() {
66 return Ok(None);
67 }
68 let before = aof.size_bytes();
69 let view = store.collect_snapshot();
70 (view, aof.begin_view_rewrite()?, before)
71 };
72 let keys = match kevy_persist::dump_aof(&tmp, &view) {
74 Ok((keys, _)) => keys,
75 Err(e) => {
76 let mut g = lock_write(shard);
77 if let Some(aof) = &mut g.aof {
78 aof.abort_concurrent_rewrite();
79 }
80 let _ = std::fs::remove_file(&tmp);
81 return Err(e);
82 }
83 };
84 let mut g = lock_write(shard);
86 let Some(aof) = &mut g.aof else { return Ok(None) };
87 let stats = match aof.finish_concurrent_rewrite(&tmp, keys) {
88 Ok(s) => s,
89 Err(e) => {
90 aof.abort_concurrent_rewrite();
91 let _ = std::fs::remove_file(&tmp);
92 return Err(e);
93 }
94 };
95 if let Some(sink) = &self.config.metric_sink {
96 sink.emit(KevyMetric::Rewrite {
97 keys: stats.keys,
98 before_bytes,
99 after_bytes: stats.bytes,
100 elapsed_ms: start.elapsed().as_millis() as u64,
101 });
102 }
103 Ok(Some(stats))
104 }
105
106 pub fn save_snapshot(&self) -> io::Result<bool> {
109 let Some(dir) = self.config.data_dir.as_ref() else {
110 return Ok(false);
111 };
112 let n = self.shards.len();
113 for (i, shard) in self.shards.iter().enumerate() {
114 let name = if n == 1 {
115 self.config.snapshot_filename.clone()
116 } else {
117 kevy_persist::layout::snapshot_file(i)
118 };
119 save_shard_snapshot(shard, &dir.join(name))?;
120 }
121 Ok(true)
122 }
123}
124
125pub(crate) fn save_shard_snapshot(
136 shard: &RwLock<Inner>,
137 path: &std::path::Path,
138) -> io::Result<()> {
139 let (view, reset_tmp) = freeze_for_save(shard)?;
140 let tmp = match kevy_persist::write_snapshot_tmp(&view, path) {
141 Ok(t) => t,
142 Err(e) => {
143 if reset_tmp.is_some()
144 && let Some(aof) = &mut lock_write(shard).aof
145 {
146 aof.abort_concurrent_rewrite();
147 }
148 return Err(e);
149 }
150 };
151 let mut g = lock_write(shard);
152 std::fs::rename(&tmp, path)?;
153 if let (Some(reset), Some(aof)) = (reset_tmp, &mut g.aof) {
154 let swap = kevy_persist::write_aof_base(&reset)
155 .and_then(|()| aof.finish_concurrent_rewrite(&reset, 0));
156 if let Err(e) = swap {
157 aof.abort_concurrent_rewrite();
158 let _ = std::fs::remove_file(&reset);
159 return Err(e);
160 }
161 }
162 Ok(())
163}
164
165fn freeze_for_save(
171 shard: &RwLock<Inner>,
172) -> io::Result<(kevy_store::SnapshotView, Option<std::path::PathBuf>)> {
173 for _ in 0..2000 {
174 {
175 let mut g = lock_write(shard);
176 let Inner { store, aof, .. } = &mut *g;
177 match aof {
178 Some(a) if a.is_rewriting() => {} Some(a) => {
180 let view = store.collect_snapshot();
181 return Ok((view, Some(a.begin_view_rewrite()?)));
182 }
183 None => return Ok((store.collect_snapshot(), None)),
184 }
185 }
186 std::thread::sleep(std::time::Duration::from_millis(5));
187 }
188 Err(io::Error::new(
189 io::ErrorKind::TimedOut,
190 "kevy-embedded: AOF rewrite still in flight after 10s; snapshot aborted",
191 ))
192}