familia 1.2.0 → 2.0.0.pre.pre

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +68 -0
  3. data/.github/workflows/docs.yml +64 -0
  4. data/.gitignore +4 -0
  5. data/.pre-commit-config.yaml +3 -1
  6. data/.rubocop.yml +16 -9
  7. data/.rubocop_todo.yml +177 -31
  8. data/.yardopts +9 -0
  9. data/CLAUDE.md +141 -0
  10. data/Gemfile +15 -2
  11. data/Gemfile.lock +76 -34
  12. data/README.md +39 -23
  13. data/bin/irb +3 -0
  14. data/docs/connection_pooling.md +317 -0
  15. data/familia.gemspec +9 -5
  16. data/lib/familia/base.rb +19 -9
  17. data/lib/familia/connection.rb +232 -65
  18. data/lib/familia/core_ext.rb +1 -1
  19. data/lib/familia/datatype/commands.rb +59 -0
  20. data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
  21. data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
  22. data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
  23. data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
  24. data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
  25. data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
  26. data/lib/familia/datatype.rb +243 -0
  27. data/lib/familia/errors.rb +5 -2
  28. data/lib/familia/features/expiration.rb +33 -34
  29. data/lib/familia/features/quantization.rb +9 -3
  30. data/lib/familia/features/safe_dump.rb +2 -3
  31. data/lib/familia/features.rb +2 -2
  32. data/lib/familia/horreum/class_methods.rb +97 -110
  33. data/lib/familia/horreum/commands.rb +46 -51
  34. data/lib/familia/horreum/connection.rb +82 -0
  35. data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
  36. data/lib/familia/horreum/serialization.rb +61 -198
  37. data/lib/familia/horreum/settings.rb +6 -17
  38. data/lib/familia/horreum/utils.rb +11 -10
  39. data/lib/familia/horreum.rb +69 -60
  40. data/lib/familia/logging.rb +12 -12
  41. data/lib/familia/multi_result.rb +72 -0
  42. data/lib/familia/refinements.rb +7 -44
  43. data/lib/familia/settings.rb +11 -11
  44. data/lib/familia/utils.rb +123 -90
  45. data/lib/familia/version.rb +4 -21
  46. data/lib/familia.rb +17 -12
  47. data/lib/middleware/database_middleware.rb +150 -0
  48. data/try/configuration/scenarios_try.rb +65 -0
  49. data/try/core/connection_try.rb +58 -0
  50. data/try/core/errors_try.rb +93 -0
  51. data/try/core/extensions_try.rb +26 -0
  52. data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
  53. data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
  54. data/try/core/middleware_try.rb +68 -0
  55. data/try/core/refinements_try.rb +39 -0
  56. data/try/core/settings_try.rb +76 -0
  57. data/try/core/tools_try.rb +54 -0
  58. data/try/core/utils_try.rb +189 -0
  59. data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
  60. data/try/datatypes/datatype_base_try.rb +69 -0
  61. data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
  62. data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
  63. data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
  64. data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
  65. data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
  66. data/try/edge_cases/empty_identifiers_try.rb +48 -0
  67. data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
  68. data/try/edge_cases/json_serialization_try.rb +85 -0
  69. data/try/edge_cases/race_conditions_try.rb +60 -0
  70. data/try/edge_cases/reserved_keywords_try.rb +59 -0
  71. data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
  72. data/try/edge_cases/ttl_side_effects_try.rb +51 -0
  73. data/try/features/expiration_try.rb +86 -0
  74. data/try/features/quantization_try.rb +90 -0
  75. data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
  76. data/try/features/safe_dump_try.rb +137 -0
  77. data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
  78. data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
  79. data/try/horreum/class_methods_try.rb +41 -0
  80. data/try/horreum/commands_try.rb +49 -0
  81. data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
  82. data/try/horreum/relations_try.rb +146 -0
  83. data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
  84. data/try/horreum/settings_try.rb +43 -0
  85. data/try/integration/cross_component_try.rb +46 -0
  86. data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
  87. data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
  88. data/try/models/datatype_base_try.rb +101 -0
  89. data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
  90. data/try/performance/benchmarks_try.rb +55 -0
  91. data/try/pooling/README.md +20 -0
  92. data/try/pooling/configurable_stress_test_try.rb +435 -0
  93. data/try/pooling/connection_pool_test_try.rb +273 -0
  94. data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  95. data/try/pooling/lib/connection_pool_metrics.rb +372 -0
  96. data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
  97. data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
  98. data/try/pooling/lib/visualize_stress_results.rb +434 -0
  99. data/try/pooling/pool_siege_try.rb +509 -0
  100. data/try/pooling/run_stress_tests_try.rb +482 -0
  101. data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
  102. data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
  103. data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
  104. data/try/prototypes/atomic_saves_v4.rb +105 -0
  105. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
  106. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  107. metadata +140 -43
  108. data/.github/workflows/ruby.yml +0 -71
  109. data/VERSION.yml +0 -4
  110. data/lib/familia/redistype/commands.rb +0 -59
  111. data/lib/familia/redistype.rb +0 -228
  112. data/lib/familia/tools.rb +0 -68
  113. data/lib/redis_middleware.rb +0 -109
  114. data/try/20_redis_type_try.rb +0 -70
  115. data/try/91_json_bug_try.rb +0 -86
