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