poml 0.0.6 → 0.0.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/docs/tutorial/advanced/performance.md +695 -0
- data/docs/tutorial/advanced/tool-registration.md +776 -0
- data/docs/tutorial/basic-usage.md +351 -0
- data/docs/tutorial/components/chat-components.md +552 -0
- data/docs/tutorial/components/formatting.md +623 -0
- data/docs/tutorial/components/index.md +366 -0
- data/docs/tutorial/components/media-components.md +259 -0
- data/docs/tutorial/components/schema-components.md +668 -0
- data/docs/tutorial/index.md +184 -0
- data/docs/tutorial/output-formats.md +688 -0
- data/docs/tutorial/quickstart.md +30 -0
- data/docs/tutorial/template-engine.md +540 -0
- data/lib/poml/components/base.rb +146 -4
- data/lib/poml/components/content.rb +10 -3
- data/lib/poml/components/data.rb +539 -19
- data/lib/poml/components/examples.rb +235 -1
- data/lib/poml/components/formatting.rb +184 -18
- data/lib/poml/components/layout.rb +7 -2
- data/lib/poml/components/lists.rb +69 -35
- data/lib/poml/components/meta.rb +134 -5
- data/lib/poml/components/output_schema.rb +19 -1
- data/lib/poml/components/template.rb +72 -61
- data/lib/poml/components/text.rb +30 -1
- data/lib/poml/components/tool.rb +81 -0
- data/lib/poml/components/tool_definition.rb +339 -10
- data/lib/poml/components/tools.rb +14 -0
- data/lib/poml/components/utilities.rb +34 -18
- data/lib/poml/components.rb +19 -0
- data/lib/poml/context.rb +19 -4
- data/lib/poml/parser.rb +88 -63
- data/lib/poml/renderer.rb +191 -9
- data/lib/poml/template_engine.rb +138 -13
- data/lib/poml/version.rb +1 -1
- data/lib/poml.rb +16 -1
- data/readme.md +154 -27
- metadata +31 -4
- data/TUTORIAL.md +0 -987
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b093fb2077a5a12052a817a3b71038b81c97892bd8ba888165bbbed56cf21d4
|
4
|
+
data.tar.gz: 46cb25b278be930f116c97b46263dead78057289f200274daee79320045b8984
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66b01f839ae216447b86d45c8cb49d016e569e3d42fc91bd82e777abdb4fab466644d4610756f4cedb1bdd4e6171f6e2c177b3773afd2ad5a08c4b9328e9c44f
|
7
|
+
data.tar.gz: 2f107a45b2ee752c6303b6cc64d385bb3c77aa345c9697eeb0942a4221c1984054d4bd256658358b6732d37b44e0ff70112e6d9b84c8b39a6c4392bb93c80938
|
@@ -0,0 +1,695 @@
|
|
1
|
+
# Performance Optimization
|
2
|
+
|
3
|
+
Learn how to optimize POML processing for speed, memory usage, and scalability in production environments.
|
4
|
+
|
5
|
+
## Performance Fundamentals
|
6
|
+
|
7
|
+
### Component Performance Characteristics
|
8
|
+
|
9
|
+
| Component Type | Processing Speed | Memory Usage | I/O Impact |
|
10
|
+
|---------------|------------------|--------------|------------|
|
11
|
+
| Text (`<p>`, `<b>`, `<i>`) | ⚡⚡⚡⚡⚡ | Very Low | None |
|
12
|
+
| Lists (`<list>`, `<item>`) | ⚡⚡⚡⚡ | Low | None |
|
13
|
+
| Templates (`{{}}`, `<if>`, `<for>`) | ⚡⚡⚡ | Medium | None |
|
14
|
+
| File Reading (`<file>`, `<img>`) | ⚡⚡ | High | High |
|
15
|
+
| External Data (`<table>` with URLs) | ⚡ | High | Very High |
|
16
|
+
|
17
|
+
### Optimization Principles
|
18
|
+
|
19
|
+
1. **Minimize I/O Operations** - Cache file reads and external data
|
20
|
+
2. **Limit Loop Iterations** - Use conditions to restrict processing
|
21
|
+
3. **Optimize Template Variables** - Pre-compute complex expressions
|
22
|
+
4. **Choose Appropriate Output Formats** - Match format to use case
|
23
|
+
5. **Enable Caching** - Reuse processed templates
|
24
|
+
|
25
|
+
## Template Engine Optimization
|
26
|
+
|
27
|
+
### Variable Pre-computation
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
# Slow: Complex computation in template
|
31
|
+
<poml>
|
32
|
+
<for variable="item" items="{{items}}">
|
33
|
+
<p>{{item.complex_calculation.expensive_method}}</p>
|
34
|
+
</for>
|
35
|
+
</poml>
|
36
|
+
|
37
|
+
# Fast: Pre-compute values
|
38
|
+
context = {
|
39
|
+
'items' => items.map do |item|
|
40
|
+
item.merge('display_value' => item.complex_calculation.expensive_method)
|
41
|
+
end
|
42
|
+
}
|
43
|
+
|
44
|
+
<poml>
|
45
|
+
<for variable="item" items="{{items}}">
|
46
|
+
<p>{{item.display_value}}</p>
|
47
|
+
</for>
|
48
|
+
</poml>
|
49
|
+
```
|
50
|
+
|
51
|
+
### Conditional Optimization
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# Slow: Check condition inside loop
|
55
|
+
<poml>
|
56
|
+
<for variable="item" items="{{all_items}}">
|
57
|
+
<if condition="{{item.visible}}">
|
58
|
+
<p>{{item.name}}</p>
|
59
|
+
</if>
|
60
|
+
</for>
|
61
|
+
</poml>
|
62
|
+
|
63
|
+
# Fast: Filter before processing
|
64
|
+
context = {
|
65
|
+
'visible_items' => all_items.select(&:visible)
|
66
|
+
}
|
67
|
+
|
68
|
+
<poml>
|
69
|
+
<for variable="item" items="{{visible_items}}">
|
70
|
+
<p>{{item.name}}</p>
|
71
|
+
</for>
|
72
|
+
</poml>
|
73
|
+
```
|
74
|
+
|
75
|
+
### Loop Limiting
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# Potentially slow: Unlimited iteration
|
79
|
+
<poml>
|
80
|
+
<for variable="item" items="{{large_dataset}}">
|
81
|
+
<p>{{item.description}}</p>
|
82
|
+
</for>
|
83
|
+
</poml>
|
84
|
+
|
85
|
+
# Optimized: Limit iteration count
|
86
|
+
<poml>
|
87
|
+
<for variable="item" items="{{large_dataset}}" limit="100">
|
88
|
+
<p>{{item.description}}</p>
|
89
|
+
</for>
|
90
|
+
</poml>
|
91
|
+
|
92
|
+
# Or pre-limit in context
|
93
|
+
context = {
|
94
|
+
'limited_items' => large_dataset.first(100)
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
## File Operation Optimization
|
99
|
+
|
100
|
+
### Caching File Reads
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
class CachedPomlProcessor
|
104
|
+
def initialize
|
105
|
+
@file_cache = {}
|
106
|
+
@poml = POML.new
|
107
|
+
end
|
108
|
+
|
109
|
+
def process_with_cache(template_path, context = {})
|
110
|
+
# Cache file content
|
111
|
+
cached_files = {}
|
112
|
+
|
113
|
+
# Pre-read files referenced in context
|
114
|
+
context.each do |key, value|
|
115
|
+
if key.end_with?('_file') && File.exist?(value)
|
116
|
+
cached_files[value] = File.read(value) unless @file_cache[value]
|
117
|
+
@file_cache[value] ||= cached_files[value]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Process with cached context
|
122
|
+
enhanced_context = context.merge(cached_files)
|
123
|
+
@poml.process_file(template_path, variables: enhanced_context)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
### Lazy File Loading
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# Template using lazy loading
|
132
|
+
<poml>
|
133
|
+
<if condition="{{show_details}}">
|
134
|
+
<file src="{{details_file}}" />
|
135
|
+
</if>
|
136
|
+
|
137
|
+
<if condition="{{include_logs}}">
|
138
|
+
<file src="{{log_file}}" maxLines="50" />
|
139
|
+
</if>
|
140
|
+
</poml>
|
141
|
+
|
142
|
+
# Context controls loading
|
143
|
+
context = {
|
144
|
+
'show_details' => user_requested_details,
|
145
|
+
'include_logs' => debug_mode_enabled,
|
146
|
+
'details_file' => 'path/to/details.txt',
|
147
|
+
'log_file' => 'path/to/debug.log'
|
148
|
+
}
|
149
|
+
```
|
150
|
+
|
151
|
+
### Streaming Large Files
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
class StreamingFileProcessor
|
155
|
+
def process_large_file(file_path, max_size: 1_000_000)
|
156
|
+
if File.size(file_path) > max_size
|
157
|
+
# Stream first portion
|
158
|
+
content = File.open(file_path) do |file|
|
159
|
+
file.read(max_size) + "\n... (truncated)"
|
160
|
+
end
|
161
|
+
else
|
162
|
+
content = File.read(file_path)
|
163
|
+
end
|
164
|
+
|
165
|
+
content
|
166
|
+
end
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
## Memory Management
|
171
|
+
|
172
|
+
### Efficient Data Structures
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
# Memory-heavy: Full object loading
|
176
|
+
context = {
|
177
|
+
'users' => User.all.map(&:to_hash) # Loads all data
|
178
|
+
}
|
179
|
+
|
180
|
+
# Memory-efficient: Lazy loading
|
181
|
+
context = {
|
182
|
+
'users' => User.limit(50).pluck(:id, :name, :email) # Minimal data
|
183
|
+
}
|
184
|
+
|
185
|
+
# For templates
|
186
|
+
<poml>
|
187
|
+
<for variable="user" items="{{users}}">
|
188
|
+
<p>{{user[1]}} ({{user[2]}})</p> <!-- name, email -->
|
189
|
+
</for>
|
190
|
+
</poml>
|
191
|
+
```
|
192
|
+
|
193
|
+
### Garbage Collection Optimization
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
class OptimizedProcessor
|
197
|
+
def process_batch(templates, contexts)
|
198
|
+
results = []
|
199
|
+
|
200
|
+
templates.each_with_index do |template, index|
|
201
|
+
result = POML.new.process(
|
202
|
+
markup: template,
|
203
|
+
variables: contexts[index]
|
204
|
+
)
|
205
|
+
results << result
|
206
|
+
|
207
|
+
# Force GC every 100 items for large batches
|
208
|
+
GC.start if (index + 1) % 100 == 0
|
209
|
+
end
|
210
|
+
|
211
|
+
results
|
212
|
+
end
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
216
|
+
### Memory Pooling
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
class PomlPool
|
220
|
+
def initialize(pool_size: 5)
|
221
|
+
@pool = Array.new(pool_size) { POML.new }
|
222
|
+
@available = @pool.dup
|
223
|
+
@mutex = Mutex.new
|
224
|
+
end
|
225
|
+
|
226
|
+
def process(template, context = {})
|
227
|
+
processor = acquire_processor
|
228
|
+
begin
|
229
|
+
processor.process(markup: template, variables: context)
|
230
|
+
ensure
|
231
|
+
release_processor(processor)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def acquire_processor
|
238
|
+
@mutex.synchronize do
|
239
|
+
@available.pop || POML.new
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def release_processor(processor)
|
244
|
+
@mutex.synchronize do
|
245
|
+
@available.push(processor) if @available.size < @pool.size
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
## Caching Strategies
|
252
|
+
|
253
|
+
### Template Caching
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
class TemplateCache
|
257
|
+
def initialize(max_size: 100, ttl: 3600)
|
258
|
+
@cache = {}
|
259
|
+
@access_times = {}
|
260
|
+
@max_size = max_size
|
261
|
+
@ttl = ttl
|
262
|
+
end
|
263
|
+
|
264
|
+
def get_or_process(template_path, context = {})
|
265
|
+
cache_key = generate_key(template_path, context)
|
266
|
+
|
267
|
+
if cached = get_cached(cache_key)
|
268
|
+
return cached
|
269
|
+
end
|
270
|
+
|
271
|
+
result = POML.new.process_file(template_path, variables: context)
|
272
|
+
store_cached(cache_key, result)
|
273
|
+
result
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def generate_key(template_path, context)
|
279
|
+
"#{template_path}:#{context.hash}"
|
280
|
+
end
|
281
|
+
|
282
|
+
def get_cached(key)
|
283
|
+
return nil unless @cache[key]
|
284
|
+
return nil if expired?(key)
|
285
|
+
|
286
|
+
@access_times[key] = Time.now
|
287
|
+
@cache[key]
|
288
|
+
end
|
289
|
+
|
290
|
+
def store_cached(key, value)
|
291
|
+
evict_if_needed
|
292
|
+
@cache[key] = value
|
293
|
+
@access_times[key] = Time.now
|
294
|
+
end
|
295
|
+
|
296
|
+
def expired?(key)
|
297
|
+
Time.now - @access_times[key] > @ttl
|
298
|
+
end
|
299
|
+
|
300
|
+
def evict_if_needed
|
301
|
+
return if @cache.size < @max_size
|
302
|
+
|
303
|
+
# Remove least recently used
|
304
|
+
lru_key = @access_times.min_by { |k, v| v }.first
|
305
|
+
@cache.delete(lru_key)
|
306
|
+
@access_times.delete(lru_key)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
```
|
310
|
+
|
311
|
+
### Context Caching
|
312
|
+
|
313
|
+
```ruby
|
314
|
+
class ContextCache
|
315
|
+
def initialize
|
316
|
+
@computed_values = {}
|
317
|
+
end
|
318
|
+
|
319
|
+
def enhanced_context(base_context)
|
320
|
+
base_context.merge(computed_values(base_context))
|
321
|
+
end
|
322
|
+
|
323
|
+
private
|
324
|
+
|
325
|
+
def computed_values(context)
|
326
|
+
cache_key = context.hash
|
327
|
+
|
328
|
+
@computed_values[cache_key] ||= {
|
329
|
+
'formatted_date' => format_date(context['date']),
|
330
|
+
'user_summary' => summarize_user(context['user']),
|
331
|
+
'processed_items' => process_items(context['items'])
|
332
|
+
}
|
333
|
+
end
|
334
|
+
|
335
|
+
def format_date(date)
|
336
|
+
return '' unless date
|
337
|
+
Date.parse(date.to_s).strftime('%B %d, %Y')
|
338
|
+
end
|
339
|
+
|
340
|
+
def summarize_user(user)
|
341
|
+
return {} unless user
|
342
|
+
{
|
343
|
+
'display_name' => "#{user['first_name']} #{user['last_name']}",
|
344
|
+
'initials' => "#{user['first_name'][0]}#{user['last_name'][0]}"
|
345
|
+
}
|
346
|
+
end
|
347
|
+
|
348
|
+
def process_items(items)
|
349
|
+
return [] unless items
|
350
|
+
items.map.with_index do |item, index|
|
351
|
+
item.merge(
|
352
|
+
'index' => index + 1,
|
353
|
+
'is_last' => index == items.length - 1
|
354
|
+
)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
```
|
359
|
+
|
360
|
+
## Profiling and Monitoring
|
361
|
+
|
362
|
+
### Performance Measurement
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
require 'benchmark'
|
366
|
+
|
367
|
+
class PomlProfiler
|
368
|
+
def self.profile_processing(template, context = {})
|
369
|
+
results = {}
|
370
|
+
|
371
|
+
# Measure overall processing
|
372
|
+
results[:total] = Benchmark.measure do
|
373
|
+
POML.new.process(markup: template, variables: context)
|
374
|
+
end
|
375
|
+
|
376
|
+
# Measure template parsing
|
377
|
+
results[:parsing] = Benchmark.measure do
|
378
|
+
POML.new.send(:parse_template, template)
|
379
|
+
end
|
380
|
+
|
381
|
+
# Measure variable substitution
|
382
|
+
results[:substitution] = Benchmark.measure do
|
383
|
+
POML.new.send(:substitute_variables, template, context)
|
384
|
+
end
|
385
|
+
|
386
|
+
results
|
387
|
+
end
|
388
|
+
|
389
|
+
def self.profile_components(template)
|
390
|
+
component_counts = Hash.new(0)
|
391
|
+
|
392
|
+
# Count component usage
|
393
|
+
template.scan(/<(\w+)/).each do |match|
|
394
|
+
component_counts[match[0]] += 1
|
395
|
+
end
|
396
|
+
|
397
|
+
component_counts
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Usage
|
402
|
+
template = File.read('complex_template.poml')
|
403
|
+
context = { 'items' => (1..1000).to_a }
|
404
|
+
|
405
|
+
profile = PomlProfiler.profile_processing(template, context)
|
406
|
+
puts "Total time: #{profile[:total].real}s"
|
407
|
+
puts "Parsing time: #{profile[:parsing].real}s"
|
408
|
+
|
409
|
+
components = PomlProfiler.profile_components(template)
|
410
|
+
puts "Component usage: #{components}"
|
411
|
+
```
|
412
|
+
|
413
|
+
### Memory Profiling
|
414
|
+
|
415
|
+
```ruby
|
416
|
+
require 'memory_profiler'
|
417
|
+
|
418
|
+
def profile_memory(template, context)
|
419
|
+
report = MemoryProfiler.report do
|
420
|
+
POML.new.process(markup: template, variables: context)
|
421
|
+
end
|
422
|
+
|
423
|
+
puts "Total allocated: #{report.total_allocated_memsize} bytes"
|
424
|
+
puts "Total retained: #{report.total_retained_memsize} bytes"
|
425
|
+
|
426
|
+
# Top allocations
|
427
|
+
report.allocated_memory_by_class.first(5).each do |allocation|
|
428
|
+
puts "#{allocation[:data]}: #{allocation[:count]} objects"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
```
|
432
|
+
|
433
|
+
### Performance Monitoring
|
434
|
+
|
435
|
+
```ruby
|
436
|
+
class PerformanceMonitor
|
437
|
+
def initialize
|
438
|
+
@metrics = {
|
439
|
+
processing_times: [],
|
440
|
+
memory_usage: [],
|
441
|
+
cache_hits: 0,
|
442
|
+
cache_misses: 0
|
443
|
+
}
|
444
|
+
end
|
445
|
+
|
446
|
+
def track_processing(template_name, &block)
|
447
|
+
start_time = Time.now
|
448
|
+
start_memory = memory_usage
|
449
|
+
|
450
|
+
result = yield
|
451
|
+
|
452
|
+
end_time = Time.now
|
453
|
+
end_memory = memory_usage
|
454
|
+
|
455
|
+
@metrics[:processing_times] << {
|
456
|
+
template: template_name,
|
457
|
+
duration: end_time - start_time,
|
458
|
+
memory_delta: end_memory - start_memory,
|
459
|
+
timestamp: start_time
|
460
|
+
}
|
461
|
+
|
462
|
+
result
|
463
|
+
end
|
464
|
+
|
465
|
+
def cache_hit!
|
466
|
+
@metrics[:cache_hits] += 1
|
467
|
+
end
|
468
|
+
|
469
|
+
def cache_miss!
|
470
|
+
@metrics[:cache_misses] += 1
|
471
|
+
end
|
472
|
+
|
473
|
+
def statistics
|
474
|
+
times = @metrics[:processing_times].map { |m| m[:duration] }
|
475
|
+
|
476
|
+
{
|
477
|
+
avg_processing_time: times.sum / times.length,
|
478
|
+
max_processing_time: times.max,
|
479
|
+
min_processing_time: times.min,
|
480
|
+
cache_hit_rate: cache_hit_rate,
|
481
|
+
total_processes: times.length
|
482
|
+
}
|
483
|
+
end
|
484
|
+
|
485
|
+
private
|
486
|
+
|
487
|
+
def memory_usage
|
488
|
+
`ps -o rss= -p #{Process.pid}`.to_i * 1024
|
489
|
+
end
|
490
|
+
|
491
|
+
def cache_hit_rate
|
492
|
+
total = @metrics[:cache_hits] + @metrics[:cache_misses]
|
493
|
+
return 0 if total == 0
|
494
|
+
(@metrics[:cache_hits].to_f / total * 100).round(2)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
```
|
498
|
+
|
499
|
+
## Production Optimization
|
500
|
+
|
501
|
+
### Asynchronous Processing
|
502
|
+
|
503
|
+
```ruby
|
504
|
+
require 'concurrent-ruby'
|
505
|
+
|
506
|
+
class AsyncPomlProcessor
|
507
|
+
def initialize(pool_size: 10)
|
508
|
+
@executor = Concurrent::ThreadPoolExecutor.new(
|
509
|
+
min_threads: 2,
|
510
|
+
max_threads: pool_size,
|
511
|
+
max_queue: 100
|
512
|
+
)
|
513
|
+
end
|
514
|
+
|
515
|
+
def process_async(templates_and_contexts)
|
516
|
+
futures = templates_and_contexts.map do |template, context|
|
517
|
+
Concurrent::Future.execute(executor: @executor) do
|
518
|
+
POML.new.process(markup: template, variables: context)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
# Wait for all to complete
|
523
|
+
futures.map(&:value)
|
524
|
+
end
|
525
|
+
|
526
|
+
def shutdown
|
527
|
+
@executor.shutdown
|
528
|
+
@executor.wait_for_termination(30)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
```
|
532
|
+
|
533
|
+
### Background Job Integration
|
534
|
+
|
535
|
+
```ruby
|
536
|
+
# Sidekiq job example
|
537
|
+
class PomlProcessingJob
|
538
|
+
include Sidekiq::Job
|
539
|
+
|
540
|
+
sidekiq_options queue: 'poml_processing', retry: 3
|
541
|
+
|
542
|
+
def perform(template_path, context, user_id)
|
543
|
+
result = OptimizedPomlProcessor.process_with_cache(template_path, context)
|
544
|
+
|
545
|
+
# Store result
|
546
|
+
ProcessingResult.create!(
|
547
|
+
user_id: user_id,
|
548
|
+
template_path: template_path,
|
549
|
+
result: result,
|
550
|
+
processed_at: Time.current
|
551
|
+
)
|
552
|
+
rescue StandardError => e
|
553
|
+
Rails.logger.error "POML processing failed: #{e.message}"
|
554
|
+
raise
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
# Usage
|
559
|
+
PomlProcessingJob.perform_async(
|
560
|
+
'templates/report.poml',
|
561
|
+
{ 'user_id' => user.id, 'data' => report_data },
|
562
|
+
user.id
|
563
|
+
)
|
564
|
+
```
|
565
|
+
|
566
|
+
### Load Balancing
|
567
|
+
|
568
|
+
```ruby
|
569
|
+
class DistributedPomlProcessor
|
570
|
+
def initialize(worker_urls)
|
571
|
+
@workers = worker_urls.map { |url| Worker.new(url) }
|
572
|
+
@current_worker = 0
|
573
|
+
end
|
574
|
+
|
575
|
+
def process(template, context)
|
576
|
+
worker = next_worker
|
577
|
+
worker.process_remote(template, context)
|
578
|
+
rescue WorkerError => e
|
579
|
+
# Failover to next worker
|
580
|
+
retry_with_next_worker(template, context, e)
|
581
|
+
end
|
582
|
+
|
583
|
+
private
|
584
|
+
|
585
|
+
def next_worker
|
586
|
+
worker = @workers[@current_worker]
|
587
|
+
@current_worker = (@current_worker + 1) % @workers.length
|
588
|
+
worker
|
589
|
+
end
|
590
|
+
|
591
|
+
def retry_with_next_worker(template, context, original_error)
|
592
|
+
@workers.each do |worker|
|
593
|
+
return worker.process_remote(template, context)
|
594
|
+
rescue WorkerError
|
595
|
+
next
|
596
|
+
end
|
597
|
+
|
598
|
+
raise original_error
|
599
|
+
end
|
600
|
+
end
|
601
|
+
```
|
602
|
+
|
603
|
+
## Performance Best Practices
|
604
|
+
|
605
|
+
### Template Design
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
# ✅ Good: Efficient template structure
|
609
|
+
<poml>
|
610
|
+
<!-- Pre-filter data -->
|
611
|
+
<for variable="item" items="{{priority_items}}">
|
612
|
+
<p><b>{{item.title}}</b></p>
|
613
|
+
<if condition="{{item.details}}">
|
614
|
+
<p>{{item.details}}</p>
|
615
|
+
</if>
|
616
|
+
</for>
|
617
|
+
</poml>
|
618
|
+
|
619
|
+
# ❌ Avoid: Inefficient nested conditions
|
620
|
+
<poml>
|
621
|
+
<for variable="item" items="{{all_items}}">
|
622
|
+
<if condition="{{item.category}} == 'important'">
|
623
|
+
<if condition="{{item.status}} == 'active'">
|
624
|
+
<if condition="{{item.user_can_view}}">
|
625
|
+
<p>{{item.title}}</p>
|
626
|
+
</if>
|
627
|
+
</if>
|
628
|
+
</if>
|
629
|
+
</for>
|
630
|
+
</poml>
|
631
|
+
```
|
632
|
+
|
633
|
+
### Context Preparation
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
# ✅ Good: Optimized context
|
637
|
+
def prepare_context(user, items)
|
638
|
+
{
|
639
|
+
'user_name' => user.full_name,
|
640
|
+
'priority_items' => items
|
641
|
+
.select(&:important?)
|
642
|
+
.select(&:active?)
|
643
|
+
.select { |item| user.can_view?(item) }
|
644
|
+
.map { |item| item.to_hash.slice(:title, :details, :created_at) }
|
645
|
+
}
|
646
|
+
end
|
647
|
+
|
648
|
+
# ❌ Avoid: Heavy context objects
|
649
|
+
def heavy_context(user, items)
|
650
|
+
{
|
651
|
+
'user' => user, # Full ActiveRecord object
|
652
|
+
'all_items' => items, # All items, not filtered
|
653
|
+
'computed_data' => expensive_computation() # Computed every time
|
654
|
+
}
|
655
|
+
end
|
656
|
+
```
|
657
|
+
|
658
|
+
### Monitoring Setup
|
659
|
+
|
660
|
+
```ruby
|
661
|
+
# Application monitoring
|
662
|
+
class PomlMetrics
|
663
|
+
def self.setup_monitoring
|
664
|
+
ActiveSupport::Notifications.subscribe('poml.process') do |name, start, finish, id, payload|
|
665
|
+
duration = finish - start
|
666
|
+
template_name = payload[:template_name]
|
667
|
+
|
668
|
+
# Send to monitoring service
|
669
|
+
StatsD.histogram('poml.processing_time', duration, tags: ["template:#{template_name}"])
|
670
|
+
|
671
|
+
# Log slow processing
|
672
|
+
if duration > 1.0
|
673
|
+
Rails.logger.warn "Slow POML processing: #{template_name} took #{duration}s"
|
674
|
+
end
|
675
|
+
end
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
# Usage in initializer
|
680
|
+
PomlMetrics.setup_monitoring
|
681
|
+
```
|
682
|
+
|
683
|
+
## Next Steps
|
684
|
+
|
685
|
+
1. **Identify Bottlenecks** - Profile your specific use cases
|
686
|
+
2. **Implement Caching** - Start with template and context caching
|
687
|
+
3. **Monitor Performance** - Set up metrics and alerting
|
688
|
+
4. **Optimize Iteratively** - Make incremental improvements
|
689
|
+
5. **Scale Appropriately** - Add async processing for heavy workloads
|
690
|
+
|
691
|
+
For related topics, see:
|
692
|
+
|
693
|
+
- [Error Handling](error-handling.md) for robust error management
|
694
|
+
- [Integration Patterns](../integration/) for production deployment
|
695
|
+
- [Template Engine](../template-engine.md) for optimization techniques
|