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.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +227 -102
  3. data/README.adoc +113 -1940
  4. data/docs/.lycheeignore +16 -0
  5. data/docs/Gemfile +24 -0
  6. data/docs/README.md +157 -0
  7. data/docs/_config.yml +151 -0
  8. data/docs/_features/error-handling.adoc +1192 -0
  9. data/docs/_features/index.adoc +80 -0
  10. data/docs/_features/monitoring.adoc +589 -0
  11. data/docs/_features/signal-handling.adoc +202 -0
  12. data/docs/_features/workflows.adoc +1235 -0
  13. data/docs/_guides/continuous-mode.adoc +736 -0
  14. data/docs/_guides/cookbook.adoc +1133 -0
  15. data/docs/_guides/index.adoc +55 -0
  16. data/docs/_guides/pipeline-mode.adoc +730 -0
  17. data/docs/_guides/troubleshooting.adoc +358 -0
  18. data/docs/_pages/architecture.adoc +1390 -0
  19. data/docs/_pages/core-concepts.adoc +1392 -0
  20. data/docs/_pages/design-principles.adoc +862 -0
  21. data/docs/_pages/getting-started.adoc +290 -0
  22. data/docs/_pages/installation.adoc +143 -0
  23. data/docs/_reference/api.adoc +1080 -0
  24. data/docs/_reference/error-reporting.adoc +670 -0
  25. data/docs/_reference/examples.adoc +181 -0
  26. data/docs/_reference/index.adoc +96 -0
  27. data/docs/_reference/troubleshooting.adoc +862 -0
  28. data/docs/_tutorials/complex-workflows.adoc +1022 -0
  29. data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
  30. data/docs/_tutorials/first-application.adoc +384 -0
  31. data/docs/_tutorials/index.adoc +48 -0
  32. data/docs/_tutorials/long-running-services.adoc +931 -0
  33. data/docs/assets/images/favicon-16.png +0 -0
  34. data/docs/assets/images/favicon-32.png +0 -0
  35. data/docs/assets/images/favicon-48.png +0 -0
  36. data/docs/assets/images/favicon.ico +0 -0
  37. data/docs/assets/images/favicon.png +0 -0
  38. data/docs/assets/images/favicon.svg +45 -0
  39. data/docs/assets/images/fractor-icon.svg +49 -0
  40. data/docs/assets/images/fractor-logo.svg +61 -0
  41. data/docs/index.adoc +131 -0
  42. data/docs/lychee.toml +39 -0
  43. data/examples/api_aggregator/README.adoc +627 -0
  44. data/examples/api_aggregator/api_aggregator.rb +376 -0
  45. data/examples/auto_detection/README.adoc +407 -29
  46. data/examples/continuous_chat_common/message_protocol.rb +1 -1
  47. data/examples/error_reporting.rb +207 -0
  48. data/examples/file_processor/README.adoc +170 -0
  49. data/examples/file_processor/file_processor.rb +615 -0
  50. data/examples/file_processor/sample_files/invalid.csv +1 -0
  51. data/examples/file_processor/sample_files/orders.xml +24 -0
  52. data/examples/file_processor/sample_files/products.json +23 -0
  53. data/examples/file_processor/sample_files/users.csv +6 -0
  54. data/examples/hierarchical_hasher/README.adoc +629 -41
  55. data/examples/image_processor/README.adoc +610 -0
  56. data/examples/image_processor/image_processor.rb +349 -0
  57. data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
  58. data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
  59. data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
  60. data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
  61. data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
  62. data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
  63. data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
  64. data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
  65. data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
  66. data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
  67. data/examples/image_processor/test_images/sample_1.png +1 -0
  68. data/examples/image_processor/test_images/sample_10.png +1 -0
  69. data/examples/image_processor/test_images/sample_2.png +1 -0
  70. data/examples/image_processor/test_images/sample_3.png +1 -0
  71. data/examples/image_processor/test_images/sample_4.png +1 -0
  72. data/examples/image_processor/test_images/sample_5.png +1 -0
  73. data/examples/image_processor/test_images/sample_6.png +1 -0
  74. data/examples/image_processor/test_images/sample_7.png +1 -0
  75. data/examples/image_processor/test_images/sample_8.png +1 -0
  76. data/examples/image_processor/test_images/sample_9.png +1 -0
  77. data/examples/log_analyzer/README.adoc +662 -0
  78. data/examples/log_analyzer/log_analyzer.rb +579 -0
  79. data/examples/log_analyzer/sample_logs/apache.log +20 -0
  80. data/examples/log_analyzer/sample_logs/json.log +15 -0
  81. data/examples/log_analyzer/sample_logs/nginx.log +15 -0
  82. data/examples/log_analyzer/sample_logs/rails.log +29 -0
  83. data/examples/multi_work_type/README.adoc +576 -26
  84. data/examples/performance_monitoring.rb +120 -0
  85. data/examples/pipeline_processing/README.adoc +740 -26
  86. data/examples/pipeline_processing/pipeline_processing.rb +2 -2
  87. data/examples/priority_work_example.rb +155 -0
  88. data/examples/producer_subscriber/README.adoc +889 -46
  89. data/examples/scatter_gather/README.adoc +829 -27
  90. data/examples/simple/README.adoc +347 -0
  91. data/examples/specialized_workers/README.adoc +622 -26
  92. data/examples/specialized_workers/specialized_workers.rb +44 -8
  93. data/examples/stream_processor/README.adoc +206 -0
  94. data/examples/stream_processor/stream_processor.rb +284 -0
  95. data/examples/web_scraper/README.adoc +625 -0
  96. data/examples/web_scraper/web_scraper.rb +285 -0
  97. data/examples/workflow/README.adoc +406 -0
  98. data/examples/workflow/circuit_breaker/README.adoc +360 -0
  99. data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
  100. data/examples/workflow/conditional/README.adoc +483 -0
  101. data/examples/workflow/conditional/conditional_workflow.rb +215 -0
  102. data/examples/workflow/dead_letter_queue/README.adoc +374 -0
  103. data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
  104. data/examples/workflow/fan_out/README.adoc +381 -0
  105. data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
  106. data/examples/workflow/retry/README.adoc +248 -0
  107. data/examples/workflow/retry/retry_workflow.rb +195 -0
  108. data/examples/workflow/simple_linear/README.adoc +267 -0
  109. data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
  110. data/examples/workflow/simplified/README.adoc +329 -0
  111. data/examples/workflow/simplified/simplified_workflow.rb +222 -0
  112. data/exe/fractor +10 -0
  113. data/lib/fractor/cli.rb +288 -0
  114. data/lib/fractor/configuration.rb +307 -0
  115. data/lib/fractor/continuous_server.rb +60 -65
  116. data/lib/fractor/error_formatter.rb +72 -0
  117. data/lib/fractor/error_report_generator.rb +152 -0
  118. data/lib/fractor/error_reporter.rb +244 -0
  119. data/lib/fractor/error_statistics.rb +147 -0
  120. data/lib/fractor/execution_tracer.rb +162 -0
  121. data/lib/fractor/logger.rb +230 -0
  122. data/lib/fractor/main_loop_handler.rb +406 -0
  123. data/lib/fractor/main_loop_handler3.rb +135 -0
  124. data/lib/fractor/main_loop_handler4.rb +299 -0
  125. data/lib/fractor/performance_metrics_collector.rb +181 -0
  126. data/lib/fractor/performance_monitor.rb +215 -0
  127. data/lib/fractor/performance_report_generator.rb +202 -0
  128. data/lib/fractor/priority_work.rb +93 -0
  129. data/lib/fractor/priority_work_queue.rb +189 -0
  130. data/lib/fractor/result_aggregator.rb +32 -0
  131. data/lib/fractor/shutdown_handler.rb +168 -0
  132. data/lib/fractor/signal_handler.rb +80 -0
  133. data/lib/fractor/supervisor.rb +382 -269
  134. data/lib/fractor/supervisor_logger.rb +88 -0
  135. data/lib/fractor/version.rb +1 -1
  136. data/lib/fractor/work.rb +12 -0
  137. data/lib/fractor/work_distribution_manager.rb +151 -0
  138. data/lib/fractor/work_queue.rb +20 -0
  139. data/lib/fractor/work_result.rb +181 -9
  140. data/lib/fractor/worker.rb +73 -0
  141. data/lib/fractor/workflow/builder.rb +210 -0
  142. data/lib/fractor/workflow/chain_builder.rb +169 -0
  143. data/lib/fractor/workflow/circuit_breaker.rb +183 -0
  144. data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
  145. data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
  146. data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
  147. data/lib/fractor/workflow/execution_hooks.rb +39 -0
  148. data/lib/fractor/workflow/execution_strategy.rb +225 -0
  149. data/lib/fractor/workflow/execution_trace.rb +134 -0
  150. data/lib/fractor/workflow/helpers.rb +191 -0
  151. data/lib/fractor/workflow/job.rb +290 -0
  152. data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
  153. data/lib/fractor/workflow/logger.rb +110 -0
  154. data/lib/fractor/workflow/pre_execution_context.rb +193 -0
  155. data/lib/fractor/workflow/retry_config.rb +156 -0
  156. data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
  157. data/lib/fractor/workflow/retry_strategy.rb +93 -0
  158. data/lib/fractor/workflow/structured_logger.rb +30 -0
  159. data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
  160. data/lib/fractor/workflow/visualizer.rb +211 -0
  161. data/lib/fractor/workflow/workflow_context.rb +132 -0
  162. data/lib/fractor/workflow/workflow_executor.rb +669 -0
  163. data/lib/fractor/workflow/workflow_result.rb +55 -0
  164. data/lib/fractor/workflow/workflow_validator.rb +295 -0
  165. data/lib/fractor/workflow.rb +333 -0
  166. data/lib/fractor/wrapped_ractor.rb +66 -101
  167. data/lib/fractor/wrapped_ractor3.rb +161 -0
  168. data/lib/fractor/wrapped_ractor4.rb +242 -0
  169. data/lib/fractor.rb +92 -4
  170. metadata +179 -6
  171. data/tests/sample.rb.bak +0 -309
  172. data/tests/sample_working.rb.bak +0 -209
