risingwave_object_store/object/
mem.rs1use std::collections::{HashMap, VecDeque};
16use std::pin::Pin;
17use std::sync::{Arc, LazyLock};
18use std::task::{Context, Poll};
19use std::time::{SystemTime, UNIX_EPOCH};
20
21use bytes::{BufMut, Bytes, BytesMut};
22use fail::fail_point;
23use futures::Stream;
24use itertools::Itertools;
25use risingwave_common::range::RangeBoundsExt;
26use thiserror::Error;
27use tokio::sync::Mutex;
28
29use super::{
30 ObjectError, ObjectMetadata, ObjectRangeBounds, ObjectResult, ObjectStore, StreamingUploader,
31};
32use crate::object::{ObjectDataStream, ObjectMetadataIter};
33
34#[derive(Error, Debug)]
35pub enum Error {
36 #[error("NotFound error: {0}")]
37 NotFound(String),
38 #[error("Other error: {0}")]
39 Other(String),
40}
41
42impl Error {
43 pub fn is_object_not_found_error(&self) -> bool {
44 matches!(self, Error::NotFound(_))
45 }
46}
47
48impl Error {
49 fn not_found(msg: impl ToString) -> Self {
50 Error::NotFound(msg.to_string())
51 }
52
53 fn other(msg: impl ToString) -> Self {
54 Error::Other(msg.to_string())
55 }
56}
57
58pub struct InMemStreamingUploader {
60 path: String,
61 buf: BytesMut,
62 objects: Arc<Mutex<HashMap<String, (ObjectMetadata, Bytes)>>>,
63}
64
65impl StreamingUploader for InMemStreamingUploader {
66 async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> {
67 fail_point!("mem_write_bytes_err", |_| Err(ObjectError::internal(
68 "mem write bytes error"
69 )));
70 self.buf.put(data);
71 Ok(())
72 }
73
74 async fn finish(self) -> ObjectResult<()> {
75 fail_point!("mem_finish_streaming_upload_err", |_| Err(
76 ObjectError::internal("mem finish streaming upload error")
77 ));
78 let obj = self.buf.freeze();
79 if obj.is_empty() {
80 Err(Error::other("upload empty object").into())
81 } else {
82 let metadata = get_obj_meta(&self.path, &obj)?;
83 self.objects.lock().await.insert(self.path, (metadata, obj));
84 Ok(())
85 }
86 }
87
88 fn get_memory_usage(&self) -> u64 {
89 self.buf.capacity() as u64
90 }
91}
92
93#[derive(Default, Clone)]
95pub struct InMemObjectStore {
96 objects: Arc<Mutex<HashMap<String, (ObjectMetadata, Bytes)>>>,
97}
98
99#[async_trait::async_trait]
100impl ObjectStore for InMemObjectStore {
101 type StreamingUploader = InMemStreamingUploader;
102
103 fn get_object_prefix(&self, _obj_id: u64, _use_new_object_prefix_strategy: bool) -> String {
104 String::default()
105 }
106
107 async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()> {
108 fail_point!("mem_upload_err", |_| Err(ObjectError::internal(
109 "mem upload error"
110 )));
111 if obj.is_empty() {
112 Err(Error::other("upload empty object").into())
113 } else {
114 let metadata = get_obj_meta(path, &obj)?;
115 self.objects
116 .lock()
117 .await
118 .insert(path.into(), (metadata, obj));
119 Ok(())
120 }
121 }
122
123 async fn streaming_upload(&self, path: &str) -> ObjectResult<Self::StreamingUploader> {
124 Ok(InMemStreamingUploader {
125 path: path.to_owned(),
126 buf: BytesMut::new(),
127 objects: self.objects.clone(),
128 })
129 }
130
131 async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult<Bytes> {
132 fail_point!("mem_read_err", |_| Err(ObjectError::internal(
133 "mem read error"
134 )));
135 self.get_object(path, range).await
136 }
137
138 async fn streaming_read(
142 &self,
143 path: &str,
144 read_range: impl ObjectRangeBounds,
145 ) -> ObjectResult<ObjectDataStream> {
146 fail_point!("mem_streaming_read_err", |_| Err(ObjectError::internal(
147 "mem streaming read error"
148 )));
149 let bytes = self.get_object(path, read_range).await?;
150
151 Ok(Box::pin(InMemDataIterator::new(bytes)))
152 }
153
154 async fn metadata(&self, path: &str) -> ObjectResult<ObjectMetadata> {
155 self.objects
156 .lock()
157 .await
158 .get(path)
159 .map(|(metadata, _)| metadata)
160 .cloned()
161 .ok_or_else(|| Error::not_found(format!("no object at path '{}'", path)).into())
162 }
163
164 async fn delete(&self, path: &str) -> ObjectResult<()> {
165 fail_point!("mem_delete_err", |_| Err(ObjectError::internal(
166 "mem delete error"
167 )));
168 self.objects.lock().await.remove(path);
169 Ok(())
170 }
171
172 async fn delete_objects(&self, paths: &[String]) -> ObjectResult<()> {
175 let mut guard = self.objects.lock().await;
176
177 for path in paths {
178 guard.remove(path);
179 }
180
181 Ok(())
182 }
183
184 async fn list(
185 &self,
186 prefix: &str,
187 start_after: Option<String>,
188 limit: Option<usize>,
189 ) -> ObjectResult<ObjectMetadataIter> {
190 let list_result = self
191 .objects
192 .lock()
193 .await
194 .iter()
195 .filter_map(|(path, (metadata, _))| {
196 if let Some(ref start_after) = start_after
197 && metadata.key.le(start_after)
198 {
199 return None;
200 }
201 if path.starts_with(prefix) {
202 return Some(metadata.clone());
203 }
204 None
205 })
206 .sorted_by(|a, b| Ord::cmp(&a.key, &b.key))
207 .take(limit.unwrap_or(usize::MAX))
208 .collect_vec();
209 Ok(Box::pin(InMemObjectIter::new(list_result)))
210 }
211
212 fn store_media_type(&self) -> &'static str {
213 "mem"
214 }
215}
216
217pub struct InMemDataIterator {
218 data: Bytes,
219 offset: usize,
220}
221
222impl InMemDataIterator {
223 pub fn new(data: Bytes) -> Self {
224 Self { data, offset: 0 }
225 }
226}
227
228impl Stream for InMemDataIterator {
229 type Item = ObjectResult<Bytes>;
230
231 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
232 const MAX_PACKET_SIZE: usize = 128 * 1024;
233 if self.offset >= self.data.len() {
234 return Poll::Ready(None);
235 }
236 let read_len = std::cmp::min(self.data.len() - self.offset, MAX_PACKET_SIZE);
237 let data = self.data.slice(self.offset..(self.offset + read_len));
238 self.offset += read_len;
239 Poll::Ready(Some(Ok(data)))
240 }
241}
242
243static SHARED: LazyLock<spin::Mutex<InMemObjectStore>> =
244 LazyLock::new(|| spin::Mutex::new(InMemObjectStore::new()));
245
246impl InMemObjectStore {
247 fn new() -> Self {
248 Self {
249 objects: Arc::new(Mutex::new(HashMap::new())),
250 }
251 }
252
253 pub fn for_test() -> Self {
255 Self::new()
256 }
257
258 pub(super) fn shared() -> Self {
263 SHARED.lock().clone()
264 }
265
266 pub fn reset_shared() {
268 *SHARED.lock() = InMemObjectStore::new();
269 }
270
271 async fn get_object(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult<Bytes> {
272 let objects = self.objects.lock().await;
273
274 let obj = objects
275 .get(path)
276 .map(|(_, obj)| obj)
277 .ok_or_else(|| Error::not_found(format!("no object at path '{}'", path)))?;
278
279 if let Some(end) = range.end()
280 && end > obj.len()
281 {
282 return Err(Error::other("bad block offset and size").into());
283 }
284
285 Ok(obj.slice(range))
286 }
287}
288
289fn get_obj_meta(path: &str, obj: &Bytes) -> ObjectResult<ObjectMetadata> {
290 Ok(ObjectMetadata {
291 key: path.to_owned(),
292 last_modified: SystemTime::now()
293 .duration_since(UNIX_EPOCH)
294 .map_err(ObjectError::internal)?
295 .as_secs_f64(),
296 total_size: obj.len(),
297 })
298}
299
300struct InMemObjectIter {
301 list_result: VecDeque<ObjectMetadata>,
302}
303
304impl InMemObjectIter {
305 fn new(list_result: Vec<ObjectMetadata>) -> Self {
306 Self {
307 list_result: list_result.into(),
308 }
309 }
310}
311
312impl Stream for InMemObjectIter {
313 type Item = ObjectResult<ObjectMetadata>;
314
315 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
316 if let Some(i) = self.list_result.pop_front() {
317 return Poll::Ready(Some(Ok(i)));
318 }
319 Poll::Ready(None)
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use futures::TryStreamExt;
326 use itertools::enumerate;
327
328 use super::*;
329
330 #[tokio::test]
331 async fn test_upload() {
332 let block = Bytes::from("123456");
333
334 let s3 = InMemObjectStore::for_test();
335 s3.upload("/abc", block).await.unwrap();
336
337 let err = s3.read("/ab", 0..3).await.unwrap_err();
339 assert!(err.is_object_not_found_error());
340
341 let bytes = s3.read("/abc", 4..6).await.unwrap();
342 assert_eq!(String::from_utf8(bytes.to_vec()).unwrap(), "56".to_owned());
343
344 s3.read("/abc", 4..8).await.unwrap_err();
346
347 s3.delete("/abc").await.unwrap();
348
349 s3.read("/abc", 0..3).await.unwrap_err();
351 }
352
353 #[tokio::test]
354 async fn test_streaming_upload() {
355 let blocks = vec![Bytes::from("123"), Bytes::from("456"), Bytes::from("789")];
356 let obj = Bytes::from("123456789");
357
358 let store = InMemObjectStore::for_test();
359 let mut uploader = store.streaming_upload("/abc").await.unwrap();
360
361 for block in blocks {
362 uploader.write_bytes(block).await.unwrap();
363 }
364 uploader.finish().await.unwrap();
365
366 let read_obj = store.read("/abc", ..).await.unwrap();
368 assert!(read_obj.eq(&obj));
369
370 let read_obj = store.read("/abc", 4..6).await.unwrap();
372 assert_eq!(
373 String::from_utf8(read_obj.to_vec()).unwrap(),
374 "56".to_owned()
375 );
376 }
377
378 #[tokio::test]
379 async fn test_metadata() {
380 let block = Bytes::from("123456");
381
382 let obj_store = InMemObjectStore::for_test();
383 obj_store.upload("/abc", block).await.unwrap();
384
385 let err = obj_store.metadata("/not_exist").await.unwrap_err();
386 assert!(err.is_object_not_found_error());
387
388 let metadata = obj_store.metadata("/abc").await.unwrap();
389 assert_eq!(metadata.total_size, 6);
390 }
391
392 async fn list_all(prefix: &str, store: &InMemObjectStore) -> Vec<ObjectMetadata> {
393 store
394 .list(prefix, None, None)
395 .await
396 .unwrap()
397 .try_collect::<Vec<_>>()
398 .await
399 .unwrap()
400 }
401
402 #[tokio::test]
403 async fn test_list() {
404 let payload = Bytes::from("123456");
405 let store = InMemObjectStore::for_test();
406 assert!(list_all("", &store).await.is_empty());
407
408 let paths = vec!["001/002/test.obj", "001/003/test.obj"];
409 for (i, path) in enumerate(paths.clone()) {
410 assert_eq!(list_all("", &store).await.len(), i);
411 store.upload(path, payload.clone()).await.unwrap();
412 assert_eq!(list_all("", &store).await.len(), i + 1);
413 }
414
415 let list_path = list_all("", &store)
416 .await
417 .iter()
418 .map(|p| p.key.clone())
419 .collect_vec();
420 assert_eq!(list_path, paths);
421
422 for i in 0..=5 {
423 assert_eq!(list_all(&paths[0][0..=i], &store).await.len(), 2);
424 }
425 for i in 6..=paths[0].len() - 1 {
426 assert_eq!(list_all(&paths[0][0..=i], &store).await.len(), 1)
427 }
428 assert!(list_all("003", &store).await.is_empty());
429
430 for (i, path) in enumerate(paths.clone()) {
431 assert_eq!(list_all("", &store).await.len(), paths.len() - i);
432 store.delete(path).await.unwrap();
433 assert_eq!(list_all("", &store).await.len(), paths.len() - i - 1);
434 }
435 }
436
437 #[tokio::test]
438 async fn test_delete_objects() {
439 let block1 = Bytes::from("123456");
440 let block2 = Bytes::from("987654");
441
442 let store = InMemObjectStore::for_test();
443 store.upload("/abc", block1).await.unwrap();
444 store.upload("/klm", block2).await.unwrap();
445
446 assert_eq!(list_all("", &store).await.len(), 2);
447
448 let str_list = [
449 String::from("/abc"),
450 String::from("/klm"),
451 String::from("/xyz"),
452 ];
453
454 store.delete_objects(&str_list).await.unwrap();
455
456 assert_eq!(list_all("", &store).await.len(), 0);
457 }
458}