kbs 0.0.1 → 0.2.0

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +52 -0
  3. data/CHANGELOG.md +68 -2
  4. data/README.md +291 -362
  5. data/docs/advanced/custom-persistence.md +775 -0
  6. data/docs/advanced/debugging.md +726 -0
  7. data/docs/advanced/index.md +8 -0
  8. data/docs/advanced/performance.md +865 -0
  9. data/docs/advanced/testing.md +827 -0
  10. data/docs/api/blackboard.md +1157 -0
  11. data/docs/api/engine.md +1047 -0
  12. data/docs/api/facts.md +1212 -0
  13. data/docs/api/index.md +12 -0
  14. data/docs/api/rules.md +1104 -0
  15. data/docs/architecture/blackboard.md +544 -0
  16. data/docs/architecture/index.md +277 -0
  17. data/docs/architecture/network-structure.md +343 -0
  18. data/docs/architecture/rete-algorithm.md +737 -0
  19. data/docs/assets/css/custom.css +83 -0
  20. data/docs/assets/images/blackboard-architecture.svg +136 -0
  21. data/docs/assets/images/compiled-network.svg +101 -0
  22. data/docs/assets/images/fact-assertion-flow.svg +117 -0
  23. data/docs/assets/images/fact-rule-relationship.svg +65 -0
  24. data/docs/assets/images/fact-structure.svg +42 -0
  25. data/docs/assets/images/inference-cycle.svg +47 -0
  26. data/docs/assets/images/kb-components.svg +43 -0
  27. data/docs/assets/images/kbs.jpg +0 -0
  28. data/docs/assets/images/pattern-matching-trace.svg +136 -0
  29. data/docs/assets/images/rete-network-layers.svg +96 -0
  30. data/docs/assets/images/rule-structure.svg +44 -0
  31. data/docs/assets/images/system-layers.svg +69 -0
  32. data/docs/assets/images/trading-signal-network.svg +139 -0
  33. data/docs/assets/js/mathjax.js +17 -0
  34. data/docs/examples/index.md +223 -0
  35. data/docs/guides/blackboard-memory.md +589 -0
  36. data/docs/guides/dsl.md +1321 -0
  37. data/docs/guides/facts.md +652 -0
  38. data/docs/guides/getting-started.md +385 -0
  39. data/docs/guides/index.md +23 -0
  40. data/docs/guides/negation.md +529 -0
  41. data/docs/guides/pattern-matching.md +561 -0
  42. data/docs/guides/persistence.md +451 -0
  43. data/docs/guides/variable-binding.md +491 -0
  44. data/docs/guides/writing-rules.md +914 -0
  45. data/docs/index.md +155 -0
  46. data/docs/installation.md +156 -0
  47. data/docs/quick-start.md +221 -0
  48. data/docs/what-is-a-fact.md +694 -0
  49. data/docs/what-is-a-knowledge-base.md +350 -0
  50. data/docs/what-is-a-rule.md +833 -0
  51. data/examples/.gitignore +1 -0
  52. data/examples/README.md +2 -2
  53. data/examples/advanced_example.rb +2 -2
  54. data/examples/advanced_example_dsl.rb +224 -0
  55. data/examples/ai_enhanced_kbs.rb +1 -1
  56. data/examples/ai_enhanced_kbs_dsl.rb +538 -0
  57. data/examples/blackboard_demo_dsl.rb +50 -0
  58. data/examples/car_diagnostic.rb +1 -1
  59. data/examples/car_diagnostic_dsl.rb +54 -0
  60. data/examples/concurrent_inference_demo.rb +5 -6
  61. data/examples/concurrent_inference_demo_dsl.rb +362 -0
  62. data/examples/csv_trading_system.rb +1 -1
  63. data/examples/csv_trading_system_dsl.rb +525 -0
  64. data/examples/iot_demo_using_dsl.rb +1 -1
  65. data/examples/portfolio_rebalancing_system.rb +2 -2
  66. data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
  67. data/examples/redis_trading_demo_dsl.rb +177 -0
  68. data/examples/rule_source_demo.rb +123 -0
  69. data/examples/run_all.rb +50 -0
  70. data/examples/run_all_dsl.rb +49 -0
  71. data/examples/stock_trading_advanced.rb +1 -1
  72. data/examples/stock_trading_advanced_dsl.rb +404 -0
  73. data/examples/temp_dsl.txt +9392 -0
  74. data/examples/timestamped_trading.rb +1 -1
  75. data/examples/timestamped_trading_dsl.rb +258 -0
  76. data/examples/trading_demo.rb +1 -1
  77. data/examples/trading_demo_dsl.rb +322 -0
  78. data/examples/working_demo.rb +1 -1
  79. data/examples/working_demo_dsl.rb +160 -0
  80. data/lib/kbs/blackboard/engine.rb +3 -3
  81. data/lib/kbs/blackboard/fact.rb +1 -1
  82. data/lib/kbs/condition.rb +1 -1
  83. data/lib/kbs/decompiler.rb +204 -0
  84. data/lib/kbs/dsl/knowledge_base.rb +101 -2
  85. data/lib/kbs/dsl/variable.rb +1 -1
  86. data/lib/kbs/dsl.rb +3 -1
  87. data/lib/kbs/{rete_engine.rb → engine.rb} +42 -1
  88. data/lib/kbs/fact.rb +1 -1
  89. data/lib/kbs/version.rb +1 -1
  90. data/lib/kbs.rb +15 -13
  91. data/mkdocs.yml +181 -0
  92. metadata +74 -9
  93. data/examples/stock_trading_system.rb.bak +0 -563
