fractor 0.1.4 → 0.1.7
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.
- checksums.yaml +4 -4
- data/.rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-main-ci-rubocop-yml +552 -0
- data/.rubocop.yml +14 -8
- data/.rubocop_todo.yml +284 -43
- data/README.adoc +111 -950
- data/docs/.lycheeignore +16 -0
- data/docs/Gemfile +24 -0
- data/docs/README.md +157 -0
- data/docs/_config.yml +151 -0
- data/docs/_features/error-handling.adoc +1192 -0
- data/docs/_features/index.adoc +80 -0
- data/docs/_features/monitoring.adoc +589 -0
- data/docs/_features/signal-handling.adoc +202 -0
- data/docs/_features/workflows.adoc +1235 -0
- data/docs/_guides/continuous-mode.adoc +736 -0
- data/docs/_guides/cookbook.adoc +1133 -0
- data/docs/_guides/index.adoc +55 -0
- data/docs/_guides/pipeline-mode.adoc +730 -0
- data/docs/_guides/troubleshooting.adoc +358 -0
- data/docs/_pages/architecture.adoc +1390 -0
- data/docs/_pages/core-concepts.adoc +1392 -0
- data/docs/_pages/design-principles.adoc +862 -0
- data/docs/_pages/getting-started.adoc +290 -0
- data/docs/_pages/installation.adoc +143 -0
- data/docs/_reference/api.adoc +1080 -0
- data/docs/_reference/error-reporting.adoc +670 -0
- data/docs/_reference/examples.adoc +181 -0
- data/docs/_reference/index.adoc +96 -0
- data/docs/_reference/troubleshooting.adoc +862 -0
- data/docs/_tutorials/complex-workflows.adoc +1022 -0
- data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
- data/docs/_tutorials/first-application.adoc +384 -0
- data/docs/_tutorials/index.adoc +48 -0
- data/docs/_tutorials/long-running-services.adoc +931 -0
- data/docs/assets/images/favicon-16.png +0 -0
- data/docs/assets/images/favicon-32.png +0 -0
- data/docs/assets/images/favicon-48.png +0 -0
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/images/favicon.svg +45 -0
- data/docs/assets/images/fractor-icon.svg +49 -0
- data/docs/assets/images/fractor-logo.svg +61 -0
- data/docs/index.adoc +131 -0
- data/docs/lychee.toml +39 -0
- data/examples/api_aggregator/README.adoc +627 -0
- data/examples/api_aggregator/api_aggregator.rb +376 -0
- data/examples/auto_detection/README.adoc +407 -29
- data/examples/auto_detection/auto_detection.rb +9 -9
- data/examples/continuous_chat_common/message_protocol.rb +53 -0
- data/examples/continuous_chat_fractor/README.adoc +217 -0
- data/examples/continuous_chat_fractor/chat_client.rb +303 -0
- data/examples/continuous_chat_fractor/chat_common.rb +83 -0
- data/examples/continuous_chat_fractor/chat_server.rb +167 -0
- data/examples/continuous_chat_fractor/simulate.rb +345 -0
- data/examples/continuous_chat_server/README.adoc +135 -0
- data/examples/continuous_chat_server/chat_client.rb +303 -0
- data/examples/continuous_chat_server/chat_server.rb +359 -0
- data/examples/continuous_chat_server/simulate.rb +343 -0
- data/examples/error_reporting.rb +207 -0
- data/examples/file_processor/README.adoc +170 -0
- data/examples/file_processor/file_processor.rb +615 -0
- data/examples/file_processor/sample_files/invalid.csv +1 -0
- data/examples/file_processor/sample_files/orders.xml +24 -0
- data/examples/file_processor/sample_files/products.json +23 -0
- data/examples/file_processor/sample_files/users.csv +6 -0
- data/examples/hierarchical_hasher/README.adoc +629 -41
- data/examples/hierarchical_hasher/hierarchical_hasher.rb +12 -8
- data/examples/image_processor/README.adoc +610 -0
- data/examples/image_processor/image_processor.rb +349 -0
- data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
- data/examples/image_processor/test_images/sample_1.png +1 -0
- data/examples/image_processor/test_images/sample_10.png +1 -0
- data/examples/image_processor/test_images/sample_2.png +1 -0
- data/examples/image_processor/test_images/sample_3.png +1 -0
- data/examples/image_processor/test_images/sample_4.png +1 -0
- data/examples/image_processor/test_images/sample_5.png +1 -0
- data/examples/image_processor/test_images/sample_6.png +1 -0
- data/examples/image_processor/test_images/sample_7.png +1 -0
- data/examples/image_processor/test_images/sample_8.png +1 -0
- data/examples/image_processor/test_images/sample_9.png +1 -0
- data/examples/log_analyzer/README.adoc +662 -0
- data/examples/log_analyzer/log_analyzer.rb +579 -0
- data/examples/log_analyzer/sample_logs/apache.log +20 -0
- data/examples/log_analyzer/sample_logs/json.log +15 -0
- data/examples/log_analyzer/sample_logs/nginx.log +15 -0
- data/examples/log_analyzer/sample_logs/rails.log +29 -0
- data/examples/multi_work_type/README.adoc +576 -26
- data/examples/multi_work_type/multi_work_type.rb +30 -29
- data/examples/performance_monitoring.rb +120 -0
- data/examples/pipeline_processing/README.adoc +740 -26
- data/examples/pipeline_processing/pipeline_processing.rb +16 -16
- data/examples/priority_work_example.rb +155 -0
- data/examples/producer_subscriber/README.adoc +889 -46
- data/examples/producer_subscriber/producer_subscriber.rb +20 -16
- data/examples/scatter_gather/README.adoc +829 -27
- data/examples/scatter_gather/scatter_gather.rb +29 -28
- data/examples/simple/README.adoc +347 -0
- data/examples/simple/sample.rb +5 -5
- data/examples/specialized_workers/README.adoc +622 -26
- data/examples/specialized_workers/specialized_workers.rb +88 -45
- data/examples/stream_processor/README.adoc +206 -0
- data/examples/stream_processor/stream_processor.rb +284 -0
- data/examples/web_scraper/README.adoc +625 -0
- data/examples/web_scraper/web_scraper.rb +285 -0
- data/examples/workflow/README.adoc +406 -0
- data/examples/workflow/circuit_breaker/README.adoc +360 -0
- data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
- data/examples/workflow/conditional/README.adoc +483 -0
- data/examples/workflow/conditional/conditional_workflow.rb +215 -0
- data/examples/workflow/dead_letter_queue/README.adoc +374 -0
- data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
- data/examples/workflow/fan_out/README.adoc +381 -0
- data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
- data/examples/workflow/retry/README.adoc +248 -0
- data/examples/workflow/retry/retry_workflow.rb +195 -0
- data/examples/workflow/simple_linear/README.adoc +267 -0
- data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
- data/examples/workflow/simplified/README.adoc +329 -0
- data/examples/workflow/simplified/simplified_workflow.rb +222 -0
- data/exe/fractor +10 -0
- data/lib/fractor/cli.rb +288 -0
- data/lib/fractor/configuration.rb +307 -0
- data/lib/fractor/continuous_server.rb +183 -0
- data/lib/fractor/error_formatter.rb +72 -0
- data/lib/fractor/error_report_generator.rb +152 -0
- data/lib/fractor/error_reporter.rb +244 -0
- data/lib/fractor/error_statistics.rb +147 -0
- data/lib/fractor/execution_tracer.rb +162 -0
- data/lib/fractor/logger.rb +230 -0
- data/lib/fractor/main_loop_handler.rb +406 -0
- data/lib/fractor/main_loop_handler3.rb +135 -0
- data/lib/fractor/main_loop_handler4.rb +299 -0
- data/lib/fractor/performance_metrics_collector.rb +181 -0
- data/lib/fractor/performance_monitor.rb +215 -0
- data/lib/fractor/performance_report_generator.rb +202 -0
- data/lib/fractor/priority_work.rb +93 -0
- data/lib/fractor/priority_work_queue.rb +189 -0
- data/lib/fractor/result_aggregator.rb +33 -1
- data/lib/fractor/shutdown_handler.rb +168 -0
- data/lib/fractor/signal_handler.rb +80 -0
- data/lib/fractor/supervisor.rb +430 -144
- data/lib/fractor/supervisor_logger.rb +88 -0
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/work.rb +12 -0
- data/lib/fractor/work_distribution_manager.rb +151 -0
- data/lib/fractor/work_queue.rb +88 -0
- data/lib/fractor/work_result.rb +181 -9
- data/lib/fractor/worker.rb +75 -1
- data/lib/fractor/workflow/builder.rb +210 -0
- data/lib/fractor/workflow/chain_builder.rb +169 -0
- data/lib/fractor/workflow/circuit_breaker.rb +183 -0
- data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
- data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
- data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
- data/lib/fractor/workflow/execution_hooks.rb +39 -0
- data/lib/fractor/workflow/execution_strategy.rb +225 -0
- data/lib/fractor/workflow/execution_trace.rb +134 -0
- data/lib/fractor/workflow/helpers.rb +191 -0
- data/lib/fractor/workflow/job.rb +290 -0
- data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
- data/lib/fractor/workflow/logger.rb +110 -0
- data/lib/fractor/workflow/pre_execution_context.rb +193 -0
- data/lib/fractor/workflow/retry_config.rb +156 -0
- data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
- data/lib/fractor/workflow/retry_strategy.rb +93 -0
- data/lib/fractor/workflow/structured_logger.rb +30 -0
- data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
- data/lib/fractor/workflow/visualizer.rb +211 -0
- data/lib/fractor/workflow/workflow_context.rb +132 -0
- data/lib/fractor/workflow/workflow_executor.rb +669 -0
- data/lib/fractor/workflow/workflow_result.rb +55 -0
- data/lib/fractor/workflow/workflow_validator.rb +295 -0
- data/lib/fractor/workflow.rb +333 -0
- data/lib/fractor/wrapped_ractor.rb +66 -91
- data/lib/fractor/wrapped_ractor3.rb +161 -0
- data/lib/fractor/wrapped_ractor4.rb +242 -0
- data/lib/fractor.rb +93 -3
- metadata +192 -6
- data/tests/sample.rb.bak +0 -309
- data/tests/sample_working.rb.bak +0 -209
|
@@ -0,0 +1,1390 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Architecture
|
|
4
|
+
nav_order: 3
|
|
5
|
+
---
|
|
6
|
+
= Architecture guide
|
|
7
|
+
|
|
8
|
+
== Overview
|
|
9
|
+
|
|
10
|
+
This guide provides an in-depth exploration of Fractor's architecture, covering
|
|
11
|
+
system components, concurrency model, workflow execution, queue management, and
|
|
12
|
+
error handling strategies.
|
|
13
|
+
|
|
14
|
+
.Architecture documentation structure
|
|
15
|
+
[source]
|
|
16
|
+
----
|
|
17
|
+
Architecture Guide
|
|
18
|
+
├── High-Level Architecture (System components & responsibilities)
|
|
19
|
+
├── Concurrency Architecture (Ractor-based parallelism)
|
|
20
|
+
├── Workflow Architecture (Multi-step processing graphs)
|
|
21
|
+
├── Queue Architecture (Work distribution & priority)
|
|
22
|
+
└── Error Handling Architecture (Error propagation & recovery)
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
== High-level architecture
|
|
26
|
+
|
|
27
|
+
=== System components
|
|
28
|
+
|
|
29
|
+
Fractor consists of four primary architectural layers:
|
|
30
|
+
|
|
31
|
+
[source]
|
|
32
|
+
----
|
|
33
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
34
|
+
│ APPLICATION LAYER │
|
|
35
|
+
│ │
|
|
36
|
+
│ Business Logic Workflows Work Definitions │
|
|
37
|
+
│ - Domain code - Job graphs - Work subclasses │
|
|
38
|
+
│ - Processing - Dependencies - Worker subclasses │
|
|
39
|
+
│ - Validation - Type safety - Results │
|
|
40
|
+
└──────────────────────────────────────────────────────────────┘
|
|
41
|
+
│
|
|
42
|
+
▼
|
|
43
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
44
|
+
│ ORCHESTRATION LAYER │
|
|
45
|
+
│ │
|
|
46
|
+
│ Supervisor WorkflowExecutor ContinuousServer │
|
|
47
|
+
│ - Pool mgmt - Job execution - Long-running │
|
|
48
|
+
│ - Distribution - Dep resolution - Threading │
|
|
49
|
+
│ - Coordination - State mgmt - Callbacks │
|
|
50
|
+
└──────────────────────────────────────────────────────────────┘
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
54
|
+
│ CONCURRENCY LAYER │
|
|
55
|
+
│ │
|
|
56
|
+
│ WrappedRactor WorkQueue ResultAggregator │
|
|
57
|
+
│ - Ractor lifecycle - Thread-safe - Result collection │
|
|
58
|
+
│ - Messaging - Batch ops - Error separation │
|
|
59
|
+
│ - Error handling - Backpressure - Callbacks │
|
|
60
|
+
└──────────────────────────────────────────────────────────────┘
|
|
61
|
+
│
|
|
62
|
+
▼
|
|
63
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
64
|
+
│ RACTOR LAYER │
|
|
65
|
+
│ │
|
|
66
|
+
│ Ractor 1 Ractor 2 Ractor 3 ... Ractor N │
|
|
67
|
+
│ - Worker - Worker - Worker - Worker │
|
|
68
|
+
│ - Isolated - Isolated - Isolated - Isolated │
|
|
69
|
+
│ - Parallel - Parallel - Parallel - Parallel │
|
|
70
|
+
└──────────────────────────────────────────────────────────────┘
|
|
71
|
+
----
|
|
72
|
+
|
|
73
|
+
=== Component responsibilities
|
|
74
|
+
|
|
75
|
+
[cols="2,3,3"]
|
|
76
|
+
|===
|
|
77
|
+
|Layer |Component |Responsibility
|
|
78
|
+
|
|
79
|
+
.4+|Application
|
|
80
|
+
|Business Logic
|
|
81
|
+
|Domain-specific processing and validation
|
|
82
|
+
|
|
83
|
+
|Workflows
|
|
84
|
+
|Define multi-step processing graphs with dependencies
|
|
85
|
+
|
|
86
|
+
|Work Classes
|
|
87
|
+
|Encapsulate input data for processing
|
|
88
|
+
|
|
89
|
+
|Worker Classes
|
|
90
|
+
|Implement processing logic for work items
|
|
91
|
+
|
|
92
|
+
.3+|Orchestration
|
|
93
|
+
|[`Supervisor`](../../lib/fractor/supervisor.rb)
|
|
94
|
+
|Manage worker pools, distribute work, collect results
|
|
95
|
+
|
|
96
|
+
|[`WorkflowExecutor`](../../lib/fractor/workflow/workflow_executor.rb)
|
|
97
|
+
|Execute workflows, resolve dependencies, track state
|
|
98
|
+
|
|
99
|
+
|[`ContinuousServer`](../../lib/fractor/continuous_server.rb)
|
|
100
|
+
|Manage long-running services with threading and callbacks
|
|
101
|
+
|
|
102
|
+
.3+|Concurrency
|
|
103
|
+
|[`WrappedRactor`](../../lib/fractor/wrapped_ractor.rb)
|
|
104
|
+
|Manage Ractor lifecycle, handle messaging
|
|
105
|
+
|
|
106
|
+
|[`WorkQueue`](../../lib/fractor/work_queue.rb) / [`PriorityWorkQueue`](../../lib/fractor/priority_work_queue.rb)
|
|
107
|
+
|Thread-safe work storage with priority support
|
|
108
|
+
|
|
109
|
+
|[`ResultAggregator`](../../lib/fractor/result_aggregator.rb)
|
|
110
|
+
|Collect and organize processing results
|
|
111
|
+
|
|
112
|
+
|Ractor
|
|
113
|
+
|Ruby Ractor
|
|
114
|
+
|Provide true parallelism with memory isolation
|
|
115
|
+
|===
|
|
116
|
+
|
|
117
|
+
=== Layered architecture benefits
|
|
118
|
+
|
|
119
|
+
The layered architecture provides several key benefits:
|
|
120
|
+
|
|
121
|
+
. *Separation of concerns*: Each layer has clear, distinct responsibilities
|
|
122
|
+
. *Testability*: Layers can be tested independently
|
|
123
|
+
. *Flexibility*: Swap implementations without affecting other layers
|
|
124
|
+
. *Maintainability*: Changes localized to relevant layers
|
|
125
|
+
. *Extensibility*: Add features by extending appropriate layers
|
|
126
|
+
|
|
127
|
+
=== Component lifecycle
|
|
128
|
+
|
|
129
|
+
.Component lifecycle in pipeline mode
|
|
130
|
+
[source]
|
|
131
|
+
----
|
|
132
|
+
Initialization Phase
|
|
133
|
+
│
|
|
134
|
+
├──> Create Supervisor
|
|
135
|
+
│ └──> Configure worker pools
|
|
136
|
+
│ └──> Validate worker classes
|
|
137
|
+
│
|
|
138
|
+
├──> Add work items
|
|
139
|
+
│ └──> Validate work objects
|
|
140
|
+
│ └──> Add to queue
|
|
141
|
+
│
|
|
142
|
+
Execution Phase
|
|
143
|
+
│
|
|
144
|
+
├──> Start workers
|
|
145
|
+
│ └──> Create WrappedRactors
|
|
146
|
+
│ └──> Spawn Ractors
|
|
147
|
+
│ └──> Instantiate Workers
|
|
148
|
+
│
|
|
149
|
+
├──> Distribution loop
|
|
150
|
+
│ └──> Ractor.select (wait for available worker)
|
|
151
|
+
│ └──> Send work to available Ractor
|
|
152
|
+
│ └──> Worker.process(work)
|
|
153
|
+
│
|
|
154
|
+
├──> Collection loop
|
|
155
|
+
│ └──> Receive WorkResult from Ractor
|
|
156
|
+
│ └──> Add to ResultAggregator
|
|
157
|
+
│ └──> Callback execution (if any)
|
|
158
|
+
│
|
|
159
|
+
Completion Phase
|
|
160
|
+
│
|
|
161
|
+
└──> All work processed
|
|
162
|
+
└──> Return results to caller
|
|
163
|
+
└──> Cleanup resources
|
|
164
|
+
----
|
|
165
|
+
|
|
166
|
+
.Component lifecycle in continuous mode
|
|
167
|
+
[source]
|
|
168
|
+
----
|
|
169
|
+
Initialization Phase
|
|
170
|
+
│
|
|
171
|
+
├──> Create Supervisor (continuous: true)
|
|
172
|
+
│ └──> Configure worker pools
|
|
173
|
+
│
|
|
174
|
+
├──> Register work sources
|
|
175
|
+
│ └──> Callback functions
|
|
176
|
+
│
|
|
177
|
+
├──> Create ContinuousServer (optional)
|
|
178
|
+
│ └──> Setup threading
|
|
179
|
+
│ └──> Results processing thread
|
|
180
|
+
│
|
|
181
|
+
Execution Phase (Infinite Loop)
|
|
182
|
+
│
|
|
183
|
+
├──> Poll work sources
|
|
184
|
+
│ └──> Execute callbacks
|
|
185
|
+
│ └──> Add new work to queue
|
|
186
|
+
│
|
|
187
|
+
├──> Timer thread wakes supervisor
|
|
188
|
+
│ └──> Check for idle workers
|
|
189
|
+
│ └──> Distribute available work
|
|
190
|
+
│
|
|
191
|
+
├──> Workers process continuously
|
|
192
|
+
│ └──> Return results
|
|
193
|
+
│ └── > Callbacks execute
|
|
194
|
+
│
|
|
195
|
+
Shutdown Phase
|
|
196
|
+
│
|
|
197
|
+
├──> Receive shutdown signal (SIGINT/SIGTERM)
|
|
198
|
+
│ └──> Set @running = false
|
|
199
|
+
│ └──> Complete in-progress work
|
|
200
|
+
│ └──> Send :shutdown to workers
|
|
201
|
+
│
|
|
202
|
+
└──> Cleanup
|
|
203
|
+
└──> Close Ractors
|
|
204
|
+
└──> Close log files
|
|
205
|
+
└──> Exit cleanly
|
|
206
|
+
----
|
|
207
|
+
|
|
208
|
+
=== Deployment topologies
|
|
209
|
+
|
|
210
|
+
==== Single-process deployment
|
|
211
|
+
|
|
212
|
+
Simplest deployment for moderate workloads:
|
|
213
|
+
|
|
214
|
+
[source]
|
|
215
|
+
----
|
|
216
|
+
┌─────────────────────────────────┐
|
|
217
|
+
│ Application Process │
|
|
218
|
+
│ │
|
|
219
|
+
│ ┌───────────────────────────┐ │
|
|
220
|
+
│ │ Supervisor │ │
|
|
221
|
+
│ │ ┌────────────────────┐ │ │
|
|
222
|
+
│ │ │ Worker Pool (N) │ │ │
|
|
223
|
+
│ │ │ - Ractor 1 │ │ │
|
|
224
|
+
│ │ │ - Ractor 2 │ │ │
|
|
225
|
+
│ │ │ - Ractor N │ │ │
|
|
226
|
+
│ │ └────────────────────┘ │ │
|
|
227
|
+
│ └───────────────────────────┘ │
|
|
228
|
+
└─────────────────────────────────┘
|
|
229
|
+
----
|
|
230
|
+
|
|
231
|
+
Best for:
|
|
232
|
+
|
|
233
|
+
* Development and testing
|
|
234
|
+
* Small to medium workloads
|
|
235
|
+
* Single machine deployments
|
|
236
|
+
* Memory-constrained environments
|
|
237
|
+
|
|
238
|
+
==== Multi-pool deployment
|
|
239
|
+
|
|
240
|
+
Multiple worker pools for different work types:
|
|
241
|
+
|
|
242
|
+
[source]
|
|
243
|
+
----
|
|
244
|
+
┌─────────────────────────────────────────────────────┐
|
|
245
|
+
│ Application Process │
|
|
246
|
+
│ │
|
|
247
|
+
│ ┌────────────────────────────────────────────┐ │
|
|
248
|
+
│ │ Supervisor │ │
|
|
249
|
+
│ │ ┌──────────────┐ ┌──────────────┐ │ │
|
|
250
|
+
│ │ │ Pool 1 │ │ Pool 2 │ │ │
|
|
251
|
+
│ │ │ CPUWorker │ │ IOWorker │ │ │
|
|
252
|
+
│ │ │ (4 ractors) │ │ (16 ractors) │ │ │
|
|
253
|
+
│ │ └──────────────┘ └──────────────┘ │ │
|
|
254
|
+
│ │ ┌──────────────┐ │ │
|
|
255
|
+
│ │ │ Pool 3 │ │ │
|
|
256
|
+
│ │ │ MixedWorker │ │ │
|
|
257
|
+
│ │ │ (8 ractors) │ │ │
|
|
258
|
+
│ │ └──────────────┘ │ │
|
|
259
|
+
│ └────────────────────────────────────────────┘ │
|
|
260
|
+
└─────────────────────────────────────────────────────┘
|
|
261
|
+
----
|
|
262
|
+
|
|
263
|
+
Best for:
|
|
264
|
+
|
|
265
|
+
* Mixed CPU/IO workloads
|
|
266
|
+
* Different performance characteristics
|
|
267
|
+
* Resource optimization
|
|
268
|
+
* Specialized processing requirements
|
|
269
|
+
|
|
270
|
+
==== Distributed queue deployment
|
|
271
|
+
|
|
272
|
+
Multiple processes consuming from shared queue:
|
|
273
|
+
|
|
274
|
+
[source]
|
|
275
|
+
----
|
|
276
|
+
┌──────────────────────┐
|
|
277
|
+
│ Work Producer │
|
|
278
|
+
│ (Web app, API) │
|
|
279
|
+
└──────┬───────────────┘
|
|
280
|
+
│
|
|
281
|
+
▼
|
|
282
|
+
┌──────────────────────┐
|
|
283
|
+
│ External Queue │
|
|
284
|
+
│ (Redis, RabbitMQ) │
|
|
285
|
+
└──┬────────┬──────┬───┘
|
|
286
|
+
│ │ │
|
|
287
|
+
▼ ▼ ▼
|
|
288
|
+
┌────┐ ┌────┐ ┌────┐
|
|
289
|
+
│App1│ │App2│ │AppN│
|
|
290
|
+
│ │ │ │ │ │
|
|
291
|
+
│Sup │ │Sup │ │Sup │
|
|
292
|
+
│ └─┐│ │ └─┐│ │ └─┐│
|
|
293
|
+
│ W │ │ W │ │ W │
|
|
294
|
+
└────┘ └────┘ └────┘
|
|
295
|
+
----
|
|
296
|
+
|
|
297
|
+
Best for:
|
|
298
|
+
|
|
299
|
+
* High-volume workloads
|
|
300
|
+
* Horizontal scaling
|
|
301
|
+
* Fault tolerance
|
|
302
|
+
* Load balancing
|
|
303
|
+
|
|
304
|
+
== Concurrency architecture
|
|
305
|
+
|
|
306
|
+
=== Ractor-based parallelism
|
|
307
|
+
|
|
308
|
+
Fractor leverages Ruby's Ractor feature for true parallelism:
|
|
309
|
+
|
|
310
|
+
[source]
|
|
311
|
+
----
|
|
312
|
+
Traditional Ruby Threading (GIL-limited)
|
|
313
|
+
┌──────────────────────────────────────────────┐
|
|
314
|
+
│ Thread 1 Thread 2 Thread 3 Thread 4 │
|
|
315
|
+
│ │ │ │ │ │
|
|
316
|
+
│ └─────────┴─────────┴─────────┘ │
|
|
317
|
+
│ GIL Bottleneck │
|
|
318
|
+
│ (One at a time) │
|
|
319
|
+
└──────────────────────────────────────────────┘
|
|
320
|
+
|
|
321
|
+
Fractor with Ractors (No GIL)
|
|
322
|
+
┌──────────────────────────────────────────────┐
|
|
323
|
+
│ Ractor1 Ractor2 Ractor3 Ractor4 │
|
|
324
|
+
│ ║ ║ ║ ║ │
|
|
325
|
+
│ ║ ║ ║ ║ │
|
|
326
|
+
│ ▼ ▼ ▼ ▼ │
|
|
327
|
+
│ CPU 1 CPU 2 CPU 3 CPU 4 │
|
|
328
|
+
│ (True parallelism on separate cores) │
|
|
329
|
+
└──────────────────────────────────────────────┘
|
|
330
|
+
----
|
|
331
|
+
|
|
332
|
+
Key advantages:
|
|
333
|
+
|
|
334
|
+
* *True parallelism*: No GIL limitations
|
|
335
|
+
* *CPU utilization*: All cores utilized simultaneously
|
|
336
|
+
* *Predictable performance*: Linear scaling with cores
|
|
337
|
+
* *Memory isolation*: No race conditions
|
|
338
|
+
|
|
339
|
+
=== Message passing protocol
|
|
340
|
+
|
|
341
|
+
Fractor uses structured message passing between components:
|
|
342
|
+
|
|
343
|
+
.Message types and flow
|
|
344
|
+
[source]
|
|
345
|
+
----
|
|
346
|
+
Supervisor ← → Ractor Communication
|
|
347
|
+
|
|
348
|
+
Messages from Ractor to Supervisor:
|
|
349
|
+
┌────────────┬──────────────────────────────────┐
|
|
350
|
+
│ Type │ Purpose │
|
|
351
|
+
├────────────┼──────────────────────────────────┤
|
|
352
|
+
│:initialize │ Ractor ready, request work │
|
|
353
|
+
│:result │ Work completed successfully │
|
|
354
|
+
│:error │ Work failed with error │
|
|
355
|
+
│:shutdown │ Shutdown acknowledged │
|
|
356
|
+
└────────────┴──────────────────────────────────┘
|
|
357
|
+
|
|
358
|
+
Messages from Supervisor to Ractor:
|
|
359
|
+
┌────────────┬──────────────────────────────────┐
|
|
360
|
+
│ Type │ Purpose │
|
|
361
|
+
├────────────┼──────────────────────────────────┤
|
|
362
|
+
│Work object │ Work item to process │
|
|
363
|
+
│:shutdown │ Graceful shutdown request │
|
|
364
|
+
└────────────┴──────────────────────────────────┘
|
|
365
|
+
----
|
|
366
|
+
|
|
367
|
+
.Complete message flow sequence
|
|
368
|
+
[example]
|
|
369
|
+
====
|
|
370
|
+
[source]
|
|
371
|
+
----
|
|
372
|
+
Time Supervisor Ractor
|
|
373
|
+
│ │
|
|
374
|
+
├──── new Ractor(Worker) ───────────────────>│
|
|
375
|
+
│ │
|
|
376
|
+
│ [Initialize]
|
|
377
|
+
│ │
|
|
378
|
+
│<──── :initialize message ─────────────────┤
|
|
379
|
+
│ { type: :initialize, │
|
|
380
|
+
│ processor: "worker-1" } │
|
|
381
|
+
│ │
|
|
382
|
+
├──── Work object ──────────────────────────>│
|
|
383
|
+
│ │
|
|
384
|
+
│ [Processing]
|
|
385
|
+
│ work.process
|
|
386
|
+
│ │
|
|
387
|
+
│<──── :result message ──────────────────────┤
|
|
388
|
+
│ { type: :result, │
|
|
389
|
+
│ result: WorkResult, │
|
|
390
|
+
│ processor: "worker-1" } │
|
|
391
|
+
│ │
|
|
392
|
+
[Collect result, send next work or mark idle]
|
|
393
|
+
│ │
|
|
394
|
+
├──── Next Work ────────────────────────────>│
|
|
395
|
+
│ │
|
|
396
|
+
│ [Continue...]
|
|
397
|
+
│ │
|
|
398
|
+
[On shutdown signal] │
|
|
399
|
+
│ │
|
|
400
|
+
├──── :shutdown ────────────────────────────>│
|
|
401
|
+
│ │
|
|
402
|
+
│ [Cleanup]
|
|
403
|
+
│ │
|
|
404
|
+
│<──── :shutdown ────────────────────────────┤
|
|
405
|
+
│ { type: :shutdown, │
|
|
406
|
+
│ processor: "worker-1" } │
|
|
407
|
+
│ │
|
|
408
|
+
[Remove from active ractors] [Terminate]
|
|
409
|
+
│ │
|
|
410
|
+
----
|
|
411
|
+
====
|
|
412
|
+
|
|
413
|
+
=== Ruby version compatibility
|
|
414
|
+
|
|
415
|
+
Fractor provides a unified user-facing API while internally adapting to the
|
|
416
|
+
different Ractor communication primitives available in Ruby 3.x and Ruby 4.0+.
|
|
417
|
+
|
|
418
|
+
[cols="2,2,3"]
|
|
419
|
+
|===
|
|
420
|
+
|Ruby Version |Ractor Primitives |Implementation Details
|
|
421
|
+
|
|
422
|
+
|3.0 - 3.x
|
|
423
|
+
|`Ractor.yield` / `Ractor.take`
|
|
424
|
+
|Workers use `Ractor.yield` to send results back. Main ractor uses `Ractor.take` to receive messages from workers.
|
|
425
|
+
|
|
426
|
+
|4.0+
|
|
427
|
+
|`Ractor::Port`
|
|
428
|
+
|Main ractor creates response ports and passes them to workers. Workers send results directly through ports.
|
|
429
|
+
|===
|
|
430
|
+
|
|
431
|
+
.Ruby 3.x implementation (WrappedRactor3)
|
|
432
|
+
[source,ruby]
|
|
433
|
+
----
|
|
434
|
+
# Worker sends results via Ractor.yield
|
|
435
|
+
class WrappedRactor3 < WrappedRactor
|
|
436
|
+
def start
|
|
437
|
+
@ractor = Ractor.new do
|
|
438
|
+
loop do
|
|
439
|
+
work = Ractor.receive
|
|
440
|
+
result = worker.process(work)
|
|
441
|
+
# Send result back using Ractor.yield
|
|
442
|
+
Ractor.yield({ type: :result, result: result })
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def receive_message
|
|
448
|
+
# Main ractor receives via Ractor.take
|
|
449
|
+
@ractor&.take
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
----
|
|
453
|
+
|
|
454
|
+
.Ruby 4.0+ implementation (WrappedRactor4)
|
|
455
|
+
[source,ruby]
|
|
456
|
+
----
|
|
457
|
+
# Worker sends results via response port
|
|
458
|
+
class WrappedRactor4 < WrappedRactor
|
|
459
|
+
attr_reader :response_port
|
|
460
|
+
|
|
461
|
+
def initialize(name, worker_class, response_port: nil)
|
|
462
|
+
super(name, worker_class)
|
|
463
|
+
@response_port = response_port # Port created by main ractor
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def start
|
|
467
|
+
@ractor = Ractor.new do
|
|
468
|
+
loop do
|
|
469
|
+
# Receive [work, response_port] from main
|
|
470
|
+
work, response_port = Ractor.receive
|
|
471
|
+
result = worker.process(work)
|
|
472
|
+
# Send result through the provided port
|
|
473
|
+
response_port << { type: :result, result: result }
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def send(work)
|
|
479
|
+
# Main sends [work, response_port] to worker
|
|
480
|
+
@ractor.send([work, @response_port])
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
----
|
|
484
|
+
|
|
485
|
+
.Implications for users
|
|
486
|
+
|
|
487
|
+
* **Identical API**: Your Work and Worker classes work the same on both versions
|
|
488
|
+
* **Automatic detection**: Fractor detects Ruby version and uses appropriate implementation
|
|
489
|
+
* **Performance**: Ruby 4.0's port-based communication may offer better performance for high-throughput scenarios
|
|
490
|
+
* **Compatibility**: Code written for Fractor works on both Ruby 3.x and 4.0+ without changes
|
|
491
|
+
|
|
492
|
+
[NOTE]
|
|
493
|
+
====
|
|
494
|
+
The internal implementation difference is transparent to users. You write your
|
|
495
|
+
Workers and Work classes once, and Fractor handles the version-specific
|
|
496
|
+
communication patterns.
|
|
497
|
+
====
|
|
498
|
+
|
|
499
|
+
=== Backpressure mechanisms
|
|
500
|
+
|
|
501
|
+
Fractor implements several backpressure mechanisms to prevent overload:
|
|
502
|
+
|
|
503
|
+
==== Queue-based backpressure
|
|
504
|
+
|
|
505
|
+
[source,ruby]
|
|
506
|
+
----
|
|
507
|
+
# Work queue fills up when workers are slower than producers
|
|
508
|
+
queue = Fractor::WorkQueue.new
|
|
509
|
+
|
|
510
|
+
# Producer blocks when queue reaches capacity (if configured)
|
|
511
|
+
# Workers pull at their own pace
|
|
512
|
+
supervisor.register_work_source do
|
|
513
|
+
# This callback controls production rate
|
|
514
|
+
if queue.size < MAX_QUEUE_SIZE
|
|
515
|
+
fetch_new_work
|
|
516
|
+
else
|
|
517
|
+
nil # Skip this cycle, allow queue to drain
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
----
|
|
521
|
+
|
|
522
|
+
==== Worker pool saturation
|
|
523
|
+
|
|
524
|
+
[source]
|
|
525
|
+
----
|
|
526
|
+
All Workers Busy
|
|
527
|
+
│
|
|
528
|
+
├──> New work added to queue
|
|
529
|
+
│ │
|
|
530
|
+
│ ├──> Queue grows
|
|
531
|
+
│ │ │
|
|
532
|
+
│ │ ├──> Producer detects high queue size
|
|
533
|
+
│ │ │ │
|
|
534
|
+
│ │ │ └──> Slow down production
|
|
535
|
+
│ │ │
|
|
536
|
+
│ │ └──> OR Add more workers (scale up)
|
|
537
|
+
│ │
|
|
538
|
+
│ └──> Workers complete tasks
|
|
539
|
+
│ │
|
|
540
|
+
│ └──> Queue drains
|
|
541
|
+
│ │
|
|
542
|
+
│ └──> Production resumes normal rate
|
|
543
|
+
│
|
|
544
|
+
Workers Available
|
|
545
|
+
----
|
|
546
|
+
|
|
547
|
+
==== Priority-based flow control
|
|
548
|
+
|
|
549
|
+
Using [`PriorityWorkQueue`](../../lib/fractor/priority_work_queue.rb):
|
|
550
|
+
|
|
551
|
+
[source,ruby]
|
|
552
|
+
----
|
|
553
|
+
# Critical work processed first
|
|
554
|
+
queue = Fractor::PriorityWorkQueue.new(
|
|
555
|
+
aging_enabled: true, # Prevent starvation
|
|
556
|
+
aging_threshold: 60 # Boost priority after 60s
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
# Low-priority work waits but won't starve
|
|
560
|
+
queue.push(PriorityWork.new(data, priority: :low))
|
|
561
|
+
queue.push(PriorityWork.new(urgent, priority: :critical))
|
|
562
|
+
|
|
563
|
+
# Critical work processed immediately
|
|
564
|
+
# Low-priority work eventually gets priority boost
|
|
565
|
+
----
|
|
566
|
+
|
|
567
|
+
=== Thread safety design
|
|
568
|
+
|
|
569
|
+
Fractor ensures thread safety through multiple mechanisms:
|
|
570
|
+
|
|
571
|
+
. *Immutable work objects*
|
|
572
|
+
+
|
|
573
|
+
[source,ruby]
|
|
574
|
+
----
|
|
575
|
+
class MyWork < Fractor::Work
|
|
576
|
+
def initialize(data)
|
|
577
|
+
# Freeze data to ensure immutability
|
|
578
|
+
super(data.freeze)
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
----
|
|
582
|
+
|
|
583
|
+
. *Isolated Ractor memory*
|
|
584
|
+
+
|
|
585
|
+
[source]
|
|
586
|
+
----
|
|
587
|
+
Ractor A Memory Ractor B Memory
|
|
588
|
+
┌─────────────┐ ┌─────────────┐
|
|
589
|
+
│ @counter │ │ @counter │
|
|
590
|
+
│ @state │ │ @state │
|
|
591
|
+
│ @worker │ │ @worker │
|
|
592
|
+
└─────────────┘ └─────────────┘
|
|
593
|
+
│ │
|
|
594
|
+
└────────╳───────────────┘
|
|
595
|
+
No shared access
|
|
596
|
+
----
|
|
597
|
+
|
|
598
|
+
. *Thread-safe queues*
|
|
599
|
+
+
|
|
600
|
+
[source,ruby]
|
|
601
|
+
----
|
|
602
|
+
# WorkQueue uses Thread::Queue internally
|
|
603
|
+
class WorkQueue
|
|
604
|
+
def initialize
|
|
605
|
+
@queue = Thread::Queue.new # Thread-safe
|
|
606
|
+
@mutex = Mutex.new # Additional safety
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
----
|
|
610
|
+
|
|
611
|
+
. *Message copying*
|
|
612
|
+
+
|
|
613
|
+
[source]
|
|
614
|
+
----
|
|
615
|
+
Send: work object copied → Ractor receives copy
|
|
616
|
+
Receive: result copied → Supervisor receives copy
|
|
617
|
+
|
|
618
|
+
No shared references between Ractors
|
|
619
|
+
----
|
|
620
|
+
|
|
621
|
+
== Workflow architecture
|
|
622
|
+
|
|
623
|
+
=== Workflow DSL implementation
|
|
624
|
+
|
|
625
|
+
The workflow DSL provides a declarative way to define complex processing graphs:
|
|
626
|
+
|
|
627
|
+
.Workflow definition structure
|
|
628
|
+
[source,ruby]
|
|
629
|
+
----
|
|
630
|
+
class MyWorkflow < Fractor::Workflow
|
|
631
|
+
workflow "processing-pipeline" do
|
|
632
|
+
# Metadata
|
|
633
|
+
input_type InputModel
|
|
634
|
+
output_type OutputModel
|
|
635
|
+
|
|
636
|
+
# Job definitions with dependencies
|
|
637
|
+
job "parse", ParseWorker
|
|
638
|
+
job "validate", ValidateWorker, needs: "parse"
|
|
639
|
+
job "transform", TransformWorker, needs: "validate"
|
|
640
|
+
job "store", StoreWorker, needs: "transform", outputs: :workflow
|
|
641
|
+
|
|
642
|
+
# Workflow boundaries
|
|
643
|
+
start_with "parse"
|
|
644
|
+
end_with "store"
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
----
|
|
648
|
+
|
|
649
|
+
=== Dependency graph resolution
|
|
650
|
+
|
|
651
|
+
The workflow executor resolves dependencies using topological sorting:
|
|
652
|
+
|
|
653
|
+
[source]
|
|
654
|
+
----
|
|
655
|
+
Dependency Graph:
|
|
656
|
+
parse
|
|
657
|
+
│
|
|
658
|
+
▼
|
|
659
|
+
validate
|
|
660
|
+
│
|
|
661
|
+
▼
|
|
662
|
+
transform
|
|
663
|
+
│
|
|
664
|
+
▼
|
|
665
|
+
store
|
|
666
|
+
|
|
667
|
+
Execution Order (topological sort):
|
|
668
|
+
1. parse (no dependencies)
|
|
669
|
+
2. validate (depends on parse)
|
|
670
|
+
3. transform (depends on validate)
|
|
671
|
+
4. store (depends on transform)
|
|
672
|
+
|
|
673
|
+
Parallel Execution Where Possible:
|
|
674
|
+
┌─────┐
|
|
675
|
+
│parse│
|
|
676
|
+
└──┬──┘
|
|
677
|
+
├────────────┐
|
|
678
|
+
▼ ▼
|
|
679
|
+
┌────────┐ ┌─────────┐
|
|
680
|
+
│validate│ │summarize│ ← Can run in parallel
|
|
681
|
+
└────┬───┘ └────┬────┘
|
|
682
|
+
│ │
|
|
683
|
+
└─────┬─────┘
|
|
684
|
+
▼
|
|
685
|
+
┌─────────┐
|
|
686
|
+
│transform│
|
|
687
|
+
└─────────┘
|
|
688
|
+
----
|
|
689
|
+
|
|
690
|
+
.Dependency resolution algorithm
|
|
691
|
+
[example]
|
|
692
|
+
====
|
|
693
|
+
[source,ruby]
|
|
694
|
+
----
|
|
695
|
+
# Pseudocode for dependency resolution
|
|
696
|
+
def resolve_execution_order(jobs)
|
|
697
|
+
# Build dependency graph
|
|
698
|
+
graph = build_graph(jobs)
|
|
699
|
+
|
|
700
|
+
# Find jobs with no dependencies (starting points)
|
|
701
|
+
ready = jobs.select { |j| j.dependencies.empty? }
|
|
702
|
+
|
|
703
|
+
# Topological sort
|
|
704
|
+
order = []
|
|
705
|
+
while ready.any?
|
|
706
|
+
# Process ready jobs
|
|
707
|
+
job = ready.shift
|
|
708
|
+
order << job
|
|
709
|
+
|
|
710
|
+
# Find newly ready jobs
|
|
711
|
+
jobs.each do |j|
|
|
712
|
+
if j.dependencies.all? { |dep| order.include?(dep) }
|
|
713
|
+
ready << j unless ready.include?(j)
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
order
|
|
719
|
+
end
|
|
720
|
+
----
|
|
721
|
+
====
|
|
722
|
+
|
|
723
|
+
=== Execution engine design
|
|
724
|
+
|
|
725
|
+
The workflow executor manages job execution:
|
|
726
|
+
|
|
727
|
+
[source]
|
|
728
|
+
----
|
|
729
|
+
WorkflowExecutor Components:
|
|
730
|
+
|
|
731
|
+
┌───────────────────────────────────────────────┐
|
|
732
|
+
│ WorkflowExecutor │
|
|
733
|
+
│ │
|
|
734
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
735
|
+
│ │ WorkflowContext │ │
|
|
736
|
+
│ │ - Input data │ │
|
|
737
|
+
│ │ - Job outputs │ │
|
|
738
|
+
│ │ - Execution state │ │
|
|
739
|
+
│ └─────────────────────────────────────────┘ │
|
|
740
|
+
│ │
|
|
741
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
742
|
+
│ │ ExecutionTrace (optional) │ │
|
|
743
|
+
│ │ - Job start/end times │ │
|
|
744
|
+
│ │ - Data flow tracking │ │
|
|
745
|
+
│ │ - Performance metrics │ │
|
|
746
|
+
│ └─────────────────────────────────────────┘ │
|
|
747
|
+
│ │
|
|
748
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
749
|
+
│ │ DeadLetterQueue (optional) │ │
|
|
750
|
+
│ │ - Failed job data │ │
|
|
751
|
+
│ │ - Error context │ │
|
|
752
|
+
│ │ - Retry tracking │ │
|
|
753
|
+
│ └─────────────────────────────────────────┘ │
|
|
754
|
+
└───────────────────────────────────────────────┘
|
|
755
|
+
----
|
|
756
|
+
|
|
757
|
+
.Workflow execution flow
|
|
758
|
+
[source]
|
|
759
|
+
----
|
|
760
|
+
1. Initialization
|
|
761
|
+
└──> Create WorkflowContext
|
|
762
|
+
└──> Store input data
|
|
763
|
+
└──> Initialize job state
|
|
764
|
+
|
|
765
|
+
2. Job Execution Loop
|
|
766
|
+
└──> For each job in execution order:
|
|
767
|
+
├──> Check dependencies satisfied
|
|
768
|
+
│ └──> All required jobs completed?
|
|
769
|
+
│
|
|
770
|
+
├──> Check conditional execution
|
|
771
|
+
│ └──> Evaluate if_condition if present
|
|
772
|
+
│
|
|
773
|
+
├──> Prepare job input
|
|
774
|
+
│ └──> Gather outputs from dependencies
|
|
775
|
+
│ └──> Transform to job input type
|
|
776
|
+
│
|
|
777
|
+
├──> Execute job
|
|
778
|
+
│ └──> Create supervisor for job workers
|
|
779
|
+
│ └──> Run work items through workers
|
|
780
|
+
│ └──> Collect results
|
|
781
|
+
│
|
|
782
|
+
├──> Handle result
|
|
783
|
+
│ ├──> Success: Store output in context
|
|
784
|
+
│ └──> Error: Apply retry/circuit breaker logic
|
|
785
|
+
│
|
|
786
|
+
└──> Update trace (if enabled)
|
|
787
|
+
|
|
788
|
+
3. Completion
|
|
789
|
+
└──> Extract workflow output
|
|
790
|
+
└──> Return WorkflowResult
|
|
791
|
+
├──> Output data
|
|
792
|
+
├──> Execution trace
|
|
793
|
+
└──> Success/failure status
|
|
794
|
+
----
|
|
795
|
+
|
|
796
|
+
=== State management
|
|
797
|
+
|
|
798
|
+
Workflow state is managed through [`WorkflowContext`](../../lib/fractor/workflow/workflow_context.rb):
|
|
799
|
+
|
|
800
|
+
[source,ruby]
|
|
801
|
+
----
|
|
802
|
+
# WorkflowContext manages:
|
|
803
|
+
# 1. Input data
|
|
804
|
+
# 2. Job outputs
|
|
805
|
+
# 3. Execution metadata
|
|
806
|
+
|
|
807
|
+
context = WorkflowContext.new(input_data)
|
|
808
|
+
|
|
809
|
+
# Store job output
|
|
810
|
+
context.set_job_output("parse", parsed_data)
|
|
811
|
+
|
|
812
|
+
# Retrieve for next job
|
|
813
|
+
input_for_validate = context.get_job_output("parse")
|
|
814
|
+
|
|
815
|
+
# Type-safe access
|
|
816
|
+
context.get_job_output_as(OutputModel, "parse")
|
|
817
|
+
----
|
|
818
|
+
|
|
819
|
+
State transitions:
|
|
820
|
+
|
|
821
|
+
[source]
|
|
822
|
+
----
|
|
823
|
+
State Lifecycle:
|
|
824
|
+
|
|
825
|
+
PENDING ──> IN_PROGRESS ──> COMPLETED
|
|
826
|
+
│ │ │
|
|
827
|
+
│ │ └──> Output stored
|
|
828
|
+
│ │
|
|
829
|
+
│ └──> ERROR ──> RETRY ──> IN_PROGRESS
|
|
830
|
+
│ │
|
|
831
|
+
│ └──> FAILED (max retries)
|
|
832
|
+
│
|
|
833
|
+
└──> SKIPPED (conditional not met)
|
|
834
|
+
----
|
|
835
|
+
|
|
836
|
+
== Queue architecture
|
|
837
|
+
|
|
838
|
+
=== Work queue implementation
|
|
839
|
+
|
|
840
|
+
Basic work queue using Ruby's thread-safe `Thread::Queue`:
|
|
841
|
+
|
|
842
|
+
[source,ruby]
|
|
843
|
+
----
|
|
844
|
+
class WorkQueue
|
|
845
|
+
def initialize
|
|
846
|
+
@queue = Thread::Queue.new
|
|
847
|
+
@mutex = Mutex.new
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
def <<(work_item)
|
|
851
|
+
validate_work!(work_item)
|
|
852
|
+
@queue << work_item
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def pop_batch(max_items = 10)
|
|
856
|
+
items = []
|
|
857
|
+
max_items.times do
|
|
858
|
+
break if @queue.empty?
|
|
859
|
+
items << @queue.pop(true) rescue break
|
|
860
|
+
end
|
|
861
|
+
items
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
----
|
|
865
|
+
|
|
866
|
+
Features:
|
|
867
|
+
|
|
868
|
+
* Thread-safe operations
|
|
869
|
+
* Batch retrieval for efficiency
|
|
870
|
+
* FIFO ordering
|
|
871
|
+
* Non-blocking pop option
|
|
872
|
+
|
|
873
|
+
=== Priority queue design
|
|
874
|
+
|
|
875
|
+
Priority queue with aging to prevent starvation:
|
|
876
|
+
|
|
877
|
+
[source]
|
|
878
|
+
----
|
|
879
|
+
PriorityWorkQueue Structure:
|
|
880
|
+
|
|
881
|
+
Priority Levels (0-4):
|
|
882
|
+
0: Critical ← Highest priority
|
|
883
|
+
1: High
|
|
884
|
+
2: Normal ← Default
|
|
885
|
+
3: Low
|
|
886
|
+
4: Background ← Lowest priority
|
|
887
|
+
|
|
888
|
+
Internal Storage:
|
|
889
|
+
┌────────────────────────────────────────┐
|
|
890
|
+
│ Sorted Array │
|
|
891
|
+
│ ┌──────────────────────────────────┐ │
|
|
892
|
+
│ │ [Critical Work 1] age: 10s │ │
|
|
893
|
+
│ │ [Critical Work 2] age: 5s │ │
|
|
894
|
+
│ │ [High Work 1] age: 30s │ │
|
|
895
|
+
│ │ [Normal Work 1] age: 45s │ │
|
|
896
|
+
│ │ [Low Work 1] age: 70s ⚡ │ │ ← Age boost!
|
|
897
|
+
│ └──────────────────────────────────┘ │
|
|
898
|
+
└────────────────────────────────────────┘
|
|
899
|
+
|
|
900
|
+
Aging Mechanism:
|
|
901
|
+
If work.age >= aging_threshold (60s):
|
|
902
|
+
Effective priority increases
|
|
903
|
+
Prevents starvation of low-priority items
|
|
904
|
+
----
|
|
905
|
+
|
|
906
|
+
.Priority calculation with aging
|
|
907
|
+
[example]
|
|
908
|
+
====
|
|
909
|
+
[source,ruby]
|
|
910
|
+
----
|
|
911
|
+
# Effective priority = base_priority - (age / threshold).floor
|
|
912
|
+
work = PriorityWork.new(data, priority: :low) # base = 3
|
|
913
|
+
|
|
914
|
+
# After 0 seconds: effective = 3 - 0 = 3 (low)
|
|
915
|
+
# After 60 seconds: effective = 3 - 1 = 2 (normal)
|
|
916
|
+
# After 120 seconds: effective = 3 - 2 = 1 (high)
|
|
917
|
+
# After 180 seconds: effective = 3 - 3 = 0 (critical)
|
|
918
|
+
----
|
|
919
|
+
====
|
|
920
|
+
|
|
921
|
+
=== Queue selection strategies
|
|
922
|
+
|
|
923
|
+
Choosing the right queue for your needs:
|
|
924
|
+
|
|
925
|
+
[cols="2,3,3,2"]
|
|
926
|
+
|===
|
|
927
|
+
|Queue Type |Use Case |Benefits |Trade-offs
|
|
928
|
+
|
|
929
|
+
|`WorkQueue`
|
|
930
|
+
|General-purpose processing
|
|
931
|
+
|Simple, FIFO, predictable
|
|
932
|
+
|No prioritization
|
|
933
|
+
|
|
934
|
+
|`PriorityWorkQueue`
|
|
935
|
+
|Mixed-priority workloads
|
|
936
|
+
|Priority handling, aging
|
|
937
|
+
|Slight overhead
|
|
938
|
+
|
|
939
|
+
|`Thread::Queue`
|
|
940
|
+
|Simple threading
|
|
941
|
+
|Built-in, minimal overhead
|
|
942
|
+
|Basic features only
|
|
943
|
+
|
|
944
|
+
|External (Redis)
|
|
945
|
+
|Distributed systems
|
|
946
|
+
|Persistence, clustering
|
|
947
|
+
|Network latency
|
|
948
|
+
|===
|
|
949
|
+
|
|
950
|
+
=== Continuous server integration
|
|
951
|
+
|
|
952
|
+
[`ContinuousServer`](../../lib/fractor/continuous_server.rb) provides high-level queue integration:
|
|
953
|
+
|
|
954
|
+
[source,ruby]
|
|
955
|
+
----
|
|
956
|
+
# Automatic queue integration
|
|
957
|
+
queue = Fractor::WorkQueue.new
|
|
958
|
+
server = Fractor::ContinuousServer.new(
|
|
959
|
+
worker_pools: [{ worker_class: MyWorker }],
|
|
960
|
+
work_queue: queue # Auto-registered
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
# Queue automatically polled
|
|
964
|
+
# No manual work source registration needed
|
|
965
|
+
server.run
|
|
966
|
+
----
|
|
967
|
+
|
|
968
|
+
Internal architecture:
|
|
969
|
+
|
|
970
|
+
[source]
|
|
971
|
+
----
|
|
972
|
+
ContinuousServer Architecture:
|
|
973
|
+
|
|
974
|
+
┌────────────────────────────────────────────────┐
|
|
975
|
+
│ ContinuousServer │
|
|
976
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
977
|
+
│ │ Main Thread │ │
|
|
978
|
+
│ │ - Signal handling │ │
|
|
979
|
+
│ │ - Lifecycle management │ │
|
|
980
|
+
│ └──────────────────────────────────────────┘ │
|
|
981
|
+
│ │
|
|
982
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
983
|
+
│ │ Supervisor Thread │ │
|
|
984
|
+
│ │ - Work distribution │ │
|
|
985
|
+
│ │ - Ractor management │ │
|
|
986
|
+
│ └──────────────────────────────────────────┘ │
|
|
987
|
+
│ │
|
|
988
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
989
|
+
│ │ Results Thread │ │
|
|
990
|
+
│ │ - Callback execution │ │
|
|
991
|
+
│ │ - Error handling │ │
|
|
992
|
+
│ └──────────────────────────────────────────┘ │
|
|
993
|
+
└────────────────────────────────────────────────┘
|
|
994
|
+
----
|
|
995
|
+
|
|
996
|
+
== Error handling architecture
|
|
997
|
+
|
|
998
|
+
=== Error propagation model
|
|
999
|
+
|
|
1000
|
+
Error propagation follows a clear path from worker to handler:
|
|
1001
|
+
|
|
1002
|
+
[source]
|
|
1003
|
+
----
|
|
1004
|
+
Error Origin Error Handling
|
|
1005
|
+
│ │
|
|
1006
|
+
▼ ▼
|
|
1007
|
+
┌──────────┐ ┌──────────┐
|
|
1008
|
+
│ Worker │ │ Handler │
|
|
1009
|
+
│ process()│ │ Callback │
|
|
1010
|
+
└────┬─────┘ └────▲─────┘
|
|
1011
|
+
│ │
|
|
1012
|
+
│ raise StandardError │
|
|
1013
|
+
▼ │
|
|
1014
|
+
┌──────────┐ │
|
|
1015
|
+
│ Ractor │ │
|
|
1016
|
+
│ rescue │ │
|
|
1017
|
+
└────┬─────┘ │
|
|
1018
|
+
│ │
|
|
1019
|
+
│ Create WorkResult │
|
|
1020
|
+
│ with error details │
|
|
1021
|
+
▼ │
|
|
1022
|
+
┌────────────┐ │
|
|
1023
|
+
│ Supervisor │ │
|
|
1024
|
+
│ select │ │
|
|
1025
|
+
└────┬───────┘ │
|
|
1026
|
+
│ │
|
|
1027
|
+
│ Receive :error message │
|
|
1028
|
+
▼ │
|
|
1029
|
+
┌──────────────┐ │
|
|
1030
|
+
│ResultAggregator │
|
|
1031
|
+
│ @errors << result │
|
|
1032
|
+
└────┬─────────┘ │
|
|
1033
|
+
│ │
|
|
1034
|
+
│ Trigger callbacks │
|
|
1035
|
+
└────────────────────────────┘
|
|
1036
|
+
----
|
|
1037
|
+
|
|
1038
|
+
No errors are silently swallowed—all failures captured and exposed.
|
|
1039
|
+
|
|
1040
|
+
=== Retry mechanism design
|
|
1041
|
+
|
|
1042
|
+
Workflow retry configuration:
|
|
1043
|
+
|
|
1044
|
+
[source,ruby]
|
|
1045
|
+
----
|
|
1046
|
+
# Job-level retry configuration
|
|
1047
|
+
job "fetch_data", APIWorker do
|
|
1048
|
+
retry_on NetworkError, max_attempts: 3, backoff: :exponential
|
|
1049
|
+
retry_on Timeout::Error, max_attempts: 5, backoff: :linear
|
|
1050
|
+
end
|
|
1051
|
+
----
|
|
1052
|
+
|
|
1053
|
+
Retry state machine:
|
|
1054
|
+
|
|
1055
|
+
[source]
|
|
1056
|
+
----
|
|
1057
|
+
┌─────────┐
|
|
1058
|
+
│ EXECUTE │
|
|
1059
|
+
└────┬────┘
|
|
1060
|
+
│
|
|
1061
|
+
┌────────┴────────┐
|
|
1062
|
+
│ │
|
|
1063
|
+
Success Failure
|
|
1064
|
+
│ │
|
|
1065
|
+
▼ ▼
|
|
1066
|
+
┌─────────┐ ┌─────────┐
|
|
1067
|
+
│COMPLETED│ │ ERROR │
|
|
1068
|
+
└─────────┘ └────┬────┘
|
|
1069
|
+
│
|
|
1070
|
+
Check retry policy
|
|
1071
|
+
│
|
|
1072
|
+
┌────────────┴────────────┐
|
|
1073
|
+
│ │
|
|
1074
|
+
Retriable Not retriable
|
|
1075
|
+
(attempts < max) (or max reached)
|
|
1076
|
+
│ │
|
|
1077
|
+
▼ ▼
|
|
1078
|
+
┌─────────┐ ┌─────────┐
|
|
1079
|
+
│ RETRY │ │ FAILED │
|
|
1080
|
+
└────┬────┘ └─────────┘
|
|
1081
|
+
│
|
|
1082
|
+
Apply backoff delay
|
|
1083
|
+
│
|
|
1084
|
+
└──> EXECUTE (again)
|
|
1085
|
+
----
|
|
1086
|
+
|
|
1087
|
+
=== Circuit breaker implementation
|
|
1088
|
+
|
|
1089
|
+
Circuit breaker prevents cascading failures:
|
|
1090
|
+
|
|
1091
|
+
[source]
|
|
1092
|
+
----
|
|
1093
|
+
Circuit Breaker States:
|
|
1094
|
+
|
|
1095
|
+
CLOSED (Normal Operation)
|
|
1096
|
+
│
|
|
1097
|
+
│ Failures < threshold
|
|
1098
|
+
▼
|
|
1099
|
+
Success ←──────┐
|
|
1100
|
+
│
|
|
1101
|
+
Failure ───────┤
|
|
1102
|
+
│
|
|
1103
|
+
Failures >= threshold
|
|
1104
|
+
│
|
|
1105
|
+
▼
|
|
1106
|
+
OPEN (Failing Fast)
|
|
1107
|
+
│
|
|
1108
|
+
│ All requests fail immediately
|
|
1109
|
+
│ Wait timeout period
|
|
1110
|
+
│
|
|
1111
|
+
▼
|
|
1112
|
+
HALF-OPEN (Testing)
|
|
1113
|
+
│
|
|
1114
|
+
│ Allow 1 request through
|
|
1115
|
+
│
|
|
1116
|
+
├──> Success ──> CLOSED
|
|
1117
|
+
│
|
|
1118
|
+
└──> Failure ──> OPEN
|
|
1119
|
+
----
|
|
1120
|
+
|
|
1121
|
+
Configuration:
|
|
1122
|
+
|
|
1123
|
+
[source,ruby]
|
|
1124
|
+
----
|
|
1125
|
+
job "external_api", APIWorker do
|
|
1126
|
+
circuit_breaker(
|
|
1127
|
+
failure_threshold: 5, # Open after 5 failures
|
|
1128
|
+
timeout: 60, # Try again after 60s
|
|
1129
|
+
half_open_attempts: 3 # Test with 3 requests
|
|
1130
|
+
)
|
|
1131
|
+
end
|
|
1132
|
+
----
|
|
1133
|
+
|
|
1134
|
+
=== Dead letter queue architecture
|
|
1135
|
+
|
|
1136
|
+
Failed items sent to DLQ for analysis and retry:
|
|
1137
|
+
|
|
1138
|
+
[source]
|
|
1139
|
+
----
|
|
1140
|
+
Dead Letter Queue Structure:
|
|
1141
|
+
|
|
1142
|
+
┌─────────────────────────────────────────────────┐
|
|
1143
|
+
│ DeadLetterQueue │
|
|
1144
|
+
│ │
|
|
1145
|
+
│ ┌───────────────────────────────────────────┐ │
|
|
1146
|
+
│ │ Entry Storage │ │
|
|
1147
|
+
│ │ - Work item │ │
|
|
1148
|
+
│ │ - Error details │ │
|
|
1149
|
+
│ │ - Timestamp │ │
|
|
1150
|
+
│ │ - Attempt count │ │
|
|
1151
|
+
│ │ - Context data │ │
|
|
1152
|
+
│ └───────────────────────────────────────────┘ │
|
|
1153
|
+
│ │
|
|
1154
|
+
│ ┌───────────────────────────────────────────┐ │
|
|
1155
|
+
│ │ Persister (optional) │ │
|
|
1156
|
+
│ │ - Save to disk/database │ │
|
|
1157
|
+
│ │ - Enable crash recovery │ │
|
|
1158
|
+
│ └───────────────────────────────────────────┘ │
|
|
1159
|
+
│ │
|
|
1160
|
+
│ ┌───────────────────────────────────────────┐ │
|
|
1161
|
+
│ │ Callbacks │ │
|
|
1162
|
+
│ │ - on_add: Notification │ │
|
|
1163
|
+
│ │ - Custom processing │ │
|
|
1164
|
+
│ └───────────────────────────────────────────┘ │
|
|
1165
|
+
└─────────────────────────────────────────────────┘
|
|
1166
|
+
----
|
|
1167
|
+
|
|
1168
|
+
Usage:
|
|
1169
|
+
|
|
1170
|
+
[source,ruby]
|
|
1171
|
+
----
|
|
1172
|
+
# Configure DLQ for workflow
|
|
1173
|
+
class MyWorkflow < Fractor::Workflow
|
|
1174
|
+
workflow "processing" do
|
|
1175
|
+
configure_dead_letter_queue(
|
|
1176
|
+
max_size: 1000,
|
|
1177
|
+
persister: DiskPersister.new("dlq"),
|
|
1178
|
+
on_add: ->(entry) { alert_ops(entry) }
|
|
1179
|
+
)
|
|
1180
|
+
|
|
1181
|
+
# Jobs...
|
|
1182
|
+
end
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
# Access DLQ after execution
|
|
1186
|
+
result = workflow.execute(input: data)
|
|
1187
|
+
failed_items = workflow.dead_letter_queue.entries
|
|
1188
|
+
----
|
|
1189
|
+
|
|
1190
|
+
== Sequence diagrams
|
|
1191
|
+
|
|
1192
|
+
=== Pipeline mode execution
|
|
1193
|
+
|
|
1194
|
+
[source]
|
|
1195
|
+
----
|
|
1196
|
+
Client Supervisor WorkQueue Ractor Worker
|
|
1197
|
+
│ │ │ │ │
|
|
1198
|
+
├─ new ──► │ │ │ │
|
|
1199
|
+
│ │ │ │ │
|
|
1200
|
+
├─ add_work_items ──────► │ │ │
|
|
1201
|
+
│ │ │ │ │
|
|
1202
|
+
├─ run ───► │ │ │ │
|
|
1203
|
+
│ │ │ │ │
|
|
1204
|
+
│ ├─ start_workers ───────► │ │
|
|
1205
|
+
│ │ │ │ │
|
|
1206
|
+
│ │ │ ├─ new ─► │
|
|
1207
|
+
│ │ │ │ │
|
|
1208
|
+
│ │◄──── :initialize ───────┤ │
|
|
1209
|
+
│ │ │ │ │
|
|
1210
|
+
│ ├──── pop ──► │ │ │
|
|
1211
|
+
│ │ │ │ │
|
|
1212
|
+
│ │◄──── work ──┤ │ │
|
|
1213
|
+
│ │ │ │ │
|
|
1214
|
+
│ ├──── work ─────────────► │ │
|
|
1215
|
+
│ │ │ │ │
|
|
1216
|
+
│ │ │ ├─ process ─► │
|
|
1217
|
+
│ │ │ │ │
|
|
1218
|
+
│ │ │ │◄── result ──┤
|
|
1219
|
+
│ │ │ │ │
|
|
1220
|
+
│ │◄──── :result ───────────┤ │
|
|
1221
|
+
│ │ │ │ │
|
|
1222
|
+
│ ├─ add_result │ │ │
|
|
1223
|
+
│ │ │ │ │
|
|
1224
|
+
│ ├──── pop ──► │ │ │
|
|
1225
|
+
│ │ │ │ │
|
|
1226
|
+
│ │◄──── work ──┤ │ │
|
|
1227
|
+
│ │ │ │ │
|
|
1228
|
+
│ ├──── work ─────────────► │ │
|
|
1229
|
+
│ │ │ │ │
|
|
1230
|
+
│ ... ... ... ...
|
|
1231
|
+
│ │ │ │ │
|
|
1232
|
+
│ ├─ (all work done) │ │
|
|
1233
|
+
│ │ │ │ │
|
|
1234
|
+
│◄─ results ┤ │ │ │
|
|
1235
|
+
│ │ │ │ │
|
|
1236
|
+
----
|
|
1237
|
+
|
|
1238
|
+
=== Continuous mode execution
|
|
1239
|
+
|
|
1240
|
+
[source]
|
|
1241
|
+
----
|
|
1242
|
+
Client ContinuousServer Supervisor WorkSource Ractor Worker
|
|
1243
|
+
│ │ │ │ │ │
|
|
1244
|
+
├─ new ───► │ │ │ │ │
|
|
1245
|
+
│ │ │ │ │ │
|
|
1246
|
+
├─ on_result(callback) ──► │ │ │ │
|
|
1247
|
+
│ │ │ │ │ │
|
|
1248
|
+
├─ run ────► │ │ │ │ │
|
|
1249
|
+
│ │ │ │ │ │
|
|
1250
|
+
│ ├─ Thread.new ─► │ │ │ │
|
|
1251
|
+
│ │ │ │ │ │
|
|
1252
|
+
│ │ ├─ start_workers ─────► │ │
|
|
1253
|
+
│ │ │ │ │ │
|
|
1254
|
+
│ │ │ │ ├─ new ─► │
|
|
1255
|
+
│ │ │ │ │ │
|
|
1256
|
+
│ │ │◄─ :initialize ───────┤ │
|
|
1257
|
+
│ │ │ │ │ │
|
|
1258
|
+
│ [Main loop] │ │ │ │
|
|
1259
|
+
│ │ │ │ │ │
|
|
1260
|
+
│ │ ├─ poll ──► │ │ │
|
|
1261
|
+
│ │ │ │ │ │
|
|
1262
|
+
│ │ │◄─ [work] ─┤ │ │
|
|
1263
|
+
│ │ │ │ │ │
|
|
1264
|
+
│ │ ├─ add_work │ │ │
|
|
1265
|
+
│ │ │ │ │ │
|
|
1266
|
+
│ │ ├─ send ──────────────► │ │
|
|
1267
|
+
│ │ │ │ │ │
|
|
1268
|
+
│ │ │ │ ├─ process ─► │
|
|
1269
|
+
│ │ │ │ │ │
|
|
1270
|
+
│ │ │ │ │◄─ result ──┤
|
|
1271
|
+
│ │ │ │ │ │
|
|
1272
|
+
│ │ │◄─ :result ───────────┤ │
|
|
1273
|
+
│ │ │ │ │ │
|
|
1274
|
+
│ │ ├─ add_result │ │
|
|
1275
|
+
│ │ │ │ │ │
|
|
1276
|
+
│ │◄─ result ─────┤ │ │ │
|
|
1277
|
+
│ │ │ │ │ │
|
|
1278
|
+
│ ├─ callback() │ │ │ │
|
|
1279
|
+
│ │ │ │ │ │
|
|
1280
|
+
│ [Loop continues] │ │ │ │
|
|
1281
|
+
│ │ │ │ │ │
|
|
1282
|
+
├─ SIGINT ─► │ │ │ │ │
|
|
1283
|
+
│ │ │ │ │ │
|
|
1284
|
+
│ ├─ stop ───────► │ │ │ │
|
|
1285
|
+
│ │ │ │ │ │
|
|
1286
|
+
│ │ ├─ :shutdown ─────────► │ │
|
|
1287
|
+
│ │ │ │ │ │
|
|
1288
|
+
│ │ │◄─ :shutdown ─────────┤ │
|
|
1289
|
+
│ │ │ │ │ │
|
|
1290
|
+
│ │◄─ cleanup ────┤ │ │ │
|
|
1291
|
+
│ │ │ │ │ │
|
|
1292
|
+
│◄─ exit ───┤ │ │ │ │
|
|
1293
|
+
│ │ │ │ │ │
|
|
1294
|
+
----
|
|
1295
|
+
|
|
1296
|
+
=== Workflow execution sequence
|
|
1297
|
+
|
|
1298
|
+
[source]
|
|
1299
|
+
----
|
|
1300
|
+
Client Workflow Executor Job1 Supervisor Ractor Worker
|
|
1301
|
+
│ │ │ │ │ │ │
|
|
1302
|
+
├─ new ─► │ │ │ │ │ │
|
|
1303
|
+
│ │ │ │ │ │ │
|
|
1304
|
+
├─ execute(input) ► │ │ │ │ │
|
|
1305
|
+
│ │ │ │ │ │ │
|
|
1306
|
+
│ │ ├─ resolve dependencies │ │
|
|
1307
|
+
│ │ │ │ │ │ │
|
|
1308
|
+
│ │ ├─ execute(Job1) ► │ │
|
|
1309
|
+
│ │ │ │ │ │ │
|
|
1310
|
+
│ │ │ ├─ new ─► │ │
|
|
1311
|
+
│ │ │ │ │ │ │
|
|
1312
|
+
│ │ │ │ ├─ start ► │ │
|
|
1313
|
+
│ │ │ │ │ │ │
|
|
1314
|
+
│ │ │ │ │ ├─ new ─► │
|
|
1315
|
+
│ │ │ │ │ │ │
|
|
1316
|
+
│ │ │ │ ├─ run │ │
|
|
1317
|
+
│ │ │ │ │ │ │
|
|
1318
|
+
│ │ │ │ │◄─ result ─ │
|
|
1319
|
+
│ │ │ │ │ │ │
|
|
1320
|
+
│ │ │ │◄─ results ─────── │
|
|
1321
|
+
│ │ │ │ │ │ │
|
|
1322
|
+
│ │ │◄─ job1_output ─┤ │ │
|
|
1323
|
+
│ │ │ │ │ │ │
|
|
1324
|
+
│ │ ├─ execute(Job2, input: job1_output)
|
|
1325
|
+
│ │ │ │ │ │ │
|
|
1326
|
+
│ │ ... ... ... ... ...
|
|
1327
|
+
│ │ │ │ │ │ │
|
|
1328
|
+
│ │◄─ WorkflowResult ┤ │ │ │
|
|
1329
|
+
│ │ │ │ │ │ │
|
|
1330
|
+
│◄─ result │ │ │ │ │ │
|
|
1331
|
+
│ │ │ │ │ │ │
|
|
1332
|
+
----
|
|
1333
|
+
|
|
1334
|
+
== Performance considerations
|
|
1335
|
+
|
|
1336
|
+
=== Monitoring and optimization
|
|
1337
|
+
|
|
1338
|
+
Key metrics to monitor:
|
|
1339
|
+
|
|
1340
|
+
[cols="2,3,2"]
|
|
1341
|
+
|===
|
|
1342
|
+
|Metric |Description |Target
|
|
1343
|
+
|
|
1344
|
+
|Worker utilization
|
|
1345
|
+
|% of time workers are busy
|
|
1346
|
+
|> 80%
|
|
1347
|
+
|
|
1348
|
+
|Queue depth
|
|
1349
|
+
|Number of items in queue
|
|
1350
|
+
|< 1000
|
|
1351
|
+
|
|
1352
|
+
|Processing latency
|
|
1353
|
+
|Time from submission to completion
|
|
1354
|
+
|Minimize
|
|
1355
|
+
|
|
1356
|
+
|Error rate
|
|
1357
|
+
|% of failed work items
|
|
1358
|
+
|< 1%
|
|
1359
|
+
|
|
1360
|
+
|Memory per Ractor
|
|
1361
|
+
|RAM used by each Ractor
|
|
1362
|
+
|< 100MB
|
|
1363
|
+
|
|
1364
|
+
|GC frequency
|
|
1365
|
+
|Garbage collection cycles
|
|
1366
|
+
|Minimize
|
|
1367
|
+
|===
|
|
1368
|
+
|
|
1369
|
+
.Optimization checklist
|
|
1370
|
+
[example]
|
|
1371
|
+
====
|
|
1372
|
+
* Optimize worker count based on workload type (CPU vs I/O)
|
|
1373
|
+
* Use priority queues for mixed-criticality workloads
|
|
1374
|
+
* Implement backpressure to prevent overload
|
|
1375
|
+
* Monitor queue depth and adjust production rate
|
|
1376
|
+
* Profile memory usage and optimize large objects
|
|
1377
|
+
* Use callbacks for immediate result processing
|
|
1378
|
+
* Enable workflow tracing only when needed
|
|
1379
|
+
* Batch work items where possible
|
|
1380
|
+
====
|
|
1381
|
+
|
|
1382
|
+
== Next steps
|
|
1383
|
+
|
|
1384
|
+
* Read link:design-principles/[Design Principles] for philosophy and rationale
|
|
1385
|
+
* Explore link:core-concepts/[Core Concepts] for component details
|
|
1386
|
+
* Try link:../guides/pipeline-mode/[Pipeline Mode] for batch processing
|
|
1387
|
+
* Try link:../guides/continuous-mode/[Continuous Mode] for long-running services
|
|
1388
|
+
* Learn link:../features/workflows/[Workflows] for complex processing
|
|
1389
|
+
* Review link:../features/error-handling/[Error Handling] strategies
|
|
1390
|
+
* Study link:../reference/examples/[Examples] for real-world patterns
|