async-batcher 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- async_batcher-0.2.2/PKG-INFO +183 -0
- async_batcher-0.2.2/README.md +151 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/batcher.py +9 -9
- {async_batcher-0.2.0 → async_batcher-0.2.2}/pyproject.toml +3 -3
- async_batcher-0.2.0/PKG-INFO +0 -101
- async_batcher-0.2.0/README.md +0 -68
- {async_batcher-0.2.0 → async_batcher-0.2.2}/LICENSE +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/aws/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/aws/dynamodb/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/aws/dynamodb/get.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/aws/dynamodb/write.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/exceptions.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/ml/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/ml/keras/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/ml/keras/model.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/ml/sklearn/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/ml/sklearn/model.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/scylladb/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/scylladb/update.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/sqlalchemy/__init__.py +0 -0
- {async_batcher-0.2.0 → async_batcher-0.2.2}/async_batcher/sqlalchemy/write.py +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: async-batcher
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: A service to batch your http requests.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Keywords: python,asyncio,rest,grpc,fastapi
|
|
7
|
+
Author: Hussein Awala
|
|
8
|
+
Author-email: hussein@awala.fr
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Provides-Extra: aws
|
|
17
|
+
Provides-Extra: keras
|
|
18
|
+
Provides-Extra: scylla
|
|
19
|
+
Provides-Extra: sklearn
|
|
20
|
+
Provides-Extra: sqlalchemy
|
|
21
|
+
Requires-Dist: aioboto3 (>=13.1,<14.0) ; extra == "aws"
|
|
22
|
+
Requires-Dist: keras (>=2.12,<3.0) ; (python_version < "3.12") and (extra == "keras")
|
|
23
|
+
Requires-Dist: keras (>=2.12,<4) ; (python_version == "3.12") and (extra == "keras")
|
|
24
|
+
Requires-Dist: scikit-learn (>=1.2,<2.0) ; (python_version < "3.12") and (extra == "sklearn")
|
|
25
|
+
Requires-Dist: scikit-learn (>=1.3,<2.0) ; (python_version == "3.12") and (extra == "sklearn")
|
|
26
|
+
Requires-Dist: scylla-driver (>=3,<4) ; extra == "scylla"
|
|
27
|
+
Requires-Dist: sqlalchemy[asyncio] (>=1.4,<3) ; extra == "sqlalchemy"
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/hussein-awala/async-batcher/issues
|
|
29
|
+
Project-URL: Documentation, https://github.com/hussein-awala/async-batcher/blob/main/README.md
|
|
30
|
+
Project-URL: Repository, https://github.com/hussein-awala/async-batcher
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# AsyncBatcher - Asynchronous Batching for Python
|
|
34
|
+
|
|
35
|
+
## Overview
|
|
36
|
+
AsyncBatcher is a generic, asynchronous batch processor for Python that efficiently groups incoming items into batches
|
|
37
|
+
and processes them asynchronously. It is designed for scenarios where multiple requests or tasks need to be handled in
|
|
38
|
+
batches to improve efficiency and throughput.
|
|
39
|
+
|
|
40
|
+
## Key Features
|
|
41
|
+
- Asynchronous processing: Uses asyncio for non-blocking execution.
|
|
42
|
+
- Batching mechanism: Groups items into batches based on size or time constraints.
|
|
43
|
+
- Concurrency control: Limits the number of concurrent batch executions.
|
|
44
|
+
- Custom processing logic: Users must implement the `process_batch` method to define batch behavior.
|
|
45
|
+
- Queue management: Uses an `asyncio.Queue` to manage incoming items.
|
|
46
|
+
- Error handling: Ensures robust error reporting and handling.
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
### 1. Receiving Items for Processing
|
|
51
|
+
- Users call `process(item)`, which adds the item to an internal queue.
|
|
52
|
+
- A `Future` object is returned immediately and the result is awaited asynchronously.
|
|
53
|
+
|
|
54
|
+
### 2. Queue Management and Batching
|
|
55
|
+
- A background task (`run()`) continuously monitors the queue.
|
|
56
|
+
- Items are collected into batches based on:
|
|
57
|
+
- `max_batch_size`: Maximum items per batch.
|
|
58
|
+
- `max_queue_time`: Maximum time an item can wait before being processed.
|
|
59
|
+
- Once a batch is ready, it is passed to the processing function.
|
|
60
|
+
|
|
61
|
+
### 3. Processing the Batch
|
|
62
|
+
- If `process_batch` is asynchronous, it is awaited directly.
|
|
63
|
+
- If `process_batch` is synchronous, it runs inside an `Executor`.
|
|
64
|
+
- Each item’s future is resolved with the corresponding processed result.
|
|
65
|
+
|
|
66
|
+
### 4. Concurrency Control
|
|
67
|
+
- If `concurrency > 0`, a semaphore ensures that only a limited number of batches are processed simultaneously.
|
|
68
|
+
- Otherwise, all batches run concurrently.
|
|
69
|
+
|
|
70
|
+
### 5. Stopping the Batcher
|
|
71
|
+
- Calling `stop(force=True)` cancels all ongoing tasks.
|
|
72
|
+
- Calling `stop(force=False)` waits for pending items to be processed before shutting down.
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
sequenceDiagram
|
|
76
|
+
participant User
|
|
77
|
+
participant AsyncBatcher
|
|
78
|
+
participant Queue as asyncio.Queue
|
|
79
|
+
participant RunLoop
|
|
80
|
+
participant Semaphore
|
|
81
|
+
participant BatchProcessor
|
|
82
|
+
|
|
83
|
+
User->>AsyncBatcher: process(item)
|
|
84
|
+
activate AsyncBatcher
|
|
85
|
+
AsyncBatcher->>Queue: put(QueueItem(item, future))
|
|
86
|
+
AsyncBatcher-->>User: returns future
|
|
87
|
+
deactivate AsyncBatcher
|
|
88
|
+
|
|
89
|
+
Note over AsyncBatcher: Starts RunLoop on first process()
|
|
90
|
+
|
|
91
|
+
loop Run Loop (run() method)
|
|
92
|
+
RunLoop->>Queue: Collect items (max_batch_size/max_queue_time)
|
|
93
|
+
activate Queue
|
|
94
|
+
Queue-->>RunLoop: Batch [QueueItem1, QueueItem2...]
|
|
95
|
+
deactivate Queue
|
|
96
|
+
|
|
97
|
+
alt Concurrency Limited (concurrency > 0)
|
|
98
|
+
RunLoop->>Semaphore: acquire()
|
|
99
|
+
activate Semaphore
|
|
100
|
+
Semaphore-->>RunLoop: acquired
|
|
101
|
+
deactivate Semaphore
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
RunLoop->>BatchProcessor: create_task(_batch_run(batch))
|
|
105
|
+
activate BatchProcessor
|
|
106
|
+
|
|
107
|
+
alt Async process_batch
|
|
108
|
+
BatchProcessor->>AsyncBatcher: await process_batch(batch)
|
|
109
|
+
else Sync process_batch
|
|
110
|
+
BatchProcessor->>Executor: run_in_executor(process_batch)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
AsyncBatcher-->>BatchProcessor: results [S1, S2...]
|
|
114
|
+
BatchProcessor->>QueueItem1.future: set_result(S1)
|
|
115
|
+
BatchProcessor->>QueueItem2.future: set_result(S2)
|
|
116
|
+
deactivate BatchProcessor
|
|
117
|
+
|
|
118
|
+
alt Concurrency Limited
|
|
119
|
+
RunLoop->>Semaphore: release()
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
Note over User, BatchProcessor: User's await future gets resolved
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## How to use
|
|
127
|
+
|
|
128
|
+
To use the library, you need to install the package in your environment. You can install the package using pip:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pip install async-batcher
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Then, you can create a new `AsyncBatcher` class by implementing the `process_batch` method:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import asyncio
|
|
138
|
+
import logging
|
|
139
|
+
|
|
140
|
+
from async_batcher.batcher import AsyncBatcher
|
|
141
|
+
|
|
142
|
+
class MyBatchProcessor(AsyncBatcher[int, int]):
|
|
143
|
+
async def process_batch(self, batch: list[int]) -> list[int]:
|
|
144
|
+
await asyncio.sleep(1) # Simulate processing delay
|
|
145
|
+
return [x * 2 for x in batch] # Example: Doubling each item
|
|
146
|
+
|
|
147
|
+
async def main():
|
|
148
|
+
batcher = MyBatchProcessor(max_batch_size=5, max_queue_time=2.0, concurrency=2)
|
|
149
|
+
results = await asyncio.gather(*[batcher.process(i) for i in range(10)])
|
|
150
|
+
print(results) # Output: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
|
|
151
|
+
await batcher.stop()
|
|
152
|
+
|
|
153
|
+
# Set logging level to DEBUG if you want to see more details and understand the flow
|
|
154
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
155
|
+
asyncio.run(main())
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Benchmark
|
|
159
|
+
|
|
160
|
+
The benchmark is available in the [BENCHMARK.md](https://github.com/hussein-awala/async-batcher/blob/main/BENCHMARK.md)
|
|
161
|
+
file.
|
|
162
|
+
|
|
163
|
+
## When to Use AsyncBatcher?
|
|
164
|
+
The AsyncBatcher library is ideal for applications that need to efficiently handle asynchronous requests in batches,
|
|
165
|
+
such as:
|
|
166
|
+
|
|
167
|
+
### Machine Learning Model Serving
|
|
168
|
+
- Batch-processing requests to optimize inference performance (e.g., TensorFlow, PyTorch, Scikit-learn).
|
|
169
|
+
|
|
170
|
+
### Database Bulk Operations
|
|
171
|
+
- Inserting multiple records in a single query to improve I/O efficiency and reduce costs (e.g., PostgreSQL, MySQL,
|
|
172
|
+
AWS DynamoDB).
|
|
173
|
+
|
|
174
|
+
### Messaging and Network Optimization
|
|
175
|
+
- Sending multiple messages in a single API call to reduce latency and costs (e.g., Kafka, RabbitMQ, AWS SQS, AWS SNS).
|
|
176
|
+
|
|
177
|
+
### Rate-Limited API Calls
|
|
178
|
+
- Aggregating requests to comply with API rate limits (e.g., GitHub API, Twitter API, OpenAI API).
|
|
179
|
+
|
|
180
|
+
## Final Notes
|
|
181
|
+
- Implement `process_batch` according to your needs.
|
|
182
|
+
- Ensure `max_batch_size` and `max_queue_time` are configured based on performance requirements.
|
|
183
|
+
- Handle exceptions inside `process_batch` to avoid failures affecting other tasks.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# AsyncBatcher - Asynchronous Batching for Python
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
AsyncBatcher is a generic, asynchronous batch processor for Python that efficiently groups incoming items into batches
|
|
5
|
+
and processes them asynchronously. It is designed for scenarios where multiple requests or tasks need to be handled in
|
|
6
|
+
batches to improve efficiency and throughput.
|
|
7
|
+
|
|
8
|
+
## Key Features
|
|
9
|
+
- Asynchronous processing: Uses asyncio for non-blocking execution.
|
|
10
|
+
- Batching mechanism: Groups items into batches based on size or time constraints.
|
|
11
|
+
- Concurrency control: Limits the number of concurrent batch executions.
|
|
12
|
+
- Custom processing logic: Users must implement the `process_batch` method to define batch behavior.
|
|
13
|
+
- Queue management: Uses an `asyncio.Queue` to manage incoming items.
|
|
14
|
+
- Error handling: Ensures robust error reporting and handling.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
### 1. Receiving Items for Processing
|
|
19
|
+
- Users call `process(item)`, which adds the item to an internal queue.
|
|
20
|
+
- A `Future` object is returned immediately and the result is awaited asynchronously.
|
|
21
|
+
|
|
22
|
+
### 2. Queue Management and Batching
|
|
23
|
+
- A background task (`run()`) continuously monitors the queue.
|
|
24
|
+
- Items are collected into batches based on:
|
|
25
|
+
- `max_batch_size`: Maximum items per batch.
|
|
26
|
+
- `max_queue_time`: Maximum time an item can wait before being processed.
|
|
27
|
+
- Once a batch is ready, it is passed to the processing function.
|
|
28
|
+
|
|
29
|
+
### 3. Processing the Batch
|
|
30
|
+
- If `process_batch` is asynchronous, it is awaited directly.
|
|
31
|
+
- If `process_batch` is synchronous, it runs inside an `Executor`.
|
|
32
|
+
- Each item’s future is resolved with the corresponding processed result.
|
|
33
|
+
|
|
34
|
+
### 4. Concurrency Control
|
|
35
|
+
- If `concurrency > 0`, a semaphore ensures that only a limited number of batches are processed simultaneously.
|
|
36
|
+
- Otherwise, all batches run concurrently.
|
|
37
|
+
|
|
38
|
+
### 5. Stopping the Batcher
|
|
39
|
+
- Calling `stop(force=True)` cancels all ongoing tasks.
|
|
40
|
+
- Calling `stop(force=False)` waits for pending items to be processed before shutting down.
|
|
41
|
+
|
|
42
|
+
```mermaid
|
|
43
|
+
sequenceDiagram
|
|
44
|
+
participant User
|
|
45
|
+
participant AsyncBatcher
|
|
46
|
+
participant Queue as asyncio.Queue
|
|
47
|
+
participant RunLoop
|
|
48
|
+
participant Semaphore
|
|
49
|
+
participant BatchProcessor
|
|
50
|
+
|
|
51
|
+
User->>AsyncBatcher: process(item)
|
|
52
|
+
activate AsyncBatcher
|
|
53
|
+
AsyncBatcher->>Queue: put(QueueItem(item, future))
|
|
54
|
+
AsyncBatcher-->>User: returns future
|
|
55
|
+
deactivate AsyncBatcher
|
|
56
|
+
|
|
57
|
+
Note over AsyncBatcher: Starts RunLoop on first process()
|
|
58
|
+
|
|
59
|
+
loop Run Loop (run() method)
|
|
60
|
+
RunLoop->>Queue: Collect items (max_batch_size/max_queue_time)
|
|
61
|
+
activate Queue
|
|
62
|
+
Queue-->>RunLoop: Batch [QueueItem1, QueueItem2...]
|
|
63
|
+
deactivate Queue
|
|
64
|
+
|
|
65
|
+
alt Concurrency Limited (concurrency > 0)
|
|
66
|
+
RunLoop->>Semaphore: acquire()
|
|
67
|
+
activate Semaphore
|
|
68
|
+
Semaphore-->>RunLoop: acquired
|
|
69
|
+
deactivate Semaphore
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
RunLoop->>BatchProcessor: create_task(_batch_run(batch))
|
|
73
|
+
activate BatchProcessor
|
|
74
|
+
|
|
75
|
+
alt Async process_batch
|
|
76
|
+
BatchProcessor->>AsyncBatcher: await process_batch(batch)
|
|
77
|
+
else Sync process_batch
|
|
78
|
+
BatchProcessor->>Executor: run_in_executor(process_batch)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
AsyncBatcher-->>BatchProcessor: results [S1, S2...]
|
|
82
|
+
BatchProcessor->>QueueItem1.future: set_result(S1)
|
|
83
|
+
BatchProcessor->>QueueItem2.future: set_result(S2)
|
|
84
|
+
deactivate BatchProcessor
|
|
85
|
+
|
|
86
|
+
alt Concurrency Limited
|
|
87
|
+
RunLoop->>Semaphore: release()
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Note over User, BatchProcessor: User's await future gets resolved
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## How to use
|
|
95
|
+
|
|
96
|
+
To use the library, you need to install the package in your environment. You can install the package using pip:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install async-batcher
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Then, you can create a new `AsyncBatcher` class by implementing the `process_batch` method:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import asyncio
|
|
106
|
+
import logging
|
|
107
|
+
|
|
108
|
+
from async_batcher.batcher import AsyncBatcher
|
|
109
|
+
|
|
110
|
+
class MyBatchProcessor(AsyncBatcher[int, int]):
|
|
111
|
+
async def process_batch(self, batch: list[int]) -> list[int]:
|
|
112
|
+
await asyncio.sleep(1) # Simulate processing delay
|
|
113
|
+
return [x * 2 for x in batch] # Example: Doubling each item
|
|
114
|
+
|
|
115
|
+
async def main():
|
|
116
|
+
batcher = MyBatchProcessor(max_batch_size=5, max_queue_time=2.0, concurrency=2)
|
|
117
|
+
results = await asyncio.gather(*[batcher.process(i) for i in range(10)])
|
|
118
|
+
print(results) # Output: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
|
|
119
|
+
await batcher.stop()
|
|
120
|
+
|
|
121
|
+
# Set logging level to DEBUG if you want to see more details and understand the flow
|
|
122
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
123
|
+
asyncio.run(main())
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Benchmark
|
|
127
|
+
|
|
128
|
+
The benchmark is available in the [BENCHMARK.md](https://github.com/hussein-awala/async-batcher/blob/main/BENCHMARK.md)
|
|
129
|
+
file.
|
|
130
|
+
|
|
131
|
+
## When to Use AsyncBatcher?
|
|
132
|
+
The AsyncBatcher library is ideal for applications that need to efficiently handle asynchronous requests in batches,
|
|
133
|
+
such as:
|
|
134
|
+
|
|
135
|
+
### Machine Learning Model Serving
|
|
136
|
+
- Batch-processing requests to optimize inference performance (e.g., TensorFlow, PyTorch, Scikit-learn).
|
|
137
|
+
|
|
138
|
+
### Database Bulk Operations
|
|
139
|
+
- Inserting multiple records in a single query to improve I/O efficiency and reduce costs (e.g., PostgreSQL, MySQL,
|
|
140
|
+
AWS DynamoDB).
|
|
141
|
+
|
|
142
|
+
### Messaging and Network Optimization
|
|
143
|
+
- Sending multiple messages in a single API call to reduce latency and costs (e.g., Kafka, RabbitMQ, AWS SQS, AWS SNS).
|
|
144
|
+
|
|
145
|
+
### Rate-Limited API Calls
|
|
146
|
+
- Aggregating requests to comply with API rate limits (e.g., GitHub API, Twitter API, OpenAI API).
|
|
147
|
+
|
|
148
|
+
## Final Notes
|
|
149
|
+
- Implement `process_batch` according to your needs.
|
|
150
|
+
- Ensure `max_batch_size` and `max_queue_time` are configured based on performance requirements.
|
|
151
|
+
- Handle exceptions inside `process_batch` to avoid failures affecting other tasks.
|
|
@@ -80,8 +80,8 @@ class AsyncBatcher(Generic[T, S], abc.ABC):
|
|
|
80
80
|
self._current_task: asyncio.Task | None = None
|
|
81
81
|
self._running_batches: dict[int, asyncio.Task] = {}
|
|
82
82
|
self._concurrency_semaphore = asyncio.Semaphore(concurrency) if concurrency > 0 else None
|
|
83
|
-
self._stop =
|
|
84
|
-
self._is_running =
|
|
83
|
+
self._stop = asyncio.Event()
|
|
84
|
+
self._is_running = asyncio.Event()
|
|
85
85
|
|
|
86
86
|
@abc.abstractmethod
|
|
87
87
|
async def process_batch(self, batch: list[T]) -> list[S] | None:
|
|
@@ -99,7 +99,7 @@ class AsyncBatcher(Generic[T, S], abc.ABC):
|
|
|
99
99
|
Returns:
|
|
100
100
|
S: The result of processing the item.
|
|
101
101
|
"""
|
|
102
|
-
if self._stop:
|
|
102
|
+
if self._stop.is_set():
|
|
103
103
|
raise RuntimeError("Batcher is stopped")
|
|
104
104
|
if self._current_task is None:
|
|
105
105
|
self._current_task = asyncio.get_running_loop().create_task(self.run())
|
|
@@ -118,7 +118,7 @@ class AsyncBatcher(Generic[T, S], abc.ABC):
|
|
|
118
118
|
return []
|
|
119
119
|
if started_at is None:
|
|
120
120
|
started_at = asyncio.get_event_loop().time()
|
|
121
|
-
while
|
|
121
|
+
while True:
|
|
122
122
|
try:
|
|
123
123
|
max_wait = self.max_queue_time - (asyncio.get_running_loop().time() - started_at)
|
|
124
124
|
if max_wait > 0:
|
|
@@ -166,7 +166,7 @@ class AsyncBatcher(Generic[T, S], abc.ABC):
|
|
|
166
166
|
|
|
167
167
|
async def run(self):
|
|
168
168
|
"""Run the batcher asynchronously."""
|
|
169
|
-
self._is_running
|
|
169
|
+
self._is_running.set()
|
|
170
170
|
task_id = 0
|
|
171
171
|
if self.concurrency > 0:
|
|
172
172
|
started_at = None
|
|
@@ -204,10 +204,10 @@ class AsyncBatcher(Generic[T, S], abc.ABC):
|
|
|
204
204
|
self._batch_run(task_id, batch)
|
|
205
205
|
)
|
|
206
206
|
task_id += 1
|
|
207
|
-
self._is_running
|
|
207
|
+
self._is_running.clear()
|
|
208
208
|
|
|
209
209
|
def _should_stop(self):
|
|
210
|
-
return self._stop and self._queue.qsize() == 0
|
|
210
|
+
return self._stop.is_set() and self._queue.qsize() == 0
|
|
211
211
|
|
|
212
212
|
async def is_running(self):
|
|
213
213
|
"""Check if the batcher is running.
|
|
@@ -215,7 +215,7 @@ class AsyncBatcher(Generic[T, S], abc.ABC):
|
|
|
215
215
|
Returns:
|
|
216
216
|
bool: True if the batcher is running, False otherwise.
|
|
217
217
|
"""
|
|
218
|
-
return self._is_running
|
|
218
|
+
return self._is_running.is_set()
|
|
219
219
|
|
|
220
220
|
async def stop(self, force: bool = False, timeout: float | None = None):
|
|
221
221
|
"""Stop the batcher asyncio task.
|
|
@@ -234,7 +234,7 @@ class AsyncBatcher(Generic[T, S], abc.ABC):
|
|
|
234
234
|
if not task.done():
|
|
235
235
|
task.cancel()
|
|
236
236
|
else:
|
|
237
|
-
self._stop
|
|
237
|
+
self._stop.set()
|
|
238
238
|
if (
|
|
239
239
|
self._current_task
|
|
240
240
|
and not self._current_task.done()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "async-batcher"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "A service to batch your http requests."
|
|
5
5
|
authors = ["Hussein Awala <hussein@awala.fr>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -15,7 +15,7 @@ packages = [{include = "async_batcher"}]
|
|
|
15
15
|
|
|
16
16
|
[tool.poetry.dependencies]
|
|
17
17
|
python = "^3.10"
|
|
18
|
-
aioboto3 = {version = "^
|
|
18
|
+
aioboto3 = {version = "^13.1", optional = true}
|
|
19
19
|
scylla-driver = {version = "^3", optional = true}
|
|
20
20
|
keras = [
|
|
21
21
|
{version = "^2.12", optional = true, markers = "python_version < '3.12'"},
|
|
@@ -39,7 +39,7 @@ mock = "^5.1.0"
|
|
|
39
39
|
pytest = "^8"
|
|
40
40
|
pytest-asyncio = "^0.23.5"
|
|
41
41
|
pre-commit = "^3"
|
|
42
|
-
types-aioboto3 = {version = "^
|
|
42
|
+
types-aioboto3 = {version = "^13.1", extras = ["dynamodb"]}
|
|
43
43
|
aiosqlite = "^0.20"
|
|
44
44
|
asyncpg = "^0.29"
|
|
45
45
|
|
async_batcher-0.2.0/PKG-INFO
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: async-batcher
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: A service to batch your http requests.
|
|
5
|
-
Home-page: https://github.com/hussein-awala/async-batcher
|
|
6
|
-
License: Apache-2.0
|
|
7
|
-
Keywords: python,asyncio,rest,grpc,fastapi
|
|
8
|
-
Author: Hussein Awala
|
|
9
|
-
Author-email: hussein@awala.fr
|
|
10
|
-
Requires-Python: >=3.10,<4.0
|
|
11
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Provides-Extra: aws
|
|
17
|
-
Provides-Extra: keras
|
|
18
|
-
Provides-Extra: scylla
|
|
19
|
-
Provides-Extra: sklearn
|
|
20
|
-
Provides-Extra: sqlalchemy
|
|
21
|
-
Requires-Dist: aioboto3 (>=12.3,<13.0) ; extra == "aws"
|
|
22
|
-
Requires-Dist: keras (>=2.12,<3.0) ; (python_version < "3.12") and (extra == "keras")
|
|
23
|
-
Requires-Dist: keras (>=2.12,<4) ; (python_version == "3.12") and (extra == "keras")
|
|
24
|
-
Requires-Dist: scikit-learn (>=1.2,<2.0) ; (python_version < "3.12") and (extra == "sklearn")
|
|
25
|
-
Requires-Dist: scikit-learn (>=1.3,<2.0) ; (python_version == "3.12") and (extra == "sklearn")
|
|
26
|
-
Requires-Dist: scylla-driver (>=3,<4) ; extra == "scylla"
|
|
27
|
-
Requires-Dist: sqlalchemy[asyncio] (>=1.4,<3) ; extra == "sqlalchemy"
|
|
28
|
-
Project-URL: Bug Tracker, https://github.com/hussein-awala/async-batcher/issues
|
|
29
|
-
Project-URL: Documentation, https://github.com/hussein-awala/async-batcher/blob/main/README.md
|
|
30
|
-
Project-URL: Repository, https://github.com/hussein-awala/async-batcher
|
|
31
|
-
Description-Content-Type: text/markdown
|
|
32
|
-
|
|
33
|
-
# Async Batcher
|
|
34
|
-
|
|
35
|
-
This project provides a Python library to batch the asynchronous requests and handle them in batches.
|
|
36
|
-
|
|
37
|
-
## How to use
|
|
38
|
-
|
|
39
|
-
To use the library, you need to install the package in your environment. You can install the package using pip:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
pip install async-batcher
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Then, you can create a new `AsyncBatcher` class by implementing the `process_batch` method:
|
|
46
|
-
|
|
47
|
-
```python
|
|
48
|
-
from async_batcher.batcher import AsyncBatcher
|
|
49
|
-
|
|
50
|
-
class MyAsyncBatcher(AsyncBatcher):
|
|
51
|
-
async def process_batch(self, batch):
|
|
52
|
-
# Process the batch
|
|
53
|
-
print(batch)
|
|
54
|
-
|
|
55
|
-
# Create a new instance of the `MyAsyncBatcher` class
|
|
56
|
-
async_batcher = MyAsyncBatcher(max_batch_size=20)
|
|
57
|
-
async_batcher.start()
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Benchmark
|
|
61
|
-
|
|
62
|
-
To evaluate the performance of the `AsyncBatcher` library, we used the [Keras example](examples/keras)
|
|
63
|
-
with [locust](https://locust.io/) to simulate multiple users making requests to the server.
|
|
64
|
-
|
|
65
|
-
In this example, we have a tensorflow model that takes ~11ms to make a single prediction and ~13ms to process
|
|
66
|
-
a batch of 200 predictions.
|
|
67
|
-
|
|
68
|
-
For this benchmark, we ran the FastAPI server with Uvicorn with 12 workers,
|
|
69
|
-
on a MacBook Pro with Apple M2 Max chip (12 cores).
|
|
70
|
-
|
|
71
|
-
Predict endpoint:
|
|
72
|
-
|
|
73
|
-

|
|
74
|
-
|
|
75
|
-
Optimized Predict endpoint:
|
|
76
|
-
|
|
77
|
-

|
|
78
|
-
|
|
79
|
-
As we can see from the graphs, for the `/predict` endpoint, the failure rate started to increase linearly at the RPS
|
|
80
|
-
of ~284 requests/second, and the 95th percentile response time was ~1100ms at this point. Then with the increase of
|
|
81
|
-
the RPS, the number of successful requests was ~130 requests/second in average, with a high failure rate
|
|
82
|
-
(~300 from ~400 requests/second).
|
|
83
|
-
|
|
84
|
-
For the `/optimized_predict` endpoint, the failure was smaller than 3 requests/second (0.00625%) during the whole test,
|
|
85
|
-
the average response time was increasing slightly with the increase of the RPS, and it reached ~120ms in average at
|
|
86
|
-
the end of the test (> 480 requests/second), with a 95th percentile response time almost stable and smaller than 500ms.
|
|
87
|
-
|
|
88
|
-
You can find check the reports for more details:
|
|
89
|
-
- [predict__report.html](assets/predict__report.html)
|
|
90
|
-
- [optimized_predict__report.html](assets/optimized_predict__report.html)
|
|
91
|
-
## Use cases
|
|
92
|
-
|
|
93
|
-
The `AsyncBatcher` library can be used in any application that needs to handle asynchronous requests in batches,
|
|
94
|
-
such as:
|
|
95
|
-
- Serving machine learning models that optimize the batch processing (e.g. TensorFlow, PyTorch, Scikit-learn, etc.)
|
|
96
|
-
- Storing multiple records in a database in a single query to optimize the I/O operations (or to reduce the cost of the
|
|
97
|
-
database operations, e.g. AWS DynamoDB)
|
|
98
|
-
- Sending multiple messages in a single request to optimize the network operations (or to reduce the cost of the network
|
|
99
|
-
operations, e.g. Kafka, RabbitMQ, AWS SQS, AWS SNS, etc.)
|
|
100
|
-
|
|
101
|
-
|
async_batcher-0.2.0/README.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Async Batcher
|
|
2
|
-
|
|
3
|
-
This project provides a Python library to batch the asynchronous requests and handle them in batches.
|
|
4
|
-
|
|
5
|
-
## How to use
|
|
6
|
-
|
|
7
|
-
To use the library, you need to install the package in your environment. You can install the package using pip:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pip install async-batcher
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Then, you can create a new `AsyncBatcher` class by implementing the `process_batch` method:
|
|
14
|
-
|
|
15
|
-
```python
|
|
16
|
-
from async_batcher.batcher import AsyncBatcher
|
|
17
|
-
|
|
18
|
-
class MyAsyncBatcher(AsyncBatcher):
|
|
19
|
-
async def process_batch(self, batch):
|
|
20
|
-
# Process the batch
|
|
21
|
-
print(batch)
|
|
22
|
-
|
|
23
|
-
# Create a new instance of the `MyAsyncBatcher` class
|
|
24
|
-
async_batcher = MyAsyncBatcher(max_batch_size=20)
|
|
25
|
-
async_batcher.start()
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Benchmark
|
|
29
|
-
|
|
30
|
-
To evaluate the performance of the `AsyncBatcher` library, we used the [Keras example](examples/keras)
|
|
31
|
-
with [locust](https://locust.io/) to simulate multiple users making requests to the server.
|
|
32
|
-
|
|
33
|
-
In this example, we have a tensorflow model that takes ~11ms to make a single prediction and ~13ms to process
|
|
34
|
-
a batch of 200 predictions.
|
|
35
|
-
|
|
36
|
-
For this benchmark, we ran the FastAPI server with Uvicorn with 12 workers,
|
|
37
|
-
on a MacBook Pro with Apple M2 Max chip (12 cores).
|
|
38
|
-
|
|
39
|
-
Predict endpoint:
|
|
40
|
-
|
|
41
|
-

|
|
42
|
-
|
|
43
|
-
Optimized Predict endpoint:
|
|
44
|
-
|
|
45
|
-

|
|
46
|
-
|
|
47
|
-
As we can see from the graphs, for the `/predict` endpoint, the failure rate started to increase linearly at the RPS
|
|
48
|
-
of ~284 requests/second, and the 95th percentile response time was ~1100ms at this point. Then with the increase of
|
|
49
|
-
the RPS, the number of successful requests was ~130 requests/second in average, with a high failure rate
|
|
50
|
-
(~300 from ~400 requests/second).
|
|
51
|
-
|
|
52
|
-
For the `/optimized_predict` endpoint, the failure was smaller than 3 requests/second (0.00625%) during the whole test,
|
|
53
|
-
the average response time was increasing slightly with the increase of the RPS, and it reached ~120ms in average at
|
|
54
|
-
the end of the test (> 480 requests/second), with a 95th percentile response time almost stable and smaller than 500ms.
|
|
55
|
-
|
|
56
|
-
You can find check the reports for more details:
|
|
57
|
-
- [predict__report.html](assets/predict__report.html)
|
|
58
|
-
- [optimized_predict__report.html](assets/optimized_predict__report.html)
|
|
59
|
-
## Use cases
|
|
60
|
-
|
|
61
|
-
The `AsyncBatcher` library can be used in any application that needs to handle asynchronous requests in batches,
|
|
62
|
-
such as:
|
|
63
|
-
- Serving machine learning models that optimize the batch processing (e.g. TensorFlow, PyTorch, Scikit-learn, etc.)
|
|
64
|
-
- Storing multiple records in a database in a single query to optimize the I/O operations (or to reduce the cost of the
|
|
65
|
-
database operations, e.g. AWS DynamoDB)
|
|
66
|
-
- Sending multiple messages in a single request to optimize the network operations (or to reduce the cost of the network
|
|
67
|
-
operations, e.g. Kafka, RabbitMQ, AWS SQS, AWS SNS, etc.)
|
|
68
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|