@@ -1,45 +1,641 @@
1
1
  = Specialized Workers Example
2
2
 
3
- == Overview
3
+ == Purpose
4
4
 
5
- This example demonstrates how to create specialized worker types in Fractor, each designed to handle specific kinds of work. This pattern is useful when different work items require fundamentally different processing approaches.
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
- == Key Concepts
7
+ == Focus
8
8
 
9
- * *Specialized Workers*: Worker classes designed for specific types of tasks
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
- == Example Explanation
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
- This example implements two specialized worker types:
18
+ == Architecture
17
19
 
18
- 1. *ComputeWorker*: Handles compute-intensive operations like matrix multiplication, image transformations, and path finding
19
- 2. *DatabaseWorker*: Handles database operations like queries, insertions, updates, and deletions
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
- Each worker is optimized for its specific task domain and processes only the work types it is designed to handle.
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
- == Features Demonstrated
271
+ # Run both supervisors
272
+ @compute_supervisor.run # <5>
273
+ @db_supervisor.run # <6>
24
274
 
25
- * Creating specialized worker types to handle different categories of work
26
- * Routing work items to the appropriate worker type
27
- * Resource optimization for different processing needs
28
- * Independent error handling for each worker type
29
- * Combining results from different worker types
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
- == Running the Example
292
+ Run the example from the project root:
32
293
 
33
- [source,sh]
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
- The example will show:
41
- * Creation of different worker types
42
- * Processing of specialized work items by their corresponding workers
43
- * Performance metrics for each work type
44
- * Separate results from each worker type
45
- * Overall processing statistics
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