paladin/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Distributed computation library for Rust.
4//!
5//! Paladin aims to simplify the challenge of writing distributed programs. It
6//! provides a declarative API, allowing developers to articulate their
7//! distributed programs clearly and concisely, without thinking about the
8//! complexities of distributed systems programming.
9//!
10//! Features:
11//! - **Declarative API**: Express distributed computations with clarity and
12//! ease.
13//! - **Automated Distribution**: Paladin’s runtime seamlessly handles task
14//! distribution and execution across the cluster.
15//! - **Simplified Development**: Concentrate on the program logic, leaving the
16//! complexities of distributed systems to Paladin.
17//! - **Infrastructure Agnostic**: Paladin is generic over its messaging backend
18//! and infrastructure provider. Bring your own infra!
19//!
20//! # How to use Paladin
21//!
22//! When writing your programs, you will interact with two core APIs,
23//! [`Operation`](crate::operation::Operation)s and
24//! [`Directive`](crate::directive::Directive)s. You will define your
25//! distributed computations in terms of
26//! [`Operation`](crate::operation::Operation)s, and construct your programs
27//! using Paladin's provided [`Directive`](crate::directive::Directive)s.
28//!
29//! In general, Paladin assumes that distributed programs will fundamentally
30//! operate over a stream or async-iterator-like data structure. In particular,
31//! Paladin's [`Directive`](crate::directive::Directive) API operates over
32//! [functorial](crate::directive::Functor) or
33//! [foldable](crate::directive::Foldable) data structures, providing methods
34//! [`map`](crate::directive::Directive::map) and
35//! [`fold`](crate::directive::Directive::fold), respectively. This
36//! generalization allows Paladin to support a wide variety of parallel data
37//! structures. Paladin provides one such data structure out of the box,
38//! [`IndexedStream`](crate::directive::indexed_stream::IndexedStream).
39//! [`IndexedStream`](crate::directive::indexed_stream::IndexedStream)
40//! implements both the [`Functor`](crate::directive::Functor) and
41//! [`Foldable`](crate::directive::Foldable) traits, and as such should be able
42//! to handle any distributed algorithm that can be expressed in terms of
43//! [`map`](crate::directive::Directive::map) and
44//! [`fold`](crate::directive::Directive::fold). We recommend using
45//! [`IndexedStream`](crate::directive::indexed_stream::IndexedStream) for all
46//! your programs, as it has been highly optimized for parallelism, although
47//! of course you are free to define your own.
48//!
49//! ## Defining Operations
50//!
51//! Operations are the semantic building blocks of your system. By implementing
52//! [`Operation`](crate::operation::Operation) for a type, it can be given to
53//! [`Directive`](crate::directive::Directive)s and remotely executed.
54//!
55//! ```
56//! use paladin::{RemoteExecute, operation::{Operation, Result}};
57//! use serde::{Deserialize, Serialize};
58//!
59//! #[derive(Serialize, Deserialize, RemoteExecute)]
60//! struct FibAt;
61//!
62//! impl Operation for FibAt {
63//! type Input = u64;
64//! type Output = u64;
65//!
66//! fn execute(&self, input: Self::Input) -> Result<Self::Output> {
67//! match input {
68//! 0 => Ok(0),
69//! 1 => Ok(1),
70//! _ => {
71//! let mut a = 0;
72//! let mut b = 1;
73//! for _ in 2..=input {
74//! let temp = a;
75//! a = b;
76//! b = temp + b;
77//! }
78//! Ok(b)
79//! }
80//! }
81//! }
82//! }
83//!
84//! # fn main() {
85//! assert_eq!(FibAt.execute(10).unwrap(), 55);
86//! # }
87//! ```
88//!
89//! ## Constructing a program
90//!
91//! Once operations have been defined, they can be plugged into Paladin's
92//! [`Directive`](crate::directive::Directive)s to construct a distributed
93//! program.
94//!
95//! ```
96//! use paladin::{RemoteExecute, operation::{Operation, Result}};
97//! use serde::{Deserialize, Serialize};
98//! #
99//! # #[derive(Serialize, Deserialize, RemoteExecute)]
100//! # struct FibAt;
101//! #
102//! # impl Operation for FibAt {
103//! # type Input = u64;
104//! # type Output = u64;
105//! #
106//! # fn execute(&self, input: Self::Input) -> Result<Self::Output> {
107//! # match input {
108//! # 0 => Ok(0),
109//! # 1 => Ok(1),
110//! # _ => {
111//! # let mut a = 0;
112//! # let mut b = 1;
113//! # for _ in 2..=input {
114//! # let temp = a;
115//! # a = b;
116//! # b = temp + b;
117//! # }
118//! # Ok(b)
119//! # }
120//! # }
121//! # }
122//! # }
123//! #
124//! use paladin::{
125//! operation::Monoid,
126//! directive::{indexed_stream::IndexedStream, Directive},
127//! runtime::Runtime,
128//! };
129//!
130//! // Define a Sum monoid.
131//! #[derive(Serialize, Deserialize, RemoteExecute)]
132//! struct Sum;
133//!
134//! impl Monoid for Sum {
135//! type Elem = u64;
136//!
137//! fn combine(&self, a: Self::Elem, b: Self::Elem) -> Result<Self::Elem> {
138//! Ok(a + b)
139//! }
140//!
141//! fn empty(&self) -> Self::Elem {
142//! 0
143//! }
144//! }
145//!
146//! #[tokio::main]
147//! async fn main() -> anyhow::Result<()> {
148//! let runtime = Runtime::in_memory().await?;
149//! let stream = IndexedStream::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
150//! // Compute the fibonacci number at each element in the stream with our
151//! // previously declared `FibAt` operation.
152//! let fibs = stream.map(&FibAt);
153//! // Sum the fibonacci numbers.
154//! let sum = fibs.fold(&Sum);
155//!
156//! // Run the computation.
157//! let result = sum.run(&runtime).await;
158//!
159//! // Close the runtime
160//! runtime.close().await?;
161//!
162//! assert_eq!(result?, 143);
163//! # Ok(())
164//! }
165//! ```
166//!
167//! In this example program, we define an algorithm for computing the fibonacci
168//! number at each element in a stream, and then summing the results. Behind the
169//! scenes, Paladin will distribute the computations across the cluster and
170//! return the result back to the main thread. Note that in this example, we're
171//! using Paladin's multi-threaded in-memory runtime, which can be useful for
172//! testing and debugging. In a real-world setting, one would use a distributed
173//! runtime, such as Paladin's AMQP runtime.
174//!
175//! ## Application and deployment architecture
176//!
177//! We suggest the following project layout:
178//! ```bash
179//! ops
180//! ├── Cargo.toml
181//! └── src
182//! └── lib.rs
183//! worker
184//! ├── Cargo.toml
185//! └── src
186//! └── main.rs
187//! leader
188//! ├── Cargo.toml
189//! └── src
190//! └── main.rs
191//! ```
192//!
193//! Here's a breakdown:
194//! - `ops`: A library with your operation definitions and their registry,
195//! shared between `worker` and `leader`.
196//! - `worker`: Executes operations on remote machines.
197//! - `leader`: Coordinates the distributed computation.
198//!
199//! For deployment, use one `leader` for multiple `workers`. Currently, only one
200//! `leader` deployment is supported. Future versions might offer state
201//! persistence for better failover and fault tolerance.
202//!
203//! ### Worker main
204//!
205//! Given that virtually all workers will do exactly the same thing, Paladin's
206//! worker runtime provides a
207//! [`WorkerRuntime::main_loop`](crate::runtime::WorkerRuntime::main_loop). In
208//! general, most of your logic should exist in `ops` and `leader`.
209pub mod acker;
210pub mod channel;
211pub mod common;
212pub mod config;
213pub mod contiguous;
214pub mod directive;
215pub mod operation;
216pub mod queue;
217pub mod runtime;
218pub mod serializer;
219pub mod task;
220pub use async_trait::async_trait;
221pub use paladin_opkind_derive::*;
222
223// Not public API. Used by generated code.
224#[doc(hidden)]
225pub mod __private {
226 #[doc(hidden)]
227 pub use bytes;
228 #[doc(hidden)]
229 pub use futures;
230 #[doc(hidden)]
231 pub use linkme;
232 #[doc(hidden)]
233 pub use tokio;
234 #[doc(hidden)]
235 pub use tracing;
236
237 #[doc(hidden)]
238 #[linkme::distributed_slice]
239 pub static OPERATIONS: [fn(
240 crate::task::AnyTask,
241 ) -> futures::future::BoxFuture<
242 'static,
243 crate::operation::Result<crate::task::AnyTaskOutput>,
244 >];
245}