Skip to content

Commit 9b5fe9d

Browse files
committed
feat: Implement MemTable flushing to JSTables
1 parent 654285f commit 9b5fe9d

5 files changed

Lines changed: 227 additions & 240 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
target/
2+
3+
# ArgusDB generated files
4+
argus.log
5+
argus.log.*
6+
jstable-*
7+

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ This stage will implement logging of all operations in ArgusDB to allow for faul
2828
This stage will allow the data stored in memory to be dumped to disk to allow storage of large data sets, resulting in a two-level LSM tree.
2929

3030
- [x] Define an on-disk format for JSTables in specs/storage.md including both the schema and the data
31-
- [] When documents are inserted past the in-memory threshold, write a new JSTable to disk and start a new in-memory structure
31+
- [x] When documents are inserted past the in-memory threshold, write a new JSTable to disk and start a new in-memory structure
3232

3333
# Stage 5 - LSM compaction
3434

src/db.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
use crate::log::{LogEntry, Logger, Operation};
2+
use crate::storage::MemTable;
3+
use serde_json::Value;
4+
use uuid::Uuid;
5+
6+
const MEMTABLE_THRESHOLD: usize = 10;
7+
8+
pub struct DB {
9+
memtable: MemTable,
10+
jstable_dir: String,
11+
jstable_count: u64,
12+
logger: Logger,
13+
}
14+
15+
impl DB {
16+
pub fn new(jstable_dir: &str) -> Self {
17+
std::fs::create_dir_all(jstable_dir).unwrap();
18+
let log_path = format!("{}/argus.log", jstable_dir);
19+
let logger = Logger::new(&log_path, 1024 * 1024).unwrap();
20+
let mut memtable = MemTable::new();
21+
22+
let log_content = std::fs::read_to_string(&log_path).unwrap_or_default();
23+
for line in log_content.lines() {
24+
if line.is_empty() {
25+
continue;
26+
}
27+
let entry: LogEntry = serde_json::from_str(line).unwrap();
28+
match entry.op {
29+
Operation::Insert { id, doc } => {
30+
memtable.insert(id, doc);
31+
}
32+
Operation::Update { id, doc } => {
33+
memtable.update(&id, doc);
34+
}
35+
Operation::Delete { id } => {
36+
memtable.delete(&id);
37+
}
38+
}
39+
}
40+
41+
DB {
42+
memtable,
43+
jstable_dir: jstable_dir.to_string(),
44+
jstable_count: 0,
45+
logger,
46+
}
47+
}
48+
49+
pub fn insert(&mut self, doc: Value) -> String {
50+
if self.memtable.len() >= MEMTABLE_THRESHOLD {
51+
self.flush();
52+
}
53+
let id = Uuid::now_v7().to_string();
54+
self.logger
55+
.log(Operation::Insert {
56+
id: id.clone(),
57+
doc: doc.clone(),
58+
})
59+
.unwrap();
60+
self.memtable.insert(id.clone(), doc);
61+
id
62+
}
63+
64+
pub fn delete(&mut self, id: &str) {
65+
self.logger
66+
.log(Operation::Delete { id: id.to_string() })
67+
.unwrap();
68+
self.memtable.delete(id);
69+
}
70+
71+
pub fn update(&mut self, id: &str, doc: Value) {
72+
self.logger
73+
.log(Operation::Update {
74+
id: id.to_string(),
75+
doc: doc.clone(),
76+
})
77+
.unwrap();
78+
self.memtable.update(id, doc);
79+
}
80+
81+
fn flush(&mut self) {
82+
let jstable_path = format!("{}/jstable-{}", self.jstable_dir, self.jstable_count);
83+
self.memtable.flush(&jstable_path).unwrap();
84+
self.jstable_count += 1;
85+
self.memtable = MemTable::new();
86+
self.logger.rotate().unwrap();
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
use serde_json::json;
94+
use tempfile::tempdir;
95+
96+
#[test]
97+
fn test_db_flush() {
98+
let dir = tempdir().unwrap();
99+
let mut db = DB::new(dir.path().to_str().unwrap());
100+
101+
for i in 0..MEMTABLE_THRESHOLD {
102+
db.insert(json!({ "a": i }));
103+
}
104+
assert_eq!(db.memtable.len(), MEMTABLE_THRESHOLD);
105+
assert_eq!(db.jstable_count, 0);
106+
107+
db.insert(json!({"a": MEMTABLE_THRESHOLD}));
108+
assert_eq!(db.memtable.len(), 1);
109+
assert_eq!(db.jstable_count, 1);
110+
111+
let jstable_path = format!("{}/jstable-0", dir.path().to_str().unwrap());
112+
let content = std::fs::read_to_string(jstable_path).unwrap();
113+
assert!(!content.is_empty());
114+
}
115+
116+
#[test]
117+
fn test_log_content() {
118+
let dir = tempdir().unwrap();
119+
let mut db = DB::new(dir.path().to_str().unwrap());
120+
let doc1 = json!({"a": 1});
121+
let id1 = db.insert(doc1.clone());
122+
123+
let doc2 = json!({"b": "hello"});
124+
db.update(&id1, doc2.clone());
125+
126+
db.delete(&id1);
127+
128+
let log_path = format!("{}/argus.log", dir.path().to_str().unwrap());
129+
let log_content = std::fs::read_to_string(log_path).unwrap();
130+
let mut lines = log_content.lines();
131+
132+
let entry1: crate::log::LogEntry = serde_json::from_str(lines.next().unwrap()).unwrap();
133+
match entry1.op {
134+
Operation::Insert { id, doc } => {
135+
assert_eq!(id, id1);
136+
assert_eq!(doc, doc1);
137+
}
138+
_ => panic!("Expected insert operation"),
139+
}
140+
141+
let entry2: crate::log::LogEntry = serde_json::from_str(lines.next().unwrap()).unwrap();
142+
match entry2.op {
143+
Operation::Update { id, doc } => {
144+
assert_eq!(id, id1);
145+
assert_eq!(doc, doc2);
146+
}
147+
_ => panic!("Expected update operation"),
148+
}
149+
150+
let entry3: crate::log::LogEntry = serde_json::from_str(lines.next().unwrap()).unwrap();
151+
match entry3.op {
152+
Operation::Delete { id } => assert_eq!(id, id1),
153+
_ => panic!("Expected delete operation"),
154+
}
155+
}
156+
157+
#[test]
158+
fn test_db_recover() {
159+
let dir = tempdir().unwrap();
160+
let mut db = DB::new(dir.path().to_str().unwrap());
161+
let doc1 = json!({"a": 1});
162+
let id1 = db.insert(doc1.clone());
163+
164+
let doc2 = json!({"b": "hello"});
165+
let id2 = db.insert(doc2.clone());
166+
167+
db.delete(&id1);
168+
169+
let db2 = DB::new(dir.path().to_str().unwrap());
170+
assert_eq!(db2.memtable.len(), 1);
171+
assert_eq!(*db2.memtable.documents.get(&id2).unwrap(), doc2);
172+
}
173+
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod schema;
22
pub mod storage;
33
pub mod log;
4+
pub mod db;
45

56
fn main() {
67
println!("Hello, world!");

0 commit comments

Comments
 (0)