Skip to main content

millipede_core/storage/
rate_limit.rs

1//! Storage wrappers that report backend health to autoscaling.
2
3use std::{path::Path, sync::Arc, time::Duration};
4
5use futures_util::{StreamExt, stream::BoxStream};
6
7use super::{
8    AddOptions, BatchAddHandle, Dataset, DatasetInfo, KeyList, KeyValueStore, KvEntry, Lease,
9    LeaseId, ListKeysOptions, ListOptions, Page, QueueOpInfo, ReclaimOptions, RequestQueue,
10    RequestSource, StorageClient, StorageResult,
11};
12use crate::{autoscale::ClientLoadSignalHandle, request::Request};
13
14fn observe<T>(handle: &ClientLoadSignalHandle, result: StorageResult<T>) -> StorageResult<T> {
15    match &result {
16        Ok(_) => handle.record_healthy(),
17        Err(error) if error.is_rate_limited() => handle.record_rate_limited(),
18        Err(_) => {}
19    }
20    result
21}
22
23/// A storage client wrapper that reports healthy and rate-limited operations to autoscaling.
24pub struct RateLimitReportingClient {
25    inner: Arc<dyn StorageClient>,
26    handle: ClientLoadSignalHandle,
27}
28
29impl RateLimitReportingClient {
30    /// Wraps `inner` with rate-limit reporting and returns it as a storage client.
31    #[allow(clippy::new_ret_no_self)]
32    pub fn new(
33        inner: Arc<dyn StorageClient>,
34        handle: ClientLoadSignalHandle,
35    ) -> Arc<dyn StorageClient> {
36        Arc::new(Self { inner, handle })
37    }
38}
39
40#[async_trait::async_trait]
41impl StorageClient for RateLimitReportingClient {
42    async fn open_dataset(&self, name: Option<&str>) -> StorageResult<Arc<dyn Dataset>> {
43        let result = self.inner.open_dataset(name).await;
44        observe(&self.handle, result).map(|inner| {
45            Arc::new(ReportingDataset {
46                inner,
47                handle: self.handle.clone(),
48            }) as Arc<dyn Dataset>
49        })
50    }
51
52    async fn open_key_value_store(
53        &self,
54        name: Option<&str>,
55    ) -> StorageResult<Arc<dyn KeyValueStore>> {
56        let result = self.inner.open_key_value_store(name).await;
57        observe(&self.handle, result).map(|inner| {
58            Arc::new(ReportingKvs {
59                inner,
60                handle: self.handle.clone(),
61            }) as Arc<dyn KeyValueStore>
62        })
63    }
64
65    async fn open_request_queue(&self, name: Option<&str>) -> StorageResult<Arc<dyn RequestQueue>> {
66        let result = self.inner.open_request_queue(name).await;
67        observe(&self.handle, result).map(|inner| {
68            Arc::new(ReportingQueue {
69                inner,
70                handle: self.handle.clone(),
71            }) as Arc<dyn RequestQueue>
72        })
73    }
74
75    async fn purge(&self) -> StorageResult<()> {
76        let result = self.inner.purge().await;
77        observe(&self.handle, result)
78    }
79}
80
81struct ReportingDataset {
82    inner: Arc<dyn Dataset>,
83    handle: ClientLoadSignalHandle,
84}
85
86#[async_trait::async_trait]
87impl Dataset for ReportingDataset {
88    async fn push_json(&self, item: serde_json::Value) -> StorageResult<()> {
89        let result = self.inner.push_json(item).await;
90        observe(&self.handle, result)
91    }
92
93    async fn push_json_batch(&self, items: Vec<serde_json::Value>) -> StorageResult<()> {
94        let result = self.inner.push_json_batch(items).await;
95        observe(&self.handle, result)
96    }
97
98    async fn list_raw(&self, opts: ListOptions) -> StorageResult<Page<serde_json::Value>> {
99        let result = self.inner.list_raw(opts).await;
100        observe(&self.handle, result)
101    }
102
103    fn stream_raw(&self, opts: ListOptions) -> BoxStream<'_, StorageResult<serde_json::Value>> {
104        let handle = self.handle.clone();
105        Box::pin(
106            self.inner
107                .stream_raw(opts)
108                .map(move |result| observe(&handle, result)),
109        )
110    }
111
112    async fn export_json(&self, path: &Path) -> StorageResult<()> {
113        let result = self.inner.export_json(path).await;
114        observe(&self.handle, result)
115    }
116
117    async fn export_csv(&self, path: &Path) -> StorageResult<()> {
118        let result = self.inner.export_csv(path).await;
119        observe(&self.handle, result)
120    }
121
122    async fn info(&self) -> StorageResult<DatasetInfo> {
123        let result = self.inner.info().await;
124        observe(&self.handle, result)
125    }
126}
127
128struct ReportingKvs {
129    inner: Arc<dyn KeyValueStore>,
130    handle: ClientLoadSignalHandle,
131}
132
133#[async_trait::async_trait]
134impl KeyValueStore for ReportingKvs {
135    async fn get_bytes(&self, key: &str) -> StorageResult<Option<KvEntry>> {
136        let result = self.inner.get_bytes(key).await;
137        observe(&self.handle, result)
138    }
139
140    async fn set_bytes(
141        &self,
142        key: &str,
143        bytes: bytes::Bytes,
144        content_type: &str,
145    ) -> StorageResult<()> {
146        let result = self.inner.set_bytes(key, bytes, content_type).await;
147        observe(&self.handle, result)
148    }
149
150    async fn delete(&self, key: &str) -> StorageResult<()> {
151        let result = self.inner.delete(key).await;
152        observe(&self.handle, result)
153    }
154
155    async fn list_keys(&self, opts: ListKeysOptions) -> StorageResult<KeyList> {
156        let result = self.inner.list_keys(opts).await;
157        observe(&self.handle, result)
158    }
159}
160
161struct ReportingQueue {
162    inner: Arc<dyn RequestQueue>,
163    handle: ClientLoadSignalHandle,
164}
165
166#[async_trait::async_trait]
167impl RequestQueue for ReportingQueue {
168    async fn add(&self, req: Request, opts: AddOptions) -> StorageResult<QueueOpInfo> {
169        let result = self.inner.add(req, opts).await;
170        observe(&self.handle, result)
171    }
172
173    async fn add_batch(
174        &self,
175        reqs: Vec<RequestSource>,
176        opts: AddOptions,
177    ) -> StorageResult<BatchAddHandle> {
178        let result = self.inner.add_batch(reqs, opts).await;
179        observe(&self.handle, result)
180    }
181
182    async fn fetch_next(&self) -> StorageResult<Option<Lease>> {
183        let result = self.inner.fetch_next().await;
184        observe(&self.handle, result)
185    }
186
187    async fn mark_handled(&self, lease: Lease) -> StorageResult<()> {
188        let result = self.inner.mark_handled(lease).await;
189        observe(&self.handle, result)
190    }
191
192    async fn reclaim(&self, lease: Lease, opts: ReclaimOptions) -> StorageResult<()> {
193        let result = self.inner.reclaim(lease, opts).await;
194        observe(&self.handle, result)
195    }
196
197    async fn renew(&self, lease_id: &LeaseId, extend_by: Duration) -> StorageResult<()> {
198        let result = self.inner.renew(lease_id, extend_by).await;
199        observe(&self.handle, result)
200    }
201
202    async fn abandon(&self, lease: Lease) -> StorageResult<()> {
203        let result = self.inner.abandon(lease).await;
204        observe(&self.handle, result)
205    }
206
207    async fn is_empty(&self) -> StorageResult<bool> {
208        let result = self.inner.is_empty().await;
209        observe(&self.handle, result)
210    }
211
212    async fn is_finished(&self) -> StorageResult<bool> {
213        let result = self.inner.is_finished().await;
214        observe(&self.handle, result)
215    }
216
217    async fn handled_count(&self) -> StorageResult<u64> {
218        let result = self.inner.handled_count().await;
219        observe(&self.handle, result)
220    }
221
222    async fn pending_count(&self) -> StorageResult<u64> {
223        let result = self.inner.pending_count().await;
224        observe(&self.handle, result)
225    }
226}