opendal 0.1.6.pre.rc.1-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/.standard.yml +20 -0
  3. data/.tool-versions +1 -0
  4. data/.yardopts +1 -0
  5. data/DEPENDENCIES.md +9 -0
  6. data/DEPENDENCIES.rust.tsv +277 -0
  7. data/Gemfile +35 -0
  8. data/README.md +159 -0
  9. data/Rakefile +149 -0
  10. data/core/CHANGELOG.md +4929 -0
  11. data/core/CONTRIBUTING.md +61 -0
  12. data/core/DEPENDENCIES.md +3 -0
  13. data/core/DEPENDENCIES.rust.tsv +185 -0
  14. data/core/LICENSE +201 -0
  15. data/core/README.md +228 -0
  16. data/core/benches/README.md +18 -0
  17. data/core/benches/ops/README.md +26 -0
  18. data/core/benches/types/README.md +9 -0
  19. data/core/benches/vs_fs/README.md +35 -0
  20. data/core/benches/vs_s3/README.md +55 -0
  21. data/core/edge/README.md +3 -0
  22. data/core/edge/file_write_on_full_disk/README.md +14 -0
  23. data/core/edge/s3_aws_assume_role_with_web_identity/README.md +18 -0
  24. data/core/edge/s3_read_on_wasm/.gitignore +3 -0
  25. data/core/edge/s3_read_on_wasm/README.md +42 -0
  26. data/core/edge/s3_read_on_wasm/webdriver.json +15 -0
  27. data/core/examples/README.md +23 -0
  28. data/core/examples/basic/README.md +15 -0
  29. data/core/examples/concurrent-upload/README.md +15 -0
  30. data/core/examples/multipart-upload/README.md +15 -0
  31. data/core/fuzz/.gitignore +5 -0
  32. data/core/fuzz/README.md +68 -0
  33. data/core/src/docs/comparisons/vs_object_store.md +183 -0
  34. data/core/src/docs/performance/concurrent_write.md +101 -0
  35. data/core/src/docs/performance/http_optimization.md +124 -0
  36. data/core/src/docs/rfcs/0000_example.md +74 -0
  37. data/core/src/docs/rfcs/0000_foyer_integration.md +111 -0
  38. data/core/src/docs/rfcs/0041_object_native_api.md +185 -0
  39. data/core/src/docs/rfcs/0044_error_handle.md +198 -0
  40. data/core/src/docs/rfcs/0057_auto_region.md +160 -0
  41. data/core/src/docs/rfcs/0069_object_stream.md +145 -0
  42. data/core/src/docs/rfcs/0090_limited_reader.md +155 -0
  43. data/core/src/docs/rfcs/0112_path_normalization.md +79 -0
  44. data/core/src/docs/rfcs/0191_async_streaming_io.md +328 -0
  45. data/core/src/docs/rfcs/0203_remove_credential.md +96 -0
  46. data/core/src/docs/rfcs/0221_create_dir.md +89 -0
  47. data/core/src/docs/rfcs/0247_retryable_error.md +87 -0
  48. data/core/src/docs/rfcs/0293_object_id.md +67 -0
  49. data/core/src/docs/rfcs/0337_dir_entry.md +191 -0
  50. data/core/src/docs/rfcs/0409_accessor_capabilities.md +67 -0
  51. data/core/src/docs/rfcs/0413_presign.md +154 -0
  52. data/core/src/docs/rfcs/0423_command_line_interface.md +268 -0
  53. data/core/src/docs/rfcs/0429_init_from_iter.md +107 -0
  54. data/core/src/docs/rfcs/0438_multipart.md +163 -0
  55. data/core/src/docs/rfcs/0443_gateway.md +73 -0
  56. data/core/src/docs/rfcs/0501_new_builder.md +111 -0
  57. data/core/src/docs/rfcs/0554_write_refactor.md +96 -0
  58. data/core/src/docs/rfcs/0561_list_metadata_reuse.md +210 -0
  59. data/core/src/docs/rfcs/0599_blocking_api.md +157 -0
  60. data/core/src/docs/rfcs/0623_redis_service.md +300 -0
  61. data/core/src/docs/rfcs/0627_split_capabilities.md +89 -0
  62. data/core/src/docs/rfcs/0661_path_in_accessor.md +126 -0
  63. data/core/src/docs/rfcs/0793_generic_kv_services.md +209 -0
  64. data/core/src/docs/rfcs/0926_object_reader.md +93 -0
  65. data/core/src/docs/rfcs/0977_refactor_error.md +151 -0
  66. data/core/src/docs/rfcs/1085_object_handler.md +73 -0
  67. data/core/src/docs/rfcs/1391_object_metadataer.md +110 -0
  68. data/core/src/docs/rfcs/1398_query_based_metadata.md +125 -0
  69. data/core/src/docs/rfcs/1420_object_writer.md +147 -0
  70. data/core/src/docs/rfcs/1477_remove_object_concept.md +159 -0
  71. data/core/src/docs/rfcs/1735_operation_extension.md +117 -0
  72. data/core/src/docs/rfcs/2083_writer_sink_api.md +106 -0
  73. data/core/src/docs/rfcs/2133_append_api.md +88 -0
  74. data/core/src/docs/rfcs/2299_chain_based_operator_api.md +99 -0
  75. data/core/src/docs/rfcs/2602_object_versioning.md +138 -0
  76. data/core/src/docs/rfcs/2758_merge_append_into_write.md +79 -0
  77. data/core/src/docs/rfcs/2774_lister_api.md +66 -0
  78. data/core/src/docs/rfcs/2779_list_with_metakey.md +143 -0
  79. data/core/src/docs/rfcs/2852_native_capability.md +58 -0
  80. data/core/src/docs/rfcs/2884_merge_range_read_into_read.md +80 -0
  81. data/core/src/docs/rfcs/3017_remove_write_copy_from.md +94 -0
  82. data/core/src/docs/rfcs/3197_config.md +237 -0
  83. data/core/src/docs/rfcs/3232_align_list_api.md +69 -0
  84. data/core/src/docs/rfcs/3243_list_prefix.md +128 -0
  85. data/core/src/docs/rfcs/3356_lazy_reader.md +111 -0
  86. data/core/src/docs/rfcs/3526_list_recursive.md +59 -0
  87. data/core/src/docs/rfcs/3574_concurrent_stat_in_list.md +80 -0
  88. data/core/src/docs/rfcs/3734_buffered_reader.md +64 -0
  89. data/core/src/docs/rfcs/3898_concurrent_writer.md +66 -0
  90. data/core/src/docs/rfcs/3911_deleter_api.md +165 -0
  91. data/core/src/docs/rfcs/4382_range_based_read.md +213 -0
  92. data/core/src/docs/rfcs/4638_executor.md +215 -0
  93. data/core/src/docs/rfcs/5314_remove_metakey.md +120 -0
  94. data/core/src/docs/rfcs/5444_operator_from_uri.md +162 -0
  95. data/core/src/docs/rfcs/5479_context.md +140 -0
  96. data/core/src/docs/rfcs/5485_conditional_reader.md +112 -0
  97. data/core/src/docs/rfcs/5495_list_with_deleted.md +81 -0
  98. data/core/src/docs/rfcs/5556_write_returns_metadata.md +121 -0
  99. data/core/src/docs/rfcs/5871_read_returns_metadata.md +112 -0
  100. data/core/src/docs/rfcs/6189_remove_native_blocking.md +106 -0
  101. data/core/src/docs/rfcs/6209_glob_support.md +132 -0
  102. data/core/src/docs/rfcs/6213_options_api.md +142 -0
  103. data/core/src/docs/rfcs/README.md +62 -0
  104. data/core/src/docs/upgrade.md +1556 -0
  105. data/core/src/services/aliyun_drive/docs.md +61 -0
  106. data/core/src/services/alluxio/docs.md +45 -0
  107. data/core/src/services/azblob/docs.md +77 -0
  108. data/core/src/services/azdls/docs.md +73 -0
  109. data/core/src/services/azfile/docs.md +65 -0
  110. data/core/src/services/b2/docs.md +54 -0
  111. data/core/src/services/cacache/docs.md +38 -0
  112. data/core/src/services/cloudflare_kv/docs.md +21 -0
  113. data/core/src/services/cos/docs.md +55 -0
  114. data/core/src/services/d1/docs.md +48 -0
  115. data/core/src/services/dashmap/docs.md +38 -0
  116. data/core/src/services/dbfs/docs.md +57 -0
  117. data/core/src/services/dropbox/docs.md +64 -0
  118. data/core/src/services/etcd/docs.md +45 -0
  119. data/core/src/services/foundationdb/docs.md +42 -0
  120. data/core/src/services/fs/docs.md +49 -0
  121. data/core/src/services/ftp/docs.md +42 -0
  122. data/core/src/services/gcs/docs.md +76 -0
  123. data/core/src/services/gdrive/docs.md +65 -0
  124. data/core/src/services/ghac/docs.md +84 -0
  125. data/core/src/services/github/docs.md +52 -0
  126. data/core/src/services/gridfs/docs.md +46 -0
  127. data/core/src/services/hdfs/docs.md +140 -0
  128. data/core/src/services/hdfs_native/docs.md +35 -0
  129. data/core/src/services/http/docs.md +45 -0
  130. data/core/src/services/huggingface/docs.md +61 -0
  131. data/core/src/services/ipfs/docs.md +45 -0
  132. data/core/src/services/ipmfs/docs.md +14 -0
  133. data/core/src/services/koofr/docs.md +51 -0
  134. data/core/src/services/lakefs/docs.md +62 -0
  135. data/core/src/services/memcached/docs.md +47 -0
  136. data/core/src/services/memory/docs.md +36 -0
  137. data/core/src/services/mini_moka/docs.md +19 -0
  138. data/core/src/services/moka/docs.md +42 -0
  139. data/core/src/services/mongodb/docs.md +49 -0
  140. data/core/src/services/monoiofs/docs.md +46 -0
  141. data/core/src/services/mysql/docs.md +47 -0
  142. data/core/src/services/obs/docs.md +54 -0
  143. data/core/src/services/onedrive/docs.md +115 -0
  144. data/core/src/services/opfs/docs.md +18 -0
  145. data/core/src/services/oss/docs.md +74 -0
  146. data/core/src/services/pcloud/docs.md +51 -0
  147. data/core/src/services/persy/docs.md +43 -0
  148. data/core/src/services/postgresql/docs.md +47 -0
  149. data/core/src/services/redb/docs.md +41 -0
  150. data/core/src/services/redis/docs.md +43 -0
  151. data/core/src/services/rocksdb/docs.md +54 -0
  152. data/core/src/services/s3/compatible_services.md +126 -0
  153. data/core/src/services/s3/docs.md +244 -0
  154. data/core/src/services/seafile/docs.md +54 -0
  155. data/core/src/services/sftp/docs.md +49 -0
  156. data/core/src/services/sled/docs.md +39 -0
  157. data/core/src/services/sqlite/docs.md +46 -0
  158. data/core/src/services/surrealdb/docs.md +54 -0
  159. data/core/src/services/swift/compatible_services.md +53 -0
  160. data/core/src/services/swift/docs.md +52 -0
  161. data/core/src/services/tikv/docs.md +43 -0
  162. data/core/src/services/upyun/docs.md +51 -0
  163. data/core/src/services/vercel_artifacts/docs.md +40 -0
  164. data/core/src/services/vercel_blob/docs.md +45 -0
  165. data/core/src/services/webdav/docs.md +49 -0
  166. data/core/src/services/webhdfs/docs.md +90 -0
  167. data/core/src/services/yandex_disk/docs.md +45 -0
  168. data/core/tests/behavior/README.md +77 -0
  169. data/core/tests/data/normal_dir/.gitkeep +0 -0
  170. data/core/tests/data/normal_file.txt +1041 -0
  171. data/core/tests/data/special_dir !@#$%^&()_+-=;',/.gitkeep +0 -0
  172. data/core/tests/data/special_file !@#$%^&()_+-=;',.txt +1041 -0
  173. data/core/users.md +13 -0
  174. data/extconf.rb +24 -0
  175. data/lib/opendal.rb +25 -0
  176. data/lib/opendal_ruby/entry.rb +35 -0
  177. data/lib/opendal_ruby/io.rb +70 -0
  178. data/lib/opendal_ruby/metadata.rb +44 -0
  179. data/lib/opendal_ruby/opendal_ruby.so +0 -0
  180. data/lib/opendal_ruby/operator.rb +29 -0
  181. data/lib/opendal_ruby/operator_info.rb +26 -0
  182. data/opendal.gemspec +91 -0
  183. data/test/blocking_op_test.rb +112 -0
  184. data/test/capability_test.rb +42 -0
  185. data/test/io_test.rb +172 -0
  186. data/test/lister_test.rb +77 -0
  187. data/test/metadata_test.rb +78 -0
  188. data/test/middlewares_test.rb +46 -0
  189. data/test/operator_info_test.rb +35 -0
  190. data/test/test_helper.rb +36 -0
  191. metadata +240 -0
