11use base64:: Engine ;
2- use std:: collections:: HashSet ;
2+ use std:: collections:: { HashMap , HashSet } ;
33
44use async_trait:: async_trait;
55use diesel:: {
@@ -69,11 +69,25 @@ impl BatchDb for MysqlDb {
6969 }
7070 }
7171
72- do_append ( self , batch_id, params. user_id , collection_id, params. bsos ) . await ?;
73- Ok ( results:: CreateBatch {
72+ let batch = results:: CreateBatch {
7473 id : encode_id ( batch_id) ,
75- size : None ,
76- } )
74+ // `size` is the committed size
75+ size : self
76+ . check_quota ( & params. user_id , & params. collection )
77+ . await ?,
78+ } ;
79+
80+ do_append (
81+ self ,
82+ params. user_id ,
83+ collection_id,
84+ batch. clone ( ) ,
85+ params. bsos ,
86+ & params. collection ,
87+ )
88+ . await ?;
89+
90+ Ok ( batch)
7791 }
7892
7993 async fn validate_batch ( & mut self , params : params:: ValidateBatch ) -> DbResult < bool > {
@@ -110,9 +124,24 @@ impl BatchDb for MysqlDb {
110124 return Err ( DbError :: batch_not_found ( ) ) ;
111125 }
112126
113- let batch_id = decode_id ( & params. batch . id ) ?;
114127 let collection_id = self . _get_collection_id ( & params. collection ) . await ?;
115- do_append ( self , batch_id, params. user_id , collection_id, params. bsos ) . await ?;
128+
129+ // `batch.size` is the committed size
130+ let mut batch = params. batch ;
131+ batch. size = self
132+ . check_quota ( & params. user_id , & params. collection )
133+ . await ?;
134+
135+ do_append (
136+ self ,
137+ params. user_id ,
138+ collection_id,
139+ batch,
140+ params. bsos ,
141+ & params. collection ,
142+ )
143+ . await ?;
144+
116145 Ok ( ( ) )
117146 }
118147
@@ -191,10 +220,11 @@ impl BatchDb for MysqlDb {
191220
192221pub async fn do_append (
193222 db : & mut MysqlDb ,
194- batch_id : i64 ,
195223 user_id : UserIdentifier ,
196224 _collection_id : i32 ,
225+ batch : results:: CreateBatch ,
197226 bsos : Vec < params:: PostCollectionBso > ,
227+ collection : & str ,
198228) -> DbResult < ( ) > {
199229 fn exist_idx ( user_id : u64 , batch_id : i64 , bso_id : & str ) -> String {
200230 // Construct something that matches the key for batch_upload_items
@@ -206,6 +236,32 @@ pub async fn do_append(
206236 )
207237 }
208238
239+ let batch_id = decode_id ( & batch. id ) ?;
240+ let uid = user_id. legacy_id as i64 ;
241+
242+ if db. quota . enabled
243+ && let Some ( size) = batch. size
244+ {
245+ let running_size: usize = bsos
246+ . iter ( )
247+ . filter_map ( |bso| bso. payload . as_ref ( ) . map ( |p| p. len ( ) ) )
248+ . sum ( ) ;
249+ let incoming_ids: Vec < String > = bsos. iter ( ) . map ( |bso| bso. id . clone ( ) ) . collect ( ) ;
250+ let pending_size = pending_batch_size ( db, uid, batch_id, & incoming_ids) . await ?;
251+ let projected_total = size + pending_size + running_size;
252+ if projected_total >= db. quota . size {
253+ let mut tags = HashMap :: default ( ) ;
254+ tags. insert ( "collection" . to_owned ( ) , collection. to_owned ( ) ) ;
255+ db. metrics . incr_with_tags ( "storage.quota.at_limit" , tags) ;
256+ if db. quota . enforced {
257+ return Err ( DbError :: quota ( ) ) ;
258+ } else {
259+ warn ! ( "Quota at limit for user ({} bytes)" , projected_total;
260+ "collection" => collection) ;
261+ }
262+ }
263+ }
264+
209265 // It's possible for the list of items to contain a duplicate key entry.
210266 // This means that we can't really call `ON DUPLICATE` here, because that's
211267 // more about inserting one item at a time. (e.g. it works great if the
@@ -281,6 +337,25 @@ pub async fn do_append(
281337 Ok ( ( ) )
282338}
283339
340+ /// Sum the pending payload bytes, excluding any ids in `incoming_ids`.
341+ async fn pending_batch_size (
342+ db : & mut MysqlDb ,
343+ user_id : i64 ,
344+ batch_id : i64 ,
345+ incoming_ids : & [ String ] ,
346+ ) -> DbResult < usize > {
347+ let current_batch_size: i64 = batch_upload_items:: table
348+ . select ( sql :: < BigInt > ( "COALESCE(SUM(payload_size), 0)" ) )
349+ . filter ( batch_upload_items:: user_id. eq ( user_id) )
350+ . filter ( batch_upload_items:: batch_id. eq ( batch_id) )
351+ . filter ( batch_upload_items:: id. ne_all ( incoming_ids) )
352+ . get_result ( & mut db. conn )
353+ . await
354+ . optional ( ) ?
355+ . unwrap_or_default ( ) ;
356+ Ok ( current_batch_size. max ( 0 ) as usize )
357+ }
358+
284359pub fn validate_batch_id ( id : & str ) -> DbResult < ( ) > {
285360 decode_id ( id) . map ( |_| ( ) )
286361}
0 commit comments