backie/README.md

155 lines
5.8 KiB
Markdown
Raw Normal View History

2023-03-09 18:12:50 +00:00
# Backie 🚲
2021-06-24 09:58:02 +00:00
2023-03-11 21:22:25 +00:00
Async persistent background task processing for Rust applications with Tokio. Queue asynchronous tasks
to be processed by workers. It's designed to be easy to use and horizontally scalable. It uses Postgres as
a storage backend and can also be extended to support other types of storage.
2023-03-11 21:22:25 +00:00
High-level overview of how Backie works:
- Client puts tasks on a queue
- Server starts a multiple workers per queue
- Worker pulls tasks off the queue and starts processing them
- Tasks are processed concurrently by multiple workers
2022-09-25 13:05:27 +00:00
2023-03-11 21:22:25 +00:00
Backie started as a fork of
[fang](https://github.com/ayrat555/fang) crate, but quickly diverged significantly in its implementation.
2022-09-25 13:05:27 +00:00
2023-03-11 21:22:25 +00:00
## Key features
2023-03-07 16:52:26 +00:00
2023-03-11 21:22:25 +00:00
Here are some of the Backie's key features:
2023-03-04 19:46:09 +00:00
2023-03-11 21:22:25 +00:00
- Async workers: Workers are started as [Tokio](https://tokio.rs/) tasks
- Application context: Tasks can access an shared user-provided application context
- Single-purpose workers: Tasks are stored together but workers are configured to execute only tasks of a specific queue
- Retries: Tasks are retried with a custom backoff mode
- Graceful shutdown: provide a future to gracefully shutdown the workers, on-the-fly tasks are not interrupted
- Recovery of unfinished tasks: Tasks that were not finished are retried on the next worker start
- Unique tasks: Tasks are not duplicated in the queue if they provide a unique hash
## Other planned features
- Task timeout: Tasks are retried if they are not completed in time
- Scheduling of tasks: Tasks can be scheduled to be executed at a specific time
2023-03-04 19:46:09 +00:00
2021-06-24 09:58:02 +00:00
## Installation
2023-03-11 21:22:25 +00:00
1. Add this to your `Cargo.toml`
```toml
[dependencies]
backie = "0.1"
```
If you are not already using, you will also want to include the following dependencies for defining your tasks:
2021-06-24 09:58:02 +00:00
```toml
[dependencies]
2023-03-11 21:22:25 +00:00
async-trait = "0.1"
serde = { version = "1.0", features = ["derive"] }
diesel = { version = "2.0", features = ["postgres", "serde_json", "chrono", "uuid"] }
diesel-async = { version = "0.2", features = ["postgres", "bb8"] }
```
2023-03-11 21:22:25 +00:00
Those dependencies are required to use the `#[async_trait]` and `#[derive(Serialize, Deserialize)]` attributes
in your task definitions and to connect to the Postgres database.
*Supports rustc 1.68+*
2023-03-07 16:52:26 +00:00
2. Create the `backie_tasks` table in the Postgres database. The migration can be found in [the migrations directory](https://github.com/rafaelcaricio/backie/blob/master/migrations/2023-03-06-151907_create_backie_tasks/up.sql).
2021-06-24 09:58:02 +00:00
## Usage
2023-03-11 21:22:25 +00:00
The [`BackgroundTask`] trait is used to define a task. You must implement this trait for all
tasks you want to execute.
One important thing to note is the use of the attribute [`BackgroundTask::TASK_NAME`] which **must** be unique for
the whole application. This attribute is critical for reconstructing the task back from the database.
2021-06-24 09:58:02 +00:00
2023-03-11 21:22:25 +00:00
The [`BackgroundTask::AppData`] can be used to argument the task with your application specific contextual information.
This is useful for example to pass a database connection pool to the task or other application configuration.
The [`BackgroundTask::run`] method is where you define the behaviour of your background task execution. This method
will be called by the task queue workers.
2021-06-24 09:58:02 +00:00
```rust
2023-03-07 16:52:26 +00:00
use async_trait::async_trait;
2023-03-11 21:22:25 +00:00
use backie::{BackgroundTask, CurrentTask};
use serde::{Deserialize, Serialize};
2021-06-24 09:58:02 +00:00
#[derive(Serialize, Deserialize)]
2023-03-11 21:22:25 +00:00
pub struct MyTask {
info: String,
}
#[async_trait]
2023-03-11 21:22:25 +00:00
impl BackgroundTask for MyTask {
const TASK_NAME: &'static str = "my_task_unique_name";
type AppData = ();
async fn run(&self, task: CurrentTask, context: Self::AppData) -> Result<(), anyhow::Error> {
// Do something
Ok(())
}
}
```
2023-03-11 21:22:25 +00:00
### Starting workers
2021-06-24 09:58:02 +00:00
2023-03-11 21:22:25 +00:00
First, we need to create a [`TaskStore`] trait instance. This is the object responsible for storing and retrieving
tasks from a database. Backie currently only supports Postgres as a storage backend via the provided
[`PgTaskStore`]. You can implement other storage backends by implementing the [`TaskStore`] trait.
```rust
2023-03-11 21:22:25 +00:00
let connection_url = "postgres://postgres:password@localhost/backie";
2023-03-11 21:22:25 +00:00
let manager = AsyncDieselConnectionManager::<AsyncPgConnection>::new(connection_url);
2023-03-07 16:52:26 +00:00
let pool = Pool::builder()
2023-03-11 21:22:25 +00:00
.max_size(3)
2023-03-07 16:52:26 +00:00
.build(manager)
.await
.unwrap();
2023-03-11 21:22:25 +00:00
let task_store = PgTaskStore::new(pool);
```
2023-03-11 21:22:25 +00:00
Then, we can use the `task_store` to start a worker pool using the [`WorkerPool`]. The [`WorkerPool`] is responsible
for starting the workers and managing their lifecycle.
```rust
2023-03-11 21:22:25 +00:00
// Register the task types I want to use and start the worker pool
let (_, queue) = WorkerPool::new(task_store, |_|())
.register_task_type::<MyTask>()
.configure_queue("default", 1, RetentionMode::default())
.start(futures::future::pending::<()>())
.await
.unwrap();
```
2023-03-11 21:22:25 +00:00
With that, we are defining that we want to execute instances of `MyTask` and that the `default` queue should
have 1 worker running using the default [`RetentionMode`] (remove from the database only successfully finished tasks).
We also defined in the `start` method that the worker pool should run forever.
2023-03-11 21:22:25 +00:00
### Queueing tasks
2021-07-04 06:07:29 +00:00
2023-03-11 21:22:25 +00:00
After stating the workers we get an instance of [`Queue`] which we can use to enqueue tasks:
2021-07-04 06:07:29 +00:00
```rust
2023-03-11 21:22:25 +00:00
let task = MyTask { info: "Hello world!".to_string() };
queue.enqueue(task).await.unwrap();
2021-07-04 06:07:29 +00:00
```
2021-06-24 09:58:02 +00:00
## Contributing
2023-03-11 21:22:25 +00:00
1. [Fork it!](https://github.com/rafaelcaricio/backie/fork)
2021-06-24 09:58:02 +00:00
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
2022-09-25 13:05:27 +00:00
5. Create a new Pull Request
2021-06-24 09:58:02 +00:00
2023-03-11 21:23:45 +00:00
## Thanks to related crates authors
2023-03-11 21:22:25 +00:00
I would like to thank the authors of the [Fang](https://github.com/ayrat555/fang) and [background_job](https://git.asonix.dog/asonix/background-jobs.git) crates which were the main inspiration for this project.
2021-07-11 10:55:52 +00:00
2023-03-11 21:22:25 +00:00
- Ayrat Badykov ([@ayrat555](https://github.com/ayrat555))
- Pepe Márquez ([@pxp9](https://github.com/pxp9))
- Riley ([asonix](https://github.com/asonix))