@@ -0,0 +1,111 @@
1
+ - Proposal Name: `lazy_reader`
2
+ - Start Date: 2023-10-22
3
+ - RFC PR: [apache/opendal#3356](https://github.com/apache/opendal/pull/3356)
4
+ - Tracking Issue: [apache/opendal#3359](https://github.com/apache/opendal/issues/3359)
5
+
6
+ # Summary
7
+
8
+ Doing read IO in a lazy way.
9
+
10
+ # Motivation
11
+
12
+ The aim is to minimize IO cost. OpenDAL sends an actual IO request to the storage when `Accessor::read()` is invoked. For storage services such as S3, this equates to an IO request. However, in practical scenarios, users typically create a reader and use `seek` to navigate to the correct position.
13
+
14
+ Take [parquet2 read_metadata](https://docs.rs/parquet2/latest/src/parquet2/read/metadata.rs.html) as an example:
15
+
16
+ ```rust
17
+ /// Reads a [`FileMetaData`] from the reader, located at the end of the file.
18
+ pub fn read_metadata<R: Read + Seek>(reader: &mut R) -> Result<FileMetaData> {
19
+ // check file is large enough to hold footer
20
+ let file_size = stream_len(reader)?;
21
+ if file_size < HEADER_SIZE + FOOTER_SIZE {
22
+ return Err(Error::oos(
23
+ "A parquet file must contain a header and footer with at least 12 bytes",
24
+ ));
25
+ }
26
+
27
+ // read and cache up to DEFAULT_FOOTER_READ_SIZE bytes from the end and process the footer
28
+ let default_end_len = min(DEFAULT_FOOTER_READ_SIZE, file_size) as usize;
29
+ reader.seek(SeekFrom::End(-(default_end_len as i64)))?;
30
+
31
+ ...
32
+
33
+ deserialize_metadata(reader, max_size)
34
+ }
35
+ ```
36
+
37
+ In `read_metadata`, we initiate a seek as soon as the reader is invoked. This action, when performed on non-seekable storage services such as s3, results in an immediate HTTP request and cancellation. By postponing the IO request until the first `read` call, we can significantly reduce the number of IO requests.
38
+
39
+ The expense of initiating and immediately aborting an HTTP request is significant. Here are the benchmark results, using a stat call as our baseline:
40
+
41
+ On minio server that setup locally:
42
+
43
+ ```rust
44
+ service_s3_read_stat/4.00 MiB
45
+ time: [315.23 µs 328.23 µs 341.42 µs]
46
+
47
+ service_s3_read_abort/4.00 MiB
48
+ time: [961.69 µs 980.68 µs 999.50 µs]
49
+ ```
50
+
51
+ On remote storage services with high latency:
52
+
53
+ ```rust
54
+ service_s3_read_stat/4.00 MiB
55
+ time: [407.85 ms 409.61 ms 411.39 ms]
56
+
57
+ service_s3_read_abort/4.00 MiB
58
+ time: [1.5282 s 1.5554 s 1.5828 s]
59
+
60
+ ```
61
+
62
+ # Guide-level explanation
63
+
64
+ There have been no changes to the API. The only modification is that the IO request has been deferred until the first `read` call, meaning no errors will be returned when calling `op.reader()`. For instance, users won't encounter a `file not found` error when invoking `op.reader()`.
65
+
66
+ # Reference-level explanation
67
+
68
+ Most changes will happen inside `CompleteLayer`. In the past, we will call `Accessor::read()` directly in `complete_reader`:
69
+
70
+ ```rust
71
+ async fn complete_reader(
72
+ &self,
73
+ path: &str,
74
+ args: OpRead,
75
+ ) -> Result<(RpRead, CompleteReader<A, A::Reader>)> {
76
+ ..
77
+
78
+ let seekable = capability.read_can_seek;
79
+ let streamable = capability.read_can_next;
80
+
81
+ let range = args.range();
82
+ let (rp, r) = self.inner.read(path, args).await?;
83
+ let content_length = rp.metadata().content_length();
84
+
85
+ ...
86
+ }
87
+ ```
88
+
89
+ In the future, we will postpone the `Accessor::read()` request until the first `read` call.
90
+
91
+ # Drawbacks
92
+
93
+ None
94
+
95
+ # Rationale and alternatives
96
+
97
+ None
98
+
99
+ # Prior art
100
+
101
+ None
102
+
103
+ # Unresolved questions
104
+
105
+ None
106
+
107
+ # Future possibilities
108
+
109
+ ## Add `read_at` for `oio::Reader`
110
+
111
+ After `oio::Reader` becomes zero cost, we can add `read_at` to `oio::Reader` to support read data by range.
@@ -0,0 +1,59 @@
1
+ - Proposal Name: `list_recursive`
2
+ - Start Date: 2023-11-08
3
+ - RFC PR: [apache/opendal#3526](https://github.com/apache/opendal/pull/3526)
4
+ - Tracking Issue: [apache/opendal#0000](https://github.com/apache/opendal/issues/0000)
5
+
6
+ # Summary
7
+
8
+ Use `recursive` to replace `delimiter`.
9
+
10
+ # Motivation
11
+
12
+ OpenDAL add `delimiter` in `list` to allow users to control the list behavior:
13
+
14
+ - `delimiter == "/"` means use `/` as delimiter of path, it behaves like list current dir.
15
+ - `delimiter == ""` means don't set delimiter of path, it behaves like list current dir and all it's children.
16
+
17
+ Ideally, we should allow users to input any delimiter such as `|`, `-`, and `+`.
18
+
19
+ The `delimiter` concept can be challenging for users unfamiliar with object storage services. Currently, only `/` and empty spaces are accepted as delimiters, despite not being fully implemented across all services. We need to inform users that `delimiter == "/"` is used to list the current directory, while `delimiter == ""` is used for recursive listing. This may not be immediately clear.
20
+
21
+ So, why not use `recursive` directly for more clear API behavior?
22
+
23
+ # Guide-level explanation
24
+
25
+ OpenDAL will use `recursive` to replace `delimiter`. Default behavior is not changed, so users that using `op.list()` is not affected.
26
+
27
+ For users who is using `op.list_with(path).delimiter(delimiter)`:
28
+
29
+ - `op.list_with(path).delimiter("")` -> `op.list_with(path).recursive(true)`
30
+ - `op.list_with(path).delimiter("/")` -> `op.list_with(path).recursive(false)`
31
+ - `op.list_with(path).delimiter(other_value)`: not supported anymore.
32
+
33
+ # Reference-level explanation
34
+
35
+ We will add `recursive` as a new arg in `OpList` and remove all fields related to `delimiter`.
36
+
37
+ # Drawbacks
38
+
39
+ ## Can't support to use `|`, `-`, and `+` as delimiter
40
+
41
+ We never support this feature before.
42
+
43
+ # Rationale and alternatives
44
+
45
+ None
46
+
47
+ # Prior art
48
+
49
+ None
50
+
51
+ # Unresolved questions
52
+
53
+ None
54
+
55
+ # Future possibilities
56
+
57
+ ## Add delete with recursive support
58
+
59
+ Some services have native support for delete with recursive, such as azfile. We can add this feature in the future if needed.
@@ -0,0 +1,80 @@
1
+ - Proposal Name: `concurrent_stat_in_list`
2
+ - Start Date: 2023-11-13
3
+ - RFC PR: [apache/opendal#3574](https://github.com/apache/opendal/pull/3574)
4
+ - Tracking Issue: [apache/opendal#3575](https://github.com/apache/opendal/issues/3575)
5
+
6
+ # Summary
7
+
8
+ Add concurrent stat in list operation.
9
+
10
+ # Motivation
11
+
12
+ [RFC-2779](https://github.com/apache/opendal/pull/2779) allows user to list with metakey.
13
+ However, the stat inside list could make the list process much slower. We should allow concurrent stat during list so that stat could be sent concurrently.
14
+
15
+
16
+ # Guide-level explanation
17
+
18
+ For users who want to concurrently run statistics in list operations, they will call the new API `concurrent`. The `concurrent` function will take a number as input, and this number will represent the maximum concurrent stat handlers.
19
+
20
+ The default behavior remains unchanged, so users using `op.list_with()` are not affected. And this implementation should be zero cost to users who don't want to do concurrent stat.
21
+
22
+ ```rust
23
+ op.lister_with(path).metakey(Metakey::ContentLength).concurrent(10).await?
24
+ ```
25
+
26
+
27
+ # Reference-level explanation
28
+
29
+ When `concurrent` is set and `list_with` is called, the list operation will be split into two parts: list and stat.
30
+
31
+ The list part will iterate through the entries inside the buffer, and if its `metakey` is unknown, it will send a stat request to the storage service.
32
+
33
+ We will add a new field `concurrent` to `OpList`. The type of `concurrent` is `Option<u32>`. If `concurrent` is `None`, it means the default behavior. If `concurrent` is `Some(n)`, it means the maximum concurrent stat handlers are `n`.
34
+
35
+ Then we could use a sized `VecDeque` to limit the maximum concurrent stat handlers. Additionally, we could use handlers `JoinHandle<T>` inside `VecDeque` to spawn and queue the stat tasks.
36
+ While iterating through the entries, we should check if the `metakey` is unknown and if the `VecDeque` is full. If the `metakey` is unknown and the `VecDeque` is full, we should wait and join the handle once it’s finished, since we need to keep the entry order.
37
+
38
+ If the metakey is unknown and the handlers are full, we should break the loop and wait for the spawned tasks inside handlers to finish. After the spawned tasks finish, we should iterate through the handlers and return the result.
39
+
40
+ If the metakey is known, we should check if the handlers are empty. If true, return the result immediately; otherwise, we should wait for the spawned tasks to finish.
41
+
42
+ # Drawbacks
43
+
44
+ 1. More memory usage
45
+ 2. More complex code
46
+ 3. More complex testing
47
+
48
+ # Rationale and alternatives
49
+
50
+ ## Why not `VecDeque<BoxFuture<'static, X>>`?
51
+
52
+ To maintain the order of returned entries, we need to pre-run future entries before returning the current one to address the slowness issue.
53
+ Although we could use `VecDeque<BoxFuture<'static, X>>` to store the spawned tasks,
54
+ using it here would prevent us from executing the async block concurrently when we only have one `cx: &mut Context<'_>`.
55
+
56
+ ## Do we need `Semaphore`?
57
+
58
+ No, we can control the concurrent number by limiting the length of the `VecDeque`.
59
+ Using a `semaphore` will introduce more cost and memory.
60
+
61
+ ## Why not using `JoinSet`?
62
+
63
+ The main reason is that `JoinSet` can't maintain the order of entries.
64
+
65
+ The other reason is that `JoinSet` requires mutability to spawn or join the next task, and `tokio::spawn()` requires the async block to be `'static`.
66
+ This implies that we need to use `Arc<T>` to wrap our `JoinSet`. However, to change the value inside `Arc`, we need to introduce a `Mutex`. Since it's inside an async block, we need to use Tokio's `Mutex` to satisfy the `Sync` bound.
67
+ Therefore, for every operation on the `JoinSet`, there will be an `.await` on the lock outside the async block, making concurrency impossible inside `poll_next()`.
68
+
69
+ # Prior art
70
+
71
+ None
72
+
73
+ # Unresolved questions
74
+
75
+ - How to implement a similar logic to `blocking` API?
76
+ - Quoted from [oowl](https://github.com/oowl): It seems these features can be implemented in blocking mode, but it may require breaking something in OpenDAL, such as using some pthread API in blocking mode.
77
+
78
+ # Future possibilities
79
+
80
+ None
@@ -0,0 +1,64 @@
1
+ - Proposal Name: `buffered_reader`
2
+ - Start Date: 2023-12-10
3
+ - RFC PR: [apache/opendal#3574](https://github.com/apache/opendal/pull/3734)
4
+ - Tracking Issue: [apache/opendal#3575](https://github.com/apache/opendal/issues/3735)
5
+
6
+ # Summary
7
+
8
+ Allowing the underlying reader to fetch data at the buffer's size to amortize the IO's overhead.
9
+
10
+ # Motivation
11
+
12
+ The objective is to mitigate the IO overhead. In certain scenarios, the reader processes the data incrementally, meaning that it utilizes the `seek()` function to navigate to a specific position within the file. Subsequently, it invokes the `read()` to reads `limit` bytes into memory and performs the decoding process.
13
+
14
+
15
+ OpenDAL triggers an IO request upon invoking `read()` if the `seek()` has reset the inner state. For storage services like S3, [research](https://www.vldb.org/pvldb/vol16/p2769-durner.pdf) suggests that an optimal IO size falls within the range of 8MiB to 16MiB. If the IO size is too small, the Time To First Byte (TTFB) dominates the overall runtime, resulting in inefficiency.
16
+
17
+ Therefore, this RFC proposes the implementation of a buffered reader to amortize the overhead of IO.
18
+
19
+ # Guide-level explanation
20
+
21
+ For users who want to buffered reader, they will call the new API `buffer`. And the default behavior remains unchanged, so users using `op.reader_with()` are not affected. The `buffer` function will take a number as input, and this number will represent the maximum buffer capability the reader is able to use.
22
+
23
+ ```rust
24
+ op.reader_with(path).buffer(32 * 1024 * 1024).await
25
+ ```
26
+
27
+ # Reference-level explanation
28
+
29
+ This feature will be implemented in the `CompleteLayer`, with the addition of a `BufferReader` struct in `raw/oio/reader/buffer_reader.rs`.
30
+
31
+ The `BufferReader` employs a `tokio::io::ReadBuf` as the inner buffer and uses `offset: Option<u64>` to track the buffered range start of the file, the buffered data should always be `file[offset..offset + buf.len()]`.
32
+
33
+
34
+ ```rust
35
+ ...
36
+ async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
37
+ BufferReader::new(self.complete_read(path, args).await)
38
+ }
39
+
40
+ ...
41
+
42
+ fn blocking_read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::BlockingReader)> {
43
+ BufferReader::new(self.complete_blocking_read(path, args))
44
+ }
45
+ ...
46
+ ```
47
+
48
+ A `buffer` field of type `Option<usize>` will be introduced to `OpRead`. If `buffer` is set to `None`, it functions with default behavior. However, if buffer is set to `Some(n)`, it denotes the maximum buffer capability that the `BufferReader` can utilize. The behavior is similar to [std::io::BufReader](https://doc.rust-lang.org/std/io/struct.BufReader.html), with the difference being that our implementation always provides the `seek_relative` (without discarding the inner buffer) if it's available; And it doesn't buffer trailing reads when the read range is smaller than the buffer capability.
49
+
50
+ # Drawbacks
51
+ None
52
+
53
+ # Rationale and alternatives
54
+ None
55
+
56
+ # Prior art
57
+ None
58
+
59
+ # Unresolved questions
60
+ None
61
+
62
+ # Future possibilities
63
+ - Concurrent fetching.
64
+ - Tailing buffering.
@@ -0,0 +1,66 @@
1
+ - Proposal Name: `concurrent_writer`
2
+ - Start Date: 2024-01-02
3
+ - RFC PR: [apache/opendal#3898](https://github.com/apache/opendal/pull/3898)
4
+ - Tracking Issue: [apache/opendal#3899](https://github.com/apache/opendal/issues/3899)
5
+
6
+ # Summary
7
+
8
+ Enhance the `Writer` by adding concurrent write capabilities.
9
+
10
+ # Motivation
11
+
12
+ Certain services, such as S3, GCS, and AzBlob, offer the `multi_write` functionality, allowing users to perform multiple write operations for uploading of large files. If a service support `multi_write`, the [Capability::write_can_multi](https://opendal.apache.org/docs/rust/opendal/struct.Capability.html#structfield.write_can_multi) metadata should be set to `true`.
13
+ ```rust
14
+ let mut writer = op.writer("path/to").await?; // a writers supports the `multi_write`.
15
+ writer.write(part0).await?;
16
+ writer.write(part1).await?; // It starts to upload after the `part0` is finished.
17
+ writer.close().await?;
18
+ ```
19
+ Currently, when invoking a `Writer` that supports the `multi_write` functionality, multiple writes are proceed serially, without fully leveraging the potential for improved throughput through concurrent uploads. We should enhance support to allow concurrent processing of multiple write operations.
20
+
21
+
22
+ # Guide-level explanation
23
+
24
+ For users who want to concurrent writer, they will call the new API `concurrent`. And the default behavior remains unchanged, so users using `op.writer_with()` are not affected. The `concurrent` function will take a number as input, and this number will represent the maximum concurrent write task amount the writer can perform.
25
+
26
+ - If `concurrent` is set to 0 or 1, it functions with default behavior(writes serially).
27
+ - However, if `concurrent` is set to number larger than 1. It enables concurrent uploading of up to `concurrent` write tasks and allows users to initiate additional write tasks without waiting to complete the previous write operation, as long as the inner task queue still has available slots.
28
+
29
+ The concurrent write feature operate independently of other features.
30
+
31
+ ```rust
32
+ let mut w = op.writer_with(path).concurrent(8).await;
33
+ w.write(part0).await?;
34
+ w.write(part1).await?; // `write` won't wait for part0.
35
+ w.close().await?; // `close` will make sure all parts are finished.
36
+ ```
37
+
38
+ # Reference-level explanation
39
+
40
+ The S3 and similar services use `MultipartUploadWriter`, while GCS uses `RangeWriter`. We can enhance these services by adding concurrent write features to them. A `concurrent` field of type `usize` will be introduced to `OpWrite` to allow the user to set the maximum concurrent write task amount. For other services that don't support `multi_write`, setting the concurrent parameter will have no effect, maintaining the default behavior.
41
+
42
+ This feature will be implemented in the `MultipartUploadWriter` and `RangeWriter`, which will utilize a `ConcurrentFutures<WriteTask>` as a task queue to store concurrent write tasks.
43
+
44
+ When the upper layer invokes `poll_write`, the `Writer` pushes write to the task queue (`ConcurrentFutures<WriteTask>`) if there are available slots, and then relinquishes control back to the upper layer. This allows for up to `concurrent` write tasks to uploaded concurrently without waiting. If the task queue is full, the `Writer` waits for the first task to yield results.
45
+
46
+ # Drawbacks
47
+
48
+ - More memory usage
49
+ - More concurrent connections
50
+
51
+ # Rationale and alternatives
52
+
53
+ None
54
+
55
+ # Prior art
56
+
57
+ None
58
+
59
+ # Unresolved questions
60
+
61
+ None
62
+
63
+ # Future possibilities
64
+
65
+ - Adding `write_at` for `fs`.
66
+ - Use `ConcurrentFutureUnordered` instead of `ConcurrentFutures.`
@@ -0,0 +1,165 @@
1
+ - Proposal Name: `deleter_api`
2
+ - Start Date: 2024-01-04
3
+ - RFC PR: [apache/opendal#3911](https://github.com/apache/opendal/pull/3911)
4
+ - Tracking Issue: [apache/opendal#3922](https://github.com/apache/opendal/issues/3922)
5
+
6
+ # Summary
7
+
8
+ Introduce the `Deleter` API to enhance batch and recursive deletion capabilities.
9
+
10
+ # Motivation
11
+
12
+ All OpenDAL's public API follow the same design:
13
+
14
+ - `read`: Execute a read operation.
15
+ - `read_with`: Execute a read operation with additional options, like range and if_match.
16
+ - `reader`: Create a reader for streaming data, enabling flexible access.
17
+ - `reader_with`: Create a reader with advanced options.
18
+
19
+ However, `delete` operations vary. OpenDAL offers several methods for file deletion:
20
+
21
+ - `delete`: Delete a single file or an empty dir.
22
+ - `remove`: Remove a list of files.
23
+ - `remove_via`: Remove files produced by a stream.
24
+ - `remove_all`: Remove all files under a path.
25
+
26
+ This design is not consistent with the other APIs, and it is not easy to use.
27
+
28
+ So I propose `Deleter` to address them all at once.
29
+
30
+ # Guide-level explanation
31
+
32
+ The following new API will be added to `Operator`:
33
+
34
+ ```diff
35
+ impl Operator {
36
+ pub async fn delete(&self, path: &str) -> Result<()>;
37
+ + pub fn delete_with(&self, path: &str) -> FutureDelete;
38
+
39
+ + pub async fn deleter(&self) -> Result<Deleter>;
40
+ + pub fn deleter_with(&self) -> FutureDeleter;
41
+ }
42
+ ```
43
+
44
+ - `delete` is the existing API, which deletes a single file or an empty dir.
45
+ - `delete_with` is an extension of the existing `delete` API, which supports additional options, such as `version`.
46
+ - `deleter` is a new API that returns a `Deleter` instance.
47
+ - `deleter_with` is an extension of the existing `deleter` API, which supports additional options, such as `concurrent`.
48
+
49
+ The following new options will be available for `delete_with` and `deleter_with`:
50
+
51
+ - `concurrent`: How many delete tasks can be performed concurrently?
52
+ - `buffer`: How many files can be buffered for send in a single batch?
53
+
54
+ Users can delete multiple files in this way:
55
+
56
+
57
+ ```rust
58
+ let deleter = op.deleter().await?;
59
+
60
+ // Add a single file to the deleter.
61
+ deleter.delete(path).await?;
62
+
63
+ // Add a stream of files to the deleter.
64
+ deleter.delete_all(&mut lister).await?;
65
+
66
+ // Close deleter, make sure all input files are deleted.
67
+ deleter.close().await?;
68
+ ```
69
+
70
+ `Deleter` also implements [`Sink`](https://docs.rs/futures/latest/futures/sink/trait.Sink.html), so all the methods of `Sink` are available for `Deleter`. For example, users can use [`forward`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html#method.forward) to forward a stream of files to `Deleter`:
71
+
72
+ ```rust
73
+ // Init a deleter to start batch delete tasks.
74
+ let deleter = op.deleter().await?;
75
+ // List all files that ends with tmp
76
+ let lister = op.lister(path).await?
77
+ .filter(|x|future::ready(x.ends_with(".tmp")));
78
+
79
+ // Forward all paths into deleter.
80
+ lister.forward(deleter).await?;
81
+
82
+ // Send all from a stream into deleter.
83
+ deleter.send_all(&mut lister).await?;
84
+
85
+ // Close the deleter.
86
+ deleter.close().await?;
87
+ ```
88
+
89
+ Users can control the behavior of `Deleter` by setting the options:
90
+
91
+ ```rust
92
+ let deleter = op.deleter_with()
93
+ // Allow up to 8 concurrent delete tasks, default to 1.
94
+ .concurrent(8)
95
+ // Configure the buffer size to 1000, default value provided by services.
96
+ .buffer(1000)
97
+ .await?;
98
+
99
+ // Add a single file to the deleter.
100
+ deleter.delete(path).await?;
101
+
102
+ // Add a stream of files to the deleter.
103
+ deleter.delete_all(&mut lister).await?;
104
+
105
+ // Close deleter, make sure all input files are deleted.
106
+ deleter.close().await?;
107
+ ```
108
+
109
+ In response to `Deleter` API, we will remove APIs like `remove`, `remove_via` and `remove_all`.
110
+
111
+ - `remove` and `remove_via` could be replaced by `Deleter` directly.
112
+ - `remove_all` could be replaced by `delete_with(path).recursive(true)`.
113
+
114
+ # Reference-level explanation
115
+
116
+ To provide those public APIs, we will add a new associated type in `Accessor`:
117
+
118
+ ```rust
119
+ trait Accessor {
120
+ ...
121
+
122
+ type Deleter = oio::Delete;
123
+ type BlockingDeleter = oio::BlockingDelete;
124
+ }
125
+ ```
126
+
127
+ And the `delete` API will be changed to return a `oio::Delete` instead:
128
+
129
+ ```diff
130
+ trait Accessor {
131
+ - async fn delete(&self) -> Result<(RpDelete, Self::Deleter)>;
132
+ + async fn delete(&self, args: OpDelete) -> Result<(RpDelete, Self::Deleter)>;
133
+ }
134
+ ```
135
+
136
+ Along with this change, we will remove the `batch` API from `Accessor`:
137
+
138
+ ```rust
139
+ trait Accessor {
140
+ - async fn batch(&self, args: OpBatch) -> Result<RpBatch>;
141
+ }
142
+ ```
143
+
144
+ # Drawbacks
145
+
146
+ - Big breaking changes.
147
+
148
+
149
+ # Rationale and alternatives
150
+
151
+ None.
152
+
153
+ # Prior art
154
+
155
+ None.
156
+
157
+ # Unresolved questions
158
+
159
+ None.
160
+
161
+ # Future possibilities
162
+
163
+ ## Add API that accepts `IntoIterator`
164
+
165
+ It's possible to add a new API that accepts `IntoIterator` so users can input `Vec<String>` or `Iter<String>` into `Deleter`.