1+ use std:: collections:: HashMap ;
2+
13use async_trait:: async_trait;
24use diesel:: {
35 self , ExpressionMethods , OptionalExtension , QueryDsl , delete,
@@ -45,7 +47,10 @@ impl BatchDb for PgDb {
4547
4648 let batch = results:: CreateBatch {
4749 id : batch_id. to_string ( ) ,
48- size : None ,
50+ // `size` is the committed collection size from `check_quota`.
51+ size : self
52+ . check_quota ( & params. user_id , & params. collection )
53+ . await ?,
4954 } ;
5055
5156 do_append (
@@ -54,6 +59,7 @@ impl BatchDb for PgDb {
5459 collection_id,
5560 batch. clone ( ) ,
5661 params. bsos ,
62+ & params. collection ,
5763 )
5864 . await ?;
5965
@@ -100,12 +106,19 @@ impl BatchDb for PgDb {
100106
101107 let collection_id = self . get_or_create_collection_id ( & params. collection ) . await ?;
102108
109+ // `batch.size` is the committed collection size from `check_quota`.
110+ let mut batch = params. batch ;
111+ batch. size = self
112+ . check_quota ( & params. user_id , & params. collection )
113+ . await ?;
114+
103115 do_append (
104116 self ,
105117 params. user_id ,
106118 collection_id,
107- params . batch ,
119+ batch,
108120 params. bsos ,
121+ & params. collection ,
109122 )
110123 . await ?;
111124
@@ -132,13 +145,7 @@ impl BatchDb for PgDb {
132145 let user_id = params. user_id . legacy_id as i64 ;
133146 let collection_id = self . get_or_create_collection_id ( & params. collection ) . await ?;
134147
135- let timestamp = self
136- . update_collection ( params:: UpdateCollection {
137- user_id : params. user_id . clone ( ) ,
138- collection_id,
139- collection : params. collection . clone ( ) ,
140- } )
141- . await ?;
148+ let timestamp = self . checked_timestamp ( ) ?;
142149 let default_ttl_seconds = DEFAULT_BSO_TTL as i64 ;
143150 let ts_datetime = timestamp. as_datetime ( ) ?;
144151
@@ -151,6 +158,14 @@ impl BatchDb for PgDb {
151158 . execute ( & mut self . conn )
152159 . await ?;
153160
161+ let timestamp = self
162+ . update_collection ( params:: UpdateCollection {
163+ user_id : params. user_id . clone ( ) ,
164+ collection_id,
165+ collection : params. collection . clone ( ) ,
166+ } )
167+ . await ?;
168+
154169 self . delete_batch ( params:: DeleteBatch {
155170 user_id : params. user_id ,
156171 collection : params. collection ,
@@ -194,14 +209,39 @@ pub async fn do_append(
194209 collection_id : i32 ,
195210 batch : results:: CreateBatch ,
196211 bsos : Vec < params:: PostCollectionBso > ,
212+ collection : & str ,
197213) -> DbResult < ( ) > {
198214 let batch_id = Uuid :: parse_str ( & batch. id )
199215 . map_err ( |e| DbError :: internal ( format ! ( "Invalid batch_id in batch: {}" , e) ) ) ?;
216+ let user_id_i64 = user_id. legacy_id as i64 ;
217+
218+ if db. quota . enabled
219+ && let Some ( size) = batch. size
220+ {
221+ let running_size: usize = bsos
222+ . iter ( )
223+ . filter_map ( |bso| bso. payload . as_ref ( ) . map ( |p| p. len ( ) ) )
224+ . sum ( ) ;
225+ let incoming_ids: Vec < String > = bsos. iter ( ) . map ( |bso| bso. id . clone ( ) ) . collect ( ) ;
226+ let pending_size =
227+ pending_batch_size ( db, user_id_i64, collection_id, & batch_id, & incoming_ids) . await ?;
228+ let projected_total = size + pending_size + running_size;
229+ if projected_total >= db. quota . size {
230+ let mut tags = HashMap :: default ( ) ;
231+ tags. insert ( "collection" . to_owned ( ) , collection. to_owned ( ) ) ;
232+ db. metrics . incr_with_tags ( "storage.quota.at_limit" , tags) ;
233+ if db. quota . enforced {
234+ return Err ( DbError :: quota ( ) ) ;
235+ } else {
236+ warn ! ( "Quota at limit for user's collection ({} bytes)" , projected_total;
237+ "collection" => collection) ;
238+ }
239+ }
240+ }
200241
201242 for bso in bsos {
202243 let ttl = bso. ttl . map ( |t| t as i64 ) ;
203244 let sortindex = bso. sortindex ;
204- let user_id_i64 = user_id. legacy_id as i64 ;
205245
206246 sql_query (
207247 "INSERT INTO batch_bsos (user_id, collection_id, batch_id, batch_bso_id, sortindex, payload, ttl)
@@ -225,6 +265,29 @@ pub async fn do_append(
225265 Ok ( ( ) )
226266}
227267
268+ /// Sum the pending payload bytes, excluding any ids in `incoming_ids`.
269+ async fn pending_batch_size (
270+ db : & mut PgDb ,
271+ user_id : i64 ,
272+ collection_id : i32 ,
273+ batch_id : & Uuid ,
274+ incoming_ids : & [ String ] ,
275+ ) -> DbResult < usize > {
276+ let current_batch_size: i64 = batch_bsos:: table
277+ . select ( sql :: < BigInt > (
278+ "COALESCE(SUM(LENGTH(COALESCE(payload, ''))), 0)::BIGINT" ,
279+ ) )
280+ . filter ( batch_bsos:: user_id. eq ( user_id) )
281+ . filter ( batch_bsos:: collection_id. eq ( collection_id) )
282+ . filter ( batch_bsos:: batch_id. eq ( batch_id) )
283+ . filter ( batch_bsos:: batch_bso_id. ne_all ( incoming_ids) )
284+ . get_result ( & mut db. conn )
285+ . await
286+ . optional ( ) ?
287+ . unwrap_or_default ( ) ;
288+ Ok ( current_batch_size. max ( 0 ) as usize )
289+ }
290+
228291pub fn validate_batch_id ( id : & str ) -> DbResult < Uuid > {
229292 Uuid :: parse_str ( id) . map_err ( |e| DbError :: internal ( format ! ( "Invalid batch_id: {}" , e) ) )
230293}
0 commit comments