data/Gemfile.lock CHANGED
@@ -1,7 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (1.1.0.pre.rc1)
4
+ familia (2.0.0.pre.pre)
5
+ benchmark
6
+ connection_pool
7
+ csv
8
+ logger
5
9
  redis (>= 4.8.1, < 6.0)
6
10
  stringio (~> 3.1.1)
7
11
  uri-redis (~> 1.3)
@@ -9,76 +13,114 @@ PATH
9
13
  GEM
10
14
  remote: https://rubygems.org/
11
15
  specs:
12
- ast (2.4.2)
16
+ ast (2.4.3)
17
+ base64 (0.3.0)
18
+ benchmark (0.4.1)
13
19
  byebug (11.1.3)
14
20
  coderay (1.1.3)
15
- connection_pool (2.4.1)
21
+ concurrent-ruby (1.3.5)
22
+ connection_pool (2.5.3)
23
+ csv (3.3.5)
24
+ diff-lcs (1.6.2)
16
25
  drydock (0.6.9)
17
- json (2.7.2)
18
- language_server-protocol (3.17.0.3)
26
+ json (2.13.0)
27
+ kramdown (2.5.1)
28
+ rexml (>= 3.3.9)
29
+ language_server-protocol (3.17.0.5)
30
+ lint_roller (1.1.0)
31
+ logger (1.7.0)
19
32
  method_source (1.1.0)
20
- parallel (1.25.1)
21
- parser (3.3.4.0)
33
+ minitest (5.25.5)
34
+ parallel (1.27.0)
35
+ parser (3.3.8.0)
22
36
  ast (~> 2.4.1)
23
37
  racc
38
+ prism (1.4.0)
24
39
  pry (0.14.2)
25
40
  coderay (~> 1.1)
26
41
  method_source (~> 1.0)
27
42
  pry-byebug (3.10.1)
28
43
  byebug (~> 11.0)
29
44
  pry (>= 0.13, < 0.15)
30
- racc (1.8.0)
45
+ racc (1.8.1)
31
46
  rainbow (3.1.1)
32
- redis (5.2.0)
47
+ redis (5.4.1)
33
48
  redis-client (>= 0.22.0)
34
- redis-client (0.22.2)
49
+ redis-client (0.25.1)
35
50
  connection_pool
36
- regexp_parser (2.9.2)
37
- rexml (3.3.2)
38
- strscan
39
- rubocop (1.65.0)
51
+ regexp_parser (2.10.0)
52
+ rexml (3.4.1)
53
+ rspec (3.13.1)
54
+ rspec-core (~> 3.13.0)
55
+ rspec-expectations (~> 3.13.0)
56
+ rspec-mocks (~> 3.13.0)
57
+ rspec-core (3.13.5)
58
+ rspec-support (~> 3.13.0)
59
+ rspec-expectations (3.13.5)
60
+ diff-lcs (>= 1.2.0, < 2.0)
61
+ rspec-support (~> 3.13.0)
62
+ rspec-mocks (3.13.5)
63
+ diff-lcs (>= 1.2.0, < 2.0)
64
+ rspec-support (~> 3.13.0)
65
+ rspec-support (3.13.4)
66
+ rubocop (1.78.0)
40
67
  json (~> 2.3)
41
- language_server-protocol (>= 3.17.0)
68
+ language_server-protocol (~> 3.17.0.2)
69
+ lint_roller (~> 1.1.0)
42
70
  parallel (~> 1.10)
