familia 2.0.0.pre5 → 2.0.0.pre7
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/.github/workflows/claude-code-review.yml +57 -0
- data/.github/workflows/claude.yml +71 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +3 -0
- data/CLAUDE.md +32 -10
- data/Gemfile +2 -2
- data/Gemfile.lock +4 -3
- data/docs/wiki/API-Reference.md +95 -18
- data/docs/wiki/Connection-Pooling-Guide.md +437 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +40 -3
- data/docs/wiki/Expiration-Feature-Guide.md +596 -0
- data/docs/wiki/Feature-System-Guide.md +631 -0
- data/docs/wiki/Features-System-Developer-Guide.md +892 -0
- data/docs/wiki/Field-System-Guide.md +784 -0
- data/docs/wiki/Home.md +82 -15
- data/docs/wiki/Implementation-Guide.md +126 -33
- data/docs/wiki/Quantization-Feature-Guide.md +721 -0
- data/docs/wiki/Relationships-Guide.md +684 -0
- data/docs/wiki/Security-Model.md +65 -25
- data/docs/wiki/Transient-Fields-Guide.md +280 -0
- data/examples/bit_encoding_integration.rb +237 -0
- data/examples/redis_command_validation_example.rb +231 -0
- data/examples/relationships_basic.rb +273 -0
- data/lib/familia/base.rb +1 -1
- data/lib/familia/connection.rb +3 -3
- data/lib/familia/data_type/types/counter.rb +38 -0
- data/lib/familia/data_type/types/hashkey.rb +18 -0
- data/lib/familia/data_type/types/lock.rb +43 -0
- data/lib/familia/data_type/types/string.rb +9 -2
- data/lib/familia/data_type.rb +9 -6
- data/lib/familia/encryption/encrypted_data.rb +137 -0
- data/lib/familia/encryption/manager.rb +21 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +20 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +20 -0
- data/lib/familia/encryption.rb +1 -1
- data/lib/familia/errors.rb +17 -3
- data/lib/familia/features/encrypted_fields/concealed_string.rb +293 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +94 -26
- data/lib/familia/features/encrypted_fields.rb +413 -4
- data/lib/familia/features/expiration.rb +319 -33
- data/lib/familia/features/quantization.rb +385 -44
- data/lib/familia/features/relationships/cascading.rb +438 -0
- data/lib/familia/features/relationships/indexing.rb +370 -0
- data/lib/familia/features/relationships/membership.rb +503 -0
- data/lib/familia/features/relationships/permission_management.rb +264 -0
- data/lib/familia/features/relationships/querying.rb +620 -0
- data/lib/familia/features/relationships/redis_operations.rb +274 -0
- data/lib/familia/features/relationships/score_encoding.rb +442 -0
- data/lib/familia/features/relationships/tracking.rb +379 -0
- data/lib/familia/features/relationships.rb +466 -0
- data/lib/familia/features/safe_dump.rb +1 -1
- data/lib/familia/features/transient_fields/redacted_string.rb +1 -1
- data/lib/familia/features/transient_fields.rb +192 -10
- data/lib/familia/features.rb +2 -1
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/{connection.rb → core/connection.rb} +2 -8
- data/lib/familia/horreum/{database_commands.rb → core/database_commands.rb} +14 -3
- data/lib/familia/horreum/core/serialization.rb +535 -0
- data/lib/familia/horreum/{utils.rb → core/utils.rb} +0 -2
- data/lib/familia/horreum/core.rb +21 -0
- data/lib/familia/horreum/{settings.rb → shared/settings.rb} +0 -2
- data/lib/familia/horreum/{definition_methods.rb → subclass/definition.rb} +45 -29
- data/lib/familia/horreum/{management_methods.rb → subclass/management.rb} +9 -8
- data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
- data/lib/familia/horreum.rb +17 -17
- data/lib/familia/validation/command_recorder.rb +336 -0
- data/lib/familia/validation/expectations.rb +519 -0
- data/lib/familia/validation/test_helpers.rb +443 -0
- data/lib/familia/validation/validator.rb +412 -0
- data/lib/familia/validation.rb +140 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +1 -1
- data/try/core/create_method_try.rb +240 -0
- data/try/core/database_consistency_try.rb +299 -0
- data/try/core/errors_try.rb +25 -4
- data/try/core/familia_try.rb +1 -1
- data/try/core/persistence_operations_try.rb +297 -0
- data/try/data_types/counter_try.rb +93 -0
- data/try/data_types/lock_try.rb +133 -0
- data/try/debugging/debug_aad_process.rb +82 -0
- data/try/debugging/debug_concealed_internal.rb +59 -0
- data/try/debugging/debug_concealed_reveal.rb +61 -0
- data/try/debugging/debug_context_aad.rb +68 -0
- data/try/debugging/debug_context_simple.rb +80 -0
- data/try/debugging/debug_cross_context.rb +62 -0
- data/try/debugging/debug_database_load.rb +64 -0
- data/try/debugging/debug_encrypted_json_check.rb +53 -0
- data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
- data/try/debugging/debug_exists_lifecycle.rb +54 -0
- data/try/debugging/debug_field_decrypt.rb +74 -0
- data/try/debugging/debug_fresh_cross_context.rb +73 -0
- data/try/debugging/debug_load_path.rb +66 -0
- data/try/debugging/debug_method_definition.rb +46 -0
- data/try/debugging/debug_method_resolution.rb +41 -0
- data/try/debugging/debug_minimal.rb +24 -0
- data/try/debugging/debug_provider.rb +68 -0
- data/try/debugging/debug_secure_behavior.rb +73 -0
- data/try/debugging/debug_string_class.rb +46 -0
- data/try/debugging/debug_test.rb +46 -0
- data/try/debugging/debug_test_design.rb +80 -0
- data/try/edge_cases/hash_symbolization_try.rb +1 -0
- data/try/edge_cases/reserved_keywords_try.rb +1 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/encryption/encryption_core_try.rb +6 -4
- data/try/features/categorical_permissions_try.rb +515 -0
- data/try/features/encrypted_fields_core_try.rb +19 -11
- data/try/features/encrypted_fields_integration_try.rb +66 -70
- data/try/features/encrypted_fields_no_cache_security_try.rb +22 -8
- data/try/features/encrypted_fields_security_try.rb +151 -144
- data/try/features/encryption_fields/aad_protection_try.rb +108 -23
- data/try/features/encryption_fields/concealed_string_core_try.rb +253 -0
- data/try/features/encryption_fields/context_isolation_try.rb +30 -8
- data/try/features/encryption_fields/error_conditions_try.rb +6 -6
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +20 -14
- data/try/features/encryption_fields/fresh_key_try.rb +27 -22
- data/try/features/encryption_fields/key_rotation_try.rb +16 -10
- data/try/features/encryption_fields/nonce_uniqueness_try.rb +15 -13
- data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
- data/try/features/encryption_fields/thread_safety_try.rb +6 -6
- data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/relationships_edge_cases_try.rb +145 -0
- data/try/features/relationships_performance_minimal_try.rb +132 -0
- data/try/features/relationships_performance_simple_try.rb +155 -0
- data/try/features/relationships_performance_try.rb +420 -0
- data/try/features/relationships_performance_working_try.rb +144 -0
- data/try/features/relationships_try.rb +237 -0
- data/try/features/safe_dump_try.rb +3 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields_integration_try.rb +1 -1
- data/try/helpers/test_helpers.rb +26 -1
- data/try/horreum/base_try.rb +14 -8
- data/try/horreum/enhanced_conflict_handling_try.rb +3 -1
- data/try/horreum/initialization_try.rb +1 -1
- data/try/horreum/relations_try.rb +2 -2
- data/try/horreum/serialization_persistent_fields_try.rb +8 -8
- data/try/horreum/serialization_try.rb +39 -4
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +1 -1
- data/try/validation/atomic_operations_try.rb.disabled +320 -0
- data/try/validation/command_validation_try.rb.disabled +207 -0
- data/try/validation/performance_validation_try.rb.disabled +324 -0
- data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
- metadata +81 -12
- data/TEST_COVERAGE.md +0 -40
- data/lib/familia/features/relatable_objects.rb +0 -125
- data/lib/familia/horreum/serialization.rb +0 -473
- data/try/features/relatable_objects_try.rb +0 -220
@@ -0,0 +1,437 @@
|
|
1
|
+
# Connection Pooling Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Familia provides robust connection pooling through a provider pattern that enables efficient Redis/Valkey connection management with support for multiple logical databases, thread safety, and optimal performance.
|
6
|
+
|
7
|
+
## Core Concepts
|
8
|
+
|
9
|
+
### Connection Provider Contract
|
10
|
+
|
11
|
+
Your connection provider **MUST** follow these rules:
|
12
|
+
|
13
|
+
1. **Database Selection**: Return connections already on the correct logical database
|
14
|
+
2. **No SELECT Commands**: Familia will NOT issue `SELECT` commands
|
15
|
+
3. **URI-based Selection**: Accept normalized URIs (e.g., `redis://localhost:6379/2`)
|
16
|
+
4. **Thread Safety**: Handle concurrent access safely
|
17
|
+
|
18
|
+
### Connection Priority System
|
19
|
+
|
20
|
+
Familia uses a three-tier connection resolution system:
|
21
|
+
|
22
|
+
1. **Thread-local connections** (middleware pattern)
|
23
|
+
2. **Connection provider** (if configured)
|
24
|
+
3. **Fallback behavior** (legacy direct connections, if allowed)
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# Priority 1: Thread-local (set by middleware)
|
28
|
+
Thread.current[:familia_connection] = redis_client
|
29
|
+
|
30
|
+
# Priority 2: Connection provider
|
31
|
+
Familia.connection_provider = ->(uri) { pool.checkout(uri) }
|
32
|
+
|
33
|
+
# Priority 3: Fallback (can be disabled)
|
34
|
+
Familia.connection_required = true # Disable fallback
|
35
|
+
```
|
36
|
+
|
37
|
+
## Basic Setup
|
38
|
+
|
39
|
+
### Simple Connection Pool
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'connection_pool'
|
43
|
+
|
44
|
+
class ConnectionManager
|
45
|
+
@pools = {}
|
46
|
+
|
47
|
+
# Configure provider at application startup
|
48
|
+
def self.setup!
|
49
|
+
Familia.connection_provider = lambda do |uri|
|
50
|
+
parsed = URI.parse(uri)
|
51
|
+
pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
|
52
|
+
|
53
|
+
@pools[pool_key] ||= ConnectionPool.new(size: 10, timeout: 5) do
|
54
|
+
Redis.new(
|
55
|
+
host: parsed.host,
|
56
|
+
port: parsed.port,
|
57
|
+
db: parsed.db || 0 # CRITICAL: Set DB on connection creation
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
@pools[pool_key].with { |conn| conn }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Initialize at app startup
|
67
|
+
ConnectionManager.setup!
|
68
|
+
```
|
69
|
+
|
70
|
+
### Multi-Database Configuration
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class DatabasePoolManager
|
74
|
+
POOL_CONFIGS = {
|
75
|
+
0 => { size: 20, timeout: 5 }, # Main application data
|
76
|
+
1 => { size: 5, timeout: 3 }, # Analytics/reporting
|
77
|
+
2 => { size: 10, timeout: 2 }, # Session/cache data
|
78
|
+
3 => { size: 15, timeout: 5 } # Background jobs
|
79
|
+
}.freeze
|
80
|
+
|
81
|
+
@pools = {}
|
82
|
+
|
83
|
+
def self.setup!
|
84
|
+
Familia.connection_provider = lambda do |uri|
|
85
|
+
parsed = URI.parse(uri)
|
86
|
+
db = parsed.db || 0
|
87
|
+
server = "#{parsed.host}:#{parsed.port}"
|
88
|
+
pool_key = "#{server}/#{db}"
|
89
|
+
|
90
|
+
@pools[pool_key] ||= begin
|
91
|
+
config = POOL_CONFIGS[db] || { size: 5, timeout: 5 }
|
92
|
+
|
93
|
+
ConnectionPool.new(**config) do
|
94
|
+
Redis.new(
|
95
|
+
host: parsed.host,
|
96
|
+
port: parsed.port,
|
97
|
+
db: db,
|
98
|
+
timeout: 1,
|
99
|
+
reconnect_attempts: 3,
|
100
|
+
inherit_socket: false
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
@pools[pool_key].with { |conn| conn }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
## Advanced Patterns
|
112
|
+
|
113
|
+
### Rails/Sidekiq Integration
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
# config/initializers/familia_pools.rb
|
117
|
+
class FamiliaPoolManager
|
118
|
+
include Singleton
|
119
|
+
|
120
|
+
def initialize
|
121
|
+
@pools = {}
|
122
|
+
setup_connection_provider
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def setup_connection_provider
|
128
|
+
Familia.connection_provider = lambda do |uri|
|
129
|
+
get_connection(uri)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def get_connection(uri)
|
134
|
+
parsed = URI.parse(uri)
|
135
|
+
pool_key = connection_key(parsed)
|
136
|
+
|
137
|
+
@pools[pool_key] ||= create_pool(parsed)
|
138
|
+
@pools[pool_key].with { |conn| conn }
|
139
|
+
end
|
140
|
+
|
141
|
+
def connection_key(parsed_uri)
|
142
|
+
"#{parsed_uri.host}:#{parsed_uri.port}/#{parsed_uri.db || 0}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def create_pool(parsed_uri)
|
146
|
+
db = parsed_uri.db || 0
|
147
|
+
|
148
|
+
ConnectionPool.new(
|
149
|
+
size: pool_size_for_database(db),
|
150
|
+
timeout: 5
|
151
|
+
) do
|
152
|
+
Redis.new(
|
153
|
+
host: parsed_uri.host,
|
154
|
+
port: parsed_uri.port,
|
155
|
+
db: db,
|
156
|
+
timeout: redis_timeout,
|
157
|
+
reconnect_attempts: 3
|
158
|
+
)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def pool_size_for_database(db)
|
163
|
+
case db
|
164
|
+
when 0 then sidekiq_concurrency + web_concurrency + 2 # Main DB
|
165
|
+
when 1 then 5 # Analytics
|
166
|
+
when 2 then web_concurrency + 2 # Sessions
|
167
|
+
else 5 # Default
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def sidekiq_concurrency
|
172
|
+
defined?(Sidekiq) ? Sidekiq.options[:concurrency] : 0
|
173
|
+
end
|
174
|
+
|
175
|
+
def web_concurrency
|
176
|
+
ENV.fetch('WEB_CONCURRENCY', 5).to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
def redis_timeout
|
180
|
+
Rails.env.production? ? 1 : 5
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Initialize the pool manager
|
185
|
+
FamiliaPoolManager.instance
|
186
|
+
```
|
187
|
+
|
188
|
+
### Request-Scoped Connections (Middleware)
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# Middleware for per-request connection management
|
192
|
+
class FamiliaConnectionMiddleware
|
193
|
+
def initialize(app)
|
194
|
+
@app = app
|
195
|
+
end
|
196
|
+
|
197
|
+
def call(env)
|
198
|
+
# Provide a single connection for the entire request
|
199
|
+
ConnectionPool.with do |conn|
|
200
|
+
Thread.current[:familia_connection] = conn
|
201
|
+
@app.call(env)
|
202
|
+
end
|
203
|
+
ensure
|
204
|
+
Thread.current[:familia_connection] = nil
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# In your Rack/Rails app
|
209
|
+
use FamiliaConnectionMiddleware
|
210
|
+
```
|
211
|
+
|
212
|
+
## Model Database Configuration
|
213
|
+
|
214
|
+
Configure models to use specific logical databases:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
class Customer < Familia::Horreum
|
218
|
+
self.logical_database = 0 # Primary application data
|
219
|
+
field :name, :email, :status
|
220
|
+
end
|
221
|
+
|
222
|
+
class AnalyticsEvent < Familia::Horreum
|
223
|
+
self.logical_database = 1 # Separate analytics database
|
224
|
+
field :event_type, :user_id, :timestamp, :properties
|
225
|
+
end
|
226
|
+
|
227
|
+
class SessionData < Familia::Horreum
|
228
|
+
self.logical_database = 2 # Fast cache database
|
229
|
+
feature :expiration
|
230
|
+
default_expiration 1.hour
|
231
|
+
field :user_id, :data, :csrf_token
|
232
|
+
end
|
233
|
+
|
234
|
+
class BackgroundJob < Familia::Horreum
|
235
|
+
self.logical_database = 3 # Job queue database
|
236
|
+
field :job_type, :payload, :status, :retry_count
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
## Performance Benefits
|
241
|
+
|
242
|
+
### Without Connection Pooling
|
243
|
+
|
244
|
+
Each operation may trigger database switches:
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
# These operations might use different connections, causing SELECT commands:
|
248
|
+
customer = Customer.find(123) # SELECT 0, then query
|
249
|
+
session = SessionData.find(456) # SELECT 2, then query
|
250
|
+
analytics = AnalyticsEvent.find(789) # SELECT 1, then query
|
251
|
+
```
|
252
|
+
|
253
|
+
### With Connection Pooling
|
254
|
+
|
255
|
+
Connections stay on the correct database:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
# Each model uses its dedicated connection pool:
|
259
|
+
customer = Customer.find(123) # Connection already on DB 0
|
260
|
+
session = SessionData.find(456) # Different connection, already on DB 2
|
261
|
+
analytics = AnalyticsEvent.find(789) # Different connection, already on DB 1
|
262
|
+
```
|
263
|
+
|
264
|
+
## Pool Sizing Guidelines
|
265
|
+
|
266
|
+
### Web Applications
|
267
|
+
- **Formula**: `(threads_per_process * processes) + buffer`
|
268
|
+
- **Puma**: `(threads * workers) + 2`
|
269
|
+
- **Unicorn**: `processes + 2`
|
270
|
+
|
271
|
+
### Background Jobs
|
272
|
+
- **Sidekiq**: `concurrency + 2`
|
273
|
+
- **DelayedJob**: `worker_processes + 2`
|
274
|
+
|
275
|
+
### Database-Specific Sizing
|
276
|
+
```ruby
|
277
|
+
def pool_size_for_database(db)
|
278
|
+
base_size = web_concurrency + sidekiq_concurrency
|
279
|
+
|
280
|
+
case db
|
281
|
+
when 0 then base_size + 5 # Main DB: highest usage
|
282
|
+
when 1 then 3 # Analytics: batch operations
|
283
|
+
when 2 then base_size + 2 # Sessions: per-request access
|
284
|
+
when 3 then sidekiq_concurrency # Jobs: worker access only
|
285
|
+
else 5 # Default for new DBs
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
## Monitoring and Debugging
|
291
|
+
|
292
|
+
### Enable Debug Mode
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
Familia.debug = true
|
296
|
+
# Shows database selection and connection provider usage
|
297
|
+
```
|
298
|
+
|
299
|
+
### Pool Usage Monitoring
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
class PoolMonitor
|
303
|
+
def self.stats
|
304
|
+
FamiliaPoolManager.instance.instance_variable_get(:@pools).map do |key, pool|
|
305
|
+
{
|
306
|
+
database: key,
|
307
|
+
size: pool.size,
|
308
|
+
available: pool.available,
|
309
|
+
checked_out: pool.size - pool.available
|
310
|
+
}
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def self.health_check
|
315
|
+
stats.each do |stat|
|
316
|
+
utilization = (stat[:checked_out] / stat[:size].to_f) * 100
|
317
|
+
puts "DB #{stat[:database]}: #{utilization.round(1)}% utilized"
|
318
|
+
warn "High utilization!" if utilization > 80
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
### Connection Testing
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
# Test concurrent access patterns
|
328
|
+
def test_concurrent_access
|
329
|
+
threads = 20.times.map do |i|
|
330
|
+
Thread.new do
|
331
|
+
50.times do |j|
|
332
|
+
Customer.create(name: "test-#{i}-#{j}")
|
333
|
+
SessionData.create(user_id: i, data: "session-#{j}")
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
threads.each(&:join)
|
339
|
+
puts "Concurrent test completed"
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
343
|
+
## Troubleshooting
|
344
|
+
|
345
|
+
### Common Issues
|
346
|
+
|
347
|
+
**1. Wrong Database Connections**
|
348
|
+
```ruby
|
349
|
+
# Problem: Provider not setting DB correctly
|
350
|
+
Redis.new(host: 'localhost', port: 6379) # Missing db: parameter
|
351
|
+
|
352
|
+
# Solution: Always specify database
|
353
|
+
Redis.new(host: 'localhost', port: 6379, db: parsed_uri.db || 0)
|
354
|
+
```
|
355
|
+
|
356
|
+
**2. Pool Exhaustion**
|
357
|
+
```ruby
|
358
|
+
# Monitor pool usage
|
359
|
+
ConnectionPool.stats # If available
|
360
|
+
# Increase pool size or reduce hold time
|
361
|
+
```
|
362
|
+
|
363
|
+
**3. Connection Leaks**
|
364
|
+
```ruby
|
365
|
+
# Always use .with for pool connections
|
366
|
+
pool.with do |conn|
|
367
|
+
# Use connection
|
368
|
+
end
|
369
|
+
|
370
|
+
# Never checkout without returning
|
371
|
+
conn = pool.checkout # ❌ Can leak
|
372
|
+
```
|
373
|
+
|
374
|
+
### Error Handling
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
Familia.connection_provider = lambda do |uri|
|
378
|
+
begin
|
379
|
+
get_pooled_connection(uri)
|
380
|
+
rescue Redis::ConnectionError => e
|
381
|
+
# Log error, potentially retry or fall back
|
382
|
+
Familia.logger.error "Connection failed: #{e.message}"
|
383
|
+
raise Familia::ConnectionError, "Pool connection failed"
|
384
|
+
end
|
385
|
+
end
|
386
|
+
```
|
387
|
+
|
388
|
+
## Best Practices
|
389
|
+
|
390
|
+
1. **Return Pre-Selected Connections**: Provider must return connections on the correct DB
|
391
|
+
2. **One Pool Per Database**: Each logical DB needs its own pool
|
392
|
+
3. **Thread Safety**: Use thread-safe pool creation and access
|
393
|
+
4. **Monitor Usage**: Track pool utilization and adjust sizes
|
394
|
+
5. **Proper Sizing**: Account for all concurrent access patterns
|
395
|
+
6. **Error Handling**: Gracefully handle connection failures
|
396
|
+
7. **Connection Validation**: Verify connections are healthy before use
|
397
|
+
|
398
|
+
## Integration Examples
|
399
|
+
|
400
|
+
### Roda Application
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
class App < Roda
|
404
|
+
plugin :hooks
|
405
|
+
|
406
|
+
before do
|
407
|
+
# Connection pooling handled automatically via provider
|
408
|
+
# Each request gets appropriate connections per database
|
409
|
+
end
|
410
|
+
|
411
|
+
route do |r|
|
412
|
+
r.get 'customers', Integer do |id|
|
413
|
+
customer = Customer.find(id) # Uses DB 0 pool
|
414
|
+
session = SessionData.find(r.env) # Uses DB 2 pool
|
415
|
+
|
416
|
+
render_json(customer: customer, session: session)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
```
|
421
|
+
|
422
|
+
### Background Jobs
|
423
|
+
|
424
|
+
```ruby
|
425
|
+
class ProcessCustomerJob
|
426
|
+
include Sidekiq::Worker
|
427
|
+
|
428
|
+
def perform(customer_id)
|
429
|
+
# Each of these uses appropriate database pool
|
430
|
+
customer = Customer.find(customer_id) # DB 0
|
431
|
+
SessionData.expire_for_user(customer_id) # DB 2
|
432
|
+
AnalyticsEvent.track('job.completed', user: customer_id) # DB 1
|
433
|
+
end
|
434
|
+
end
|
435
|
+
```
|
436
|
+
|
437
|
+
This connection pooling system provides the foundation for scalable, performant Familia applications with proper resource management across multiple logical databases.
|
@@ -3,6 +3,8 @@
|
|
3
3
|
## Quick Start
|
4
4
|
|
5
5
|
Add encrypted field support to any Familia model in one line:
|
6
|
+
|
7
|
+
```ruby
|
6
8
|
class User < Familia::Horreum
|
7
9
|
encrypted_field :diary_entry
|
8
10
|
end
|
@@ -12,7 +14,8 @@ end
|
|
12
14
|
|
13
15
|
- **Automatic Encryption**: Fields are encrypted before storing in Redis/Valkey
|
14
16
|
- **Transparent Decryption**: Access encrypted fields like normal attributes
|
15
|
-
- **
|
17
|
+
- **Modular Providers**: Pluggable encryption algorithms (XChaCha20-Poly1305, AES-GCM)
|
18
|
+
- **Secure by Default**: Uses authenticated encryption with automatic algorithm selection
|
16
19
|
- **Zero Boilerplate**: No manual encrypt/decrypt calls needed
|
17
20
|
|
18
21
|
## When to Use
|
@@ -41,7 +44,7 @@ customer = Customer.new(
|
|
41
44
|
)
|
42
45
|
|
43
46
|
customer.save
|
44
|
-
customer.
|
47
|
+
customer.secret_recipe # => "Add extra vanilla" (decrypted automatically)
|
45
48
|
```
|
46
49
|
|
47
50
|
## Configuration
|
@@ -49,7 +52,7 @@ customer.credit_card # => "4111-1111-1111-1111" (decrypted automatically)
|
|
49
52
|
Set your encryption key in environment:
|
50
53
|
|
51
54
|
```bash
|
52
|
-
export FAMILIA_ENCRYPTION_KEY=$(
|
55
|
+
export FAMILIA_ENCRYPTION_KEY=$(openssl rand -base64 32)
|
53
56
|
```
|
54
57
|
|
55
58
|
Configure in your app:
|
@@ -60,5 +63,39 @@ Familia.configure do |config|
|
|
60
63
|
v1: ENV['FAMILIA_ENCRYPTION_KEY']
|
61
64
|
}
|
62
65
|
config.current_key_version = :v1
|
66
|
+
|
67
|
+
# Optional: customize personalization for key derivation
|
68
|
+
config.encryption_personalization = 'MyApp-2024'
|
63
69
|
end
|
64
70
|
```
|
71
|
+
|
72
|
+
## Advanced Features
|
73
|
+
|
74
|
+
### Per-Field Algorithm Selection
|
75
|
+
|
76
|
+
Choose specific encryption algorithms for different fields:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class SecretVault < Familia::Horreum
|
80
|
+
# Use default best-available algorithm
|
81
|
+
encrypted_field :user_secret
|
82
|
+
|
83
|
+
# Force specific algorithm (when implemented)
|
84
|
+
# encrypted_field :ultra_secure_data, algorithm: 'xchacha20poly1305'
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
### Provider Priority System
|
89
|
+
|
90
|
+
Familia automatically selects the best available encryption provider:
|
91
|
+
|
92
|
+
1. **XChaCha20-Poly1305** (Priority: 100) - Requires `rbnacl` gem
|
93
|
+
2. **AES-256-GCM** (Priority: 50) - Uses OpenSSL, always available
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
# Check current encryption status
|
97
|
+
puts Familia::Encryption.status
|
98
|
+
# => { default_algorithm: "xchacha20poly1305",
|
99
|
+
# available_algorithms: ["xchacha20poly1305", "aes-256-gcm"],
|
100
|
+
# using_hardware: false }
|
101
|
+
```
|