@@ -0,0 +1,865 @@
1
+ # Performance Tuning
2
+
3
+ Optimize KBS applications for speed, scalability, and efficiency. This guide covers profiling, benchmarking, rule optimization, and storage backend selection.
4
+
5
+ ## Performance Overview
6
+
7
+ KBS performance depends on:
8
+
9
+ 1. **Rule Complexity** - Number of conditions, predicates, and joins
10
+ 2. **Fact Volume** - Size of working memory
11
+ 3. **Network Structure** - Shared nodes and network branching
12
+ 4. **Storage Backend** - SQLite, Redis, or in-memory
13
+ 5. **Action Efficiency** - Time spent in rule actions
14
+
15
+ ## Benchmarking
16
+
17
+ ### Basic Benchmark
18
+
19
+ ```ruby
20
+ require 'benchmark'
21
+ require 'kbs'
22
+
23
+ kb = KBS.knowledge_base do
24
+ rule "simple_rule" do
25
+ on :fact, value: :v?
26
+ perform do |facts, bindings|
27
+ # Simple action
28
+ end
29
+ end
30
+ end
31
+
32
+ # Benchmark fact addition
33
+ time = Benchmark.measure do
34
+ 10_000.times do |i|
35
+ kb.fact :fact, value: i
36
+ end
37
+ end
38
+
39
+ puts "Added 10,000 facts in #{time.real} seconds"
40
+ puts "#{(10_000 / time.real).round(2)} facts/second"
41
+
42
+ # Benchmark engine run
43
+ time = Benchmark.measure do
44
+ kb.run
45
+ end
46
+
47
+ puts "Ran engine in #{time.real} seconds"
48
+ ```
49
+
50
+ ### Comprehensive Benchmark
51
+
52
+ ```ruby
53
+ require 'benchmark'
54
+
55
+ class KBSBenchmark
56
+ def initialize(engine_type: :memory)
57
+ @engine_type = engine_type
58
+ @results = {}
59
+ end
60
+
61
+ def setup_engine
62
+ case @engine_type
63
+ when :memory
64
+ # Return a new knowledge base context
65
+ @kb_context = :memory
66
+ when :blackboard_sqlite
67
+ @kb_context = :blackboard_sqlite
68
+ @db_path = ':memory:'
69
+ when :blackboard_redis
70
+ require 'kbs/blackboard/persistence/redis_store'
71
+ @kb_context = :blackboard_redis
72
+ @store = KBS::Blackboard::Persistence::RedisStore.new(
73
+ url: 'redis://localhost:6379/15' # Test database
74
+ )
75
+ end
76
+ end
77
+
78
+ def benchmark_fact_addition(count: 10_000)
79
+ setup_engine
80
+
81
+ kb = case @kb_context
82
+ when :memory
83
+ KBS.knowledge_base do
84
+ # Add facts in benchmark block
85
+ end
86
+ when :blackboard_sqlite
87
+ engine = KBS::Blackboard::Engine.new(db_path: @db_path)
88
+ KBS.knowledge_base(engine: engine)
89
+ when :blackboard_redis
90
+ engine = KBS::Blackboard::Engine.new(store: @store)
91
+ KBS.knowledge_base(engine: engine)
92
+ end
93
+
94
+ time = Benchmark.measure do
95
+ count.times do |i|
96
+ kb.fact :fact, id: i, value: rand(1000)
97
+ end
98
+ end
99
+
100
+ @results[:fact_addition] = {
101
+ count: count,
102
+ time: time.real,
103
+ rate: (count / time.real).round(2)
104
+ }
105
+ end
106
+
107
+ def benchmark_simple_rules(fact_count: 1000, rule_count: 10)
108
+ setup_engine
109
+
110
+ kb = case @kb_context
111
+ when :memory
112
+ KBS.knowledge_base do
113
+ # Add rules
114
+ rule_count.times do |i|
115
+ rule "rule_#{i}" do
116
+ on :fact, value: :v?
117
+ perform do |facts, bindings|
118
+ # Minimal action
119
+ end
120
+ end
121
+ end
122
+
123
+ # Add facts
124
+ fact_count.times do |i|
125
+ fact :fact, value: i
126
+ end
127
+ end
128
+ when :blackboard_sqlite
129
+ engine = KBS::Blackboard::Engine.new(db_path: @db_path)
130
+ kb = KBS.knowledge_base(engine: engine) do
131
+ rule_count.times do |i|
132
+ rule "rule_#{i}" do
133
+ on :fact, value: :v?
134
+ perform { }
135
+ end
136
+ end
137
+ fact_count.times do |i|
138
+ fact :fact, value: i
139
+ end
140
+ end
141
+ when :blackboard_redis
142
+ engine = KBS::Blackboard::Engine.new(store: @store)
143
+ kb = KBS.knowledge_base(engine: engine) do
144
+ rule_count.times do |i|
145
+ rule "rule_#{i}" do
146
+ on :fact, value: :v?
147
+ perform { }
148
+ end
149
+ end
150
+ fact_count.times do |i|
151
+ fact :fact, value: i
152
+ end
153
+ end
154
+ end
155
+
156
+ # Benchmark engine run
157
+ time = Benchmark.measure do
158
+ kb.run
159
+ end
160
+
161
+ @results[:simple_rules] = {
162
+ fact_count: fact_count,
163
+ rule_count: rule_count,
164
+ time: time.real
165
+ }
166
+ end
167
+
168
+ def benchmark_complex_joins(fact_count: 500)
169
+ setup_engine
170
+
171
+ kb = case @kb_context
172
+ when :memory
173
+ KBS.knowledge_base do
174
+ # Rule with 3-way join
175
+ rule "complex_join" do
176
+ on :a, id: :id?, value: :v?
177
+ on :b, a_id: :id?, score: :s?
178
+ on :c, b_score: :s?
179
+ perform do |facts, bindings|
180
+ # Action
181
+ end
182
+ end
183
+
184
+ # Add facts
185
+ fact_count.times do |i|
186
+ fact :a, id: i, value: rand(100)
187
+ fact :b, a_id: i, score: rand(100)
188
+ fact :c, b_score: i
189
+ end
190
+ end
191
+ end
192
+
193
+ # Benchmark
194
+ time = Benchmark.measure do
195
+ kb.run
196
+ end
197
+
198
+ @results[:complex_joins] = {
199
+ fact_count: fact_count * 3,
200
+ time: time.real
201
+ }
202
+ end
203
+
204
+ def benchmark_negation(fact_count: 1000)
205
+ setup_engine
206
+
207
+ kb = KBS.knowledge_base do
208
+ # Rule with negation
209
+ rule "negation_rule" do
210
+ on :positive, id: :id?
211
+ without :negative, id: :id?
212
+ perform do |facts, bindings|
213
+ # Action
214
+ end
215
+ end
216
+
217
+ # Add facts (50% will match)
218
+ fact_count.times do |i|
219
+ fact :positive, id: i
220
+ fact :negative, id: i if i.even?
221
+ end
222
+ end
223
+
224
+ # Benchmark
225
+ time = Benchmark.measure do
226
+ kb.run
227
+ end
228
+
229
+ @results[:negation] = {
230
+ fact_count: fact_count + (fact_count / 2),
231
+ time: time.real
232
+ }
233
+ end
234
+
235
+ def run_all
236
+ puts "=== KBS Performance Benchmark (#{@engine_type}) ==="
237
+
238
+ benchmark_fact_addition
239
+ puts "\nFact Addition:"
240
+ puts " #{@results[:fact_addition][:count]} facts in #{@results[:fact_addition][:time].round(4)}s"
241
+ puts " Rate: #{@results[:fact_addition][:rate]} facts/sec"
242
+
243
+ benchmark_simple_rules
244
+ puts "\nSimple Rules:"
245
+ puts " #{@results[:simple_rules][:rule_count]} rules, #{@results[:simple_rules][:fact_count]} facts"
246
+ puts " Time: #{@results[:simple_rules][:time].round(4)}s"
247
+
248
+ benchmark_complex_joins
249
+ puts "\nComplex Joins (3-way):"
250
+ puts " #{@results[:complex_joins][:fact_count]} facts"
251
+ puts " Time: #{@results[:complex_joins][:time].round(4)}s"
252
+
253
+ benchmark_negation
254
+ puts "\nNegation:"
255
+ puts " #{@results[:negation][:fact_count]} facts"
256
+ puts " Time: #{@results[:negation][:time].round(4)}s"
257
+
258
+ @results
259
+ end
260
+ end
261
+
262
+ # Run benchmarks
263
+ memory_bench = KBSBenchmark.new(engine_type: :memory)
264
+ memory_results = memory_bench.run_all
265
+
266
+ # Compare with blackboard
267
+ blackboard_bench = KBSBenchmark.new(engine_type: :blackboard_sqlite)
268
+ blackboard_results = blackboard_bench.run_all
269
+
270
+ # Compare
271
+ puts "\n=== Performance Comparison ==="
272
+ puts "Fact addition: Memory is #{(blackboard_results[:fact_addition][:time] / memory_results[:fact_addition][:time]).round(2)}x faster"
273
+ ```
274
+
275
+ ## Rule Optimization
276
+
277
+ ### Condition Ordering
278
+
279
+ Order conditions from most to least selective:
280
+
281
+ ```ruby
282
+ # Bad: Generic condition first
283
+ KBS::Rule.new("inefficient") do |r|
284
+ r.conditions = [
285
+ KBS::Condition.new(:any_event, {}), # Matches ALL events (large alpha memory)
286
+ KBS::Condition.new(:critical_error, { severity: "critical" }) # Selective
287
+ ]
288
+ end
289
+
290
+ # Good: Selective condition first
291
+ KBS::Rule.new("efficient") do |r|
292
+ r.conditions = [
293
+ KBS::Condition.new(:critical_error, { severity: "critical" }), # Selective
294
+ KBS::Condition.new(:any_event, { error_id: :id? }) # Filtered by join
295
+ ]
296
+ end
297
+ ```
298
+
299
+ **Why it matters:**
300
+
301
+ ```
302
+ Bad ordering:
303
+ any_event alpha: 10,000 facts
304
+ Join produces 10,000 tokens
305
+ critical_error alpha: 5 facts
306
+ Join filters down to 5 final matches
307
+ → 10,000 token propagations
308
+
309
+ Good ordering:
310
+ critical_error alpha: 5 facts
311
+ Join produces 5 tokens
312
+ any_event alpha: 10,000 facts
313
+ Join filters to 5 final matches
314
+ → 5 token propagations (2000x fewer!)
315
+ ```
316
+
317
+ ### Predicate Efficiency
318
+
319
+ Use simple predicates:
320
+
321
+ ```ruby
322
+ # Bad: Complex predicate
323
+ KBS::Condition.new(:data, { value: :v? }, predicate: lambda { |f|
324
+ # Expensive operations
325
+ json = JSON.parse(f[:raw_data])
326
+ result = ComplexCalculation.new(json).process
327
+ result > threshold
328
+ })
329
+
330
+ # Good: Pre-process data
331
+ engine.add_fact(:data, {
332
+ value: calculate_value(raw_data), # Pre-calculated
333
+ processed: true
334
+ })
335
+
336
+ KBS::Condition.new(:data, { value: :v? }, predicate: lambda { |f|
337
+ f[:value] > threshold # Simple comparison
338
+ })
339
+ ```
340
+
341
+ ### Network Sharing
342
+
343
+ Leverage shared alpha and beta memories:
344
+
345
+ ```ruby
346
+ # Inefficient: Duplicate alpha nodes
347
+ rule1 = KBS::Rule.new("rule1") do |r|
348
+ r.conditions = [
349
+ KBS::Condition.new(:sensor, { type: "temperature", value: :v1? })
350
+ ]
351
+ end
352
+
353
+ rule2 = KBS::Rule.new("rule2") do |r|
354
+ r.conditions = [
355
+ KBS::Condition.new(:sensor, { type: "temperature", value: :v2? }) # SAME pattern
356
+ ]
357
+ end
358
+
359
+ # Engine automatically shares alpha memory for :sensor + type="temperature"
360
+ # Adding 1 temperature sensor fact activates BOTH rules efficiently
361
+ ```
362
+
363
+ **Sharing visualization:**
364
+
365
+ ```
366
+ Facts → AlphaMemory(:sensor, type=temperature) ──┬─→ Rule1
367
+ └─→ Rule2
368
+
369
+ Instead of:
370
+ Facts → AlphaMemory1(:sensor) → Rule1
371
+ └→ AlphaMemory2(:sensor) → Rule2 (duplicate work)
372
+ ```
373
+
374
+ ### Minimize Negations
375
+
376
+ Negations are expensive:
377
+
378
+ ```ruby
379
+ # Expensive: Multiple negations
380
+ KBS::Rule.new("many_negations") do |r|
381
+ r.conditions = [
382
+ KBS::Condition.new(:a, {}),
383
+ KBS::Condition.new(:b, {}, negated: true),
384
+ KBS::Condition.new(:c, {}, negated: true),
385
+ KBS::Condition.new(:d, {}, negated: true)
386
+ ]
387
+ end
388
+ # Each negation checks alpha memory on every token
389
+
390
+ # Better: Use positive logic
391
+ engine.add_fact(:conditions_clear, {}) unless b_exists? || c_exists? || d_exists?
392
+
393
+ KBS::Rule.new("positive_logic") do |r|
394
+ r.conditions = [
395
+ KBS::Condition.new(:a, {}),
396
+ KBS::Condition.new(:conditions_clear, {})
397
+ ]
398
+ end
399
+ ```
400
+
401
+ ### Batch Operations
402
+
403
+ Group related operations:
404
+
405
+ ```ruby
406
+ # Inefficient: Add facts one by one with run after each
407
+ 1000.times do |i|
408
+ engine.add_fact(:item, { id: i })
409
+ engine.run # Run engine 1000 times!
410
+ end
411
+
412
+ # Efficient: Batch add, then run once
413
+ 1000.times do |i|
414
+ engine.add_fact(:item, { id: i })
415
+ end
416
+ engine.run # Run engine once
417
+ ```
418
+
419
+ ## Storage Backend Selection
420
+
421
+ ### Performance Characteristics
422
+
423
+ ```ruby
424
+ require 'benchmark'
425
+
426
+ # In-memory (fastest)
427
+ memory_engine = KBS::Engine.new
428
+
429
+ # SQLite (persistent, slower)
430
+ sqlite_engine = KBS::Blackboard::Engine.new(db_path: 'test.db')
431
+
432
+ # Redis (persistent, fast)
433
+ require 'kbs/blackboard/persistence/redis_store'
434
+ redis_store = KBS::Blackboard::Persistence::RedisStore.new(
435
+ url: 'redis://localhost:6379/0'
436
+ )
437
+ redis_engine = KBS::Blackboard::Engine.new(store: redis_store)
438
+
439
+ # Benchmark
440
+ engines = {
441
+ memory: memory_engine,
442
+ sqlite: sqlite_engine,
443
+ redis: redis_engine
444
+ }
445
+
446
+ engines.each do |name, engine|
447
+ time = Benchmark.measure do
448
+ 10_000.times { |i| engine.add_fact(:test, { value: i }) }
449
+ end
450
+
451
+ puts "#{name}: #{(10_000 / time.real).round(2)} facts/sec"
452
+ end
453
+
454
+ # Typical results:
455
+ # memory: 50,000 facts/sec
456
+ # sqlite: 5,000 facts/sec
457
+ # redis: 25,000 facts/sec
458
+ ```
459
+
460
+ ### Backend Decision Matrix
461
+
462
+ **In-Memory (`KBS::Engine`)**:
463
+ - ✅ Fastest (no I/O)
464
+ - ✅ Simple (no setup)
465
+ - ❌ No persistence
466
+ - **Use when:** Prototyping, short-lived processes, pure computation
467
+
468
+ **SQLite (`KBS::Blackboard::Engine`)**:
469
+ - ✅ Persistent
470
+ - ✅ ACID transactions
471
+ - ✅ No dependencies
472
+ - ❌ Slower writes (~5,000/sec)
473
+ - **Use when:** Single process, moderate load, need durability
474
+
475
+ **Redis (`RedisStore`)**:
476
+ - ✅ Fast (~25,000/sec)
477
+ - ✅ Distributed
478
+ - ✅ Scalable
479
+ - ❌ Requires Redis server
480
+ - **Use when:** High throughput, multiple processes, real-time systems
481
+
482
+ **Hybrid (`HybridStore`)**:
483
+ - ✅ Fast (Redis) + durable (SQLite)
484
+ - ❌ Most complex
485
+ - **Use when:** Production, need both speed and audit trail
486
+
487
+ ### SQLite Optimization
488
+
489
+ ```ruby
490
+ engine = KBS::Blackboard::Engine.new(
491
+ db_path: 'optimized.db',
492
+ journal_mode: 'WAL', # Write-Ahead Logging (better concurrency)
493
+ synchronous: 'NORMAL', # Balance safety/speed
494
+ cache_size: -64000, # 64MB cache
495
+ busy_timeout: 5000 # Wait 5s for locks
496
+ )
497
+
498
+ # Results: 2-3x faster than default settings
499
+ ```
500
+
501
+ ### Redis Optimization
502
+
503
+ ```ruby
504
+ store = KBS::Blackboard::Persistence::RedisStore.new(
505
+ url: 'redis://localhost:6379/0',
506
+ pool_size: 10, # Connection pooling
507
+ pool_timeout: 5, # Pool timeout
508
+ reconnect_attempts: 3 # Retry on failure
509
+ )
510
+
511
+ engine = KBS::Blackboard::Engine.new(store: store)
512
+
513
+ # Enable Redis persistence (optional)
514
+ # In redis.conf:
515
+ # save 900 1
516
+ # appendonly yes
517
+ ```
518
+
519
+ ## Profiling
520
+
521
+ ### Ruby Profiler
522
+
523
+ ```ruby
524
+ require 'ruby-prof'
525
+
526
+ engine = KBS::Engine.new
527
+
528
+ # Add rules and facts
529
+ # ...
530
+
531
+ # Profile engine run
532
+ result = RubyProf.profile do
533
+ engine.run
534
+ end
535
+
536
+ # Print results
537
+ printer = RubyProf::FlatPrinter.new(result)
538
+ printer.print(STDOUT, min_percent: 2)
539
+
540
+ # Or use call graph
541
+ printer = RubyProf::CallTreePrinter.new(result)
542
+ File.open('profile.out', 'w') { |f| printer.print(f) }
543
+ # View with kcachegrind or qcachegrind
544
+ ```
545
+
546
+ ### Stackprof (Sampling Profiler)
547
+
548
+ ```ruby
549
+ require 'stackprof'
550
+
551
+ engine = KBS::Engine.new
552
+
553
+ # Add rules and facts
554
+ # ...
555
+
556
+ # Profile
557
+ StackProf.run(mode: :cpu, out: 'stackprof.dump') do
558
+ 1000.times { engine.run }
559
+ end
560
+
561
+ # Analyze
562
+ # $ stackprof stackprof.dump --text
563
+ # $ stackprof stackprof.dump --method 'KBS::JoinNode#left_activate'
564
+ ```
565
+
566
+ ### Custom Instrumentation
567
+
568
+ ```ruby
569
+ class InstrumentedEngine < KBS::Engine
570
+ def initialize
571
+ super
572
+ @metrics = {
573
+ fact_additions: 0,
574
+ rule_firings: 0,
575
+ alpha_activations: 0,
576
+ beta_activations: 0
577
+ }
578
+ end
579
+
580
+ def add_fact(type, attributes = {})
581
+ @metrics[:fact_additions] += 1
582
+ super
583
+ end
584
+
585
+ def run
586
+ start = Time.now
587
+ result = super
588
+ elapsed = Time.now - start
589
+
590
+ puts "Engine run: #{elapsed}s"
591
+ puts " Facts: #{facts.size}"
592
+ puts " Rules fired: #{@metrics[:rule_firings]}"
593
+
594
+ result
595
+ end
596
+
597
+ def report_metrics
598
+ @metrics
599
+ end
600
+ end
601
+ ```
602
+
603
+ ## Common Bottlenecks
604
+
605
+ ### 1. Large Alpha Memories
606
+
607
+ **Problem**: Conditions matching many facts slow down joins
608
+
609
+ ```ruby
610
+ # Slow: Matches ALL events
611
+ KBS::Condition.new(:event, {}) # Alpha memory: 100,000 facts
612
+ ```
613
+
614
+ **Solution**: Add constraints
615
+
616
+ ```ruby
617
+ # Fast: Matches specific events
618
+ KBS::Condition.new(:event, { type: "error", severity: "critical" })
619
+ # Alpha memory: 50 facts
620
+ ```
621
+
622
+ ### 2. Expensive Predicates
623
+
624
+ **Problem**: Complex predicates evaluated repeatedly
625
+
626
+ ```ruby
627
+ # Slow: Expensive predicate called for every fact
628
+ KBS::Condition.new(:data, {}, predicate: lambda { |f|
629
+ expensive_calculation(f[:raw_data])
630
+ })
631
+ ```
632
+
633
+ **Solution**: Pre-calculate
634
+
635
+ ```ruby
636
+ # Fast: Calculate once when adding fact
637
+ processed_value = expensive_calculation(raw_data)
638
+ engine.add_fact(:data, { processed: processed_value })
639
+
640
+ KBS::Condition.new(:data, { processed: :v? })
641
+ ```
642
+
643
+ ### 3. Action Overhead
644
+
645
+ **Problem**: Slow actions block engine
646
+
647
+ ```ruby
648
+ # Slow: Action makes API call
649
+ r.action = lambda do |facts, bindings|
650
+ result = HTTParty.get("https://api.example.com/process") # Blocks!
651
+ engine.add_fact(:result, result)
652
+ end
653
+ ```
654
+
655
+ **Solution**: Async processing
656
+
657
+ ```ruby
658
+ # Fast: Queue action, process asynchronously
659
+ r.action = lambda do |facts, bindings|
660
+ engine.send_message(:api_queue, {
661
+ url: "https://api.example.com/process",
662
+ fact_id: facts[0].id
663
+ }, priority: 50)
664
+ end
665
+
666
+ # Separate worker processes messages
667
+ worker = Thread.new do
668
+ loop do
669
+ msg = engine.pop_message(:api_queue)
670
+ break unless msg
671
+
672
+ result = HTTParty.get(msg[:content][:url])
673
+ engine.add_fact(:result, result)
674
+ end
675
+ end
676
+ ```
677
+
678
+ ### 4. Memory Leaks
679
+
680
+ **Problem**: Facts accumulate indefinitely
681
+
682
+ ```ruby
683
+ # Memory grows unbounded
684
+ loop do
685
+ engine.add_fact(:sensor_reading, {
686
+ value: read_sensor(),
687
+ timestamp: Time.now
688
+ })
689
+ engine.run
690
+ end
691
+ # After 1 hour: 360,000 facts in memory!
692
+ ```
693
+
694
+ **Solution**: Clean up old facts
695
+
696
+ ```ruby
697
+ # Cleanup rule
698
+ cleanup_rule = KBS::Rule.new("cleanup_old_readings", priority: 1) do |r|
699
+ r.conditions = [
700
+ KBS::Condition.new(:sensor_reading, {
701
+ timestamp: :time?
702
+ }, predicate: lambda { |f|
703
+ (Time.now - f[:timestamp]) > 300 # 5 minutes old
704
+ })
705
+ ]
706
+
707
+ r.action = lambda do |facts, bindings|
708
+ engine.remove_fact(facts[0])
709
+ end
710
+ end
711
+ ```
712
+
713
+ ## Optimization Checklist
714
+
715
+ ### Rule Design
716
+
717
+ - [ ] Order conditions from most to least selective
718
+ - [ ] Minimize negations (use positive logic where possible)
719
+ - [ ] Keep predicates simple
720
+ - [ ] Pre-calculate expensive values
721
+ - [ ] Share patterns across rules
722
+
723
+ ### Fact Management
724
+
725
+ - [ ] Remove facts when no longer needed
726
+ - [ ] Batch fact additions
727
+ - [ ] Use specific fact types (not generic `:data`)
728
+ - [ ] Avoid duplicate facts
729
+
730
+ ### Actions
731
+
732
+ - [ ] Keep actions fast
733
+ - [ ] Avoid blocking I/O in actions
734
+ - [ ] Use message passing for async work
735
+ - [ ] Don't add/remove many facts in single action
736
+
737
+ ### Storage
738
+
739
+ - [ ] Choose backend based on requirements:
740
+ - In-memory for speed
741
+ - SQLite for persistence + moderate load
742
+ - Redis for persistence + high load
743
+ - Hybrid for production
744
+ - [ ] Optimize SQLite with WAL mode
745
+ - [ ] Use connection pooling for Redis
746
+ - [ ] Monitor database size
747
+
748
+ ### Monitoring
749
+
750
+ - [ ] Profile before optimizing
751
+ - [ ] Measure fact addition rate
752
+ - [ ] Track engine run time
753
+ - [ ] Monitor memory usage
754
+ - [ ] Log rule firing frequency
755
+
756
+ ## Performance Targets
757
+
758
+ ### Expected Performance (In-Memory)
759
+
760
+ | Operation | Target | Notes |
761
+ |-----------|--------|-------|
762
+ | Add fact | 50,000/sec | Simple facts, no rules |
763
+ | Simple rule (1 condition) | 10,000/sec | Per fact |
764
+ | Complex rule (3+ conditions) | 1,000/sec | Per fact |
765
+ | Engine run (1000 facts, 10 rules) | < 100ms | Total time |
766
+ | Negation check | 10,000/sec | Per token |
767
+
768
+ ### Expected Performance (SQLite)
769
+
770
+ | Operation | Target | Notes |
771
+ |-----------|--------|-------|
772
+ | Add fact | 5,000/sec | With WAL mode |
773
+ | Query facts | 100,000/sec | Indexed queries |
774
+ | Transaction | 1,000/sec | Commit rate |
775
+
776
+ ### Expected Performance (Redis)
777
+
778
+ | Operation | Target | Notes |
779
+ |-----------|--------|-------|
780
+ | Add fact | 25,000/sec | Network overhead |
781
+ | Query facts | 50,000/sec | Hash operations |
782
+ | Message queue | 50,000/sec | Sorted set operations |
783
+
784
+ ## Scaling Strategies
785
+
786
+ ### Vertical Scaling
787
+
788
+ **Increase single-process performance:**
789
+
790
+ ```ruby
791
+ # 1. Use faster backend
792
+ store = KBS::Blackboard::Persistence::RedisStore.new(...)
793
+ engine = KBS::Blackboard::Engine.new(store: store)
794
+
795
+ # 2. Optimize rules
796
+ # - Order conditions
797
+ # - Minimize negations
798
+ # - Batch operations
799
+
800
+ # 3. Pre-process data
801
+ # - Calculate values before adding facts
802
+ # - Index frequently queried attributes
803
+ ```
804
+
805
+ ### Horizontal Scaling
806
+
807
+ **Multiple processes sharing Redis:**
808
+
809
+ ```ruby
810
+ # Process 1: Data collector
811
+ collector_store = KBS::Blackboard::Persistence::RedisStore.new(
812
+ url: 'redis://localhost:6379/0'
813
+ )
814
+ collector = KBS::Blackboard::Engine.new(store: collector_store)
815
+
816
+ # Collect data
817
+ loop do
818
+ data = fetch_data()
819
+ collector.add_fact(:raw_data, data)
820
+ end
821
+
822
+ # Process 2: Rule processor
823
+ processor_store = KBS::Blackboard::Persistence::RedisStore.new(
824
+ url: 'redis://localhost:6379/0' # Same Redis!
825
+ )
826
+ processor = KBS::Blackboard::Engine.new(store: processor_store)
827
+
828
+ # Add rules
829
+ processor.add_rule(...)
830
+
831
+ # Process data
832
+ loop do
833
+ processor.run
834
+ sleep 1
835
+ end
836
+ ```
837
+
838
+ ### Partitioning
839
+
840
+ **Split facts by domain:**
841
+
842
+ ```ruby
843
+ # Engine 1: Temperature monitoring
844
+ temp_engine = KBS::Blackboard::Engine.new(db_path: 'temp.db')
845
+ # Handles :temperature_reading, :hvac_control
846
+
847
+ # Engine 2: Security monitoring
848
+ security_engine = KBS::Blackboard::Engine.new(db_path: 'security.db')
849
+ # Handles :motion_sensor, :door_sensor, :alarm
850
+
851
+ # Coordinator: Coordinates between engines
852
+ coordinator_engine = KBS::Blackboard::Engine.new(db_path: 'coordinator.db')
853
+ # Handles cross-domain rules
854
+ ```
855
+
856
+ ## Next Steps
857
+
858
+ - **[Debugging Guide](debugging.md)** - Debug performance issues
859
+ - **[Testing Guide](testing.md)** - Performance testing strategies
860
+ - **[Custom Persistence](custom-persistence.md)** - Optimize custom backends
861
+ - **[Architecture](../architecture/index.md)** - Understand network structure
862
+
863
+ ---
864
+
865
+ *Premature optimization is the root of all evil. Profile first, then optimize the bottlenecks.*