fractor 0.1.6 → 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_todo.yml +227 -102
- data/README.adoc +113 -1940
- 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/continuous_chat_common/message_protocol.rb +1 -1
- 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/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/performance_monitoring.rb +120 -0
- data/examples/pipeline_processing/README.adoc +740 -26
- data/examples/pipeline_processing/pipeline_processing.rb +2 -2
- data/examples/priority_work_example.rb +155 -0
- data/examples/producer_subscriber/README.adoc +889 -46
- data/examples/scatter_gather/README.adoc +829 -27
- data/examples/simple/README.adoc +347 -0
- data/examples/specialized_workers/README.adoc +622 -26
- data/examples/specialized_workers/specialized_workers.rb +44 -8
- 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 +60 -65
- 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 +32 -0
- data/lib/fractor/shutdown_handler.rb +168 -0
- data/lib/fractor/signal_handler.rb +80 -0
- data/lib/fractor/supervisor.rb +382 -269
- 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 +20 -0
- data/lib/fractor/work_result.rb +181 -9
- data/lib/fractor/worker.rb +73 -0
- 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 -101
- data/lib/fractor/wrapped_ractor3.rb +161 -0
- data/lib/fractor/wrapped_ractor4.rb +242 -0
- data/lib/fractor.rb +92 -4
- metadata +179 -6
- data/tests/sample.rb.bak +0 -309
- data/tests/sample_working.rb.bak +0 -209
|
@@ -1,45 +1,641 @@
|
|
|
1
1
|
= Specialized Workers Example
|
|
2
2
|
|
|
3
|
-
==
|
|
3
|
+
== Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Demonstrates how to create specialized worker types in Fractor, where each worker class is designed to handle a specific category of work using separate worker pools with independent configurations.
|
|
6
6
|
|
|
7
|
-
==
|
|
7
|
+
== Focus
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
* *Work Type Differentiation*: Each worker specializes in processing a particular category of work
|
|
11
|
-
* *Resource Optimization*: Workers can be tailored to the specific resources needed by each work type
|
|
12
|
-
* *Domain-Specific Processing*: Separate worker implementations for different processing domains
|
|
9
|
+
This example focuses on demonstrating:
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
* Specialized worker classes designed for specific task domains
|
|
12
|
+
* Multiple independent worker pools with different configurations
|
|
13
|
+
* Work type routing to appropriate specialized workers
|
|
14
|
+
* Resource optimization per worker type
|
|
15
|
+
* Independent scaling and tuning for different workloads
|
|
16
|
+
* Comparison with polymorphic worker approach
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
== Architecture
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
.Specialized Worker Pools
|
|
21
|
+
[source]
|
|
22
|
+
----
|
|
23
|
+
Work Items (Mixed Types)
|
|
24
|
+
│
|
|
25
|
+
├─→ ComputeWork (Matrix Multiply)
|
|
26
|
+
├─→ ComputeWork (Image Transform)
|
|
27
|
+
├─→ ComputeWork (Path Finding)
|
|
28
|
+
├─→ DatabaseWork (SELECT)
|
|
29
|
+
├─→ DatabaseWork (INSERT)
|
|
30
|
+
├─→ DatabaseWork (UPDATE)
|
|
31
|
+
└─→ DatabaseWork (DELETE)
|
|
32
|
+
│
|
|
33
|
+
├─────────────────────┬─────────────────────┐
|
|
34
|
+
│ │ │
|
|
35
|
+
▼ ▼ ▼
|
|
36
|
+
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
|
|
37
|
+
│ Compute Supervisor│ │Database Supervisor│ │ │
|
|
38
|
+
│ │ │ │ │ (Independent) │
|
|
39
|
+
└───────────────────┘ └───────────────────┘ └───────────────────┘
|
|
40
|
+
│ │
|
|
41
|
+
│ ComputeWork │ DatabaseWork
|
|
42
|
+
│ Queue │ Queue
|
|
43
|
+
│ │
|
|
44
|
+
┌─────┴─────┐ ┌─────┴─────┐
|
|
45
|
+
│ │ │ │
|
|
46
|
+
▼ ▼ ▼ ▼
|
|
47
|
+
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
|
48
|
+
│Compute│ │Compute│ │Database│ │Database│
|
|
49
|
+
│Worker│ │Worker│ │Worker│ │Worker│
|
|
50
|
+
│Ractor│ │Ractor│ │Ractor│ │Ractor│
|
|
51
|
+
│ 1 │ │ 2 │ │ 1 │ │ 2 │
|
|
52
|
+
└──────┘ └──────┘ └──────┘ └──────┘
|
|
53
|
+
│ │ │ │
|
|
54
|
+
│ │ │ │
|
|
55
|
+
└────┬────┘ └────┬────┘
|
|
56
|
+
│ │
|
|
57
|
+
▼ ▼
|
|
58
|
+
Compute Results Database Results
|
|
59
|
+
(matrix ops, (query results,
|
|
60
|
+
transforms, rows affected,
|
|
61
|
+
path finding) execution time)
|
|
62
|
+
----
|
|
63
|
+
|
|
64
|
+
.Worker Specialization vs Polymorphism
|
|
65
|
+
[source]
|
|
66
|
+
----
|
|
67
|
+
Specialized Workers │ Polymorphic Workers
|
|
68
|
+
(This Example) │ (Multi-Work Type Example)
|
|
69
|
+
─────────────────────────────┼─────────────────────────────
|
|
70
|
+
│
|
|
71
|
+
┌─────────────┐ │ ┌─────────────┐
|
|
72
|
+
│ Supervisor │ │ │ Supervisor │
|
|
73
|
+
│ Pool 1 │ │ │ Pool 1 │
|
|
74
|
+
├─────────────┤ │ ├─────────────┤
|
|
75
|
+
│ComputeWorker│ │ │MultiFormat │
|
|
76
|
+
│ (2 workers)│ │ │ Worker │
|
|
77
|
+
│ │ │ │ (4 workers)│
|
|
78
|
+
│Only handles │ │ │ │
|
|
79
|
+
│ComputeWork │ │ │Handles both │
|
|
80
|
+
└─────────────┘ │ │types │
|
|
81
|
+
│ └─────────────┘
|
|
82
|
+
┌─────────────┐ │
|
|
83
|
+
│ Supervisor │ │ Worker detects type
|
|
84
|
+
│ Pool 2 │ │ and routes internally:
|
|
85
|
+
├─────────────┤ │ if ComputeWork
|
|
86
|
+
│DatabaseWork │ │ process_compute()
|
|
87
|
+
│ (2 workers)│ │ elsif DatabaseWork
|
|
88
|
+
│ │ │ process_database()
|
|
89
|
+
│Only handles │ │
|
|
90
|
+
│DatabaseWork │ │
|
|
91
|
+
└─────────────┘ │
|
|
92
|
+
│
|
|
93
|
+
Benefits: │ Benefits:
|
|
94
|
+
- Clear separation │ - Simpler config
|
|
95
|
+
- Independent scaling │ - Code reuse
|
|
96
|
+
- Resource tuning │ - Flexible mixing
|
|
97
|
+
- Type safety │
|
|
98
|
+
----
|
|
99
|
+
|
|
100
|
+
== Key Components
|
|
101
|
+
|
|
102
|
+
=== Specialized Work Types
|
|
103
|
+
|
|
104
|
+
Define work types for different domains:
|
|
105
|
+
|
|
106
|
+
[source,ruby]
|
|
107
|
+
----
|
|
108
|
+
class ComputeWork < Fractor::Work
|
|
109
|
+
def initialize(data, operation = :default, parameters = {})
|
|
110
|
+
super({
|
|
111
|
+
data: data,
|
|
112
|
+
operation: operation,
|
|
113
|
+
parameters: parameters
|
|
114
|
+
})
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def operation; input[:operation]; end
|
|
118
|
+
def parameters; input[:parameters]; end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class DatabaseWork < Fractor::Work
|
|
122
|
+
def initialize(data = "", query_type = :select, table = "unknown",
|
|
123
|
+
conditions = {})
|
|
124
|
+
super({
|
|
125
|
+
data: data,
|
|
126
|
+
query_type: query_type,
|
|
127
|
+
table: table,
|
|
128
|
+
conditions: conditions
|
|
129
|
+
})
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def query_type; input[:query_type]; end
|
|
133
|
+
def table; input[:table]; end
|
|
134
|
+
def conditions; input[:conditions]; end
|
|
135
|
+
end
|
|
136
|
+
----
|
|
137
|
+
|
|
138
|
+
=== Specialized Workers
|
|
139
|
+
|
|
140
|
+
Each worker handles only its specific work type:
|
|
141
|
+
|
|
142
|
+
[source,ruby]
|
|
143
|
+
----
|
|
144
|
+
class ComputeWorker < Fractor::Worker
|
|
145
|
+
def initialize
|
|
146
|
+
@compute_resources = { memory: 1024, cpu_cores: 4 } # <1>
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def process(work)
|
|
150
|
+
# Type guard - only handle ComputeWork
|
|
151
|
+
unless work.is_a?(ComputeWork) # <2>
|
|
152
|
+
return Fractor::WorkResult.new(
|
|
153
|
+
error: "ComputeWorker can only process ComputeWork",
|
|
154
|
+
work: work
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Perform compute-intensive operations
|
|
159
|
+
result = case work.operation # <3>
|
|
160
|
+
when :matrix_multiply then matrix_multiply(work.data)
|
|
161
|
+
when :image_transform then image_transform(work.data)
|
|
162
|
+
when :path_finding then path_finding(work.data)
|
|
163
|
+
else default_computation(work.data)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
Fractor::WorkResult.new(
|
|
167
|
+
result: {
|
|
168
|
+
operation: work.operation,
|
|
169
|
+
computation_result: result,
|
|
170
|
+
resources_used: @compute_resources
|
|
171
|
+
},
|
|
172
|
+
work: work
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
def matrix_multiply(data) # <4>
|
|
179
|
+
# CPU-intensive matrix operations
|
|
180
|
+
sleep(rand(0.05..0.2))
|
|
181
|
+
"Matrix result..."
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
class DatabaseWorker < Fractor::Worker
|
|
186
|
+
def initialize
|
|
187
|
+
@db_connection = { pool_size: 5, timeout: 30 } # <5>
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def process(work)
|
|
191
|
+
# Type guard - only handle DatabaseWork
|
|
192
|
+
unless work.is_a?(DatabaseWork)
|
|
193
|
+
return Fractor::WorkResult.new(
|
|
194
|
+
error: "DatabaseWorker can only process DatabaseWork",
|
|
195
|
+
work: work
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Perform database operations
|
|
200
|
+
result = case work.query_type # <6>
|
|
201
|
+
when :select then perform_select(work.table)
|
|
202
|
+
when :insert then perform_insert(work.table, work.data)
|
|
203
|
+
when :update then perform_update(work.table, work.data)
|
|
204
|
+
when :delete then perform_delete(work.table)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
Fractor::WorkResult.new(
|
|
208
|
+
result: {
|
|
209
|
+
query_type: work.query_type,
|
|
210
|
+
table: work.table,
|
|
211
|
+
rows_affected: result[:rows_affected],
|
|
212
|
+
execution_time: result[:time]
|
|
213
|
+
},
|
|
214
|
+
work: work
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def perform_select(table) # <7>
|
|
221
|
+
# I/O-intensive database operations
|
|
222
|
+
sleep(rand(0.01..0.1))
|
|
223
|
+
{ rows_affected: rand(0..20), time: 0.05 }
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
----
|
|
227
|
+
<1> Worker-specific resource configuration
|
|
228
|
+
<2> Type guard ensures only correct work type is processed
|
|
229
|
+
<3> Route to operation-specific methods
|
|
230
|
+
<4> CPU-intensive computation methods
|
|
231
|
+
<5> Database-specific resources (connection pool)
|
|
232
|
+
<6> Route to query-specific methods
|
|
233
|
+
<7> I/O-intensive database operations
|
|
234
|
+
|
|
235
|
+
=== Multiple Supervisor Configuration
|
|
236
|
+
|
|
237
|
+
Create separate supervisors for each worker type:
|
|
238
|
+
|
|
239
|
+
[source,ruby]
|
|
240
|
+
----
|
|
241
|
+
class HybridSystem
|
|
242
|
+
def initialize(compute_workers: 2, db_workers: 2)
|
|
243
|
+
# Separate supervisor for compute operations
|
|
244
|
+
@compute_supervisor = Fractor::Supervisor.new(
|
|
245
|
+
worker_pools: [
|
|
246
|
+
{ worker_class: ComputeWorker, num_workers: compute_workers } # <1>
|
|
247
|
+
]
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Separate supervisor for database operations
|
|
251
|
+
@db_supervisor = Fractor::Supervisor.new(
|
|
252
|
+
worker_pools: [
|
|
253
|
+
{ worker_class: DatabaseWorker, num_workers: db_workers } # <2>
|
|
254
|
+
]
|
|
255
|
+
)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def process_mixed_workload(compute_tasks, db_tasks)
|
|
259
|
+
# Route compute work to compute supervisor
|
|
260
|
+
compute_work_items = compute_tasks.map do |task|
|
|
261
|
+
ComputeWork.new(task[:data], task[:operation], task[:parameters])
|
|
262
|
+
end
|
|
263
|
+
@compute_supervisor.add_work_items(compute_work_items) # <3>
|
|
20
264
|
|
|
21
|
-
|
|
265
|
+
# Route database work to database supervisor
|
|
266
|
+
db_work_items = db_tasks.map do |task|
|
|
267
|
+
DatabaseWork.new(task[:data], task[:query_type], task[:table])
|
|
268
|
+
end
|
|
269
|
+
@db_supervisor.add_work_items(db_work_items) # <4>
|
|
22
270
|
|
|
23
|
-
|
|
271
|
+
# Run both supervisors
|
|
272
|
+
@compute_supervisor.run # <5>
|
|
273
|
+
@db_supervisor.run # <6>
|
|
24
274
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
275
|
+
# Collect results from both supervisors
|
|
276
|
+
{
|
|
277
|
+
computation: format_compute_results(@compute_supervisor.results),
|
|
278
|
+
database: format_db_results(@db_supervisor.results)
|
|
279
|
+
}
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
----
|
|
283
|
+
<1> Independent worker pool for compute operations
|
|
284
|
+
<2> Independent worker pool for database operations
|
|
285
|
+
<3> Add compute work to compute supervisor
|
|
286
|
+
<4> Add database work to database supervisor
|
|
287
|
+
<5> Run compute supervisor (can be parallelized)
|
|
288
|
+
<6> Run database supervisor (can be parallelized)
|
|
289
|
+
|
|
290
|
+
== Usage
|
|
30
291
|
|
|
31
|
-
|
|
292
|
+
Run the example from the project root:
|
|
32
293
|
|
|
33
|
-
[source,
|
|
294
|
+
[source,shell]
|
|
34
295
|
----
|
|
35
296
|
ruby examples/specialized_workers/specialized_workers.rb
|
|
36
297
|
----
|
|
37
298
|
|
|
38
299
|
== Expected Output
|
|
39
300
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
301
|
+
[example]
|
|
302
|
+
====
|
|
303
|
+
[source]
|
|
304
|
+
----
|
|
305
|
+
Starting Specialized Workers Example
|
|
306
|
+
===================================
|
|
307
|
+
This example demonstrates two specialized worker types:
|
|
308
|
+
1. ComputeWorker: Handles compute-intensive operations
|
|
309
|
+
2. DatabaseWorker: Handles database operations
|
|
310
|
+
Each worker is designed to process a specific type of work.
|
|
311
|
+
|
|
312
|
+
Processing with 2 compute workers and 2 database workers...
|
|
313
|
+
|
|
314
|
+
Received compute results: 3 items
|
|
315
|
+
Received database results: 4 items
|
|
316
|
+
Processing Results:
|
|
317
|
+
-----------------
|
|
318
|
+
Compute Tasks: 3 submitted, 3 completed
|
|
319
|
+
Database Tasks: 4 submitted, 4 completed
|
|
320
|
+
|
|
321
|
+
Computation Results:
|
|
322
|
+
Task 1 (matrix_multiply):
|
|
323
|
+
Result: Matrix multiplication result: 10x10 matrix, determinant=47
|
|
324
|
+
Resources: {:memory=>1024, :cpu_cores=>4}
|
|
325
|
+
|
|
326
|
+
Task 2 (image_transform):
|
|
327
|
+
Result: Image transformation applied: rotate, scale, blur with parameters {...}
|
|
328
|
+
Resources: {:memory=>1024, :cpu_cores=>4}
|
|
329
|
+
|
|
330
|
+
Task 3 (path_finding):
|
|
331
|
+
Result: Path found using dijkstra: 12 steps, cost=45
|
|
332
|
+
Resources: {:memory=>1024, :cpu_cores=>4}
|
|
333
|
+
|
|
334
|
+
Database Results:
|
|
335
|
+
Query 1 (select on users):
|
|
336
|
+
Rows affected: 15
|
|
337
|
+
Execution time: 0.045 seconds
|
|
338
|
+
Data: [{:id=>1, :name=>"Record 1"}, {:id=>2, :name=>"Record 2"}...]
|
|
339
|
+
|
|
340
|
+
Query 2 (insert on orders):
|
|
341
|
+
Rows affected: 1
|
|
342
|
+
Execution time: 0.023 seconds
|
|
343
|
+
Data: {:id=>5847}
|
|
344
|
+
|
|
345
|
+
Query 3 (update on products):
|
|
346
|
+
Rows affected: 7
|
|
347
|
+
Execution time: 0.038 seconds
|
|
348
|
+
|
|
349
|
+
Query 4 (delete on sessions):
|
|
350
|
+
Rows affected: 3
|
|
351
|
+
Execution time: 0.019 seconds
|
|
352
|
+
|
|
353
|
+
Processing completed in 0.52 seconds
|
|
354
|
+
----
|
|
355
|
+
====
|
|
356
|
+
|
|
357
|
+
== Learning Points
|
|
358
|
+
|
|
359
|
+
=== Worker Specialization Benefits
|
|
360
|
+
|
|
361
|
+
==== Clear Separation of Concerns
|
|
362
|
+
|
|
363
|
+
Each worker focuses on one domain:
|
|
364
|
+
|
|
365
|
+
[source,ruby]
|
|
366
|
+
----
|
|
367
|
+
# ComputeWorker only knows about computations
|
|
368
|
+
class ComputeWorker < Fractor::Worker
|
|
369
|
+
def process(work)
|
|
370
|
+
unless work.is_a?(ComputeWork)
|
|
371
|
+
return error_result("Not a ComputeWork")
|
|
372
|
+
end
|
|
373
|
+
# Only compute operations here
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# DatabaseWorker only knows about database operations
|
|
378
|
+
class DatabaseWorker < Fractor::Worker
|
|
379
|
+
def process(work)
|
|
380
|
+
unless work.is_a?(DatabaseWork)
|
|
381
|
+
return error_result("Not a DatabaseWork")
|
|
382
|
+
end
|
|
383
|
+
# Only database operations here
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
----
|
|
387
|
+
|
|
388
|
+
Benefits:
|
|
389
|
+
- Easier to test (single responsibility)
|
|
390
|
+
- Easier to maintain (clear boundaries)
|
|
391
|
+
- Easier to understand (focused purpose)
|
|
392
|
+
|
|
393
|
+
==== Independent Resource Configuration
|
|
394
|
+
|
|
395
|
+
Tune each worker type separately:
|
|
396
|
+
|
|
397
|
+
[source,ruby]
|
|
398
|
+
----
|
|
399
|
+
# CPU-intensive workers
|
|
400
|
+
@compute_supervisor = Fractor::Supervisor.new(
|
|
401
|
+
worker_pools: [
|
|
402
|
+
{ worker_class: ComputeWorker, num_workers: Etc.nprocessors } # <1>
|
|
403
|
+
]
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# I/O-intensive workers
|
|
407
|
+
@db_supervisor = Fractor::Supervisor.new(
|
|
408
|
+
worker_pools: [
|
|
409
|
+
{ worker_class: DatabaseWorker, num_workers: Etc.nprocessors * 2 } # <2>
|
|
410
|
+
]
|
|
411
|
+
)
|
|
412
|
+
----
|
|
413
|
+
<1> CPU-bound: one worker per core
|
|
414
|
+
<2> I/O-bound: over-subscribe for better throughput
|
|
415
|
+
|
|
416
|
+
==== Independent Scaling
|
|
417
|
+
|
|
418
|
+
Scale each worker type based on workload:
|
|
419
|
+
|
|
420
|
+
[source,ruby]
|
|
421
|
+
----
|
|
422
|
+
# Dynamic scaling based on queue sizes
|
|
423
|
+
compute_workers = compute_queue.size > 100 ? 8 : 4
|
|
424
|
+
db_workers = db_queue.size > 50 ? 10 : 5
|
|
425
|
+
|
|
426
|
+
system = HybridSystem.new(
|
|
427
|
+
compute_workers: compute_workers,
|
|
428
|
+
db_workers: db_workers
|
|
429
|
+
)
|
|
430
|
+
----
|
|
431
|
+
|
|
432
|
+
=== Comparison with Polymorphic Approach
|
|
433
|
+
|
|
434
|
+
==== When to Use Specialized Workers
|
|
435
|
+
|
|
436
|
+
Use specialized workers when:
|
|
437
|
+
|
|
438
|
+
* **Different resource profiles**: CPU-intensive vs I/O-intensive
|
|
439
|
+
* **Independent scaling needs**: Different worker counts per type
|
|
440
|
+
* **Clear domain boundaries**: Distinct processing logic
|
|
441
|
+
* **Type safety requirements**: Prevent wrong work types
|
|
442
|
+
* **Team specialization**: Different teams maintain different workers
|
|
443
|
+
|
|
444
|
+
Example:
|
|
445
|
+
[source,ruby]
|
|
446
|
+
----
|
|
447
|
+
# Good: Clear separation for different resource needs
|
|
448
|
+
system = HybridSystem.new(
|
|
449
|
+
compute_workers: 4, # CPU-bound
|
|
450
|
+
db_workers: 10, # I/O-bound
|
|
451
|
+
network_workers: 20 # Network I/O-bound
|
|
452
|
+
)
|
|
453
|
+
----
|
|
454
|
+
|
|
455
|
+
==== When to Use Polymorphic Workers
|
|
456
|
+
|
|
457
|
+
Use polymorphic workers (link:../multi_work_type/README.adoc[Multi-Work Type Example]) when:
|
|
458
|
+
|
|
459
|
+
* **Similar resource profiles**: All work types use similar resources
|
|
460
|
+
* **Unified scaling**: Same worker count for all types
|
|
461
|
+
* **Flexible mixing**: Work types can be intermixed freely
|
|
462
|
+
* **Simpler configuration**: Single worker pool
|
|
463
|
+
|
|
464
|
+
Example:
|
|
465
|
+
[source,ruby]
|
|
466
|
+
----
|
|
467
|
+
# Good: All types have similar processing characteristics
|
|
468
|
+
supervisor = Fractor::Supervisor.new(
|
|
469
|
+
worker_pools: [
|
|
470
|
+
{ worker_class: ContentProcessor } # Handles text, images, videos
|
|
471
|
+
]
|
|
472
|
+
)
|
|
473
|
+
----
|
|
474
|
+
|
|
475
|
+
=== Worker Pool Management
|
|
476
|
+
|
|
477
|
+
==== Sequential Execution
|
|
478
|
+
|
|
479
|
+
Run supervisors sequentially:
|
|
480
|
+
|
|
481
|
+
[source,ruby]
|
|
482
|
+
----
|
|
483
|
+
@compute_supervisor.run # Wait for compute to finish
|
|
484
|
+
@db_supervisor.run # Then run database operations
|
|
485
|
+
----
|
|
486
|
+
|
|
487
|
+
Use when:
|
|
488
|
+
- Later operations depend on earlier ones
|
|
489
|
+
- Memory constraints require sequential processing
|
|
490
|
+
- Resource contention must be avoided
|
|
491
|
+
|
|
492
|
+
==== Parallel Execution
|
|
493
|
+
|
|
494
|
+
Run supervisors in threads:
|
|
495
|
+
|
|
496
|
+
[source,ruby]
|
|
497
|
+
----
|
|
498
|
+
threads = [
|
|
499
|
+
Thread.new { @compute_supervisor.run },
|
|
500
|
+
Thread.new { @db_supervisor.run }
|
|
501
|
+
]
|
|
502
|
+
threads.each(&:join)
|
|
503
|
+
----
|
|
504
|
+
|
|
505
|
+
Use when:
|
|
506
|
+
- Operations are independent
|
|
507
|
+
- System resources allow parallel execution
|
|
508
|
+
- Faster completion time is important
|
|
509
|
+
|
|
510
|
+
==== Hybrid Execution
|
|
511
|
+
|
|
512
|
+
Mix sequential and parallel:
|
|
513
|
+
|
|
514
|
+
[source,ruby]
|
|
515
|
+
----
|
|
516
|
+
# Phase 1: Extract and validate (parallel)
|
|
517
|
+
extract_thread = Thread.new { @extract_supervisor.run }
|
|
518
|
+
validate_thread = Thread.new { @validate_supervisor.run }
|
|
519
|
+
[extract_thread, validate_thread].each(&:join)
|
|
520
|
+
|
|
521
|
+
# Phase 2: Transform (sequential, depends on Phase 1)
|
|
522
|
+
@transform_supervisor.run
|
|
523
|
+
|
|
524
|
+
# Phase 3: Load (sequential, depends on Phase 2)
|
|
525
|
+
@load_supervisor.run
|
|
526
|
+
----
|
|
527
|
+
|
|
528
|
+
=== Resource Optimization Patterns
|
|
529
|
+
|
|
530
|
+
==== Memory-Bound Workers
|
|
531
|
+
|
|
532
|
+
Limit workers to prevent memory exhaustion:
|
|
533
|
+
|
|
534
|
+
[source,ruby]
|
|
535
|
+
----
|
|
536
|
+
class LargeDataWorker < Fractor::Worker
|
|
537
|
+
MAX_WORKERS = 2 # Limit due to memory usage
|
|
538
|
+
|
|
539
|
+
def process(work)
|
|
540
|
+
# Process large datasets
|
|
541
|
+
large_dataset = load_dataset(work.data) # 1GB+
|
|
542
|
+
process_dataset(large_dataset)
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
supervisor = Fractor::Supervisor.new(
|
|
547
|
+
worker_pools: [
|
|
548
|
+
{ worker_class: LargeDataWorker, num_workers: LargeDataWorker::MAX_WORKERS }
|
|
549
|
+
]
|
|
550
|
+
)
|
|
551
|
+
----
|
|
552
|
+
|
|
553
|
+
==== CPU-Bound Workers
|
|
554
|
+
|
|
555
|
+
Match CPU cores:
|
|
556
|
+
|
|
557
|
+
[source,ruby]
|
|
558
|
+
----
|
|
559
|
+
class CpuIntensiveWorker < Fractor::Worker
|
|
560
|
+
def process(work)
|
|
561
|
+
# CPU-intensive computation
|
|
562
|
+
complex_algorithm(work.data)
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
supervisor = Fractor::Supervisor.new(
|
|
567
|
+
worker_pools: [
|
|
568
|
+
{ worker_class: CpuIntensiveWorker, num_workers: Etc.nprocessors }
|
|
569
|
+
]
|
|
570
|
+
)
|
|
571
|
+
----
|
|
572
|
+
|
|
573
|
+
==== I/O-Bound Workers
|
|
574
|
+
|
|
575
|
+
Over-subscribe for better throughput:
|
|
576
|
+
|
|
577
|
+
[source,ruby]
|
|
578
|
+
----
|
|
579
|
+
class NetworkWorker < Fractor::Worker
|
|
580
|
+
def process(work)
|
|
581
|
+
# Network I/O - spends most time waiting
|
|
582
|
+
http_client.get(work.url)
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
supervisor = Fractor::Supervisor.new(
|
|
587
|
+
worker_pools: [
|
|
588
|
+
{ worker_class: NetworkWorker, num_workers: Etc.nprocessors * 3 }
|
|
589
|
+
]
|
|
590
|
+
)
|
|
591
|
+
----
|
|
592
|
+
|
|
593
|
+
== Use Cases
|
|
594
|
+
|
|
595
|
+
=== ETL Pipeline
|
|
596
|
+
|
|
597
|
+
Different workers for each stage:
|
|
598
|
+
|
|
599
|
+
[source,ruby]
|
|
600
|
+
----
|
|
601
|
+
etl_system = ETLSystem.new(
|
|
602
|
+
extract_workers: 10, # I/O-bound: fetch from APIs
|
|
603
|
+
transform_workers: 4, # CPU-bound: data transformation
|
|
604
|
+
load_workers: 5 # I/O-bound: write to database
|
|
605
|
+
)
|
|
606
|
+
----
|
|
607
|
+
|
|
608
|
+
=== Microservices
|
|
609
|
+
|
|
610
|
+
Specialized workers for each service:
|
|
611
|
+
|
|
612
|
+
[source,ruby]
|
|
613
|
+
----
|
|
614
|
+
microservices = MicroserviceOrchestrator.new(
|
|
615
|
+
auth_workers: 8, # Authentication service
|
|
616
|
+
payment_workers: 4, # Payment processing
|
|
617
|
+
notification_workers: 12, # Email/SMS notifications
|
|
618
|
+
analytics_workers: 6 # Analytics processing
|
|
619
|
+
)
|
|
620
|
+
----
|
|
621
|
+
|
|
622
|
+
=== Multi-Tenant System
|
|
623
|
+
|
|
624
|
+
Workers per tenant category:
|
|
625
|
+
|
|
626
|
+
[source,ruby]
|
|
627
|
+
----
|
|
628
|
+
multi_tenant = MultiTenantSystem.new(
|
|
629
|
+
premium_workers: 10, # High-priority tenants
|
|
630
|
+
standard_workers: 5, # Standard tenants
|
|
631
|
+
trial_workers: 2 # Trial tenants
|
|
632
|
+
)
|
|
633
|
+
----
|
|
634
|
+
|
|
635
|
+
== Next Steps
|
|
636
|
+
|
|
637
|
+
After understanding specialized workers, explore:
|
|
638
|
+
|
|
639
|
+
* link:../multi_work_type/README.adoc[Multi-Work Type] - Polymorphic worker approach for comparison
|
|
640
|
+
* link:../pipeline_processing/README.adoc[Pipeline Processing] - Sequential multi-stage processing
|
|
641
|
+
* link:../simple/README.adoc[Simple Example] - Basic Fractor concepts
|