43
71
  parser (>= 3.3.0.2)
44
72
  rainbow (>= 2.2.2, < 4.0)
45
- regexp_parser (>= 2.4, < 3.0)
46
- rexml (>= 3.2.5, < 4.0)
47
- rubocop-ast (>= 1.31.1, < 2.0)
73
+ regexp_parser (>= 2.9.3, < 3.0)
74
+ rubocop-ast (>= 1.45.1, < 2.0)
48
75
  ruby-progressbar (~> 1.7)
49
- unicode-display_width (>= 2.4.0, < 3.0)
50
- rubocop-ast (1.31.3)
51
- parser (>= 3.3.1.0)
52
- rubocop-performance (1.21.1)
53
- rubocop (>= 1.48.1, < 2.0)
54
- rubocop-ast (>= 1.31.1, < 2.0)
55
- rubocop-thread_safety (0.5.1)
56
- rubocop (>= 0.90.0)
76
+ unicode-display_width (>= 2.4.0, < 4.0)
77
+ rubocop-ast (1.46.0)
78
+ parser (>= 3.3.7.2)
79
+ prism (~> 1.4)
80
+ rubocop-performance (1.25.0)
81
+ lint_roller (~> 1.1)
82
+ rubocop (>= 1.75.0, < 2.0)
83
+ rubocop-ast (>= 1.38.0, < 2.0)
84
+ rubocop-thread_safety (0.7.3)
85
+ lint_roller (~> 1.1)
86
+ rubocop (~> 1.72, >= 1.72.1)
87
+ rubocop-ast (>= 1.44.0, < 2.0)
88
+ ruby-prof (1.7.2)
89
+ base64
57
90
  ruby-progressbar (1.13.0)
91
+ stackprof (0.2.27)
58
92
  storable (0.10.0)
59
- stringio (3.1.1)
60
- strscan (3.1.0)
93
+ stringio (3.1.7)
61
94
  sysinfo (0.10.0)
62
95
  drydock (< 1.0)
63
96
  storable (~> 0.10)
64
- tryouts (2.4.1)
65
- sysinfo (~> 0.10)
66
- unicode-display_width (2.5.0)
97
+ tryouts (3.1.1)
98
+ minitest (~> 5.0)
99
+ rspec (~> 3.0)
100
+ sysinfo (>= 0.8, < 1.0)
101
+ unicode-display_width (3.1.4)
102
+ unicode-emoji (~> 4.0, >= 4.0.4)
103
+ unicode-emoji (4.0.4)
67
104
  uri-redis (1.3.0)
105
+ yard (0.9.37)
68
106
 
69
107
  PLATFORMS
70
- arm64-darwin-23
71
108
  arm64-darwin-24
72
109
  ruby
73
110
 
74
111
  DEPENDENCIES
75
112
  byebug (~> 11.0)
113
+ concurrent-ruby (~> 1.3.5)
76
114
  familia!
115
+ kramdown
77
116
  pry-byebug (~> 3.10.1)
78
117
  rubocop
79
118
  rubocop-performance
80
119
  rubocop-thread_safety
81
- tryouts (~> 2.4)
120
+ ruby-prof
121
+ stackprof
122
+ tryouts (~> 3.1.1)
123
+ yard (~> 0.9)
82
124
 
83
125
  BUNDLED WITH
84
- 2.5.13
126
+ 2.6.2
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Familia - 1.1.0-rc1 (November 2024)
1
+ # Familia - 2.0.0
2
2
 
3
- **Organize and store Ruby objects in Redis. A powerful Ruby ORM (of sorts) for Redis.**
3
+ **Organize and store Ruby objects in Valkey/Redis. A powerful Ruby ORM (of sorts) for Valkey/Redis.**
4
4
 
5
- Familia provides a flexible and feature-rich way to interact with Redis using Ruby objects. It's designed to make working with Redis as natural as working with Ruby classes, while offering advanced features for complex data management.
5
+ Familia provides a flexible and feature-rich way to interact with Valkey using Ruby objects. It's designed to make working with Valkey as natural as working with Ruby classes, while offering advanced features for complex data management.
6
6
 
7
7
  ## Installation
8
8
 
@@ -18,11 +18,11 @@ Get it in one of the following ways:
18
18
 
19
19
  ### 1. Defining Horreum Classes
20
20
 
