33use std:: sync:: Arc ;
44
55use anyhow:: Result ;
6- use chrono:: { DateTime , Utc } ;
76use uuid:: Uuid ;
87
98use crate :: {
109 cache:: {
1110 Cache , CacheTrait ,
12- keys:: { PROJECT_CACHE_KEY , WORKSPACE_LIMITS_CACHE_KEY , WORKSPACE_PARTIAL_USAGE_CACHE_KEY } ,
11+ keys:: { PROJECT_CACHE_KEY , WORKSPACE_BYTES_USAGE_CACHE_KEY , WORKSPACE_LIMITS_CACHE_KEY } ,
1312 } ,
14- ch,
13+ ch:: limits :: get_workspace_bytes_ingested_by_project_ids ,
1514 db:: { self , DB , projects:: ProjectWithWorkspaceBillingInfo , stats:: WorkspaceLimitsExceeded } ,
1615} ;
1716
18- // Threshold in bytes (16MB) - only recompute workspace limits after this much data is written
19- const RECOMPUTE_THRESHOLD_BYTES : usize = 16 * 1024 * 1024 ; // 16MB
20-
2117pub async fn get_workspace_limit_exceeded_by_project_id (
2218 db : Arc < DB > ,
2319 clickhouse : clickhouse:: Client ,
@@ -39,13 +35,29 @@ pub async fn get_workspace_limit_exceeded_by_project_id(
3935 match cache_res {
4036 Ok ( Some ( workspace_limits_exceeded) ) => Ok ( workspace_limits_exceeded) ,
4137 Ok ( None ) | Err ( _) => {
42- let workspace_limits_exceeded = is_workspace_over_limit (
43- clickhouse,
38+ let bytes_ingested = match get_workspace_bytes_ingested_by_project_ids (
39+ clickhouse. clone ( ) ,
4440 project_info. workspace_project_ids ,
45- project_info. bytes_limit ,
4641 project_info. reset_time ,
4742 )
48- . await ?;
43+ . await
44+ {
45+ Ok ( bytes_ingested) => bytes_ingested as i64 ,
46+ Err ( e) => {
47+ log:: error!(
48+ "Failed to get workspace bytes ingested for project [{}]: {:?}" ,
49+ project_id,
50+ e
51+ ) ;
52+ 0 as i64
53+ }
54+ } ;
55+
56+ let workspace_limits_exceeded = WorkspaceLimitsExceeded {
57+ steps : false ,
58+ bytes_ingested : bytes_ingested >= project_info. bytes_limit ,
59+ } ;
60+
4961 let _ = cache
5062 . insert :: < WorkspaceLimitsExceeded > ( & cache_key, workspace_limits_exceeded. clone ( ) )
5163 . await ;
@@ -62,72 +74,85 @@ pub async fn update_workspace_limit_exceeded_by_project_id(
6274 written_bytes : usize ,
6375) -> Result < ( ) > {
6476 tokio:: spawn ( async move {
65- let project_info = get_workspace_info_for_project_id ( db. clone ( ) , cache. clone ( ) , project_id)
66- . await
67- . map_err ( |e| {
68- log:: error!(
69- "Failed to get workspace info for project [{}]: {:?}" ,
70- project_id,
71- e
72- ) ;
73- } )
74- . unwrap ( ) ;
77+ let project_info =
78+ match get_workspace_info_for_project_id ( db. clone ( ) , cache. clone ( ) , project_id) . await {
79+ Ok ( info) => info,
80+ Err ( e) => {
81+ log:: error!(
82+ "Failed to get workspace info for project [{}]: {:?}" ,
83+ project_id,
84+ e
85+ ) ;
86+ return ;
87+ }
88+ } ;
7589 let workspace_id = project_info. workspace_id ;
7690 if project_info. tier_name . trim ( ) . to_lowercase ( ) != "free" {
7791 // We don't need to update the workspace limits cache for non-free tiers
7892 return ;
7993 }
8094
81- let partial_usage_cache_key = format ! ( "{WORKSPACE_PARTIAL_USAGE_CACHE_KEY }:{workspace_id}" ) ;
95+ let bytes_usage_cache_key = format ! ( "{WORKSPACE_BYTES_USAGE_CACHE_KEY }:{workspace_id}" ) ;
8296 let limits_cache_key = format ! ( "{WORKSPACE_LIMITS_CACHE_KEY}:{workspace_id}" ) ;
8397
84- // Get current partial usage from cache
85- let cache_result = cache. get :: < usize > ( & partial_usage_cache_key) . await ;
86-
87- // If cache is missing or errored, we should recompute
88- let ( current_partial_usage, cache_available) = match cache_result {
89- Ok ( Some ( value) ) => ( value, true ) ,
90- Ok ( None ) | Err ( _) => ( 0 , false ) ,
91- } ;
92-
93- let new_partial_usage = current_partial_usage + written_bytes;
94-
95- // Recompute if: cache was unavailable, or we've accumulated at least RECOMPUTE_THRESHOLD_BYTES
96- let should_recompute = !cache_available || new_partial_usage >= RECOMPUTE_THRESHOLD_BYTES ;
97-
98- if should_recompute {
99- // Perform the heavy computation
100- let workspace_limits_exceeded = is_workspace_over_limit (
101- clickhouse,
102- project_info. workspace_project_ids ,
103- project_info. bytes_limit ,
104- project_info. reset_time ,
105- )
106- . await
107- . map_err ( |e| {
108- log:: error!(
109- "Failed to update workspace limit exceeded for project [{}]: {:?}" ,
110- project_id,
111- e
112- ) ;
113- } )
114- . unwrap ( ) ;
115-
116- // Update the limits cache
117- let _ = cache
118- . insert :: < WorkspaceLimitsExceeded > (
119- & limits_cache_key,
120- workspace_limits_exceeded. clone ( ) ,
98+ // First, try to read from cache to check if it exists
99+ let cache_result = cache. get :: < i64 > ( & bytes_usage_cache_key) . await ;
100+
101+ match cache_result {
102+ Ok ( Some ( _) ) => {
103+ // Cache exists - atomically increment it
104+ let increment_result = cache
105+ . increment ( & bytes_usage_cache_key, written_bytes as i64 )
106+ . await ;
107+
108+ if let Ok ( Some ( new_partial_usage) ) = increment_result {
109+ let workspace_limits_exceeded = WorkspaceLimitsExceeded {
110+ steps : false ,
111+ bytes_ingested : new_partial_usage >= project_info. bytes_limit ,
112+ } ;
113+
114+ // Update the limits cache
115+ let _ = cache
116+ . insert :: < WorkspaceLimitsExceeded > (
117+ & limits_cache_key,
118+ workspace_limits_exceeded,
119+ )
120+ . await ;
121+ }
122+ }
123+ Ok ( None ) | Err ( _) => {
124+ // Cache miss or error - perform full recomputation
125+ let bytes_ingested = match get_workspace_bytes_ingested_by_project_ids (
126+ clickhouse. clone ( ) ,
127+ project_info. workspace_project_ids ,
128+ project_info. reset_time ,
121129 )
122- . await ;
123-
124- // Reset the partial usage counter
125- let _ = cache. insert :: < usize > ( & partial_usage_cache_key, 0 ) . await ;
126- } else {
127- // Just update the partial usage counter
128- let _ = cache
129- . insert :: < usize > ( & partial_usage_cache_key, new_partial_usage)
130- . await ;
130+ . await
131+ {
132+ Ok ( bytes_ingested) => bytes_ingested as i64 ,
133+ Err ( e) => {
134+ log:: error!(
135+ "Failed to get workspace bytes ingested for project [{}]: {:?}" ,
136+ project_id,
137+ e
138+ ) ;
139+ 0 as i64
140+ }
141+ } ;
142+
143+ let workspace_limits_exceeded = WorkspaceLimitsExceeded {
144+ steps : false ,
145+ bytes_ingested : bytes_ingested >= project_info. bytes_limit ,
146+ } ;
147+
148+ let _ = cache
149+ . insert :: < WorkspaceLimitsExceeded > ( & limits_cache_key, workspace_limits_exceeded)
150+ . await ;
151+
152+ let _ = cache
153+ . insert :: < i64 > ( & bytes_usage_cache_key, bytes_ingested as i64 )
154+ . await ;
155+ }
131156 }
132157 } ) ;
133158
@@ -155,16 +180,3 @@ async fn get_workspace_info_for_project_id(
155180 }
156181 }
157182}
158-
159- async fn is_workspace_over_limit (
160- clickhouse : clickhouse:: Client ,
161- project_ids : Vec < Uuid > ,
162- bytes_limit : i64 ,
163- reset_time : DateTime < Utc > ,
164- ) -> Result < WorkspaceLimitsExceeded > {
165- let workspace_limits_exceeded =
166- ch:: limits:: is_workspace_over_limit ( clickhouse, project_ids, reset_time, bytes_limit)
167- . await ?;
168-
169- Ok ( workspace_limits_exceeded)
170- }
0 commit comments