Skip to content

Commit 9b42908

Browse files
committed
feat: Implement log recovery
1 parent 7b62eff commit 9b42908

4 files changed

Lines changed: 160 additions & 32 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ This stage will implement logging of all operations in ArgusDB to allow for faul
2020

2121
- [x] Define a log file format in specs/logging.md
2222
- [x] Add log entries whenever new documents are inserted
23-
- [] Implement log recovery to read the log on restart and recover from a crash
23+
- [x] Implement log recovery to read the log on restart and recover from a crash
2424
- [] Implement log rotation and deletion of old log files
2525

2626
# Stage 4 - Disk storage

specs/logging.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ Each log entry is a JSON object with the following fields:
99
- `ts`: An ISO 8601 timestamp of when the operation occurred.
1010
- `op`: The type of operation. Can be one of "insert", "update", or "delete".
1111
- `doc`: The document for "insert" and "update" operations.
12-
- `id`: The document ID for "update" and "delete" operations.
12+
- `id`: The document ID for all operations.
1313

1414
### Insert operation
1515

1616
```json
1717
{
1818
"ts": "2026-01-10T12:00:00.000Z",
1919
"op": "insert",
20+
"id": "01H4J3J4J3J4J3J4J3J4J3J4J3",
2021
"doc": { "a": 1 }
2122
}
2223
```

src/log.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::path::Path;
88
#[derive(Serialize, Deserialize, Debug)]
99
pub enum Operation {
1010
Insert {
11+
id: String,
1112
doc: Value,
1213
},
1314
Update {

src/storage.rs

Lines changed: 156 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,154 @@ pub struct MemTable {
1313
impl MemTable {
1414
pub fn new(log_path: &str) -> Self {
1515
let logger = Logger::new(log_path).unwrap();
16-
MemTable {
16+
let mut memtable = MemTable {
1717
documents: BTreeMap::new(),
1818
schema: Schema {
1919
types: vec![],
2020
properties: None,
2121
items: None,
2222
},
2323
logger,
24-
}
24+
};
25+
memtable.recover(log_path);
26+
memtable
2527
}
2628

27-
pub fn insert(&mut self, doc: Value) -> String {
28-
let doc_schema = infer_schema(&doc);
29-
self.schema.merge(doc_schema.clone());
30-
let id = Uuid::now_v7().to_string();
31-
self.documents.insert(id.clone(), doc.clone());
32-
self.logger
33-
.log(Operation::Insert { doc })
34-
.expect("Failed to log insert");
35-
id
36-
}
29+
fn recover(&mut self, log_path: &str) {
3730

38-
pub fn delete(&mut self, id: &str) {
39-
self.documents.remove(id);
40-
self.logger
41-
.log(Operation::Delete { id: id.to_string() })
42-
.expect("Failed to log delete");
43-
}
31+
let log_content = std::fs::read_to_string(log_path).unwrap_or_default();
32+
33+
for line in log_content.lines() {
34+
35+
if line.is_empty() {
36+
37+
continue;
38+
39+
}
40+
41+
let entry: crate::log::LogEntry = serde_json::from_str(line).unwrap();
42+
43+
match entry.op {
44+
45+
Operation::Insert { id, doc } => {
46+
47+
self.insert_with_id(&id, doc);
48+
49+
}
50+
51+
Operation::Update { id, doc } => {
52+
53+
self._update(&id, doc);
54+
55+
}
56+
57+
Operation::Delete { id } => {
58+
59+
self._delete(&id);
60+
61+
}
62+
63+
}
64+
65+
}
66+
67+
}
68+
69+
70+
71+
fn insert_with_id(&mut self, id: &str, doc: Value) {
72+
73+
let doc_schema = infer_schema(&doc);
74+
75+
self.schema.merge(doc_schema);
76+
77+
self.documents.insert(id.to_string(), doc);
78+
79+
}
80+
81+
82+
83+
pub fn insert(&mut self, doc: Value) -> String {
84+
85+
let doc_schema = infer_schema(&doc);
86+
87+
self.schema.merge(doc_schema.clone());
88+
89+
let id = Uuid::now_v7().to_string();
90+
91+
self.documents.insert(id.clone(), doc.clone());
92+
93+
self.logger
94+
95+
.log(Operation::Insert {
96+
97+
id: id.clone(),
98+
99+
doc,
100+
101+
})
102+
103+
.expect("Failed to log insert");
104+
105+
id
106+
107+
}
108+
109+
110+
111+
fn _delete(&mut self, id: &str) {
112+
113+
self.documents.remove(id);
114+
115+
}
116+
117+
118+
119+
pub fn delete(&mut self, id: &str) {
120+
121+
self._delete(id);
122+
123+
self.logger
124+
125+
.log(Operation::Delete { id: id.to_string() })
126+
127+
.expect("Failed to log delete");
128+
129+
}
130+
131+
132+
133+
fn _update(&mut self, id: &str, doc: Value) {
134+
135+
let doc_schema = infer_schema(&doc);
136+
137+
self.schema.merge(doc_schema);
138+
139+
self.documents.insert(id.to_string(), doc);
140+
141+
}
142+
143+
144+
145+
pub fn update(&mut self, id: &str, doc: Value) {
146+
147+
self._update(id, doc.clone());
148+
149+
self.logger
150+
151+
.log(Operation::Update {
152+
153+
id: id.to_string(),
154+
155+
doc,
156+
157+
})
158+
159+
.expect("Failed to log update");
160+
161+
}
44162

45-
pub fn update(&mut self, id: &str, doc: Value) {
46-
let doc_schema = infer_schema(&doc);
47-
self.schema.merge(doc_schema);
48-
self.documents.insert(id.to_string(), doc.clone());
49-
self.logger
50-
.log(Operation::Update {
51-
id: id.to_string(),
52-
doc,
53-
})
54-
.expect("Failed to log update");
55163
}
56-
}
57164

58165
#[cfg(test)]
59166
mod tests {
@@ -125,7 +232,10 @@ mod tests {
125232

126233
let entry1: crate::log::LogEntry = serde_json::from_str(lines.next().unwrap()).unwrap();
127234
match entry1.op {
128-
Operation::Insert { doc } => assert_eq!(doc, doc1),
235+
Operation::Insert { id, doc } => {
236+
assert_eq!(id, id1);
237+
assert_eq!(doc, doc1);
238+
}
129239
_ => panic!("Expected insert operation"),
130240
}
131241

@@ -144,4 +254,20 @@ mod tests {
144254
_ => panic!("Expected delete operation"),
145255
}
146256
}
257+
258+
#[test]
259+
fn test_memtable_recover() {
260+
let (log_file, mut memtable) = create_test_memtable();
261+
let doc1 = json!({"a": 1});
262+
let id1 = memtable.insert(doc1.clone());
263+
264+
let doc2 = json!({"b": "hello"});
265+
let id2 = memtable.insert(doc2.clone());
266+
267+
memtable.delete(&id1);
268+
269+
let memtable2 = MemTable::new(log_file.path().to_str().unwrap());
270+
assert_eq!(memtable2.documents.len(), 1);
271+
assert_eq!(*memtable2.documents.get(&id2).unwrap(), doc2);
272+
}
147273
}

0 commit comments

Comments
 (0)