@@ -13,6 +13,9 @@ use crate::{
1313 ch:: limits:: get_workspace_bytes_ingested_by_project_ids,
1414 db:: { self , DB , projects:: ProjectWithWorkspaceBillingInfo , stats:: WorkspaceLimitsExceeded } ,
1515} ;
16+ // For workspaces over the limit, expire the cache after 24 hours,
17+ // so that it resets in the next billing period (+/- 1 day).
18+ const WORKSPACE_USAGE_EXCEEDED_TTL_SECONDS : u64 = 60 * 60 * 24 ; // 24 hours
1619
1720pub async fn get_workspace_limit_exceeded_by_project_id (
1821 db : Arc < DB > ,
@@ -49,7 +52,7 @@ pub async fn get_workspace_limit_exceeded_by_project_id(
4952 project_id,
5053 e
5154 ) ;
52- 0 as i64
55+ 0
5356 }
5457 } ;
5558
@@ -59,7 +62,11 @@ pub async fn get_workspace_limit_exceeded_by_project_id(
5962 } ;
6063
6164 let _ = cache
62- . insert :: < WorkspaceLimitsExceeded > ( & cache_key, workspace_limits_exceeded. clone ( ) )
65+ . insert_with_ttl :: < WorkspaceLimitsExceeded > (
66+ & cache_key,
67+ workspace_limits_exceeded. clone ( ) ,
68+ WORKSPACE_USAGE_EXCEEDED_TTL_SECONDS ,
69+ )
6370 . await ;
6471 Ok ( workspace_limits_exceeded)
6572 }
@@ -73,88 +80,99 @@ pub async fn update_workspace_limit_exceeded_by_project_id(
7380 project_id : Uuid ,
7481 written_bytes : usize ,
7582) -> Result < ( ) > {
76- tokio:: spawn ( async move {
77- let project_info =
78- match get_workspace_info_for_project_id ( db. clone ( ) , cache. clone ( ) , project_id) . await {
79- Ok ( info) => info,
83+ let project_info =
84+ match get_workspace_info_for_project_id ( db. clone ( ) , cache. clone ( ) , project_id) . await {
85+ Ok ( info) => info,
86+ Err ( e) => {
87+ log:: error!(
88+ "Failed to get workspace info for project [{}]: {:?}" ,
89+ project_id,
90+ e
91+ ) ;
92+ return Err ( anyhow:: anyhow!(
93+ "Failed to get workspace info for project [{}]: {:?}" ,
94+ project_id,
95+ e
96+ ) ) ;
97+ }
98+ } ;
99+ let workspace_id = project_info. workspace_id ;
100+ if project_info. tier_name . trim ( ) . to_lowercase ( ) != "free" {
101+ // We don't need to update the workspace limits cache for non-free tiers
102+ return Ok ( ( ) ) ;
103+ }
104+
105+ let bytes_usage_cache_key = format ! ( "{WORKSPACE_BYTES_USAGE_CACHE_KEY}:{workspace_id}" ) ;
106+ let limits_cache_key = format ! ( "{WORKSPACE_LIMITS_CACHE_KEY}:{workspace_id}" ) ;
107+
108+ // First, try to read from cache to check if it exists
109+ let cache_result = cache. get :: < i64 > ( & bytes_usage_cache_key) . await ;
110+
111+ match cache_result {
112+ Ok ( Some ( _) ) => {
113+ // Cache exists - atomically increment it
114+ let increment_result = cache
115+ . increment ( & bytes_usage_cache_key, written_bytes as i64 )
116+ . await ;
117+
118+ if let Ok ( new_partial_usage) = increment_result {
119+ let workspace_limits_exceeded = WorkspaceLimitsExceeded {
120+ steps : false ,
121+ bytes_ingested : new_partial_usage >= project_info. bytes_limit ,
122+ } ;
123+
124+ // Update the limits cache
125+ cache
126+ . insert_with_ttl :: < WorkspaceLimitsExceeded > (
127+ & limits_cache_key,
128+ workspace_limits_exceeded,
129+ WORKSPACE_USAGE_EXCEEDED_TTL_SECONDS ,
130+ )
131+ . await ?;
132+ }
133+ }
134+ Ok ( None ) | Err ( _) => {
135+ // Cache miss or error - perform full recomputation
136+ let bytes_ingested = match get_workspace_bytes_ingested_by_project_ids (
137+ clickhouse. clone ( ) ,
138+ project_info. workspace_project_ids ,
139+ project_info. reset_time ,
140+ )
141+ . await
142+ {
143+ Ok ( bytes_ingested) => bytes_ingested as i64 ,
80144 Err ( e) => {
81145 log:: error!(
82- "Failed to get workspace info for project [{}]: {:?}" ,
146+ "Failed to get workspace bytes ingested for project [{}]: {:?}" ,
83147 project_id,
84148 e
85149 ) ;
86- return ;
150+ 0 as i64
87151 }
88152 } ;
89- let workspace_id = project_info. workspace_id ;
90- if project_info. tier_name . trim ( ) . to_lowercase ( ) != "free" {
91- // We don't need to update the workspace limits cache for non-free tiers
92- return ;
93- }
94153
95- let bytes_usage_cache_key = format ! ( "{WORKSPACE_BYTES_USAGE_CACHE_KEY}:{workspace_id}" ) ;
96- let limits_cache_key = format ! ( "{WORKSPACE_LIMITS_CACHE_KEY}:{workspace_id}" ) ;
97-
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 ,
129- )
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- } ;
154+ let workspace_limits_exceeded = WorkspaceLimitsExceeded {
155+ steps : false ,
156+ bytes_ingested : bytes_ingested >= project_info. bytes_limit ,
157+ } ;
147158
148- let _ = cache
149- . insert :: < WorkspaceLimitsExceeded > ( & limits_cache_key, workspace_limits_exceeded)
150- . await ;
159+ cache
160+ . insert_with_ttl :: < WorkspaceLimitsExceeded > (
161+ & limits_cache_key,
162+ workspace_limits_exceeded,
163+ WORKSPACE_USAGE_EXCEEDED_TTL_SECONDS ,
164+ )
165+ . await ?;
151166
152- let _ = cache
153- . insert :: < i64 > ( & bytes_usage_cache_key, bytes_ingested as i64 )
154- . await ;
155- }
167+ cache
168+ . insert_with_ttl :: < i64 > (
169+ & bytes_usage_cache_key,
170+ bytes_ingested as i64 ,
171+ WORKSPACE_USAGE_EXCEEDED_TTL_SECONDS ,
172+ )
173+ . await ?;
156174 }
157- } ) ;
175+ }
158176
159177 Ok ( ( ) )
160178}
@@ -173,9 +191,9 @@ async fn get_workspace_info_for_project_id(
173191 Ok ( None ) | Err ( _) => {
174192 let info =
175193 db:: projects:: get_project_and_workspace_billing_info ( & db. pool , & project_id) . await ?;
176- let _ = cache
194+ cache
177195 . insert :: < ProjectWithWorkspaceBillingInfo > ( & cache_key, info. clone ( ) )
178- . await ;
196+ . await ? ;
179197 Ok ( info)
180198 }
181199 }
0 commit comments