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(&self, job: &chronon_core::models::Job) -> Result<()> {
79 self.sql.upsert_job(job).await
80 }
81
82 async fn get_job(&self, job_id: &str) -> Result<Option<chronon_core::models::Job>> {
83 self.sql.get_job(job_id).await
84 }
85
86 async fn get_job_by_name(&self, job_name: &str) -> Result<Option<chronon_core::models::Job>> {
87 self.sql.get_job_by_name(job_name).await
88 }
89
90 async fn list_jobs(&self) -> Result<Vec<chronon_core::models::Job>> {
91 self.sql.list_jobs().await
92 }
93
94 async fn list_due_jobs(&self, before: DateTime<Utc>) -> Result<Vec<chronon_core::models::Job>> {
95 self.sql.list_due_jobs(before).await
96 }
97
98 async fn pause_job(&self, job_id: &str) -> Result<()> {
99 self.sql.pause_job(job_id).await
100 }
101
102 async fn resume_job(&self, job_id: &str) -> Result<()> {
103 self.sql.resume_job(job_id).await
104 }
105
106 async fn create_run(&self, run: &Run) -> Result<()> {
107 self.sql.create_run(run).await?;
108 if run.status == RunStatus::Queued {
109 let pool = run_pool_key(run.pool_id.as_deref());
110 self.redis
111 .enqueue_run(pool, &run.run_id, run.scheduled_for)
112 .await?;
113 }
114 Ok(())
115 }
116
117 async fn update_run(&self, run: &Run) -> Result<()> {
118 self.sql.update_run(run).await
119 }
120
121 async fn get_run(&self, run_id: &str) -> Result<Option<Run>> {
122 self.sql.get_run(run_id).await
123 }
124
125 async fn list_runs_for_job(&self, job_id: &str, limit: usize) -> Result<Vec<Run>> {
126 self.sql.list_runs_for_job(job_id, limit).await
127 }
128
129 async fn list_runs_filtered(
130 &self,
131 job_id: Option<&str>,
132 status: Option<RunStatus>,
133 offset: usize,
134 limit: usize,
135 ) -> Result<Vec<Run>> {
136 self.sql
137 .list_runs_filtered(job_id, status, offset, limit)
138 .await
139 }
140
141 async fn claim_next_queued(
142 &self,
143 pool_id: &str,
144 worker_id: &str,
145 now: DateTime<Utc>,
146 lease_ttl_secs: i64,
147 ) -> Result<Option<Run>> {
148 claim::claim_next_queued(
149 &self.sql,
150 &self.redis,
151 pool_id,
152 worker_id,
153 now,
154 lease_ttl_secs,
155 )
156 .await
157 }
158
159 async fn claim_run_by_id(
160 &self,
161 run_id: &str,
162 pool_id: &str,
163 worker_id: &str,
164 now: DateTime<Utc>,
165 lease_ttl_secs: i64,
166 ) -> Result<Option<Run>> {
167 self.sql
168 .claim_run_by_id(run_id, pool_id, worker_id, now, lease_ttl_secs)
169 .await
170 }
171
172 async fn claim_runs_by_ids(
173 &self,
174 run_ids: &[&str],
175 pool_id: &str,
176 worker_id: &str,
177 now: DateTime<Utc>,
178 lease_ttl_secs: i64,
179 ) -> Result<Vec<Run>> {
180 self.sql
181 .claim_runs_by_ids(run_ids, pool_id, worker_id, now, lease_ttl_secs)
182 .await
183 }
184
185 async fn renew_run_lease(
186 &self,
187 run_id: &str,
188 worker_id: &str,
189 now: DateTime<Utc>,
190 lease_ttl_secs: i64,
191 ) -> Result<bool> {
192 self.sql
193 .renew_run_lease(run_id, worker_id, now, lease_ttl_secs)
194 .await
195 }
196
197 async fn append_revision(&self, revision: &chronon_core::models::JobRevision) -> Result<()> {
198 self.sql.append_revision(revision).await
199 }
200
201 async fn list_revisions(&self, job_id: &str) -> Result<Vec<chronon_core::models::JobRevision>> {
202 self.sql.list_revisions(job_id).await
203 }
204
205 async fn upsert_script(&self, script: &chronon_core::models::Script) -> Result<()> {
206 self.sql.upsert_script(script).await
207 }
208
209 async fn get_script(&self, script_name: &str) -> Result<Option<chronon_core::models::Script>> {
210 self.sql.get_script(script_name).await
211 }
212
213 async fn try_claim_run_once(
214 &self,
215 job_id: &str,
216 claimed_by: &str,
217 now: DateTime<Utc>,
218 claim_ttl_secs: i64,
219 ) -> Result<bool> {
220 self.sql
221 .try_claim_run_once(job_id, claimed_by, now, claim_ttl_secs)
222 .await
223 }
224
225 async fn mark_run_once_completed(
226 &self,
227 job_id: &str,
228 completed_at: DateTime<Utc>,
229 ) -> Result<()> {
230 self.sql.mark_run_once_completed(job_id, completed_at).await
231 }
232
233 async fn release_run_once_claim(
234 &self,
235 job_id: &str,
236 claimed_by: &str,
237 now: DateTime<Utc>,
238 ) -> Result<()> {
239 self.sql
240 .release_run_once_claim(job_id, claimed_by, now)
241 .await
242 }
243
244 async fn find_due_job_ids_in_partitions(
245 &self,
246 owned_partitions: &[u32],
247 due_until: DateTime<Utc>,
248 limit: u32,
249 ) -> Result<Vec<String>> {
250 self.sql
251 .find_due_job_ids_in_partitions(owned_partitions, due_until, limit)
252 .await
253 }
254
255 async fn min_next_run_at_in_partitions(
256 &self,
257 owned_partitions: &[u32],
258 ) -> Result<Option<DateTime<Utc>>> {
259 self.sql
260 .min_next_run_at_in_partitions(owned_partitions)
261 .await
262 }
263
264 async fn claim_job_for_tick(
265 &self,
266 job_id: &str,
267 claim_id: &str,
268 now: DateTime<Utc>,
269 lease_ttl_secs: i64,
270 ) -> Result<bool> {
271 self.sql
272 .claim_job_for_tick(job_id, claim_id, now, lease_ttl_secs)
273 .await
274 }
275
276 async fn release_job_tick_claim(&self, job_id: &str) -> Result<()> {
277 self.sql.release_job_tick_claim(job_id).await
278 }
279
280 async fn persist_post_tick_job_state(
281 &self,
282 job_id: &str,
283 next_run_at: Option<DateTime<Utc>>,
284 ) -> Result<()> {
285 self.sql
286 .persist_post_tick_job_state(job_id, next_run_at)
287 .await
288 }
289
290 async fn try_acquire_leader(&self, instance_id: &str, ttl_secs: i64) -> Result<bool> {
291 self.sql.try_acquire_leader(instance_id, ttl_secs).await
292 }
293
294 async fn renew_leader_lease(&self, instance_id: &str, ttl_secs: i64) -> Result<()> {
295 self.sql.renew_leader_lease(instance_id, ttl_secs).await
296 }
297
298 async fn get_leader(&self) -> Result<Option<chronon_core::models::SchedulerLeader>> {
299 self.sql.get_leader().await
300 }
301
302 async fn upsert_partition_assignment(
303 &self,
304 assignment: &chronon_core::models::PartitionAssignment,
305 ) -> Result<()> {
306 self.sql.upsert_partition_assignment(assignment).await
307 }
308
309 async fn list_partition_assignments(
310 &self,
311 ) -> Result<Vec<chronon_core::models::PartitionAssignment>> {
312 self.sql.list_partition_assignments().await
313 }
314
315 async fn register_worker(&self, worker: &chronon_core::models::Worker) -> Result<()> {
316 self.sql.register_worker(worker).await
317 }
318
319 async fn heartbeat_worker(&self, worker_id: &str, at: DateTime<Utc>) -> Result<()> {
320 self.sql.heartbeat_worker(worker_id, at).await
321 }
322}