chronon_backend_redis/composite/
mod.rs1mod claim;
6
7use std::fmt;
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use chrono::{DateTime, Utc};
12
13use chronon_backend_sql_common::run_pool_key;
14use chronon_core::models::{Run, RunStatus};
15use chronon_core::store::SchedulerStore;
16use chronon_core::Result;
17
18use crate::queue::RedisQueueLayer;
19
20pub struct PostgresRedisSchedulerStore {
35 sql: Arc<dyn SchedulerStore>,
36 redis: RedisQueueLayer,
37}
38
39impl std::fmt::Debug for PostgresRedisSchedulerStore {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("PostgresRedisSchedulerStore")
42 .finish_non_exhaustive()
43 }
44}
45
46impl PostgresRedisSchedulerStore {
47 #[must_use]
71 pub fn new(sql: Arc<dyn SchedulerStore>, redis: RedisQueueLayer) -> Self {
72 Self { sql, redis }
73 }
74}
75
76#[async_trait]
77impl SchedulerStore for PostgresRedisSchedulerStore {
78 async fn upsert_job(
79 &self,
80 job: &chronon_core::models::Job,
81 ) -> Result<()> {
82 self.sql.upsert_job(job).await
83 }
84
85 async fn get_job(&self, job_id: &str) -> Result<Option<chronon_core::models::Job>> {
86 self.sql.get_job(job_id).await
87 }
88
89 async fn get_job_by_name(
90 &self,
91 job_name: &str,
92 ) -> Result<Option<chronon_core::models::Job>> {
93 self.sql.get_job_by_name(job_name).await
94 }
95
96 async fn list_jobs(&self) -> Result<Vec<chronon_core::models::Job>> {
97 self.sql.list_jobs().await
98 }
99
100 async fn list_due_jobs(
101 &self,
102 before: DateTime<Utc>,
103 ) -> Result<Vec<chronon_core::models::Job>> {
104 self.sql.list_due_jobs(before).await
105 }
106
107 async fn pause_job(&self, job_id: &str) -> Result<()> {
108 self.sql.pause_job(job_id).await
109 }
110
111 async fn resume_job(&self, job_id: &str) -> Result<()> {
112 self.sql.resume_job(job_id).await
113 }
114
115 async fn create_run(&self, run: &Run) -> Result<()> {
116 self.sql.create_run(run).await?;
117 if run.status == RunStatus::Queued {
118 let pool = run_pool_key(run.pool_id.as_deref());
119 self.redis
120 .enqueue_run(pool, &run.run_id, run.scheduled_for)
121 .await?;
122 }
123 Ok(())
124 }
125
126 async fn update_run(&self, run: &Run) -> Result<()> {
127 self.sql.update_run(run).await
128 }
129
130 async fn get_run(&self, run_id: &str) -> Result<Option<Run>> {
131 self.sql.get_run(run_id).await
132 }
133
134 async fn list_runs_for_job(&self, job_id: &str, limit: usize) -> Result<Vec<Run>> {
135 self.sql.list_runs_for_job(job_id, limit).await
136 }
137
138 async fn list_runs_filtered(
139 &self,
140 job_id: Option<&str>,
141 status: Option<RunStatus>,
142 offset: usize,
143 limit: usize,
144 ) -> Result<Vec<Run>> {
145 self.sql
146 .list_runs_filtered(job_id, status, offset, limit)
147 .await
148 }
149
150 async fn claim_next_queued(
151 &self,
152 pool_id: &str,
153 worker_id: &str,
154 now: DateTime<Utc>,
155 lease_ttl_secs: i64,
156 ) -> Result<Option<Run>> {
157 claim::claim_next_queued(
158 &self.sql,
159 &self.redis,
160 pool_id,
161 worker_id,
162 now,
163 lease_ttl_secs,
164 )
165 .await
166 }
167
168 async fn claim_run_by_id(
169 &self,
170 run_id: &str,
171 pool_id: &str,
172 worker_id: &str,
173 now: DateTime<Utc>,
174 lease_ttl_secs: i64,
175 ) -> Result<Option<Run>> {
176 self.sql
177 .claim_run_by_id(run_id, pool_id, worker_id, now, lease_ttl_secs)
178 .await
179 }
180
181 async fn claim_runs_by_ids(
182 &self,
183 run_ids: &[&str],
184 pool_id: &str,
185 worker_id: &str,
186 now: DateTime<Utc>,
187 lease_ttl_secs: i64,
188 ) -> Result<Vec<Run>> {
189 self.sql
190 .claim_runs_by_ids(run_ids, pool_id, worker_id, now, lease_ttl_secs)
191 .await
192 }
193
194 async fn renew_run_lease(
195 &self,
196 run_id: &str,
197 worker_id: &str,
198 now: DateTime<Utc>,
199 lease_ttl_secs: i64,
200 ) -> Result<bool> {
201 self.sql
202 .renew_run_lease(run_id, worker_id, now, lease_ttl_secs)
203 .await
204 }
205
206 async fn append_revision(
207 &self,
208 revision: &chronon_core::models::JobRevision,
209 ) -> Result<()> {
210 self.sql.append_revision(revision).await
211 }
212
213 async fn list_revisions(
214 &self,
215 job_id: &str,
216 ) -> Result<Vec<chronon_core::models::JobRevision>> {
217 self.sql.list_revisions(job_id).await
218 }
219
220 async fn upsert_script(&self, script: &chronon_core::models::Script) -> Result<()> {
221 self.sql.upsert_script(script).await
222 }
223
224 async fn get_script(
225 &self,
226 script_name: &str,
227 ) -> Result<Option<chronon_core::models::Script>> {
228 self.sql.get_script(script_name).await
229 }
230
231 async fn try_claim_run_once(
232 &self,
233 job_id: &str,
234 claimed_by: &str,
235 now: DateTime<Utc>,
236 claim_ttl_secs: i64,
237 ) -> Result<bool> {
238 self.sql
239 .try_claim_run_once(job_id, claimed_by, now, claim_ttl_secs)
240 .await
241 }
242
243 async fn mark_run_once_completed(
244 &self,
245 job_id: &str,
246 completed_at: DateTime<Utc>,
247 ) -> Result<()> {
248 self.sql
249 .mark_run_once_completed(job_id, completed_at)
250 .await
251 }
252
253 async fn release_run_once_claim(
254 &self,
255 job_id: &str,
256 claimed_by: &str,
257 now: DateTime<Utc>,
258 ) -> Result<()> {
259 self.sql
260 .release_run_once_claim(job_id, claimed_by, now)
261 .await
262 }
263
264 async fn find_due_job_ids_in_partitions(
265 &self,
266 owned_partitions: &[u32],
267 due_until: DateTime<Utc>,
268 limit: u32,
269 ) -> Result<Vec<String>> {
270 self.sql
271 .find_due_job_ids_in_partitions(owned_partitions, due_until, limit)
272 .await
273 }
274
275 async fn min_next_run_at_in_partitions(
276 &self,
277 owned_partitions: &[u32],
278 ) -> Result<Option<DateTime<Utc>>> {
279 self.sql
280 .min_next_run_at_in_partitions(owned_partitions)
281 .await
282 }
283
284 async fn claim_job_for_tick(
285 &self,
286 job_id: &str,
287 claim_id: &str,
288 now: DateTime<Utc>,
289 lease_ttl_secs: i64,
290 ) -> Result<bool> {
291 self.sql
292 .claim_job_for_tick(job_id, claim_id, now, lease_ttl_secs)
293 .await
294 }
295
296 async fn release_job_tick_claim(&self, job_id: &str) -> Result<()> {
297 self.sql.release_job_tick_claim(job_id).await
298 }
299
300 async fn persist_post_tick_job_state(
301 &self,
302 job_id: &str,
303 next_run_at: Option<DateTime<Utc>>,
304 ) -> Result<()> {
305 self.sql
306 .persist_post_tick_job_state(job_id, next_run_at)
307 .await
308 }
309
310 async fn try_acquire_leader(&self, instance_id: &str, ttl_secs: i64) -> Result<bool> {
311 self.sql.try_acquire_leader(instance_id, ttl_secs).await
312 }
313
314 async fn renew_leader_lease(&self, instance_id: &str, ttl_secs: i64) -> Result<()> {
315 self.sql.renew_leader_lease(instance_id, ttl_secs).await
316 }
317
318 async fn get_leader(&self) -> Result<Option<chronon_core::models::SchedulerLeader>> {
319 self.sql.get_leader().await
320 }
321
322 async fn upsert_partition_assignment(
323 &self,
324 assignment: &chronon_core::models::PartitionAssignment,
325 ) -> Result<()> {
326 self.sql.upsert_partition_assignment(assignment).await
327 }
328
329 async fn list_partition_assignments(
330 &self,
331 ) -> Result<Vec<chronon_core::models::PartitionAssignment>> {
332 self.sql.list_partition_assignments().await
333 }
334
335 async fn register_worker(&self, worker: &chronon_core::models::Worker) -> Result<()> {
336 self.sql.register_worker(worker).await
337 }
338
339 async fn heartbeat_worker(&self, worker_id: &str, at: DateTime<Utc>) -> Result<()> {
340 self.sql.heartbeat_worker(worker_id, at).await
341 }
342}