Database worker
Worker used to clean up pending subscriptions and old idempotency records
This commit is contained in:
58
src/database_worker.rs
Normal file
58
src/database_worker.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use sqlx::{
|
||||||
|
PgPool,
|
||||||
|
postgres::{PgConnectOptions, PgPoolOptions},
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub async fn run_until_stopped(configuration: PgConnectOptions) -> Result<(), anyhow::Error> {
|
||||||
|
let connection_pool = PgPoolOptions::new().connect_lazy_with(configuration);
|
||||||
|
worker_loop(connection_pool).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn worker_loop(connection_pool: PgPool) -> Result<(), anyhow::Error> {
|
||||||
|
loop {
|
||||||
|
if let Err(e) = clean_pending_subscriptions(&connection_pool).await {
|
||||||
|
tracing::error!("{:?}", e);
|
||||||
|
}
|
||||||
|
if let Err(e) = clean_idempotency_keys(&connection_pool).await {
|
||||||
|
tracing::error!("{:?}", e);
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clean_pending_subscriptions(connection_pool: &PgPool) -> Result<(), anyhow::Error> {
|
||||||
|
let result = sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM subscriptions
|
||||||
|
WHERE status = 'pending_confirmation'
|
||||||
|
AND subscribed_at < NOW() - INTERVAL '24 hours'
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.execute(connection_pool)
|
||||||
|
.await
|
||||||
|
.context("Failed to clean up subscriptions table.")?;
|
||||||
|
match result.rows_affected() {
|
||||||
|
n if n > 0 => tracing::info!("Cleaned up {} expired subscriptions.", n),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clean_idempotency_keys(connection_pool: &PgPool) -> Result<(), anyhow::Error> {
|
||||||
|
let result = sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM idempotency
|
||||||
|
WHERE created_at < NOW() - INTERVAL '1 hour'
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.execute(connection_pool)
|
||||||
|
.await
|
||||||
|
.context("Failed to clean up idempontency table.")?;
|
||||||
|
match result.rows_affected() {
|
||||||
|
n if n > 0 => tracing::info!("Cleaned up {} old idempotency records.", n),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use std::time::Duration;
|
|||||||
use tracing::{Span, field::display};
|
use tracing::{Span, field::display};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub async fn run_worker_until_stopped(configuration: Settings) -> Result<(), anyhow::Error> {
|
pub async fn run_until_stopped(configuration: Settings) -> Result<(), anyhow::Error> {
|
||||||
let connection_pool = PgPoolOptions::new().connect_lazy_with(configuration.database.with_db());
|
let connection_pool = PgPoolOptions::new().connect_lazy_with(configuration.database.with_db());
|
||||||
let email_client = EmailClient::build(configuration.email_client).unwrap();
|
let email_client = EmailClient::build(configuration.email_client).unwrap();
|
||||||
worker_loop(connection_pool, email_client).await
|
worker_loop(connection_pool, email_client).await
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod authentication;
|
pub mod authentication;
|
||||||
pub mod configuration;
|
pub mod configuration;
|
||||||
|
pub mod database_worker;
|
||||||
pub mod domain;
|
pub mod domain;
|
||||||
pub mod email_client;
|
pub mod email_client;
|
||||||
pub mod idempotency;
|
pub mod idempotency;
|
||||||
|
|||||||
13
src/main.rs
13
src/main.rs
@@ -1,6 +1,6 @@
|
|||||||
use zero2prod::{
|
use zero2prod::{
|
||||||
configuration::get_configuration, issue_delivery_worker::run_worker_until_stopped,
|
configuration::get_configuration, database_worker, issue_delivery_worker, startup::Application,
|
||||||
startup::Application, telemetry::init_subscriber,
|
telemetry::init_subscriber,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -11,11 +11,16 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
let application = Application::build(configuration.clone()).await?;
|
let application = Application::build(configuration.clone()).await?;
|
||||||
|
|
||||||
let application_task = tokio::spawn(application.run_until_stopped());
|
let application_task = tokio::spawn(application.run_until_stopped());
|
||||||
let worker_task = tokio::spawn(run_worker_until_stopped(configuration));
|
let database_worker_task = tokio::spawn(database_worker::run_until_stopped(
|
||||||
|
configuration.database.with_db(),
|
||||||
|
));
|
||||||
|
let delivery_worker_task =
|
||||||
|
tokio::spawn(issue_delivery_worker::run_until_stopped(configuration));
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = application_task => {},
|
_ = application_task => {},
|
||||||
_ = worker_task => {},
|
_ = database_worker_task => {},
|
||||||
|
_ = delivery_worker_task => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ where
|
|||||||
)
|
)
|
||||||
.with(
|
.with(
|
||||||
tracing_subscriber::fmt::layer()
|
tracing_subscriber::fmt::layer()
|
||||||
.compact()
|
.pretty()
|
||||||
.with_writer(sink)
|
.with_writer(sink)
|
||||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE),
|
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user