|
1 | 1 | # Datagraph |
2 | 2 |
|
3 | | -An asynchronous data processing library based on dataflows. Datagraph empowers you to design declaratively, abstracting the notion of discrete "tasks" and "queues" into "processors" and "flows" that treat IO as continuous streams of manipulatable information. |
| 3 | +A distributed asynchronous Python data processing framework based on dataflows. Datagraph empowers you to design declaratively, abstracting the notion of discrete "tasks" and "queues" into "processors" and "flows" that treat IO as continuous streams of manipulatable information. |
4 | 4 |
|
5 | | -Datagraph is framework-agnostic, meaning it is not tied to any particular messaging or distributed queue system. You can use it locally, with Celery, |
| 5 | +Datagraph is lightweight and infrastructure-agnostic: it is not tied to any particular messaging or distributed queue system. You can use it locally, with Celery, |
6 | 6 | or with any other asynchronous function framework; all you need is a notion of "running a function by name" to implement an `Executor`. |
7 | 7 |
|
8 | 8 | Out-of-the-box, there are `Executors` available for local execution and [Celery](https://docs.celeryq.dev/en/stable/index.html). |
9 | 9 |
|
10 | 10 | The only runtime dependency is a RESP3-compliant key/value store. Primary support is for [Valkey](https://valkey.io/). |
11 | 11 |
|
12 | | -## Features |
13 | | - |
14 | | -WIP docs |
15 | | - |
16 | | -- async first, anyio for trio/asyncio |
17 | | -- distributed first |
18 | | -- decentralized |
19 | | - - processors can be implemented in different services |
20 | | - - no central task broker or management service |
21 | | -- fastapi-style processor dependency injection |
22 | | -- optimistic parallel processing via directed streams |
23 | | - - since processors operate on streams you can rely on high degrees of parallelism vs. sequential DAGs |
24 | | - - it can still be used as a workflow engine/canvas through task partitioning (processors can wait for complete input streams before starting) |
25 | | -- RESP3 Streams |
26 | | -- not complicated |
27 | | - - the API is intentionally minimal and simple, because there's no reason for it not to be (data pipelines are hard enough, why fight with the implementation you don't control?) |
28 | | -- visualization |
29 | | - - sometimes it's easier to grasp and talk with graphs, so there's a built-in way to visualize how data moves through a Flow |
30 | | - |
31 | | -## Rationale |
32 | | - |
33 | | -For the most part, distributed task frameworks all share a core design tenant: tasks accept discrete inputs and produce discrete outputs. |
34 | | -That model works well for one-off operations, or chains of operations dealing with relatively minimal data; your workflow makes sense as a series of steps, and |
35 | | -finishes when the last step completes. If you have tasks A, B, and C, you can pass data into A, then it can be passed to B, then C. |
36 | | - |
37 | | -In the discrete model, by definition, C cannot run until B is finished, and B cannot run until A is finished: it is serial. When you're dealing with lots of |
38 | | -data, or perhaps starting with data that isn't readily available in entirety, that restriction begins to pose a problem. If you're building an ETL pipeline, and one day need to process 20,000 files (not unreasonable for large business), your users would likely be upset if they had to wait for all 20,000 files to finish processing in steps A and B before they were loaded into the application by step C. |
39 | | - |
40 | | -The procedural way of dealing with this is usually three-fold, all for the purpose of stabily reducing time-to-completion: |
41 | | -1. Break up the 20,000 files into more manageable groups, then run multiple pipelines. |
42 | | -2. Implement some mechanism for coordianting and managing those independent groups across the tasks. |
43 | | - - In the example above, that would likely _also_ mean implementing some homebrew monitoring tool or integrating with something like OpenTelemetry. |
44 | | -3. Rent more and bigger hardware (from AWS, etc.) and utilize some Horizontal Autoscaling-like feature so you can run 100 As, 100 Bs, and 100 Cs. |
45 | | - |
46 | | -All of that is fine, of course, assuming you can afford it. But, naturally, it comes with other problems that have significantly less obvious answers: what do you do when something fails? Do you ensure your tasks are idempotent and restart everything? Do you implement some partial-success, partial-failure model with a deadletter queue? Do you just pay for Azure Data Factory, or perhaps a team to figure out Apache Kafka? |
47 | | - |
48 | | -With Datagraph's approach, you can be a lot more _declarative_. Instead of focusing on the mechanics of a workflow, in either execution or deployment, you can focus on what your workflow achieves. You don't write steps, but implement processors; steps accept and produce discrete values, processors manipulate **continuous streams**. |
49 | | - |
50 | | -By treating data as continuous streams, you get three huge advantages: |
51 | | -1. Processors can manipulate data as soon as it becomes available, rather than waiting for all of it to exist before starting. |
52 | | -2. You don't have to worry about task order or scheduling. Everything that can run in parallel does, _automatically_. |
53 | | -3. Your resource requirements don't strictly grow with scale; processing as streams means you are easily free to process as much or as little as you want at any given time without increasing CPU or (more likely) RAM. |
54 | | - |
55 | | -In this model, applications like ETL pipelines make a lot more sense. You can start processing individual files as they're uploaded, transform one while the previous is extracting, and don't need to design around buying an EC2 instance with enough RAM to store 20,000 bitmap-filled PDFs. |
56 | | - |
57 | | -There's also a fourth benefit. If you're writing a complex application, or just love imperative programming, you'll probably want to be able to run asynchronous tasks regardless. You can use Datagraph for that too; the benefit to defining a workflow using streams is that you can generalize procedural tasks as processors dealing in streams of 1 value. |
58 | | - |
59 | | -The result of all of this is a simpler but equally robust system that imparts a smaller cognitive load on your developers. |
60 | | - |
61 | | -## Installation |
62 | | - |
63 | | -WIP |
| 12 | +> See [Why?](#why) for a more in-depth walk through of Datagraph's advantages. |
| 13 | +
|
| 14 | +### Features |
| 15 | + |
| 16 | +- Async-first, with support for both Trio and asyncio. |
| 17 | +- Lightweight and infrastructure-agnostic. |
| 18 | + - No central task broker or management service. |
| 19 | + - You only need a RESP3-compliant key/value store. |
| 20 | +- Distributed and decentralized. |
| 21 | + - Processors can be implemented in different services. |
| 22 | +- FastAPI-style processor dependency injection. |
| 23 | +- Optimistic parallel processing via directed streams. |
| 24 | + - Since processors operate on streams, you can rely on high degrees of parallelism vs. sequential DAGs. |
| 25 | + - Supports use as a discrete task canvas through partitioning (processors can wait for complete input streams before starting). |
| 26 | +- Not complicated. |
| 27 | + - The API is intentionally small and simple, because there's no reason for it not to be (data pipelines are hard enough, why fight with the implementation you don't control?) |
| 28 | +- Visualization |
| 29 | + - Sometimes it's easier to grasp and talk with graphs, so there's a built-in way to visualize how data moves through a Flow. |
| 30 | + |
| 31 | +### Installation |
| 32 | + |
| 33 | +```bash |
| 34 | +pdm add datagraph |
| 35 | +# or |
| 36 | +pip install datagraph |
| 37 | +``` |
64 | 38 |
|
65 | | -## Usage |
| 39 | +### Usage |
66 | 40 |
|
67 | 41 | This is a contrived example, but illustrates many of the basic features you'd want to use: |
68 | 42 |
|
@@ -140,6 +114,58 @@ anyio.run(main, backend="trio") |
140 | 114 | # >>> 8 |
141 | 115 | ``` |
142 | 116 |
|
| 117 | +## Why? |
| 118 | + |
| 119 | +### Why Streams? |
| 120 | + |
| 121 | +For the most part, distributed task frameworks all share a core design tenant: tasks accept discrete inputs and produce discrete outputs. |
| 122 | +That model works well for one-off operations, or chains of operations dealing with relatively minimal data; your workflow makes sense as a series of steps, and |
| 123 | +finishes when the last step completes. If you have tasks A, B, and C, you can pass data into A, then it can be passed to B, then C. |
| 124 | + |
| 125 | +In the discrete model, by definition, C cannot run until B is finished, and B cannot run until A is finished: it is serial. When you're dealing with lots of |
| 126 | +data, or perhaps starting with data that isn't readily available in entirety, that restriction begins to pose a problem. If you're building an ETL pipeline, and one day need to process 20,000 files (not unreasonable for large business), your users would likely be upset if they had to wait for all 20,000 files to finish processing in steps A and B before they were loaded into the application by step C. |
| 127 | + |
| 128 | +The procedural way of dealing with this would be three-fold, all for the purpose of stabily reducing time-to-completion: |
| 129 | +1. Break up the 20,000 files into more manageable groups, then run multiple pipelines. |
| 130 | +2. Implement some mechanism for coordianting and managing those independent groups. |
| 131 | +3. Rent more, bigger, and faster hardware (from AWS, etc.) and utilize some Horizontal Autoscaling-like feature so you can run 100 As, 100 Bs, and 100 Cs. |
| 132 | + |
| 133 | +All of that is fine, of course, assuming you can afford it. But, naturally, it comes with other problems that have significantly less obvious answers: what do you do when something fails? Do you ensure your tasks are idempotent and restart everything? Do you implement some partial-success, partial-failure model with a deadletter queue? The list can go on. |
| 134 | + |
| 135 | +Streams solve these problems by enabling data to flow through your pipeline continuously. Instead of waiting for entire batches to complete, each piece of data moves through the system as soon as it's ready, with tasks C, B, and A all running in parallel on different pieces of data. The result? Lower latency, better resource utilization, and real-time results. |
| 136 | + |
| 137 | +### Why Datagraph? |
| 138 | + |
| 139 | +While many stream processing frameworks exist, most come with significant complexity and operational overhead. Datagraph offers a fundamentally different approach: |
| 140 | + |
| 141 | +1. **No external runtime or complex infrastructure** |
| 142 | + - Unlike Kafka/Flink/Spark, Datagraph is pure Python with a single RESP3 store dependency. |
| 143 | + - No JVM tuning, no Zookeeper clusters, no dedicated stream processing servers, fewer headaches. |
| 144 | + |
| 145 | +2. **Developer experience first** |
| 146 | + - FastAPI-style dependency injection with a clean, declarative API. |
| 147 | + - Define processors with simple decorators. |
| 148 | + - Designed around async generators to match the continuous dataflow model. |
| 149 | + - Supports both Trio and asyncio. |
| 150 | + |
| 151 | +3. **Infrastructure freedom** |
| 152 | + - Pluggable executor architecture lets you run anywhere. |
| 153 | + - Start with `LocalExecutor` and switch to Celery, Kubernetes, or serverless without code changes. |
| 154 | + - No platform or vendor lock-in. Most of your time can be spent on the logic, not the infra. |
| 155 | + |
| 156 | +4. **Right-sized for practical workloads** |
| 157 | + - Optimized for the 10K-100K messages/sec range on commodity hardware |
| 158 | + - Perfect for ETL, IoT ingestion, and real-time dashboards |
| 159 | + - Built-in backpressure prevents fast producers from overwhelming slow consumers |
| 160 | + |
| 161 | +5. **Visualization included** |
| 162 | + - Built-in graph rendering shows data flow at a glance |
| 163 | + - Diagnose bottlenecks visually rather than through logs |
| 164 | + |
| 165 | +Datagraph doesn't aim to replace Apache Beam or Flink for petabyte-scale analytics. Instead, it targets the vast middle ground where teams need streaming capabilities without the operational complexity of distributed stream processing clusters. |
| 166 | + |
| 167 | +By focusing on **simplicity**, **Python-native ergonomics**, and **just-enough streaming**, Datagraph lets you build, test, and scale real-time pipelines in minutes—not months. |
| 168 | + |
143 | 169 | ## License |
144 | 170 |
|
145 | 171 | This library is licensed under the [BSD 3-Clause License](./LICENSE). |
0 commit comments