21
- Familia uses the concept of "Horreum" classes to represent Redis-backed objects:
21
+ Familia uses the concept of "Horreum" classes to represent Valkey-compatible objects:
22
22
 
23
23
  ```ruby
24
24
  class Flower < Familia::Horreum
25
- identifier :token
25
+ identifier_field :token
26
26
  field :name
27
27
  list :owners
28
28
  set :tags
@@ -38,20 +38,20 @@ You can define identifiers in various ways:
38
38
 
39
39
  ```ruby
40
40
  class User < Familia::Horreum
41
- identifier :email
41
+ identifier_field :email
42
42
  # or
43
- identifier -> (user) { "user:#{user.email}" }
43
+ identifier_field -> (user) { "user:#{user.email}" }
44
44
  # or
45
- identifier [:type, :email]
45
+ identifier_field [:type, :email]
46
46
 
47
47
  field :email
48
48
  field :type
49
49
  end
50
50
  ```
51
51
 
52
- ### 3. Redis Data Types
52
+ ### 3. Data Types
53
53
 
54
- Familia supports various Redis data types:
54
+ Familia supports various Valkey-compatible data types:
55
55
 
56
56
  ```ruby
57
57
  class Product < Familia::Horreum
@@ -63,9 +63,9 @@ class Product < Familia::Horreum
63
63
  end
64
64
  ```
65
65
 
66
- ### 4. Class-level Redis Types
66
+ ### 4. Class-level Valkey-compatible Types
67
67
 
68
- You can also define Redis types at the class level:
68
+ You can also define Valkey-compatible types at the class level:
69
69
 
70
70
  ```ruby
71
71
  class Customer < Familia::Horreum
@@ -78,12 +78,12 @@ end
78
78
 
79
79
  ### 5. Automatic Expiration
80
80
 
81
- Use the expiration feature to set TTL for objects:
81
+ Use the expiration feature to set default TTL for objects:
82
82
 
83
83
  ```ruby
84
84
  class Session < Familia::Horreum
85
85
  feature :expiration
86
- ttl 180.minutes
86
+ default_expiration 180.minutes
87
87
  end
88
88
  ```
89
89
 
@@ -110,7 +110,7 @@ Use quantization for time-based metrics:
110
110
  ```ruby
111
111
  class DailyMetric < Familia::Horreum
112
112
  feature :quantization
113
- string :counter, ttl: 1.day, quantize: [10.minutes, '%H:%M']
113
+ string :counter, default_expiration: 1.day, quantize: [10.minutes, '%H:%M']
114
114
  end
115
115
  ```
116
116
 
@@ -140,13 +140,6 @@ class Customer < Familia::Horreum
140
140
  verified && !reset_requested
141
141
  end
142
142
  end
143
-
144
- class Session < Familia::Horreum
145
- def external_identifier
146
- elements = [ipaddress || 'UNKNOWNIP', custid || 'anon']
147
- @external_identifier ||= Familia.generate_sha_hash(elements)
148
- end
149
- end
150
143
  ```
151
144
  ### 10. Open-ended Serialization
152
145
 
@@ -171,6 +164,29 @@ user.transaction do |conn|
171
164
  end
172
165
  ```
173
166
 
167
+ ### 12. Connection Management and Pooling
168
+
169
+ Familia supports custom connection providers for advanced scenarios like connection pooling:
170
+
171
+ ```ruby
172
+ # Using connection_pool gem for thread-safe pooling
173
+ require 'connection_pool'
174
+
175
+ # Create pools for each logical database
176
+ pools = {
177
+ "redis://localhost:6379/0" => ConnectionPool.new(size: 10) { Redis.new(db: 0) },
178
+ "redis://localhost:6379/1" => ConnectionPool.new(size: 5) { Redis.new(db: 1) }
179
+ }
180
+
181
+ # Configure Familia to use the pools
182
+ Familia.connection_provider = lambda do |uri|
183
+ pool = pools[uri] || pools["redis://localhost:6379/0"]
184
+ pool.with { |conn| conn }
185
+ end
186
+ ```
187
+
188
+ See the [Connection Pooling Guide](docs/connection_pooling.md) for detailed examples.
189
+
174
190
 
175
191
  ## Usage Examples
176
192
 
@@ -225,7 +241,7 @@ end
225
241
 
226
242
  ## Conclusion
227
243
 
