Skip to content

Commit 5c1fd97

Browse files
committed
feat: replace hourly cleanup cron with S3 lifecycle policy set at startup
1 parent 1454465 commit 5c1fd97

2 files changed

Lines changed: 37 additions & 79 deletions

File tree

agents/src/main.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,7 @@ async fn main() {
3030
config,
3131
});
3232

33-
tokio::spawn({
34-
let state = Arc::clone(&state);
35-
async move {
36-
let mut interval = tokio::time::interval(std::time::Duration::from_secs(60 * 60));
37-
interval.tick().await;
38-
loop {
39-
interval.tick().await;
40-
state.storage.cleanup_expired().await;
41-
}
42-
}
43-
});
33+
state.storage.ensure_lifecycle_policy().await;
4434

4535
let app = Router::new()
4636
.merge(routes::router())

agents/src/storage.rs

Lines changed: 36 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::config::Config;
88
use crate::error::AppError;
99

1010
const PRESIGN_EXPIRY: Duration = Duration::from_secs(24 * 60 * 60);
11-
const CLEANUP_MAX_AGE: Duration = Duration::from_secs(24 * 60 * 60);
11+
const LIFECYCLE_EXPIRY_DAYS: i32 = 1;
1212

1313
pub struct Storage {
1414
client: Client,
@@ -83,73 +83,41 @@ impl Storage {
8383
Ok(UploadResult { presigned_url })
8484
}
8585

86-
pub async fn cleanup_expired(&self) {
87-
let cutoff = aws_sdk_s3::primitives::DateTime::from_secs_f64(
88-
std::time::SystemTime::now()
89-
.duration_since(std::time::UNIX_EPOCH)
90-
.unwrap_or_default()
91-
.as_secs_f64()
92-
- CLEANUP_MAX_AGE.as_secs_f64(),
93-
);
94-
95-
let mut continuation_token: Option<String> = None;
96-
97-
loop {
98-
let mut request = self
99-
.client
100-
.list_objects_v2()
101-
.bucket(&self.bucket)
102-
.prefix("uploads/");
103-
104-
if let Some(token) = &continuation_token {
105-
request = request.continuation_token(token);
106-
}
107-
108-
let response = match request.send().await {
109-
Ok(response) => response,
110-
Err(e) => {
111-
tracing::error!("cleanup: failed to list objects: {e}");
112-
return;
113-
}
114-
};
115-
116-
let mut deleted = 0;
117-
for object in response.contents() {
118-
let is_expired = object
119-
.last_modified()
120-
.map(|modified| *modified < cutoff)
121-
.unwrap_or(false);
122-
123-
if !is_expired {
124-
continue;
125-
}
126-
127-
let Some(key) = object.key() else {
128-
continue;
129-
};
130-
131-
if let Err(e) = self
132-
.client
133-
.delete_object()
134-
.bucket(&self.bucket)
135-
.key(key)
136-
.send()
137-
.await
138-
{
139-
tracing::error!("cleanup: failed to delete {key}: {e}");
140-
} else {
141-
deleted += 1;
142-
}
143-
}
144-
145-
if deleted > 0 {
146-
tracing::info!("cleanup: deleted {deleted} expired files");
147-
}
148-
149-
match response.next_continuation_token() {
150-
Some(token) => continuation_token = Some(token.to_string()),
151-
None => break,
152-
}
86+
pub async fn ensure_lifecycle_policy(&self) {
87+
use aws_sdk_s3::types::{
88+
BucketLifecycleConfiguration, ExpirationStatus, LifecycleExpiration, LifecycleRule,
89+
LifecycleRuleFilter,
90+
};
91+
92+
let rule = LifecycleRule::builder()
93+
.id("expire-uploads-24h")
94+
.status(ExpirationStatus::Enabled)
95+
.filter(LifecycleRuleFilter::builder().prefix("uploads/").build())
96+
.expiration(
97+
LifecycleExpiration::builder()
98+
.days(LIFECYCLE_EXPIRY_DAYS)
99+
.build(),
100+
)
101+
.build()
102+
.expect("valid lifecycle rule");
103+
104+
let config = BucketLifecycleConfiguration::builder()
105+
.rules(rule)
106+
.build()
107+
.expect("valid lifecycle config");
108+
109+
match self
110+
.client
111+
.put_bucket_lifecycle_configuration()
112+
.bucket(&self.bucket)
113+
.lifecycle_configuration(config)
114+
.send()
115+
.await
116+
{
117+
Ok(_) => tracing::info!(
118+
"lifecycle policy set: uploads/ expire after {LIFECYCLE_EXPIRY_DAYS} day(s)"
119+
),
120+
Err(e) => tracing::error!("failed to set lifecycle policy: {e}"),
153121
}
154122
}
155123
}

0 commit comments

Comments
 (0)