228
- Familia provides a powerful and flexible way to work with Redis in Ruby applications. Its features like automatic expiration, safe dumping, and quantization make it suitable for a wide range of use cases, from simple key-value storage to complex time-series data management.
244
+ Familia provides a powerful and flexible way to work with Valkey-compatible in Ruby applications. Its features like automatic expiration, safe dumping, and quantization make it suitable for a wide range of use cases, from simple key-value storage to complex time-series data management.
229
245
 
230
246
  For more information, visit:
231
247
  - [Github Repository](https://github.com/delano/familia)
data/bin/irb ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ irb -ruri/redis -Ilib -r familia -Itry -r helpers/test_helpers
@@ -0,0 +1,317 @@
1
+ # Connection Pooling with Familia
2
+
3
+ Familia uses a flexible connection provider pattern that allows you to implement connection pooling in your application. This guide shows how to configure connection pools for optimal performance with multiple logical databases.
4
+
5
+ ## Key Concepts
6
+
7
+ 1. **Connection Provider Contract**: When you provide a `connection_provider`, it MUST return connections already on the correct logical database. Familia will NOT issue SELECT commands after receiving a connection from the provider.
8
+
9
+ 2. **URI-based Selection**: Familia passes normalized URIs (e.g., `redis://localhost:6379/2`) to your provider, encoding the logical database in the URI.
10
+
11
+ 3. **One Pool Per Database**: Since Familia models can use different logical databases, you typically need one connection pool per unique database.
12
+
13
+ ## Basic Connection Pool Setup
14
+
15
+ ### Example 1: Simple Connection Pool
16
+
17
+ ```ruby
18
+ require 'connection_pool'
19
+ require 'familia'
20
+
21
+ class MyApp
22
+ def self.setup_familia_pools
23
+ @pools = {}
24
+
25
+ Familia.connection_provider = lambda do |uri|
26
+ parsed = URI.parse(uri)
27
+ pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
28
+
29
+ # Create a pool for each unique database
30
+ @pools[pool_key] ||= ConnectionPool.new(size: 10, timeout: 5) do
31
+ Redis.new(
32
+ host: parsed.host,
33
+ port: parsed.port,
34
+ db: parsed.db || 0 # Connection created with correct DB
35
+ )
36
+ end
37
+
38
+ # Return a connection from the pool
39
+ @pools[pool_key].with { |conn| conn }
40
+ end
41
+ end
42
+ end
43
+ ```
44
+
45
+ ### Example 2: Advanced Pooling with Different Configurations
46
+
47
+ ```ruby
48
+ class MyApp
49
+ # Different pool sizes based on expected traffic
50
+ POOL_CONFIGS = {
51
+ 0 => { size: 20, timeout: 5 }, # High-traffic main database
52
+ 1 => { size: 5, timeout: 5 }, # Low-traffic analytics database
53
+ 2 => { size: 10, timeout: 5 }, # Medium-traffic cache database
54
+ 3 => { size: 5, timeout: 5 } # Session database
55
+ }
56
+
57
+ def self.setup_familia_pools
58
+ @pools = {}
59
+
60
+ Familia.connection_provider = lambda do |uri|
61
+ parsed = URI.parse(uri)
62
+ db = parsed.db || 0
63
+ pool_key = "#{parsed.host}:#{parsed.port}/#{db}"
64
+
65
+ @pools[pool_key] ||= begin
66
+ config = POOL_CONFIGS[db] || { size: 5, timeout: 5 }
67
+ ConnectionPool.new(**config) do
68
+ Redis.new(host: parsed.host, port: parsed.port, db: db)
69
+ end
70
+ end
71
+
72
+ @pools[pool_key].with { |conn| conn }
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Example 3: Using with Puma/Multi-threaded Servers
79
+
80
+ ```ruby
81
+ # In config/initializers/familia.rb
82
+
83
+ # Global connection pools shared across threads
84
+ $redis_pools = {}
85
+ $pool_mutex = Mutex.new
86
+
87
+ Familia.connection_provider = lambda do |uri|
88
+ parsed = URI.parse(uri)
89
+ pool_key = parsed.to_s
90
+
91
+ # Thread-safe pool creation
92
+ $pool_mutex.synchronize do
93
+ $redis_pools[pool_key] ||= ConnectionPool.new(
94
+ size: ENV.fetch('RAILS_MAX_THREADS', 5).to_i,
95
+ timeout: 5
96
+ ) do
97
+ Redis.new(
98
+ host: parsed.host,
99
+ port: parsed.port,
100
+ db: parsed.db || 0,
101
+ timeout: 1, # Connection timeout
102
+ reconnect_attempts: 3
103
+ )
104
+ end
105
+ end
106
+
107
+ $redis_pools[pool_key].with { |conn| conn }
108
+ end
109
+ ```
110
+
111
+ ### Example 4: Using with Sidekiq
112
+
113
+ ```ruby
114
+ # Sidekiq has its own connection pool management
115
+ # This example shows how to share pools between Sidekiq and web processes
116
+
117
+ class RedisConnectionPools
118
+ include Singleton
119
+
120
+ def initialize
121
+ @pools = {}
122
+ @mutex = Mutex.new
123
+ end
124
+
125
+ def get_connection(uri)
126
+ parsed = URI.parse(uri)
127
+ pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
128
+
129
+ pool = @mutex.synchronize do
130
+ @pools[pool_key] ||= ConnectionPool.new(
131
+ size: determine_pool_size,
132
+ timeout: 5
133
+ ) do
134
+ Redis.new(
135
+ host: parsed.host,
136
+ port: parsed.port,
137
+ db: parsed.db || 0
138
+ )
139
+ end
140
+ end
141
+
142
+ pool.with { |conn| conn }
143
+ end
144
+
145
+ private
146
+
147
+ def determine_pool_size
148
+ if defined?(Sidekiq)
149
+ # Sidekiq workers need more connections
150
+ Sidekiq.options[:concurrency] + 2
151
+ else
152
+ # Web processes need fewer connections
153
+ ENV.fetch('RAILS_MAX_THREADS', 5).to_i
154
+ end
155
+ end
156
+ end
157
+
158
+ # Configure Familia
159
+ Familia.connection_provider = lambda do |uri|
160
+ RedisConnectionPools.instance.get_connection(uri)
161
+ end
162
+ ```
163
+
164
+ ## Model Configuration
165
+
166
+ Models can specify different logical databases:
167
+
168
+ ```ruby
169
+ # Models using different logical databases
170
+ class Customer < Familia::Horreum
171
+ self.logical_database = 0 # Main application data
172
+ field :name
173
+ field :email
174
+ end
175
+
176
+ class Analytics < Familia::Horreum
177
+ self.logical_database = 1 # Analytics data
178
+ field :event_type
179
+ field :timestamp
180
+ end
181
+
182
+ class Session < Familia::Horreum
183
+ self.logical_database = 2 # Session/cache data
184
+ feature :expiration
185
+ default_expiration 1.hour
186
+ field :user_id
187
+ field :data
188
+ end
189
+
190
+ # Models can share the same logical database
191
+ class Order < Familia::Horreum
192
+ self.logical_database = 0 # Shares DB with Customer
193
+ field :customer_id
194
+ field :total
195
+ end
196
+ ```
197
+
198
+ ## Performance Optimization
199
+
200
+ ### Avoiding SELECT Command Overhead
201
+
202
+ Without proper pooling configuration, each Redis operation might issue a SELECT command:
203
+
204
+ ```
205
+ # Bad: Without connection provider
206
+ SET key value # Connection on DB 0
207
+ SELECT 2 # Switch to DB 2
208
+ SET key2 value2 # Now on DB 2
209
+ SELECT 0 # Switch back
210
+ GET key # Now on DB 0
211
+ ```
212
+
213
+ With the connection provider pattern:
214
+
215
+ ```
216
+ # Good: With connection provider
217
+ SET key value # Connection already on correct DB
218
+ SET key2 value2 # Different connection, already on correct DB
219
+ GET key # Original connection, still on correct DB
220
+ ```
221
+
222
+ ### Pool Sizing Guidelines
223
+
224
+ 1. **Web Applications**: `pool_size = number_of_threads + buffer`
225
+ ```ruby
226
+ size: ENV.fetch('RAILS_MAX_THREADS', 5).to_i + 2
227
+ ```
228
+
229
+ 2. **Background Jobs**: `pool_size = concurrency + buffer`
230
+ ```ruby
231
+ size: Sidekiq.options[:concurrency] + 5
232
+ ```
233
+
234
+ 3. **Mixed Workloads**: Size based on the logical database's usage pattern
235
+ ```ruby
236
+ POOL_CONFIGS = {
237
+ 0 => { size: 20 }, # High-traffic main DB
238
+ 1 => { size: 5 }, # Low-traffic analytics
239
+ 2 => { size: 15 } # Medium-traffic cache
240
+ }
241
+ ```
242
+
243
+ ## Testing Your Configuration
244
+
245
+ ```ruby
246
+ # Test helper to verify pools are working correctly
247
+ class ConnectionPoolTester
248
+ def self.test_pools
249
+ # Create test models using different databases
250
+ class TestModel0 < Familia::Horreum
251
+ self.logical_database = 0
252
+ field :value
253
+ end
254
+
255
+ class TestModel1 < Familia::Horreum
256
+ self.logical_database = 1
257
+ field :value
258
+ end
259
+
260
+ # Test concurrent access
261
+ threads = 10.times.map do |i|
262
+ Thread.new do
263
+ 100.times do |j|
264
+ # This should use the pool for DB 0
265
+ TestModel0.create(value: "thread-#{i}-#{j}")
266
+
267
+ # This should use the pool for DB 1
268
+ TestModel1.create(value: "thread-#{i}-#{j}")
269
+ end
270
+ end
271
+ end
272
+
273
+ threads.each(&:join)
274
+ puts "Pool test completed successfully"
275
+ end
276
+ end
277
+ ```
278
+
279
+ ## Monitoring and Debugging
280
+
281
+ Enable debug mode to verify connection providers are returning connections on the correct database:
282
+
283
+ ```ruby
284
+ Familia.debug = true # Logs warnings if provider returns wrong DB
285
+ ```
286
+
287
+ Monitor pool usage:
288
+
289
+ ```ruby
290
+ # Add monitoring to your connection provider
291
+ Familia.connection_provider = lambda do |uri|
292
+ pool = get_pool_for(uri)
293
+
294
+ # Log pool statistics
295
+ Rails.logger.info "Pool stats: #{pool.size} size, #{pool.available} available"
296
+
297
+ pool.with { |conn| conn }
298
+ end
299
+ ```
300
+
301
+ ## Common Pitfalls
302
+
303
+ 1. **Not returning DB-ready connections**: Your provider MUST return connections already on the correct database.
304
+
305
+ 2. **Creating too many pools**: Ensure you're keying pools correctly to avoid creating duplicate pools for the same database.
306
+
307
+ 3. **Forgetting thread safety**: Pool creation should be thread-safe in multi-threaded environments.
308
+
309
+ 4. **Incorrect pool sizing**: Monitor your connection usage and adjust pool sizes accordingly.
310
+
311
+ ## Summary
312
+
313
+ - Familia delegates connection management to your application via `connection_provider`
314
+ - Providers must return connections already on the correct logical database
315
+ - Use the `connection_pool` gem for robust pooling
316
+ - Create one pool per unique logical database
317
+ - Monitor and tune pool sizes based on your workload
data/familia.gemspec CHANGED
@@ -1,12 +1,12 @@
1
- # frozen_string_literal: true
1
+ # lib/familia/settings.rb
2
2
 
3
3
  require_relative 'lib/familia/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'familia'
7
- spec.version = Familia::VERSION.to_s
8
- spec.summary = 'An ORM for Redis in Ruby.'
9
- spec.description = "Familia: #{spec.summary}. Organize and store ruby objects in Redis"
7
+ spec.version = Familia::VERSION
8
+ spec.summary = 'An ORM for Valkey-compatible databases in Ruby.'
9
+ spec.description = "Familia: #{spec.summary}. Organize and store ruby objects in Valkey/Redis"
10
10
  spec.authors = ['Delano Mandelbaum']
11
11
  spec.email = 'gems@solutious.com'
12
12
  spec.homepage = 'https://github.com/delano/familia'
@@ -17,8 +17,12 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.required_ruby_version = Gem::Requirement.new('>= 2.7.8')
20
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.4')
21
21
 
22
+ spec.add_dependency 'benchmark'
23
+ spec.add_dependency 'connection_pool'
24
+ spec.add_dependency 'csv'
25
+ spec.add_dependency 'logger'
22
26
  spec.add_dependency 'redis', '>= 4.8.1', '< 6.0'
23
27
  spec.add_dependency 'stringio', '~> 3.1.1'
24
28
  spec.add_dependency 'uri-redis', '~> 1.3'